Feature: Managed Prometheus (AMP) (#5441)
This commit is contained in:
		
							parent
							
								
									f23b44e256
								
							
						
					
					
						commit
						a2a1967ef8
					
				| @ -20,6 +20,33 @@ | |||||||
| - [ ] update_certificate_options | - [ ] update_certificate_options | ||||||
| </details> | </details> | ||||||
| 
 | 
 | ||||||
|  | ## amp | ||||||
|  | <details> | ||||||
|  | <summary>61% implemented</summary> | ||||||
|  | 
 | ||||||
|  | - [ ] create_alert_manager_definition | ||||||
|  | - [ ] create_logging_configuration | ||||||
|  | - [X] create_rule_groups_namespace | ||||||
|  | - [X] create_workspace | ||||||
|  | - [ ] delete_alert_manager_definition | ||||||
|  | - [ ] delete_logging_configuration | ||||||
|  | - [X] delete_rule_groups_namespace | ||||||
|  | - [X] delete_workspace | ||||||
|  | - [ ] describe_alert_manager_definition | ||||||
|  | - [ ] describe_logging_configuration | ||||||
|  | - [X] describe_rule_groups_namespace | ||||||
|  | - [X] describe_workspace | ||||||
|  | - [X] list_rule_groups_namespaces | ||||||
|  | - [X] list_tags_for_resource | ||||||
|  | - [X] list_workspaces | ||||||
|  | - [ ] put_alert_manager_definition | ||||||
|  | - [X] put_rule_groups_namespace | ||||||
|  | - [X] tag_resource | ||||||
|  | - [X] untag_resource | ||||||
|  | - [ ] update_logging_configuration | ||||||
|  | - [X] update_workspace_alias | ||||||
|  | </details> | ||||||
|  | 
 | ||||||
| ## apigateway | ## apigateway | ||||||
| <details> | <details> | ||||||
| <summary>65% implemented</summary> | <summary>65% implemented</summary> | ||||||
| @ -6224,7 +6251,6 @@ | |||||||
| - account | - account | ||||||
| - acm-pca | - acm-pca | ||||||
| - alexaforbusiness | - alexaforbusiness | ||||||
| - amp |  | ||||||
| - amplify | - amplify | ||||||
| - amplifybackend | - amplifybackend | ||||||
| - amplifyuibuilder | - amplifyuibuilder | ||||||
|  | |||||||
							
								
								
									
										75
									
								
								docs/docs/services/amp.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								docs/docs/services/amp.rst
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,75 @@ | |||||||
|  | .. _implementedservice_amp: | ||||||
|  | 
 | ||||||
|  | .. |start-h3| raw:: html | ||||||
|  | 
 | ||||||
|  |     <h3> | ||||||
|  | 
 | ||||||
|  | .. |end-h3| raw:: html | ||||||
|  | 
 | ||||||
|  |     </h3> | ||||||
|  | 
 | ||||||
|  | === | ||||||
|  | amp | ||||||
|  | === | ||||||
|  | 
 | ||||||
|  | .. autoclass:: moto.amp.models.PrometheusServiceBackend | ||||||
|  | 
 | ||||||
|  | |start-h3| Example usage |end-h3| | ||||||
|  | 
 | ||||||
|  | .. sourcecode:: python | ||||||
|  | 
 | ||||||
|  |             @mock_amp | ||||||
|  |             def test_amp_behaviour: | ||||||
|  |                 boto3.client("amp") | ||||||
|  |                 ... | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | |start-h3| Implemented features for this service |end-h3| | ||||||
|  | 
 | ||||||
|  | - [ ] create_alert_manager_definition | ||||||
|  | - [ ] create_logging_configuration | ||||||
|  | - [X] create_rule_groups_namespace | ||||||
|  |    | ||||||
|  |         The ClientToken-parameter is not yet implemented | ||||||
|  |          | ||||||
|  | 
 | ||||||
|  | - [X] create_workspace | ||||||
|  |    | ||||||
|  |         The ClientToken-parameter is not yet implemented | ||||||
|  |          | ||||||
|  | 
 | ||||||
|  | - [ ] delete_alert_manager_definition | ||||||
|  | - [ ] delete_logging_configuration | ||||||
|  | - [X] delete_rule_groups_namespace | ||||||
|  |    | ||||||
|  |         The ClientToken-parameter is not yet implemented | ||||||
|  |          | ||||||
|  | 
 | ||||||
|  | - [X] delete_workspace | ||||||
|  |    | ||||||
|  |         The ClientToken-parameter is not yet implemented | ||||||
|  |          | ||||||
|  | 
 | ||||||
|  | - [ ] describe_alert_manager_definition | ||||||
|  | - [ ] describe_logging_configuration | ||||||
|  | - [X] describe_rule_groups_namespace | ||||||
|  | - [X] describe_workspace | ||||||
|  | - [X] list_rule_groups_namespaces | ||||||
|  | - [X] list_tags_for_resource | ||||||
|  | - [X] list_workspaces | ||||||
|  | - [ ] put_alert_manager_definition | ||||||
|  | - [X] put_rule_groups_namespace | ||||||
|  |    | ||||||
|  |         The ClientToken-parameter is not yet implemented | ||||||
|  |          | ||||||
|  | 
 | ||||||
|  | - [X] tag_resource | ||||||
|  | - [X] untag_resource | ||||||
|  | - [ ] update_logging_configuration | ||||||
|  | - [X] update_workspace_alias | ||||||
|  |    | ||||||
|  |         The ClientToken-parameter is not yet implemented | ||||||
|  |          | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| @ -16,6 +16,7 @@ def lazy_load(module_name, element, boto3_name=None, backend=None): | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| mock_acm = lazy_load(".acm", "mock_acm") | mock_acm = lazy_load(".acm", "mock_acm") | ||||||
|  | mock_amp = lazy_load(".amp", "mock_amp") | ||||||
| mock_apigateway = lazy_load(".apigateway", "mock_apigateway") | mock_apigateway = lazy_load(".apigateway", "mock_apigateway") | ||||||
| mock_apigatewayv2 = lazy_load(".apigatewayv2", "mock_apigatewayv2") | mock_apigatewayv2 = lazy_load(".apigatewayv2", "mock_apigatewayv2") | ||||||
| mock_appsync = lazy_load(".appsync", "mock_appsync") | mock_appsync = lazy_load(".appsync", "mock_appsync") | ||||||
|  | |||||||
							
								
								
									
										5
									
								
								moto/amp/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								moto/amp/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | |||||||
|  | """amp module initialization; sets value for base decorator.""" | ||||||
|  | from .models import amp_backends | ||||||
|  | from ..core.models import base_decorator | ||||||
|  | 
 | ||||||
|  | mock_amp = base_decorator(amp_backends) | ||||||
							
								
								
									
										40
									
								
								moto/amp/exceptions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								moto/amp/exceptions.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | |||||||
|  | import json | ||||||
|  | from moto.core.exceptions import JsonRESTError | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class AmpException(JsonRESTError): | ||||||
|  |     pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class ResourceNotFoundException(AmpException): | ||||||
|  |     def __init__(self, message, resource_id, resource_type): | ||||||
|  |         super().__init__("ResourceNotFoundException", message) | ||||||
|  |         self.description = json.dumps( | ||||||
|  |             { | ||||||
|  |                 "resourceId": resource_id, | ||||||
|  |                 "message": self.message, | ||||||
|  |                 "resourceType": resource_type, | ||||||
|  |             } | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class WorkspaceNotFound(ResourceNotFoundException): | ||||||
|  |     code = 404 | ||||||
|  | 
 | ||||||
|  |     def __init__(self, workspace_id): | ||||||
|  |         super().__init__( | ||||||
|  |             "Workspace not found", | ||||||
|  |             resource_id=workspace_id, | ||||||
|  |             resource_type="AWS::APS::Workspace", | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class RuleGroupNamespaceNotFound(ResourceNotFoundException): | ||||||
|  |     code = 404 | ||||||
|  | 
 | ||||||
|  |     def __init__(self, name): | ||||||
|  |         super().__init__( | ||||||
|  |             "RuleGroupNamespace not found", | ||||||
|  |             resource_id=name, | ||||||
|  |             resource_type="AWS::APS::RuleGroupNamespace", | ||||||
|  |         ) | ||||||
							
								
								
									
										168
									
								
								moto/amp/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								moto/amp/models.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,168 @@ | |||||||
|  | """PrometheusServiceBackend class with methods for supported APIs.""" | ||||||
|  | 
 | ||||||
|  | from moto.core import BaseBackend, BaseModel | ||||||
|  | from moto.core.utils import BackendDict, unix_time | ||||||
|  | from moto.utilities.paginator import paginate | ||||||
|  | from moto.utilities.tagging_service import TaggingService | ||||||
|  | from typing import Dict | ||||||
|  | from uuid import uuid4 | ||||||
|  | from .exceptions import RuleGroupNamespaceNotFound, WorkspaceNotFound | ||||||
|  | from .utils import PAGINATION_MODEL | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class RuleGroupNamespace(BaseModel): | ||||||
|  |     def __init__(self, account_id, region, workspace_id, name, data, tag_fn): | ||||||
|  |         self.name = name | ||||||
|  |         self.data = data | ||||||
|  |         self.tag_fn = tag_fn | ||||||
|  |         self.arn = f"arn:aws:aps:{region}:{account_id}:rulegroupsnamespace/{workspace_id}/{self.name}" | ||||||
|  |         self.created_at = unix_time() | ||||||
|  |         self.modified_at = self.created_at | ||||||
|  | 
 | ||||||
|  |     def update(self, new_data): | ||||||
|  |         self.data = new_data | ||||||
|  |         self.modified_at = unix_time() | ||||||
|  | 
 | ||||||
|  |     def to_dict(self): | ||||||
|  |         return { | ||||||
|  |             "name": self.name, | ||||||
|  |             "arn": self.arn, | ||||||
|  |             "status": {"statusCode": "ACTIVE"}, | ||||||
|  |             "createdAt": self.created_at, | ||||||
|  |             "modifiedAt": self.modified_at, | ||||||
|  |             "data": self.data, | ||||||
|  |             "tags": self.tag_fn(self.arn), | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Workspace(BaseModel): | ||||||
|  |     def __init__(self, account_id, region, alias, tag_fn): | ||||||
|  |         self.alias = alias | ||||||
|  |         self.workspace_id = f"ws-{uuid4()}" | ||||||
|  |         self.arn = f"arn:aws:aps:{region}:{account_id}:workspace/{self.workspace_id}" | ||||||
|  |         self.endpoint = f"https://aps-workspaces.{region}.amazonaws.com/workspaces/{self.workspace_id}/" | ||||||
|  |         self.status = {"statusCode": "ACTIVE"} | ||||||
|  |         self.created_at = unix_time() | ||||||
|  |         self.tag_fn = tag_fn | ||||||
|  |         self.rule_group_namespaces = dict() | ||||||
|  | 
 | ||||||
|  |     def to_dict(self): | ||||||
|  |         return { | ||||||
|  |             "alias": self.alias, | ||||||
|  |             "arn": self.arn, | ||||||
|  |             "workspaceId": self.workspace_id, | ||||||
|  |             "status": self.status, | ||||||
|  |             "createdAt": self.created_at, | ||||||
|  |             "prometheusEndpoint": self.endpoint, | ||||||
|  |             "tags": self.tag_fn(self.arn), | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class PrometheusServiceBackend(BaseBackend): | ||||||
|  |     """Implementation of PrometheusService APIs.""" | ||||||
|  | 
 | ||||||
|  |     def __init__(self, region_name, account_id): | ||||||
|  |         super().__init__(region_name, account_id) | ||||||
|  |         self.workspaces: Dict(str, Workspace) = dict() | ||||||
|  |         self.tagger = TaggingService() | ||||||
|  | 
 | ||||||
|  |     def create_workspace(self, alias, tags): | ||||||
|  |         """ | ||||||
|  |         The ClientToken-parameter is not yet implemented | ||||||
|  |         """ | ||||||
|  |         workspace = Workspace( | ||||||
|  |             self.account_id, | ||||||
|  |             self.region_name, | ||||||
|  |             alias=alias, | ||||||
|  |             tag_fn=self.list_tags_for_resource, | ||||||
|  |         ) | ||||||
|  |         self.workspaces[workspace.workspace_id] = workspace | ||||||
|  |         self.tag_resource(workspace.arn, tags) | ||||||
|  |         return workspace | ||||||
|  | 
 | ||||||
|  |     def describe_workspace(self, workspace_id) -> Workspace: | ||||||
|  |         if workspace_id not in self.workspaces: | ||||||
|  |             raise WorkspaceNotFound(workspace_id) | ||||||
|  |         return self.workspaces[workspace_id] | ||||||
|  | 
 | ||||||
|  |     def list_tags_for_resource(self, resource_arn): | ||||||
|  |         return self.tagger.get_tag_dict_for_resource(resource_arn) | ||||||
|  | 
 | ||||||
|  |     def update_workspace_alias(self, alias, workspace_id): | ||||||
|  |         """ | ||||||
|  |         The ClientToken-parameter is not yet implemented | ||||||
|  |         """ | ||||||
|  |         self.workspaces[workspace_id].alias = alias | ||||||
|  | 
 | ||||||
|  |     def delete_workspace(self, workspace_id): | ||||||
|  |         """ | ||||||
|  |         The ClientToken-parameter is not yet implemented | ||||||
|  |         """ | ||||||
|  |         self.workspaces.pop(workspace_id, None) | ||||||
|  | 
 | ||||||
|  |     @paginate(pagination_model=PAGINATION_MODEL) | ||||||
|  |     def list_workspaces(self, alias): | ||||||
|  |         if alias: | ||||||
|  |             return [w for w in self.workspaces.values() if w.alias == alias] | ||||||
|  |         return list(self.workspaces.values()) | ||||||
|  | 
 | ||||||
|  |     def tag_resource(self, resource_arn, tags): | ||||||
|  |         tags = self.tagger.convert_dict_to_tags_input(tags) | ||||||
|  |         self.tagger.tag_resource(resource_arn, tags) | ||||||
|  | 
 | ||||||
|  |     def untag_resource(self, resource_arn, tag_keys): | ||||||
|  |         self.tagger.untag_resource_using_names(resource_arn, tag_keys) | ||||||
|  | 
 | ||||||
|  |     def create_rule_groups_namespace( | ||||||
|  |         self, data, name, tags, workspace_id | ||||||
|  |     ) -> RuleGroupNamespace: | ||||||
|  |         """ | ||||||
|  |         The ClientToken-parameter is not yet implemented | ||||||
|  |         """ | ||||||
|  |         workspace = self.describe_workspace(workspace_id) | ||||||
|  |         group = RuleGroupNamespace( | ||||||
|  |             account_id=self.account_id, | ||||||
|  |             region=self.region_name, | ||||||
|  |             workspace_id=workspace_id, | ||||||
|  |             name=name, | ||||||
|  |             data=data, | ||||||
|  |             tag_fn=self.list_tags_for_resource, | ||||||
|  |         ) | ||||||
|  |         workspace.rule_group_namespaces[name] = group | ||||||
|  |         self.tag_resource(group.arn, tags) | ||||||
|  |         return group | ||||||
|  | 
 | ||||||
|  |     def delete_rule_groups_namespace(self, name, workspace_id) -> None: | ||||||
|  |         """ | ||||||
|  |         The ClientToken-parameter is not yet implemented | ||||||
|  |         """ | ||||||
|  |         ws = self.describe_workspace(workspace_id) | ||||||
|  |         ws.rule_group_namespaces.pop(name, None) | ||||||
|  | 
 | ||||||
|  |     def describe_rule_groups_namespace(self, name, workspace_id) -> RuleGroupNamespace: | ||||||
|  |         ws = self.describe_workspace(workspace_id) | ||||||
|  |         if name not in ws.rule_group_namespaces: | ||||||
|  |             raise RuleGroupNamespaceNotFound(name=name) | ||||||
|  |         return ws.rule_group_namespaces[name] | ||||||
|  | 
 | ||||||
|  |     def put_rule_groups_namespace(self, data, name, workspace_id) -> RuleGroupNamespace: | ||||||
|  |         """ | ||||||
|  |         The ClientToken-parameter is not yet implemented | ||||||
|  |         """ | ||||||
|  |         ns = self.describe_rule_groups_namespace(name=name, workspace_id=workspace_id) | ||||||
|  |         ns.update(data) | ||||||
|  |         return ns | ||||||
|  | 
 | ||||||
|  |     @paginate(pagination_model=PAGINATION_MODEL) | ||||||
|  |     def list_rule_groups_namespaces(self, name, workspace_id): | ||||||
|  |         ws = self.describe_workspace(workspace_id) | ||||||
|  |         if name: | ||||||
|  |             return [ | ||||||
|  |                 ns | ||||||
|  |                 for ns_name, ns in ws.rule_group_namespaces.items() | ||||||
|  |                 if ns_name.startswith(name) | ||||||
|  |             ] | ||||||
|  |         return list(ws.rule_group_namespaces.values()) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | amp_backends = BackendDict(PrometheusServiceBackend, "amp") | ||||||
							
								
								
									
										141
									
								
								moto/amp/responses.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								moto/amp/responses.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,141 @@ | |||||||
|  | """Handles incoming amp requests, invokes methods, returns responses.""" | ||||||
|  | import json | ||||||
|  | 
 | ||||||
|  | from moto.core.responses import BaseResponse | ||||||
|  | from .models import amp_backends, PrometheusServiceBackend | ||||||
|  | from urllib.parse import unquote | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class PrometheusServiceResponse(BaseResponse): | ||||||
|  |     """Handler for PrometheusService requests and responses.""" | ||||||
|  | 
 | ||||||
|  |     def tags(self, request, full_url, headers): | ||||||
|  |         self.setup_class(request, full_url, headers) | ||||||
|  |         if request.method == "GET": | ||||||
|  |             return self.list_tags_for_resource() | ||||||
|  |         if request.method == "POST": | ||||||
|  |             return self.tag_resource() | ||||||
|  |         if request.method == "DELETE": | ||||||
|  |             return self.untag_resource() | ||||||
|  | 
 | ||||||
|  |     def __init__(self): | ||||||
|  |         super().__init__(service_name="amp") | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def amp_backend(self) -> PrometheusServiceBackend: | ||||||
|  |         """Return backend instance specific for this region.""" | ||||||
|  |         return amp_backends[self.current_account][self.region] | ||||||
|  | 
 | ||||||
|  |     def create_workspace(self): | ||||||
|  |         params = json.loads(self.body) | ||||||
|  |         alias = params.get("alias") | ||||||
|  |         tags = params.get("tags") | ||||||
|  |         workspace = self.amp_backend.create_workspace(alias=alias, tags=tags) | ||||||
|  |         return json.dumps(dict(workspace.to_dict())) | ||||||
|  | 
 | ||||||
|  |     def describe_workspace(self): | ||||||
|  |         workspace_id = self.path.split("/")[-1] | ||||||
|  |         workspace = self.amp_backend.describe_workspace(workspace_id=workspace_id) | ||||||
|  |         return json.dumps(dict(workspace=workspace.to_dict())) | ||||||
|  | 
 | ||||||
|  |     def list_tags_for_resource(self): | ||||||
|  |         resource_arn = unquote(self.path).split("tags/")[-1] | ||||||
|  |         tags = self.amp_backend.list_tags_for_resource(resource_arn=resource_arn) | ||||||
|  |         return json.dumps(dict(tags=tags)) | ||||||
|  | 
 | ||||||
|  |     def update_workspace_alias(self): | ||||||
|  |         params = json.loads(self.body) | ||||||
|  |         alias = params.get("alias") | ||||||
|  |         workspace_id = self.path.split("/")[-2] | ||||||
|  |         self.amp_backend.update_workspace_alias(alias=alias, workspace_id=workspace_id) | ||||||
|  |         return json.dumps(dict()) | ||||||
|  | 
 | ||||||
|  |     def delete_workspace(self): | ||||||
|  |         workspace_id = self.path.split("/")[-1] | ||||||
|  |         self.amp_backend.delete_workspace(workspace_id=workspace_id) | ||||||
|  |         return json.dumps(dict()) | ||||||
|  | 
 | ||||||
|  |     def list_workspaces(self): | ||||||
|  |         alias = self._get_param("alias") | ||||||
|  |         max_results = self._get_int_param("maxResults") | ||||||
|  |         next_token = self._get_param("nextToken") | ||||||
|  |         workspaces, next_token = self.amp_backend.list_workspaces( | ||||||
|  |             alias, max_results=max_results, next_token=next_token | ||||||
|  |         ) | ||||||
|  |         return json.dumps( | ||||||
|  |             {"nextToken": next_token, "workspaces": [w.to_dict() for w in workspaces]} | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     def tag_resource(self): | ||||||
|  |         params = json.loads(self.body) | ||||||
|  |         resource_arn = unquote(self.path).split("tags/")[-1] | ||||||
|  |         tags = params.get("tags") | ||||||
|  |         self.amp_backend.tag_resource(resource_arn=resource_arn, tags=tags) | ||||||
|  |         return json.dumps(dict()) | ||||||
|  | 
 | ||||||
|  |     def untag_resource(self): | ||||||
|  |         resource_arn = unquote(self.path).split("tags/")[-1] | ||||||
|  |         tag_keys = self.querystring.get("tagKeys", []) | ||||||
|  |         self.amp_backend.untag_resource(resource_arn=resource_arn, tag_keys=tag_keys) | ||||||
|  |         return json.dumps(dict()) | ||||||
|  | 
 | ||||||
|  |     def create_rule_groups_namespace(self): | ||||||
|  |         params = json.loads(self.body) | ||||||
|  |         data = params.get("data") | ||||||
|  |         name = params.get("name") | ||||||
|  |         tags = params.get("tags") | ||||||
|  |         workspace_id = unquote(self.path).split("/")[-2] | ||||||
|  |         rule_group_namespace = self.amp_backend.create_rule_groups_namespace( | ||||||
|  |             data=data, | ||||||
|  |             name=name, | ||||||
|  |             tags=tags, | ||||||
|  |             workspace_id=workspace_id, | ||||||
|  |         ) | ||||||
|  |         return json.dumps(rule_group_namespace.to_dict()) | ||||||
|  | 
 | ||||||
|  |     def delete_rule_groups_namespace(self): | ||||||
|  |         name = unquote(self.path).split("/")[-1] | ||||||
|  |         workspace_id = unquote(self.path).split("/")[-3] | ||||||
|  |         self.amp_backend.delete_rule_groups_namespace( | ||||||
|  |             name=name, | ||||||
|  |             workspace_id=workspace_id, | ||||||
|  |         ) | ||||||
|  |         return json.dumps(dict()) | ||||||
|  | 
 | ||||||
|  |     def describe_rule_groups_namespace(self): | ||||||
|  |         name = unquote(self.path).split("/")[-1] | ||||||
|  |         workspace_id = unquote(self.path).split("/")[-3] | ||||||
|  |         ns = self.amp_backend.describe_rule_groups_namespace( | ||||||
|  |             name=name, workspace_id=workspace_id | ||||||
|  |         ) | ||||||
|  |         return json.dumps(dict(ruleGroupsNamespace=ns.to_dict())) | ||||||
|  | 
 | ||||||
|  |     def put_rule_groups_namespace(self): | ||||||
|  |         params = json.loads(self.body) | ||||||
|  |         data = params.get("data") | ||||||
|  |         name = unquote(self.path).split("/")[-1] | ||||||
|  |         workspace_id = unquote(self.path).split("/")[-3] | ||||||
|  |         ns = self.amp_backend.put_rule_groups_namespace( | ||||||
|  |             data=data, | ||||||
|  |             name=name, | ||||||
|  |             workspace_id=workspace_id, | ||||||
|  |         ) | ||||||
|  |         return json.dumps(ns.to_dict()) | ||||||
|  | 
 | ||||||
|  |     def list_rule_groups_namespaces(self): | ||||||
|  |         max_results = self._get_int_param("maxResults") | ||||||
|  |         next_token = self._get_param("nextToken") | ||||||
|  |         name = self._get_param("name") | ||||||
|  |         workspace_id = unquote(self.path).split("/")[-2] | ||||||
|  |         namespaces, next_token = self.amp_backend.list_rule_groups_namespaces( | ||||||
|  |             max_results=max_results, | ||||||
|  |             name=name, | ||||||
|  |             next_token=next_token, | ||||||
|  |             workspace_id=workspace_id, | ||||||
|  |         ) | ||||||
|  |         return json.dumps( | ||||||
|  |             dict( | ||||||
|  |                 nextToken=next_token, | ||||||
|  |                 ruleGroupsNamespaces=[ns.to_dict() for ns in namespaces], | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
							
								
								
									
										21
									
								
								moto/amp/urls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								moto/amp/urls.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | |||||||
|  | """amp base URL and path.""" | ||||||
|  | from .responses import PrometheusServiceResponse | ||||||
|  | 
 | ||||||
|  | url_bases = [ | ||||||
|  |     r"https?://aps\.(.+)\.amazonaws\.com", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | response = PrometheusServiceResponse() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | url_paths = { | ||||||
|  |     "{0}/workspaces$": response.dispatch, | ||||||
|  |     "{0}/workspaces/(?P<workspace_id>[^/]+)$": response.dispatch, | ||||||
|  |     "{0}/workspaces/(?P<workspace_id>[^/]+)/alias$": response.dispatch, | ||||||
|  |     "{0}/workspaces/(?P<workspace_id>[^/]+)/rulegroupsnamespaces$": response.dispatch, | ||||||
|  |     "{0}/workspaces/(?P<workspace_id>[^/]+)/rulegroupsnamespaces/(?P<name>[^/]+)$": response.dispatch, | ||||||
|  |     "{0}/tags/(?P<resource_arn>[^/]+)$": response.dispatch, | ||||||
|  |     "{0}/tags/(?P<arn_prefix>[^/]+)/(?P<workspace_id>[^/]+)$": response.tags, | ||||||
|  |     "{0}/tags/(?P<arn_prefix>[^/]+)/(?P<workspace_id>[^/]+)/(?P<ns_name>[^/]+)$": response.tags, | ||||||
|  | } | ||||||
							
								
								
									
										14
									
								
								moto/amp/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								moto/amp/utils.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | |||||||
|  | PAGINATION_MODEL = { | ||||||
|  |     "list_workspaces": { | ||||||
|  |         "input_token": "next_token", | ||||||
|  |         "limit_key": "max_results", | ||||||
|  |         "limit_default": 100,  # This should be the sum of the directory limits | ||||||
|  |         "unique_attribute": "arn", | ||||||
|  |     }, | ||||||
|  |     "list_rule_groups_namespaces": { | ||||||
|  |         "input_token": "next_token", | ||||||
|  |         "limit_key": "max_results", | ||||||
|  |         "limit_default": 100,  # This should be the sum of the directory limits | ||||||
|  |         "unique_attribute": "name", | ||||||
|  |     }, | ||||||
|  | } | ||||||
| @ -3,6 +3,7 @@ import re | |||||||
| 
 | 
 | ||||||
| backend_url_patterns = [ | backend_url_patterns = [ | ||||||
|     ("acm", re.compile("https?://acm\\.(.+)\\.amazonaws\\.com")), |     ("acm", re.compile("https?://acm\\.(.+)\\.amazonaws\\.com")), | ||||||
|  |     ("amp", re.compile("https?://aps\\.(.+)\\.amazonaws\\.com")), | ||||||
|     ("apigateway", re.compile("https?://apigateway\\.(.+)\\.amazonaws.com")), |     ("apigateway", re.compile("https?://apigateway\\.(.+)\\.amazonaws.com")), | ||||||
|     ( |     ( | ||||||
|         "applicationautoscaling", |         "applicationautoscaling", | ||||||
|  | |||||||
| @ -1,5 +1,8 @@ | |||||||
| acm: | acm: | ||||||
|   - TestAccACMCertificateDataSource |   - TestAccACMCertificateDataSource | ||||||
|  | amp: | ||||||
|  |   - TestAccAMPWorkspace | ||||||
|  |   - TestAccAMPRuleGroupNamespace | ||||||
| apigateway: | apigateway: | ||||||
|   - TestAccAPIGatewayGatewayResponse |   - TestAccAPIGatewayGatewayResponse | ||||||
| apigatewayv2: | apigatewayv2: | ||||||
|  | |||||||
							
								
								
									
										0
									
								
								tests/test_amp/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								tests/test_amp/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										173
									
								
								tests/test_amp/test_amp_rulegroupnamespaces.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								tests/test_amp/test_amp_rulegroupnamespaces.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,173 @@ | |||||||
|  | """Unit tests for amp-supported APIs.""" | ||||||
|  | import boto3 | ||||||
|  | import pytest | ||||||
|  | import sure  # noqa # pylint: disable=unused-import | ||||||
|  | 
 | ||||||
|  | from botocore.exceptions import ClientError | ||||||
|  | from moto import mock_amp | ||||||
|  | 
 | ||||||
|  | # See our Development Tips on writing tests for hints on how to write good tests: | ||||||
|  | # http://docs.getmoto.org/en/latest/docs/contributing/development_tips/tests.html | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_amp | ||||||
|  | def test_create_rule_groups_namespace(): | ||||||
|  |     client = boto3.client("amp", region_name="ap-southeast-1") | ||||||
|  |     workspace_id = client.create_workspace()["workspaceId"] | ||||||
|  |     resp = client.create_rule_groups_namespace( | ||||||
|  |         data=b"asdf", name="my first rule group", workspaceId=workspace_id | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     resp.should.have.key("arn") | ||||||
|  |     resp.should.have.key("name").equals("my first rule group") | ||||||
|  |     resp.should.have.key("status") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_amp | ||||||
|  | def test_delete_rule_groups_namespace(): | ||||||
|  |     client = boto3.client("amp", region_name="us-east-2") | ||||||
|  |     workspace_id = client.create_workspace()["workspaceId"] | ||||||
|  |     client.create_rule_groups_namespace( | ||||||
|  |         data=b"asdf", name="myname", workspaceId=workspace_id | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     client.delete_rule_groups_namespace(name="myname", workspaceId=workspace_id) | ||||||
|  | 
 | ||||||
|  |     with pytest.raises(ClientError) as exc: | ||||||
|  |         client.describe_rule_groups_namespace(name="myname", workspaceId=workspace_id) | ||||||
|  |     err = exc.value.response["Error"] | ||||||
|  |     err["Code"].should.equal("ResourceNotFoundException") | ||||||
|  |     err["Message"].should.equal("RuleGroupNamespace not found") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_amp | ||||||
|  | def test_describe_rule_groups_namespace(): | ||||||
|  |     client = boto3.client("amp", region_name="us-east-2") | ||||||
|  | 
 | ||||||
|  |     workspace_id = client.create_workspace()["workspaceId"] | ||||||
|  |     client.create_rule_groups_namespace( | ||||||
|  |         data=b"asdf", name="myname", workspaceId=workspace_id | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     resp = client.describe_rule_groups_namespace( | ||||||
|  |         name="myname", workspaceId=workspace_id | ||||||
|  |     ) | ||||||
|  |     resp.should.have.key("ruleGroupsNamespace") | ||||||
|  |     ns = resp["ruleGroupsNamespace"] | ||||||
|  | 
 | ||||||
|  |     ns.should.have.key("arn") | ||||||
|  |     ns.should.have.key("createdAt") | ||||||
|  |     ns.should.have.key("data").equals(b"asdf") | ||||||
|  |     ns.should.have.key("modifiedAt") | ||||||
|  |     ns.should.have.key("name").equals("myname") | ||||||
|  |     ns.should.have.key("status") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_amp | ||||||
|  | def test_put_rule_groups_namespace(): | ||||||
|  |     client = boto3.client("amp", region_name="eu-west-1") | ||||||
|  | 
 | ||||||
|  |     workspace_id = client.create_workspace()["workspaceId"] | ||||||
|  |     client.create_rule_groups_namespace( | ||||||
|  |         data=b"asdf", name="myname", workspaceId=workspace_id | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     client.put_rule_groups_namespace( | ||||||
|  |         name="myname", workspaceId=workspace_id, data=b"updated" | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     resp = client.describe_rule_groups_namespace( | ||||||
|  |         name="myname", workspaceId=workspace_id | ||||||
|  |     ) | ||||||
|  |     resp.should.have.key("ruleGroupsNamespace") | ||||||
|  |     ns = resp["ruleGroupsNamespace"] | ||||||
|  | 
 | ||||||
|  |     ns.should.have.key("arn") | ||||||
|  |     ns.should.have.key("createdAt") | ||||||
|  |     ns.should.have.key("data").equals(b"updated") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_amp | ||||||
|  | def test_list_rule_groups_namespaces(): | ||||||
|  |     client = boto3.client("amp", region_name="ap-southeast-1") | ||||||
|  |     w_id = client.create_workspace()["workspaceId"] | ||||||
|  |     for idx in range(15): | ||||||
|  |         client.create_rule_groups_namespace( | ||||||
|  |             data=b"a", name=f"ns{idx}", workspaceId=w_id | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     resp = client.list_rule_groups_namespaces(workspaceId=w_id) | ||||||
|  |     resp.should.have.key("ruleGroupsNamespaces").length_of(15) | ||||||
|  |     resp.shouldnt.have.key("nextToken") | ||||||
|  | 
 | ||||||
|  |     resp = client.list_rule_groups_namespaces(workspaceId=w_id, name="ns1") | ||||||
|  |     resp.should.have.key("ruleGroupsNamespaces").length_of(6) | ||||||
|  |     names = [ns["name"] for ns in resp["ruleGroupsNamespaces"]] | ||||||
|  |     set(names).should.equal({"ns10", "ns13", "ns1", "ns12", "ns11", "ns14"}) | ||||||
|  | 
 | ||||||
|  |     resp = client.list_rule_groups_namespaces(workspaceId=w_id, name="ns10") | ||||||
|  |     resp.should.have.key("ruleGroupsNamespaces").length_of(1) | ||||||
|  |     names = [ns["name"] for ns in resp["ruleGroupsNamespaces"]] | ||||||
|  |     set(names).should.equal({"ns10"}) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_amp | ||||||
|  | def test_list_rule_groups_namespaces__paginated(): | ||||||
|  |     client = boto3.client("amp", region_name="ap-southeast-1") | ||||||
|  |     w_id = client.create_workspace()["workspaceId"] | ||||||
|  |     for idx in range(125): | ||||||
|  |         client.create_rule_groups_namespace( | ||||||
|  |             data=b"a", name=f"ns{idx}", workspaceId=w_id | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     # default pagesize is 100 | ||||||
|  |     page1 = client.list_rule_groups_namespaces(workspaceId=w_id) | ||||||
|  |     page1.should.have.key("ruleGroupsNamespaces").length_of(100) | ||||||
|  |     page1.should.have.key("nextToken") | ||||||
|  | 
 | ||||||
|  |     # We can ask for a smaller pagesize | ||||||
|  |     page2 = client.list_rule_groups_namespaces( | ||||||
|  |         workspaceId=w_id, maxResults=15, nextToken=page1["nextToken"] | ||||||
|  |     ) | ||||||
|  |     page2.should.have.key("ruleGroupsNamespaces").length_of(15) | ||||||
|  |     page2.should.have.key("nextToken") | ||||||
|  | 
 | ||||||
|  |     page3 = client.list_rule_groups_namespaces( | ||||||
|  |         workspaceId=w_id, maxResults=15, nextToken=page2["nextToken"] | ||||||
|  |     ) | ||||||
|  |     page3.should.have.key("ruleGroupsNamespaces").length_of(10) | ||||||
|  |     page3.shouldnt.have.key("nextToken") | ||||||
|  | 
 | ||||||
|  |     # We could request all of them in one go | ||||||
|  |     full_page = client.list_rule_groups_namespaces(workspaceId=w_id, maxResults=150) | ||||||
|  |     full_page.should.have.key("ruleGroupsNamespaces").length_of(125) | ||||||
|  |     full_page.shouldnt.have.key("nextToken") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_amp | ||||||
|  | def test_tag_resource(): | ||||||
|  |     client = boto3.client("amp", region_name="us-east-2") | ||||||
|  | 
 | ||||||
|  |     w_id = client.create_workspace()["workspaceId"] | ||||||
|  |     ns = client.create_rule_groups_namespace( | ||||||
|  |         data=b"a", name="ns", workspaceId=w_id, tags={"t": "v"} | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     arn = ns["arn"] | ||||||
|  | 
 | ||||||
|  |     client.tag_resource(resourceArn=arn, tags={"t1": "v1", "t2": "v2"}) | ||||||
|  | 
 | ||||||
|  |     client.list_tags_for_resource(resourceArn=arn)["tags"].should.equal( | ||||||
|  |         {"t": "v", "t1": "v1", "t2": "v2"} | ||||||
|  |     ) | ||||||
|  |     client.describe_rule_groups_namespace(workspaceId=w_id, name="ns")[ | ||||||
|  |         "ruleGroupsNamespace" | ||||||
|  |     ]["tags"].should.equal({"t": "v", "t1": "v1", "t2": "v2"}) | ||||||
|  | 
 | ||||||
|  |     client.untag_resource(resourceArn=arn, tagKeys=["t1"]) | ||||||
|  |     client.list_tags_for_resource(resourceArn=arn)["tags"].should.equal( | ||||||
|  |         {"t": "v", "t2": "v2"} | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     client.untag_resource(resourceArn=arn, tagKeys=["t", "t2"]) | ||||||
|  |     client.list_tags_for_resource(resourceArn=arn)["tags"].should.equal({}) | ||||||
							
								
								
									
										149
									
								
								tests/test_amp/test_amp_workspaces.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								tests/test_amp/test_amp_workspaces.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,149 @@ | |||||||
|  | import boto3 | ||||||
|  | import pytest | ||||||
|  | import sure  # noqa # pylint: disable=unused-import | ||||||
|  | 
 | ||||||
|  | from botocore.exceptions import ClientError | ||||||
|  | from moto import mock_amp | ||||||
|  | 
 | ||||||
|  | # See our Development Tips on writing tests for hints on how to write good tests: | ||||||
|  | # http://docs.getmoto.org/en/latest/docs/contributing/development_tips/tests.html | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_amp | ||||||
|  | def test_create_workspace(): | ||||||
|  |     client = boto3.client("amp", region_name="ap-southeast-1") | ||||||
|  |     resp = client.create_workspace(alias="test", clientToken="mytoken") | ||||||
|  | 
 | ||||||
|  |     resp.should.have.key("arn") | ||||||
|  |     resp.should.have.key("status").equals({"statusCode": "ACTIVE"}) | ||||||
|  |     resp.should.have.key("workspaceId") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_amp | ||||||
|  | def test_describe_workspace(): | ||||||
|  |     client = boto3.client("amp", region_name="eu-west-1") | ||||||
|  |     workspace_id = client.create_workspace(alias="test", clientToken="mytoken")[ | ||||||
|  |         "workspaceId" | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     resp = client.describe_workspace(workspaceId=workspace_id) | ||||||
|  |     resp.should.have.key("workspace") | ||||||
|  | 
 | ||||||
|  |     workspace = resp["workspace"] | ||||||
|  |     workspace.should.have.key("alias") | ||||||
|  |     workspace.should.have.key("arn") | ||||||
|  |     workspace.should.have.key("createdAt") | ||||||
|  |     workspace.should.have.key("prometheusEndpoint") | ||||||
|  |     workspace.should.have.key("status").equals({"statusCode": "ACTIVE"}) | ||||||
|  |     workspace.should.have.key("workspaceId").equals(workspace_id) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_amp | ||||||
|  | def test_list_workspaces(): | ||||||
|  |     client = boto3.client("amp", region_name="ap-southeast-1") | ||||||
|  |     client.create_workspace(alias="test") | ||||||
|  |     client.create_workspace(alias="another") | ||||||
|  |     client.create_workspace() | ||||||
|  | 
 | ||||||
|  |     resp = client.list_workspaces() | ||||||
|  |     resp.should.have.key("workspaces").length_of(3) | ||||||
|  |     resp.shouldnt.have.key("nextToken") | ||||||
|  | 
 | ||||||
|  |     resp = client.list_workspaces(alias="another") | ||||||
|  |     resp.should.have.key("workspaces").length_of(1) | ||||||
|  |     resp["workspaces"][0].should.have.key("alias").equals("another") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_amp | ||||||
|  | def test_list_workspaces__paginated(): | ||||||
|  |     client = boto3.client("amp", region_name="ap-southeast-1") | ||||||
|  |     for _ in range(125): | ||||||
|  |         client.create_workspace() | ||||||
|  | 
 | ||||||
|  |     # default pagesize is 100 | ||||||
|  |     page1 = client.list_workspaces() | ||||||
|  |     page1.should.have.key("workspaces").length_of(100) | ||||||
|  |     page1.should.have.key("nextToken") | ||||||
|  | 
 | ||||||
|  |     # We can ask for a smaller pagesize | ||||||
|  |     page2 = client.list_workspaces(maxResults=15, nextToken=page1["nextToken"]) | ||||||
|  |     page2.should.have.key("workspaces").length_of(15) | ||||||
|  |     page2.should.have.key("nextToken") | ||||||
|  | 
 | ||||||
|  |     page3 = client.list_workspaces(maxResults=15, nextToken=page2["nextToken"]) | ||||||
|  |     page3.should.have.key("workspaces").length_of(10) | ||||||
|  |     page3.shouldnt.have.key("nextToken") | ||||||
|  | 
 | ||||||
|  |     # We could request all of them in one go | ||||||
|  |     full_page = client.list_workspaces(maxResults=150) | ||||||
|  |     full_page.should.have.key("workspaces").length_of(125) | ||||||
|  |     full_page.shouldnt.have.key("nextToken") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_amp | ||||||
|  | def test_list_tags_for_resource(): | ||||||
|  |     client = boto3.client("amp", region_name="ap-southeast-1") | ||||||
|  |     arn = client.create_workspace( | ||||||
|  |         alias="test", clientToken="mytoken", tags={"t1": "v1", "t2": "v2"} | ||||||
|  |     )["arn"] | ||||||
|  | 
 | ||||||
|  |     resp = client.list_tags_for_resource(resourceArn=arn) | ||||||
|  |     resp.should.have.key("tags").equals({"t1": "v1", "t2": "v2"}) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_amp | ||||||
|  | def test_update_workspace_alias(): | ||||||
|  |     client = boto3.client("amp", region_name="ap-southeast-1") | ||||||
|  | 
 | ||||||
|  |     workspace_id = client.create_workspace(alias="initial")["workspaceId"] | ||||||
|  | 
 | ||||||
|  |     w = client.describe_workspace(workspaceId=workspace_id)["workspace"] | ||||||
|  |     w.should.have.key("alias").equals("initial") | ||||||
|  | 
 | ||||||
|  |     client.update_workspace_alias(alias="updated", workspaceId=workspace_id) | ||||||
|  | 
 | ||||||
|  |     w = client.describe_workspace(workspaceId=workspace_id)["workspace"] | ||||||
|  |     w.should.have.key("alias").equals("updated") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_amp | ||||||
|  | def test_delete_workspace(): | ||||||
|  |     client = boto3.client("amp", region_name="us-east-2") | ||||||
|  | 
 | ||||||
|  |     workspace_id = client.create_workspace(alias="test", clientToken="mytoken")[ | ||||||
|  |         "workspaceId" | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     client.delete_workspace(workspaceId=workspace_id) | ||||||
|  | 
 | ||||||
|  |     with pytest.raises(ClientError) as exc: | ||||||
|  |         client.describe_workspace(workspaceId=workspace_id) | ||||||
|  |     err = exc.value.response["Error"] | ||||||
|  |     err["Code"].should.equal("ResourceNotFoundException") | ||||||
|  |     err["Message"].should.equal("Workspace not found") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_amp | ||||||
|  | def test_tag_resource(): | ||||||
|  |     client = boto3.client("amp", region_name="us-east-2") | ||||||
|  | 
 | ||||||
|  |     workspace = client.create_workspace(alias="test", tags={"t": "v"}) | ||||||
|  |     arn = workspace["arn"] | ||||||
|  |     workspace_id = workspace["workspaceId"] | ||||||
|  | 
 | ||||||
|  |     client.tag_resource(resourceArn=arn, tags={"t1": "v1", "t2": "v2"}) | ||||||
|  | 
 | ||||||
|  |     client.list_tags_for_resource(resourceArn=arn)["tags"].should.equal( | ||||||
|  |         {"t": "v", "t1": "v1", "t2": "v2"} | ||||||
|  |     ) | ||||||
|  |     client.describe_workspace(workspaceId=workspace_id)["workspace"][ | ||||||
|  |         "tags" | ||||||
|  |     ].should.equal({"t": "v", "t1": "v1", "t2": "v2"}) | ||||||
|  | 
 | ||||||
|  |     client.untag_resource(resourceArn=arn, tagKeys=["t1"]) | ||||||
|  |     client.list_tags_for_resource(resourceArn=arn)["tags"].should.equal( | ||||||
|  |         {"t": "v", "t2": "v2"} | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     client.untag_resource(resourceArn=arn, tagKeys=["t", "t2"]) | ||||||
|  |     client.list_tags_for_resource(resourceArn=arn)["tags"].should.equal({}) | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user