Logs: Tagging support (#6734)
This commit is contained in:
parent
ffc8b7dd08
commit
956dd265f0
@ -4368,7 +4368,7 @@
|
|||||||
|
|
||||||
## logs
|
## logs
|
||||||
<details>
|
<details>
|
||||||
<summary>60% implemented</summary>
|
<summary>66% implemented</summary>
|
||||||
|
|
||||||
- [ ] associate_kms_key
|
- [ ] associate_kms_key
|
||||||
- [ ] cancel_export_task
|
- [ ] cancel_export_task
|
||||||
@ -4402,7 +4402,7 @@
|
|||||||
- [ ] get_log_group_fields
|
- [ ] get_log_group_fields
|
||||||
- [ ] get_log_record
|
- [ ] get_log_record
|
||||||
- [X] get_query_results
|
- [X] get_query_results
|
||||||
- [ ] list_tags_for_resource
|
- [X] list_tags_for_resource
|
||||||
- [X] list_tags_log_group
|
- [X] list_tags_log_group
|
||||||
- [ ] put_account_policy
|
- [ ] put_account_policy
|
||||||
- [ ] put_data_protection_policy
|
- [ ] put_data_protection_policy
|
||||||
@ -4417,10 +4417,10 @@
|
|||||||
- [X] start_query
|
- [X] start_query
|
||||||
- [ ] stop_query
|
- [ ] stop_query
|
||||||
- [X] tag_log_group
|
- [X] tag_log_group
|
||||||
- [ ] tag_resource
|
- [X] tag_resource
|
||||||
- [ ] test_metric_filter
|
- [ ] test_metric_filter
|
||||||
- [X] untag_log_group
|
- [X] untag_log_group
|
||||||
- [ ] untag_resource
|
- [X] untag_resource
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## managedblockchain
|
## managedblockchain
|
||||||
@ -5792,7 +5792,7 @@
|
|||||||
|
|
||||||
## resourcegroupstaggingapi
|
## resourcegroupstaggingapi
|
||||||
<details>
|
<details>
|
||||||
<summary>37% implemented</summary>
|
<summary>50% implemented</summary>
|
||||||
|
|
||||||
- [ ] describe_report_creation
|
- [ ] describe_report_creation
|
||||||
- [ ] get_compliance_summary
|
- [ ] get_compliance_summary
|
||||||
@ -5800,7 +5800,7 @@
|
|||||||
- [X] get_tag_keys
|
- [X] get_tag_keys
|
||||||
- [X] get_tag_values
|
- [X] get_tag_values
|
||||||
- [ ] start_report_creation
|
- [ ] start_report_creation
|
||||||
- [ ] tag_resources
|
- [X] tag_resources
|
||||||
- [ ] untag_resources
|
- [ ] untag_resources
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
@ -38,7 +38,9 @@ logs
|
|||||||
- [X] delete_metric_filter
|
- [X] delete_metric_filter
|
||||||
- [ ] delete_query_definition
|
- [ ] delete_query_definition
|
||||||
- [X] delete_resource_policy
|
- [X] delete_resource_policy
|
||||||
Remove resource policy with a policy name matching given name.
|
|
||||||
|
Remove resource policy with a policy name matching given name.
|
||||||
|
|
||||||
|
|
||||||
- [X] delete_retention_policy
|
- [X] delete_retention_policy
|
||||||
- [X] delete_subscription_filter
|
- [X] delete_subscription_filter
|
||||||
@ -55,7 +57,8 @@ logs
|
|||||||
|
|
||||||
- [ ] describe_query_definitions
|
- [ ] describe_query_definitions
|
||||||
- [X] describe_resource_policies
|
- [X] describe_resource_policies
|
||||||
Return list of resource policies.
|
|
||||||
|
Return list of resource policies.
|
||||||
|
|
||||||
The next_token and limit arguments are ignored. The maximum
|
The next_token and limit arguments are ignored. The maximum
|
||||||
number of resource policies per region is a small number (less
|
number of resource policies per region is a small number (less
|
||||||
@ -79,7 +82,7 @@ logs
|
|||||||
Not all query commands are implemented yet. Please raise an issue if you encounter unexpected results.
|
Not all query commands are implemented yet. Please raise an issue if you encounter unexpected results.
|
||||||
|
|
||||||
|
|
||||||
- [ ] list_tags_for_resource
|
- [X] list_tags_for_resource
|
||||||
- [X] list_tags_log_group
|
- [X] list_tags_log_group
|
||||||
- [ ] put_account_policy
|
- [ ] put_account_policy
|
||||||
- [ ] put_data_protection_policy
|
- [ ] put_data_protection_policy
|
||||||
@ -93,15 +96,17 @@ logs
|
|||||||
- [X] put_metric_filter
|
- [X] put_metric_filter
|
||||||
- [ ] put_query_definition
|
- [ ] put_query_definition
|
||||||
- [X] put_resource_policy
|
- [X] put_resource_policy
|
||||||
Creates/updates resource policy and return policy object
|
|
||||||
|
Creates/updates resource policy and return policy object
|
||||||
|
|
||||||
|
|
||||||
- [X] put_retention_policy
|
- [X] put_retention_policy
|
||||||
- [X] put_subscription_filter
|
- [X] put_subscription_filter
|
||||||
- [X] start_query
|
- [X] start_query
|
||||||
- [ ] stop_query
|
- [ ] stop_query
|
||||||
- [X] tag_log_group
|
- [X] tag_log_group
|
||||||
- [ ] tag_resource
|
- [X] tag_resource
|
||||||
- [ ] test_metric_filter
|
- [ ] test_metric_filter
|
||||||
- [X] untag_log_group
|
- [X] untag_log_group
|
||||||
- [ ] untag_resource
|
- [X] untag_resource
|
||||||
|
|
||||||
|
@ -31,6 +31,10 @@ resourcegroupstaggingapi
|
|||||||
- [X] get_tag_keys
|
- [X] get_tag_keys
|
||||||
- [X] get_tag_values
|
- [X] get_tag_values
|
||||||
- [ ] start_report_creation
|
- [ ] start_report_creation
|
||||||
- [ ] tag_resources
|
- [X] tag_resources
|
||||||
|
|
||||||
|
Only Logs and RDS resources are currently supported
|
||||||
|
|
||||||
|
|
||||||
- [ ] untag_resources
|
- [ ] untag_resources
|
||||||
|
|
||||||
|
@ -761,7 +761,7 @@ class LambdaFunction(CloudFormationModel, DockerModel):
|
|||||||
|
|
||||||
def _invoke_lambda(self, event: Optional[str] = None) -> Tuple[str, bool, str]:
|
def _invoke_lambda(self, event: Optional[str] = None) -> Tuple[str, bool, str]:
|
||||||
# Create the LogGroup if necessary, to write the result to
|
# Create the LogGroup if necessary, to write the result to
|
||||||
self.logs_backend.ensure_log_group(self.logs_group_name, [])
|
self.logs_backend.ensure_log_group(self.logs_group_name)
|
||||||
# TODO: context not yet implemented
|
# TODO: context not yet implemented
|
||||||
if event is None:
|
if event is None:
|
||||||
event = dict() # type: ignore[assignment]
|
event = dict() # type: ignore[assignment]
|
||||||
|
@ -858,7 +858,7 @@ class Job(threading.Thread, BaseModel, DockerModel, ManagedState):
|
|||||||
|
|
||||||
# Send to cloudwatch
|
# Send to cloudwatch
|
||||||
self.log_stream_name = self._stream_name
|
self.log_stream_name = self._stream_name
|
||||||
self._log_backend.ensure_log_group(self._log_group, None)
|
self._log_backend.ensure_log_group(self._log_group)
|
||||||
self._log_backend.ensure_log_stream(
|
self._log_backend.ensure_log_stream(
|
||||||
self._log_group, self.log_stream_name
|
self._log_group, self.log_stream_name
|
||||||
)
|
)
|
||||||
|
@ -14,6 +14,7 @@ from moto.logs.logs_query import execute_query
|
|||||||
from moto.moto_api._internal import mock_random
|
from moto.moto_api._internal import mock_random
|
||||||
from moto.s3.models import s3_backends
|
from moto.s3.models import s3_backends
|
||||||
from moto.utilities.paginator import paginate
|
from moto.utilities.paginator import paginate
|
||||||
|
from moto.utilities.tagging_service import TaggingService
|
||||||
from .utils import PAGINATION_MODEL, EventMessageFilter
|
from .utils import PAGINATION_MODEL, EventMessageFilter
|
||||||
|
|
||||||
MAX_RESOURCE_POLICIES_PER_REGION = 10
|
MAX_RESOURCE_POLICIES_PER_REGION = 10
|
||||||
@ -373,7 +374,6 @@ class LogGroup(CloudFormationModel):
|
|||||||
account_id: str,
|
account_id: str,
|
||||||
region: str,
|
region: str,
|
||||||
name: str,
|
name: str,
|
||||||
tags: Optional[Dict[str, str]],
|
|
||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
):
|
):
|
||||||
self.name = name
|
self.name = name
|
||||||
@ -381,7 +381,6 @@ class LogGroup(CloudFormationModel):
|
|||||||
self.region = region
|
self.region = region
|
||||||
self.arn = f"arn:aws:logs:{region}:{account_id}:log-group:{name}"
|
self.arn = f"arn:aws:logs:{region}:{account_id}:log-group:{name}"
|
||||||
self.creation_time = int(unix_time_millis())
|
self.creation_time = int(unix_time_millis())
|
||||||
self.tags = tags
|
|
||||||
self.streams: Dict[str, LogStream] = dict() # {name: LogStream}
|
self.streams: Dict[str, LogStream] = dict() # {name: LogStream}
|
||||||
# AWS defaults to Never Expire for log group retention
|
# AWS defaults to Never Expire for log group retention
|
||||||
self.retention_in_days = kwargs.get("RetentionInDays")
|
self.retention_in_days = kwargs.get("RetentionInDays")
|
||||||
@ -607,21 +606,6 @@ class LogGroup(CloudFormationModel):
|
|||||||
def set_retention_policy(self, retention_in_days: Optional[str]) -> None:
|
def set_retention_policy(self, retention_in_days: Optional[str]) -> None:
|
||||||
self.retention_in_days = retention_in_days
|
self.retention_in_days = retention_in_days
|
||||||
|
|
||||||
def list_tags(self) -> Dict[str, str]:
|
|
||||||
return self.tags if self.tags else {}
|
|
||||||
|
|
||||||
def tag(self, tags: Dict[str, str]) -> None:
|
|
||||||
if self.tags:
|
|
||||||
self.tags.update(tags)
|
|
||||||
else:
|
|
||||||
self.tags = tags
|
|
||||||
|
|
||||||
def untag(self, tags_to_remove: List[str]) -> None:
|
|
||||||
if self.tags:
|
|
||||||
self.tags = {
|
|
||||||
k: v for (k, v) in self.tags.items() if k not in tags_to_remove
|
|
||||||
}
|
|
||||||
|
|
||||||
def describe_subscription_filters(self) -> Iterable[SubscriptionFilter]:
|
def describe_subscription_filters(self) -> Iterable[SubscriptionFilter]:
|
||||||
return self.subscription_filters.values()
|
return self.subscription_filters.values()
|
||||||
|
|
||||||
@ -741,6 +725,7 @@ class LogsBackend(BaseBackend):
|
|||||||
self.queries: Dict[str, LogQuery] = dict()
|
self.queries: Dict[str, LogQuery] = dict()
|
||||||
self.resource_policies: Dict[str, LogResourcePolicy] = dict()
|
self.resource_policies: Dict[str, LogResourcePolicy] = dict()
|
||||||
self.destinations: Dict[str, Destination] = dict()
|
self.destinations: Dict[str, Destination] = dict()
|
||||||
|
self.tagger = TaggingService()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def default_vpc_endpoint_service(
|
def default_vpc_endpoint_service(
|
||||||
@ -763,17 +748,18 @@ class LogsBackend(BaseBackend):
|
|||||||
value=log_group_name,
|
value=log_group_name,
|
||||||
)
|
)
|
||||||
self.groups[log_group_name] = LogGroup(
|
self.groups[log_group_name] = LogGroup(
|
||||||
self.account_id, self.region_name, log_group_name, tags, **kwargs
|
self.account_id, self.region_name, log_group_name, **kwargs
|
||||||
)
|
)
|
||||||
|
self.tag_resource(self.groups[log_group_name].arn, tags)
|
||||||
return self.groups[log_group_name]
|
return self.groups[log_group_name]
|
||||||
|
|
||||||
def ensure_log_group(
|
def ensure_log_group(self, log_group_name: str) -> None:
|
||||||
self, log_group_name: str, tags: Optional[Dict[str, str]]
|
|
||||||
) -> None:
|
|
||||||
if log_group_name in self.groups:
|
if log_group_name in self.groups:
|
||||||
return
|
return
|
||||||
self.groups[log_group_name] = LogGroup(
|
self.groups[log_group_name] = LogGroup(
|
||||||
self.account_id, self.region_name, log_group_name, tags
|
self.account_id,
|
||||||
|
self.region_name,
|
||||||
|
log_group_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
def delete_log_group(self, log_group_name: str) -> None:
|
def delete_log_group(self, log_group_name: str) -> None:
|
||||||
@ -801,7 +787,11 @@ class LogsBackend(BaseBackend):
|
|||||||
raise ResourceNotFoundException()
|
raise ResourceNotFoundException()
|
||||||
|
|
||||||
def put_destination(
|
def put_destination(
|
||||||
self, destination_name: str, role_arn: str, target_arn: str
|
self,
|
||||||
|
destination_name: str,
|
||||||
|
role_arn: str,
|
||||||
|
target_arn: str,
|
||||||
|
tags: Dict[str, str],
|
||||||
) -> Destination:
|
) -> Destination:
|
||||||
for _, destination in self.destinations.items():
|
for _, destination in self.destinations.items():
|
||||||
if destination.destination_name == destination_name:
|
if destination.destination_name == destination_name:
|
||||||
@ -814,6 +804,7 @@ class LogsBackend(BaseBackend):
|
|||||||
self.account_id, self.region_name, destination_name, role_arn, target_arn
|
self.account_id, self.region_name, destination_name, role_arn, target_arn
|
||||||
)
|
)
|
||||||
self.destinations[destination.arn] = destination
|
self.destinations[destination.arn] = destination
|
||||||
|
self.tag_resource(destination.arn, tags)
|
||||||
return destination
|
return destination
|
||||||
|
|
||||||
def delete_destination(self, destination_name: str) -> None:
|
def delete_destination(self, destination_name: str) -> None:
|
||||||
@ -1010,7 +1001,8 @@ class LogsBackend(BaseBackend):
|
|||||||
self.groups[log_group_name].set_retention_policy(None)
|
self.groups[log_group_name].set_retention_policy(None)
|
||||||
|
|
||||||
def describe_resource_policies(self) -> List[LogResourcePolicy]:
|
def describe_resource_policies(self) -> List[LogResourcePolicy]:
|
||||||
"""Return list of resource policies.
|
"""
|
||||||
|
Return list of resource policies.
|
||||||
|
|
||||||
The next_token and limit arguments are ignored. The maximum
|
The next_token and limit arguments are ignored. The maximum
|
||||||
number of resource policies per region is a small number (less
|
number of resource policies per region is a small number (less
|
||||||
@ -1022,7 +1014,9 @@ class LogsBackend(BaseBackend):
|
|||||||
def put_resource_policy(
|
def put_resource_policy(
|
||||||
self, policy_name: str, policy_doc: str
|
self, policy_name: str, policy_doc: str
|
||||||
) -> LogResourcePolicy:
|
) -> LogResourcePolicy:
|
||||||
"""Creates/updates resource policy and return policy object"""
|
"""
|
||||||
|
Creates/updates resource policy and return policy object
|
||||||
|
"""
|
||||||
if policy_name in self.resource_policies:
|
if policy_name in self.resource_policies:
|
||||||
policy = self.resource_policies[policy_name]
|
policy = self.resource_policies[policy_name]
|
||||||
policy.update(policy_doc)
|
policy.update(policy_doc)
|
||||||
@ -1034,7 +1028,9 @@ class LogsBackend(BaseBackend):
|
|||||||
return policy
|
return policy
|
||||||
|
|
||||||
def delete_resource_policy(self, policy_name: str) -> None:
|
def delete_resource_policy(self, policy_name: str) -> None:
|
||||||
"""Remove resource policy with a policy name matching given name."""
|
"""
|
||||||
|
Remove resource policy with a policy name matching given name.
|
||||||
|
"""
|
||||||
if policy_name not in self.resource_policies:
|
if policy_name not in self.resource_policies:
|
||||||
raise ResourceNotFoundException(
|
raise ResourceNotFoundException(
|
||||||
msg=f"Policy with name [{policy_name}] does not exist"
|
msg=f"Policy with name [{policy_name}] does not exist"
|
||||||
@ -1045,19 +1041,19 @@ class LogsBackend(BaseBackend):
|
|||||||
if log_group_name not in self.groups:
|
if log_group_name not in self.groups:
|
||||||
raise ResourceNotFoundException()
|
raise ResourceNotFoundException()
|
||||||
log_group = self.groups[log_group_name]
|
log_group = self.groups[log_group_name]
|
||||||
return log_group.list_tags()
|
return self.list_tags_for_resource(log_group.arn)
|
||||||
|
|
||||||
def tag_log_group(self, log_group_name: str, tags: Dict[str, str]) -> None:
|
def tag_log_group(self, log_group_name: str, tags: Dict[str, str]) -> None:
|
||||||
if log_group_name not in self.groups:
|
if log_group_name not in self.groups:
|
||||||
raise ResourceNotFoundException()
|
raise ResourceNotFoundException()
|
||||||
log_group = self.groups[log_group_name]
|
log_group = self.groups[log_group_name]
|
||||||
log_group.tag(tags)
|
self.tag_resource(log_group.arn, tags)
|
||||||
|
|
||||||
def untag_log_group(self, log_group_name: str, tags: List[str]) -> None:
|
def untag_log_group(self, log_group_name: str, tags: List[str]) -> None:
|
||||||
if log_group_name not in self.groups:
|
if log_group_name not in self.groups:
|
||||||
raise ResourceNotFoundException()
|
raise ResourceNotFoundException()
|
||||||
log_group = self.groups[log_group_name]
|
log_group = self.groups[log_group_name]
|
||||||
log_group.untag(tags)
|
self.untag_resource(log_group.arn, tags)
|
||||||
|
|
||||||
def put_metric_filter(
|
def put_metric_filter(
|
||||||
self,
|
self,
|
||||||
@ -1213,5 +1209,14 @@ class LogsBackend(BaseBackend):
|
|||||||
raise ResourceNotFoundException()
|
raise ResourceNotFoundException()
|
||||||
return str(mock_random.uuid4())
|
return str(mock_random.uuid4())
|
||||||
|
|
||||||
|
def list_tags_for_resource(self, resource_arn: str) -> Dict[str, str]:
|
||||||
|
return self.tagger.get_tag_dict_for_resource(resource_arn)
|
||||||
|
|
||||||
|
def tag_resource(self, arn: str, tags: Dict[str, str]) -> None:
|
||||||
|
self.tagger.tag_resource(arn, TaggingService.convert_dict_to_tags_input(tags))
|
||||||
|
|
||||||
|
def untag_resource(self, arn: str, tag_keys: List[str]) -> None:
|
||||||
|
self.tagger.untag_resource_using_names(arn, tag_keys)
|
||||||
|
|
||||||
|
|
||||||
logs_backends = BackendDict(LogsBackend, "logs")
|
logs_backends = BackendDict(LogsBackend, "logs")
|
||||||
|
@ -187,9 +187,13 @@ class LogsResponse(BaseResponse):
|
|||||||
destination_name = self._get_param("destinationName")
|
destination_name = self._get_param("destinationName")
|
||||||
role_arn = self._get_param("roleArn")
|
role_arn = self._get_param("roleArn")
|
||||||
target_arn = self._get_param("targetArn")
|
target_arn = self._get_param("targetArn")
|
||||||
|
tags = self._get_param("tags")
|
||||||
|
|
||||||
destination = self.logs_backend.put_destination(
|
destination = self.logs_backend.put_destination(
|
||||||
destination_name, role_arn, target_arn
|
destination_name,
|
||||||
|
role_arn,
|
||||||
|
target_arn,
|
||||||
|
tags,
|
||||||
)
|
)
|
||||||
result = {"destination": destination.to_dict()}
|
result = {"destination": destination.to_dict()}
|
||||||
return json.dumps(result)
|
return json.dumps(result)
|
||||||
@ -435,3 +439,20 @@ class LogsResponse(BaseResponse):
|
|||||||
log_group_name=log_group_name, destination=destination
|
log_group_name=log_group_name, destination=destination
|
||||||
)
|
)
|
||||||
return json.dumps(dict(taskId=str(task_id)))
|
return json.dumps(dict(taskId=str(task_id)))
|
||||||
|
|
||||||
|
def list_tags_for_resource(self) -> str:
|
||||||
|
resource_arn = self._get_param("resourceArn")
|
||||||
|
tags = self.logs_backend.list_tags_for_resource(resource_arn)
|
||||||
|
return json.dumps({"tags": tags})
|
||||||
|
|
||||||
|
def tag_resource(self) -> str:
|
||||||
|
resource_arn = self._get_param("resourceArn")
|
||||||
|
tags = self._get_param("tags")
|
||||||
|
self.logs_backend.tag_resource(resource_arn, tags)
|
||||||
|
return "{}"
|
||||||
|
|
||||||
|
def untag_resource(self) -> str:
|
||||||
|
resource_arn = self._get_param("resourceArn")
|
||||||
|
tag_keys = self._get_param("tagKeys")
|
||||||
|
self.logs_backend.untag_resource(resource_arn, tag_keys)
|
||||||
|
return "{}"
|
||||||
|
@ -10,6 +10,7 @@ from moto.elb.models import elb_backends, ELBBackend
|
|||||||
from moto.elbv2.models import elbv2_backends, ELBv2Backend
|
from moto.elbv2.models import elbv2_backends, ELBv2Backend
|
||||||
from moto.glue.models import glue_backends, GlueBackend
|
from moto.glue.models import glue_backends, GlueBackend
|
||||||
from moto.kinesis.models import kinesis_backends, KinesisBackend
|
from moto.kinesis.models import kinesis_backends, KinesisBackend
|
||||||
|
from moto.logs.models import logs_backends, LogsBackend
|
||||||
from moto.kms.models import kms_backends, KmsBackend
|
from moto.kms.models import kms_backends, KmsBackend
|
||||||
from moto.rds.models import rds_backends, RDSBackend
|
from moto.rds.models import rds_backends, RDSBackend
|
||||||
from moto.glacier.models import glacier_backends, GlacierBackend
|
from moto.glacier.models import glacier_backends, GlacierBackend
|
||||||
@ -31,7 +32,7 @@ class ResourceGroupsTaggingAPIBackend(BaseBackend):
|
|||||||
# Like 'someuuid': {'gen': <generator>, 'misc': None}
|
# Like 'someuuid': {'gen': <generator>, 'misc': None}
|
||||||
# Misc is there for peeking from a generator and it cant
|
# Misc is there for peeking from a generator and it cant
|
||||||
# fit in the current request. As we only store generators
|
# fit in the current request. As we only store generators
|
||||||
# theres not really any point to clean up
|
# there is really no point cleaning up
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def s3_backend(self) -> S3Backend:
|
def s3_backend(self) -> S3Backend:
|
||||||
@ -61,6 +62,10 @@ class ResourceGroupsTaggingAPIBackend(BaseBackend):
|
|||||||
def kms_backend(self) -> KmsBackend:
|
def kms_backend(self) -> KmsBackend:
|
||||||
return kms_backends[self.account_id][self.region_name]
|
return kms_backends[self.account_id][self.region_name]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def logs_backend(self) -> LogsBackend:
|
||||||
|
return logs_backends[self.account_id][self.region_name]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def rds_backend(self) -> RDSBackend:
|
def rds_backend(self) -> RDSBackend:
|
||||||
return rds_backends[self.account_id][self.region_name]
|
return rds_backends[self.account_id][self.region_name]
|
||||||
@ -101,7 +106,7 @@ class ResourceGroupsTaggingAPIBackend(BaseBackend):
|
|||||||
# Check key matches
|
# Check key matches
|
||||||
filters.append(lambda t, v, key=tag_filter_dict["Key"]: t == key)
|
filters.append(lambda t, v, key=tag_filter_dict["Key"]: t == key)
|
||||||
elif len(values) == 1:
|
elif len(values) == 1:
|
||||||
# Check its exactly the same as key, value
|
# Check it's exactly the same as key, value
|
||||||
filters.append(
|
filters.append(
|
||||||
lambda t, v, key=tag_filter_dict["Key"], value=values[0]: t == key # type: ignore
|
lambda t, v, key=tag_filter_dict["Key"], value=values[0]: t == key # type: ignore
|
||||||
and v == value
|
and v == value
|
||||||
@ -371,6 +376,21 @@ class ResourceGroupsTaggingAPIBackend(BaseBackend):
|
|||||||
|
|
||||||
yield {"ResourceARN": f"{kms_key.arn}", "Tags": tags}
|
yield {"ResourceARN": f"{kms_key.arn}", "Tags": tags}
|
||||||
|
|
||||||
|
# LOGS
|
||||||
|
if (
|
||||||
|
not resource_type_filters
|
||||||
|
or "logs" in resource_type_filters
|
||||||
|
or "logs:loggroup" in resource_type_filters
|
||||||
|
):
|
||||||
|
for group in self.logs_backend.groups.values():
|
||||||
|
log_tags = self.logs_backend.list_tags_for_resource(group.arn)
|
||||||
|
tags = format_tags(log_tags)
|
||||||
|
|
||||||
|
if not log_tags or not tag_filter(tags):
|
||||||
|
# Skip if no tags, or invalid filter
|
||||||
|
continue
|
||||||
|
yield {"ResourceARN": group.arn, "Tags": tags}
|
||||||
|
|
||||||
# RDS resources
|
# RDS resources
|
||||||
resource_map: Dict[str, Dict[str, Any]] = {
|
resource_map: Dict[str, Dict[str, Any]] = {
|
||||||
"rds:cluster": self.rds_backend.clusters,
|
"rds:cluster": self.rds_backend.clusters,
|
||||||
@ -733,7 +753,7 @@ class ResourceGroupsTaggingAPIBackend(BaseBackend):
|
|||||||
self, resource_arns: List[str], tags: Dict[str, str]
|
self, resource_arns: List[str], tags: Dict[str, str]
|
||||||
) -> Dict[str, Dict[str, Any]]:
|
) -> Dict[str, Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Only RDS resources are currently supported
|
Only Logs and RDS resources are currently supported
|
||||||
"""
|
"""
|
||||||
missing_resources = []
|
missing_resources = []
|
||||||
missing_error: Dict[str, Any] = {
|
missing_error: Dict[str, Any] = {
|
||||||
@ -746,6 +766,8 @@ class ResourceGroupsTaggingAPIBackend(BaseBackend):
|
|||||||
self.rds_backend.add_tags_to_resource(
|
self.rds_backend.add_tags_to_resource(
|
||||||
arn, TaggingService.convert_dict_to_tags_input(tags)
|
arn, TaggingService.convert_dict_to_tags_input(tags)
|
||||||
)
|
)
|
||||||
|
if arn.startswith("arn:aws:logs:"):
|
||||||
|
self.logs_backend.tag_resource(arn, tags)
|
||||||
else:
|
else:
|
||||||
missing_resources.append(arn)
|
missing_resources.append(arn)
|
||||||
return {arn: missing_error for arn in missing_resources}
|
return {arn: missing_error for arn in missing_resources}
|
||||||
|
@ -367,6 +367,13 @@ lambda:
|
|||||||
- TestAccLambdaFunctionURL_Alias
|
- TestAccLambdaFunctionURL_Alias
|
||||||
- TestAccLambdaFunctionURL_basic
|
- TestAccLambdaFunctionURL_basic
|
||||||
- TestAccLambdaFunctionURL_TwoURLs
|
- TestAccLambdaFunctionURL_TwoURLs
|
||||||
|
logs:
|
||||||
|
- TestAccLogsDestination_
|
||||||
|
- TestAccLogsGroupDataSource_basic
|
||||||
|
- TestAccLogsGroupsDataSource_basic
|
||||||
|
- TestAccLogsGroup_basic
|
||||||
|
- TestAccLogsGroup_tags
|
||||||
|
- TestAccLogsStream
|
||||||
meta:
|
meta:
|
||||||
- TestAccMetaBillingServiceAccountDataSource
|
- TestAccMetaBillingServiceAccountDataSource
|
||||||
mq:
|
mq:
|
||||||
|
@ -440,7 +440,7 @@ def test_create_log_group(kms_key_id):
|
|||||||
create_logs_params["kmsKeyId"] = kms_key_id
|
create_logs_params["kmsKeyId"] = kms_key_id
|
||||||
|
|
||||||
# When
|
# When
|
||||||
response = conn.create_log_group(**create_logs_params)
|
conn.create_log_group(**create_logs_params)
|
||||||
response = conn.describe_log_groups()
|
response = conn.describe_log_groups()
|
||||||
|
|
||||||
# Then
|
# Then
|
||||||
@ -992,16 +992,16 @@ def test_list_tags_log_group():
|
|||||||
log_group_name = "dummy"
|
log_group_name = "dummy"
|
||||||
tags = {"tag_key_1": "tag_value_1", "tag_key_2": "tag_value_2"}
|
tags = {"tag_key_1": "tag_value_1", "tag_key_2": "tag_value_2"}
|
||||||
|
|
||||||
response = conn.create_log_group(logGroupName=log_group_name)
|
conn.create_log_group(logGroupName=log_group_name)
|
||||||
response = conn.list_tags_log_group(logGroupName=log_group_name)
|
response = conn.list_tags_log_group(logGroupName=log_group_name)
|
||||||
assert response["tags"] == {}
|
assert response["tags"] == {}
|
||||||
|
|
||||||
response = conn.delete_log_group(logGroupName=log_group_name)
|
conn.delete_log_group(logGroupName=log_group_name)
|
||||||
response = conn.create_log_group(logGroupName=log_group_name, tags=tags)
|
conn.create_log_group(logGroupName=log_group_name, tags=tags)
|
||||||
response = conn.list_tags_log_group(logGroupName=log_group_name)
|
response = conn.list_tags_log_group(logGroupName=log_group_name)
|
||||||
assert response["tags"] == tags
|
assert response["tags"] == tags
|
||||||
|
|
||||||
response = conn.delete_log_group(logGroupName=log_group_name)
|
conn.delete_log_group(logGroupName=log_group_name)
|
||||||
|
|
||||||
|
|
||||||
@mock_logs
|
@mock_logs
|
||||||
@ -1009,47 +1009,43 @@ def test_tag_log_group():
|
|||||||
conn = boto3.client("logs", TEST_REGION)
|
conn = boto3.client("logs", TEST_REGION)
|
||||||
log_group_name = "dummy"
|
log_group_name = "dummy"
|
||||||
tags = {"tag_key_1": "tag_value_1"}
|
tags = {"tag_key_1": "tag_value_1"}
|
||||||
response = conn.create_log_group(logGroupName=log_group_name)
|
conn.create_log_group(logGroupName=log_group_name)
|
||||||
|
|
||||||
response = conn.tag_log_group(logGroupName=log_group_name, tags=tags)
|
conn.tag_log_group(logGroupName=log_group_name, tags=tags)
|
||||||
response = conn.list_tags_log_group(logGroupName=log_group_name)
|
response = conn.list_tags_log_group(logGroupName=log_group_name)
|
||||||
assert response["tags"] == tags
|
assert response["tags"] == tags
|
||||||
|
|
||||||
tags_with_added_value = {"tag_key_1": "tag_value_1", "tag_key_2": "tag_value_2"}
|
tags_with_added_value = {"tag_key_1": "tag_value_1", "tag_key_2": "tag_value_2"}
|
||||||
response = conn.tag_log_group(
|
conn.tag_log_group(logGroupName=log_group_name, tags={"tag_key_2": "tag_value_2"})
|
||||||
logGroupName=log_group_name, tags={"tag_key_2": "tag_value_2"}
|
|
||||||
)
|
|
||||||
response = conn.list_tags_log_group(logGroupName=log_group_name)
|
response = conn.list_tags_log_group(logGroupName=log_group_name)
|
||||||
assert response["tags"] == tags_with_added_value
|
assert response["tags"] == tags_with_added_value
|
||||||
|
|
||||||
tags_with_updated_value = {"tag_key_1": "tag_value_XX", "tag_key_2": "tag_value_2"}
|
tags_with_updated_value = {"tag_key_1": "tag_value_XX", "tag_key_2": "tag_value_2"}
|
||||||
response = conn.tag_log_group(
|
conn.tag_log_group(logGroupName=log_group_name, tags={"tag_key_1": "tag_value_XX"})
|
||||||
logGroupName=log_group_name, tags={"tag_key_1": "tag_value_XX"}
|
|
||||||
)
|
|
||||||
response = conn.list_tags_log_group(logGroupName=log_group_name)
|
response = conn.list_tags_log_group(logGroupName=log_group_name)
|
||||||
assert response["tags"] == tags_with_updated_value
|
assert response["tags"] == tags_with_updated_value
|
||||||
|
|
||||||
response = conn.delete_log_group(logGroupName=log_group_name)
|
conn.delete_log_group(logGroupName=log_group_name)
|
||||||
|
|
||||||
|
|
||||||
@mock_logs
|
@mock_logs
|
||||||
def test_untag_log_group():
|
def test_untag_log_group():
|
||||||
conn = boto3.client("logs", TEST_REGION)
|
conn = boto3.client("logs", TEST_REGION)
|
||||||
log_group_name = "dummy"
|
log_group_name = "dummy"
|
||||||
response = conn.create_log_group(logGroupName=log_group_name)
|
conn.create_log_group(logGroupName=log_group_name)
|
||||||
|
|
||||||
tags = {"tag_key_1": "tag_value_1", "tag_key_2": "tag_value_2"}
|
tags = {"tag_key_1": "tag_value_1", "tag_key_2": "tag_value_2"}
|
||||||
response = conn.tag_log_group(logGroupName=log_group_name, tags=tags)
|
conn.tag_log_group(logGroupName=log_group_name, tags=tags)
|
||||||
response = conn.list_tags_log_group(logGroupName=log_group_name)
|
response = conn.list_tags_log_group(logGroupName=log_group_name)
|
||||||
assert response["tags"] == tags
|
assert response["tags"] == tags
|
||||||
|
|
||||||
tags_to_remove = ["tag_key_1"]
|
tags_to_remove = ["tag_key_1"]
|
||||||
remaining_tags = {"tag_key_2": "tag_value_2"}
|
remaining_tags = {"tag_key_2": "tag_value_2"}
|
||||||
response = conn.untag_log_group(logGroupName=log_group_name, tags=tags_to_remove)
|
conn.untag_log_group(logGroupName=log_group_name, tags=tags_to_remove)
|
||||||
response = conn.list_tags_log_group(logGroupName=log_group_name)
|
response = conn.list_tags_log_group(logGroupName=log_group_name)
|
||||||
assert response["tags"] == remaining_tags
|
assert response["tags"] == remaining_tags
|
||||||
|
|
||||||
response = conn.delete_log_group(logGroupName=log_group_name)
|
conn.delete_log_group(logGroupName=log_group_name)
|
||||||
|
|
||||||
|
|
||||||
@mock_logs
|
@mock_logs
|
||||||
|
40
tests/test_logs/test_logs_tags.py
Normal file
40
tests/test_logs/test_logs_tags.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import boto3
|
||||||
|
|
||||||
|
from moto import mock_logs
|
||||||
|
|
||||||
|
|
||||||
|
@mock_logs
|
||||||
|
def test_destination_tags():
|
||||||
|
logs = boto3.client("logs", "us-west-2")
|
||||||
|
destination_name = "test-destination"
|
||||||
|
role_arn = "arn:aws:iam::123456789012:role/my-subscription-role"
|
||||||
|
target_arn = "arn:aws:kinesis:us-east-1:123456789012:stream/my-kinesis-stream"
|
||||||
|
|
||||||
|
destination_arn = logs.put_destination(
|
||||||
|
destinationName=destination_name,
|
||||||
|
targetArn=target_arn,
|
||||||
|
roleArn=role_arn,
|
||||||
|
tags={"key1": "val1"},
|
||||||
|
)["destination"]["arn"]
|
||||||
|
|
||||||
|
_verify_tag_operations(destination_arn, logs)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_logs
|
||||||
|
def test_log_groups_tags():
|
||||||
|
logs = boto3.client("logs", "us-west-2")
|
||||||
|
log_group_name = "test"
|
||||||
|
|
||||||
|
logs.create_log_group(logGroupName=log_group_name, tags={"key1": "val1"})
|
||||||
|
arn = logs.describe_log_groups()["logGroups"][0]["arn"]
|
||||||
|
|
||||||
|
_verify_tag_operations(arn, logs)
|
||||||
|
|
||||||
|
|
||||||
|
def _verify_tag_operations(arn, logs):
|
||||||
|
logs.tag_resource(resourceArn=arn, tags={"key2": "val2"})
|
||||||
|
tags = logs.list_tags_for_resource(resourceArn=arn)["tags"]
|
||||||
|
assert tags == {"key1": "val1", "key2": "val2"}
|
||||||
|
logs.untag_resource(resourceArn=arn, tagKeys=["key2"])
|
||||||
|
tags = logs.list_tags_for_resource(resourceArn=arn)["tags"]
|
||||||
|
assert tags == {"key1": "val1"}
|
@ -6,14 +6,13 @@ def test_log_group_to_describe_dict():
|
|||||||
# Given
|
# Given
|
||||||
region = "us-east-1"
|
region = "us-east-1"
|
||||||
name = "test-log-group"
|
name = "test-log-group"
|
||||||
tags = {"TestTag": "TestValue"}
|
|
||||||
kms_key_id = (
|
kms_key_id = (
|
||||||
"arn:aws:kms:us-east-1:000000000000:key/51d81fab-b138-4bd2-8a09-07fd6d37224d"
|
"arn:aws:kms:us-east-1:000000000000:key/51d81fab-b138-4bd2-8a09-07fd6d37224d"
|
||||||
)
|
)
|
||||||
kwargs = dict(kmsKeyId=kms_key_id)
|
kwargs = dict(kmsKeyId=kms_key_id)
|
||||||
|
|
||||||
# When
|
# When
|
||||||
log_group = LogGroup(DEFAULT_ACCOUNT_ID, region, name, tags, **kwargs)
|
log_group = LogGroup(DEFAULT_ACCOUNT_ID, region, name, **kwargs)
|
||||||
describe_dict = log_group.to_describe_dict()
|
describe_dict = log_group.to_describe_dict()
|
||||||
|
|
||||||
# Then
|
# Then
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
import boto3
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from moto import mock_logs
|
||||||
|
from moto import mock_resourcegroupstaggingapi
|
||||||
|
|
||||||
|
|
||||||
|
@mock_logs
|
||||||
|
@mock_resourcegroupstaggingapi
|
||||||
|
class TestLogsTagging(unittest.TestCase):
|
||||||
|
def setUp(self) -> None:
|
||||||
|
self.logs = boto3.client("logs", region_name="us-east-2")
|
||||||
|
self.rtapi = boto3.client("resourcegroupstaggingapi", region_name="us-east-2")
|
||||||
|
self.resources_tagged = []
|
||||||
|
self.resources_untagged = []
|
||||||
|
for i in range(3):
|
||||||
|
self.logs.create_log_group(logGroupName=f"test{i}", tags={"key1": "val1"})
|
||||||
|
self.arns = [lg["arn"] for lg in self.logs.describe_log_groups()["logGroups"]]
|
||||||
|
|
||||||
|
def test_get_resources_logs(self):
|
||||||
|
resp = self.rtapi.get_resources(ResourceTypeFilters=["logs"])
|
||||||
|
assert len(resp["ResourceTagMappingList"]) == 3
|
||||||
|
|
||||||
|
resp = self.rtapi.get_resources(ResourceTypeFilters=["logs:loggroup"])
|
||||||
|
assert len(resp["ResourceTagMappingList"]) == 3
|
||||||
|
|
||||||
|
def test_tag_resources_logs(self):
|
||||||
|
# WHEN
|
||||||
|
# we tag resources
|
||||||
|
self.rtapi.tag_resources(
|
||||||
|
ResourceARNList=self.arns,
|
||||||
|
Tags={"key2": "val2"},
|
||||||
|
)
|
||||||
|
|
||||||
|
# THEN
|
||||||
|
# we can retrieve the tags using the RDS API
|
||||||
|
def get_tags(arn):
|
||||||
|
return self.logs.list_tags_for_resource(resourceArn=arn)["tags"]
|
||||||
|
|
||||||
|
for arn in self.arns:
|
||||||
|
assert get_tags(arn) == {"key1": "val1", "key2": "val2"}
|
Loading…
Reference in New Issue
Block a user