From af08d71310862727669cb1a5041df64472857191 Mon Sep 17 00:00:00 2001 From: Guilherme Martins Crocetti Date: Wed, 1 Apr 2020 21:57:46 -0300 Subject: [PATCH 1/5] add support for RetentionInDays for LogGroup --- moto/cloudwatch/models.py | 2 +- moto/logs/models.py | 28 +++++++++---------- .../test_cloudformation_stack_integration.py | 1 + tests/test_logs/test_logs.py | 13 ++++----- 4 files changed, 20 insertions(+), 24 deletions(-) diff --git a/moto/cloudwatch/models.py b/moto/cloudwatch/models.py index a8a1b1d19..4cd4df156 100644 --- a/moto/cloudwatch/models.py +++ b/moto/cloudwatch/models.py @@ -431,7 +431,7 @@ class LogGroup(BaseModel): properties = cloudformation_json["Properties"] log_group_name = properties["LogGroupName"] tags = properties.get("Tags", {}) - return logs_backends[region_name].create_log_group(log_group_name, tags) + return logs_backends[region_name].create_log_group(log_group_name, tags, **properties) cloudwatch_backends = {} diff --git a/moto/logs/models.py b/moto/logs/models.py index 5e21d8793..755605734 100644 --- a/moto/logs/models.py +++ b/moto/logs/models.py @@ -134,7 +134,7 @@ class LogStream: return None, 0 events = sorted( - filter(filter_func, self.events), key=lambda event: event.timestamp, + filter(filter_func, self.events), key=lambda event: event.timestamp ) direction, index = get_index_and_direction_from_token(next_token) @@ -169,11 +169,7 @@ class LogStream: if end_index > final_index: end_index = final_index elif end_index < 0: - return ( - [], - "b/{:056d}".format(0), - "f/{:056d}".format(0), - ) + return ([], "b/{:056d}".format(0), "f/{:056d}".format(0)) events_page = [ event.to_response_dict() for event in events[start_index : end_index + 1] @@ -219,7 +215,7 @@ class LogStream: class LogGroup: - def __init__(self, region, name, tags): + def __init__(self, region, name, tags, **kwargs): self.name = name self.region = region self.arn = "arn:aws:logs:{region}:1:log-group:{log_group}".format( @@ -228,9 +224,9 @@ class LogGroup: self.creationTime = int(unix_time_millis()) self.tags = tags self.streams = dict() # {name: LogStream} - self.retentionInDays = ( - None # AWS defaults to Never Expire for log group retention - ) + self.retention_in_days = kwargs.get( + "RetentionInDays" + ) # AWS defaults to Never Expire for log group retention def create_log_stream(self, log_stream_name): if log_stream_name in self.streams: @@ -368,12 +364,12 @@ class LogGroup: "storedBytes": sum(s.storedBytes for s in self.streams.values()), } # AWS only returns retentionInDays if a value is set for the log group (ie. not Never Expire) - if self.retentionInDays: - log_group["retentionInDays"] = self.retentionInDays + if self.retention_in_days: + log_group["retentionInDays"] = self.retention_in_days return log_group def set_retention_policy(self, retention_in_days): - self.retentionInDays = retention_in_days + self.retention_in_days = retention_in_days def list_tags(self): return self.tags if self.tags else {} @@ -401,10 +397,12 @@ class LogsBackend(BaseBackend): self.__dict__ = {} self.__init__(region_name) - def create_log_group(self, log_group_name, tags): + def create_log_group(self, log_group_name, tags, **kwargs): if log_group_name in self.groups: raise ResourceAlreadyExistsException() - self.groups[log_group_name] = LogGroup(self.region_name, log_group_name, tags) + self.groups[log_group_name] = LogGroup( + self.region_name, log_group_name, tags, **kwargs + ) return self.groups[log_group_name] def ensure_log_group(self, log_group_name, tags): diff --git a/tests/test_cloudformation/test_cloudformation_stack_integration.py b/tests/test_cloudformation/test_cloudformation_stack_integration.py index e50179660..b7fe580da 100644 --- a/tests/test_cloudformation/test_cloudformation_stack_integration.py +++ b/tests/test_cloudformation/test_cloudformation_stack_integration.py @@ -2380,6 +2380,7 @@ def test_create_log_group_using_fntransform(): logs_conn = boto3.client("logs", region_name="us-west-2") log_group = logs_conn.describe_log_groups()["logGroups"][0] log_group["logGroupName"].should.equal("some-log-group") + log_group["retentionInDays"].should.be.equal(90) @mock_cloudformation diff --git a/tests/test_logs/test_logs.py b/tests/test_logs/test_logs.py index e8f60ff03..2429d7e93 100644 --- a/tests/test_logs/test_logs.py +++ b/tests/test_logs/test_logs.py @@ -12,17 +12,14 @@ _logs_region = "us-east-1" if settings.TEST_SERVER_MODE else "us-west-2" @mock_logs -def test_log_group_create(): +def test_create_log_group(): conn = boto3.client("logs", "us-west-2") - log_group_name = "dummy" - response = conn.create_log_group(logGroupName=log_group_name) - response = conn.describe_log_groups(logGroupNamePrefix=log_group_name) - assert len(response["logGroups"]) == 1 - # AWS defaults to Never Expire for log group retention - assert response["logGroups"][0].get("retentionInDays") == None + response = conn.create_log_group(logGroupName="dummy") + response = conn.describe_log_groups() - response = conn.delete_log_group(logGroupName=log_group_name) + response["logGroups"].should.have.length_of(1) + response["logGroups"][0].should_not.have.key("retentionInDays") @mock_logs From c15ca133b85a228060489758ae76c75583bd4c65 Mon Sep 17 00:00:00 2001 From: Guilherme Martins Crocetti Date: Wed, 1 Apr 2020 22:00:20 -0300 Subject: [PATCH 2/5] add support for Fn::GetAtt in event's cloudformation --- moto/events/models.py | 8 ++++ .../test_cloudformation_stack_integration.py | 37 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/moto/events/models.py b/moto/events/models.py index f68b63e38..3a6f1bbc7 100644 --- a/moto/events/models.py +++ b/moto/events/models.py @@ -59,6 +59,14 @@ class Rule(BaseModel): if index is not None: self.targets.pop(index) + def get_cfn_attribute(self, attribute_name): + from moto.cloudformation.exceptions import UnformattedGetAttTemplateException + + if attribute_name == "Arn": + return self.arn + + raise UnformattedGetAttTemplateException() + @classmethod def create_from_cloudformation_json( cls, resource_name, cloudformation_json, region_name diff --git a/tests/test_cloudformation/test_cloudformation_stack_integration.py b/tests/test_cloudformation/test_cloudformation_stack_integration.py index b7fe580da..94367f1dc 100644 --- a/tests/test_cloudformation/test_cloudformation_stack_integration.py +++ b/tests/test_cloudformation/test_cloudformation_stack_integration.py @@ -2463,3 +2463,40 @@ def test_stack_events_create_rule_without_name_integration(): rules = boto3.client("events", "us-west-2").list_rules() rules["Rules"][0]["Name"].should.contain("test_stack-Event-") + + +@mock_cloudformation +@mock_events +@mock_logs +def test_stack_events_create_rule_as_target(): + events_template = { + "AWSTemplateFormatVersion": "2010-09-09", + "Resources": { + "SecurityGroup": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "LogGroupName": {"Fn::GetAtt": ["Event", "Arn"]}, + "RetentionInDays": 3, + } + }, + "Event": { + "Type": "AWS::Events::Rule", + "Properties": { + "State": "ENABLED", + "ScheduleExpression": "rate(5 minutes)", + }, + } + }, + } + cf_conn = boto3.client("cloudformation", "us-west-2") + cf_conn.create_stack( + StackName="test_stack", TemplateBody=json.dumps(events_template), + ) + + rules = boto3.client("events", "us-west-2").list_rules() + log_groups = boto3.client("logs", "us-west-2").describe_log_groups() + + rules["Rules"][0]["Name"].should.contain("test_stack-Event-") + + log_groups["logGroups"][0]["logGroupName"].should.equal(rules["Rules"][0]["Arn"]) + log_groups["logGroups"][0]["retentionInDays"].should.equal(3) From c25f6a72da03e2ee9fd939bded406ae7f56f2339 Mon Sep 17 00:00:00 2001 From: Guilherme Martins Crocetti Date: Wed, 1 Apr 2020 22:11:50 -0300 Subject: [PATCH 3/5] refactor put_rule test --- tests/test_events/test_events.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/tests/test_events/test_events.py b/tests/test_events/test_events.py index 27006ff1b..5b4e958d6 100644 --- a/tests/test_events/test_events.py +++ b/tests/test_events/test_events.py @@ -79,13 +79,23 @@ def generate_environment(): @mock_events def test_put_rule(): client = boto3.client("events", "us-west-2") - client.list_rules()["Rules"].should.have.length_of(0) - rule_data = get_random_rule() + rule_data = { + "Name": "my-event", + "ScheduleExpression": "rate(5 minutes)", + "EventPattern": '{"source": ["test-source"]}', + } + client.put_rule(**rule_data) - client.list_rules()["Rules"].should.have.length_of(1) + rules = client.list_rules()["Rules"] + + rules.should.have.length_of(1) + rules[0]["Name"].should.equal(rule_data["Name"]) + rules[0]["ScheduleExpression"].should.equal(rule_data["ScheduleExpression"]) + rules[0]["EventPattern"].should.equal(rule_data["EventPattern"]) + rules[0]["State"].should.equal("ENABLED") @mock_events From 759107445394efe2e169935dfc6ae161f898aec4 Mon Sep 17 00:00:00 2001 From: Guilherme Martins Crocetti Date: Wed, 1 Apr 2020 22:12:17 -0300 Subject: [PATCH 4/5] add physical_resource_id support for Rule --- moto/events/models.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/moto/events/models.py b/moto/events/models.py index 3a6f1bbc7..e1224242e 100644 --- a/moto/events/models.py +++ b/moto/events/models.py @@ -26,6 +26,10 @@ class Rule(BaseModel): self.role_arn = kwargs.get("RoleArn") self.targets = [] + @property + def physical_resource_id(self): + return self.name + # This song and dance for targets is because we need order for Limits and NextTokens, but can't use OrderedDicts # with Python 2.6, so tracking it with an array it is. def _check_target_exists(self, target_id): From 503eeb51aea4391012ef022f51347a9276d010be Mon Sep 17 00:00:00 2001 From: Guilherme Martins Crocetti Date: Wed, 1 Apr 2020 22:48:40 -0300 Subject: [PATCH 5/5] style with black --- moto/cloudwatch/models.py | 4 +++- .../test_cloudformation_stack_integration.py | 16 +++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/moto/cloudwatch/models.py b/moto/cloudwatch/models.py index 4cd4df156..bc941809b 100644 --- a/moto/cloudwatch/models.py +++ b/moto/cloudwatch/models.py @@ -431,7 +431,9 @@ class LogGroup(BaseModel): properties = cloudformation_json["Properties"] log_group_name = properties["LogGroupName"] tags = properties.get("Tags", {}) - return logs_backends[region_name].create_log_group(log_group_name, tags, **properties) + return logs_backends[region_name].create_log_group( + log_group_name, tags, **properties + ) cloudwatch_backends = {} diff --git a/tests/test_cloudformation/test_cloudformation_stack_integration.py b/tests/test_cloudformation/test_cloudformation_stack_integration.py index 94367f1dc..c99bf16f4 100644 --- a/tests/test_cloudformation/test_cloudformation_stack_integration.py +++ b/tests/test_cloudformation/test_cloudformation_stack_integration.py @@ -2373,9 +2373,7 @@ def test_create_log_group_using_fntransform(): } cf_conn = boto3.client("cloudformation", "us-west-2") - cf_conn.create_stack( - StackName="test_stack", TemplateBody=json.dumps(template), - ) + cf_conn.create_stack(StackName="test_stack", TemplateBody=json.dumps(template)) logs_conn = boto3.client("logs", region_name="us-west-2") log_group = logs_conn.describe_log_groups()["logGroups"][0] @@ -2401,7 +2399,7 @@ def test_stack_events_create_rule_integration(): } cf_conn = boto3.client("cloudformation", "us-west-2") cf_conn.create_stack( - StackName="test_stack", TemplateBody=json.dumps(events_template), + StackName="test_stack", TemplateBody=json.dumps(events_template) ) rules = boto3.client("events", "us-west-2").list_rules() @@ -2429,7 +2427,7 @@ def test_stack_events_delete_rule_integration(): } cf_conn = boto3.client("cloudformation", "us-west-2") cf_conn.create_stack( - StackName="test_stack", TemplateBody=json.dumps(events_template), + StackName="test_stack", TemplateBody=json.dumps(events_template) ) rules = boto3.client("events", "us-west-2").list_rules() @@ -2458,7 +2456,7 @@ def test_stack_events_create_rule_without_name_integration(): } cf_conn = boto3.client("cloudformation", "us-west-2") cf_conn.create_stack( - StackName="test_stack", TemplateBody=json.dumps(events_template), + StackName="test_stack", TemplateBody=json.dumps(events_template) ) rules = boto3.client("events", "us-west-2").list_rules() @@ -2477,7 +2475,7 @@ def test_stack_events_create_rule_as_target(): "Properties": { "LogGroupName": {"Fn::GetAtt": ["Event", "Arn"]}, "RetentionInDays": 3, - } + }, }, "Event": { "Type": "AWS::Events::Rule", @@ -2485,12 +2483,12 @@ def test_stack_events_create_rule_as_target(): "State": "ENABLED", "ScheduleExpression": "rate(5 minutes)", }, - } + }, }, } cf_conn = boto3.client("cloudformation", "us-west-2") cf_conn.create_stack( - StackName="test_stack", TemplateBody=json.dumps(events_template), + StackName="test_stack", TemplateBody=json.dumps(events_template) ) rules = boto3.client("events", "us-west-2").list_rules()