Merge pull request #2859 from gmcrocetti/fix-rules-cfn

CloudFormation support for events
This commit is contained in:
Bert Blommers 2020-04-02 07:46:43 +01:00 committed by GitHub
commit c87ab973c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 88 additions and 33 deletions

View File

@ -431,7 +431,9 @@ class LogGroup(BaseModel):
properties = cloudformation_json["Properties"] properties = cloudformation_json["Properties"]
log_group_name = properties["LogGroupName"] log_group_name = properties["LogGroupName"]
tags = properties.get("Tags", {}) 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 = {} cloudwatch_backends = {}

View File

@ -26,6 +26,10 @@ class Rule(BaseModel):
self.role_arn = kwargs.get("RoleArn") self.role_arn = kwargs.get("RoleArn")
self.targets = [] 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 # 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. # with Python 2.6, so tracking it with an array it is.
def _check_target_exists(self, target_id): def _check_target_exists(self, target_id):
@ -59,6 +63,14 @@ class Rule(BaseModel):
if index is not None: if index is not None:
self.targets.pop(index) 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name

View File

@ -134,7 +134,7 @@ class LogStream:
return None, 0 return None, 0
events = sorted( 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) direction, index = get_index_and_direction_from_token(next_token)
@ -169,11 +169,7 @@ class LogStream:
if end_index > final_index: if end_index > final_index:
end_index = final_index end_index = final_index
elif end_index < 0: elif end_index < 0:
return ( return ([], "b/{:056d}".format(0), "f/{:056d}".format(0))
[],
"b/{:056d}".format(0),
"f/{:056d}".format(0),
)
events_page = [ events_page = [
event.to_response_dict() for event in events[start_index : end_index + 1] event.to_response_dict() for event in events[start_index : end_index + 1]
@ -219,7 +215,7 @@ class LogStream:
class LogGroup: class LogGroup:
def __init__(self, region, name, tags): def __init__(self, region, name, tags, **kwargs):
self.name = name self.name = name
self.region = region self.region = region
self.arn = "arn:aws:logs:{region}:1:log-group:{log_group}".format( 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.creationTime = int(unix_time_millis())
self.tags = tags self.tags = tags
self.streams = dict() # {name: LogStream} self.streams = dict() # {name: LogStream}
self.retentionInDays = ( self.retention_in_days = kwargs.get(
None # AWS defaults to Never Expire for log group retention "RetentionInDays"
) ) # AWS defaults to Never Expire for log group retention
def create_log_stream(self, log_stream_name): def create_log_stream(self, log_stream_name):
if log_stream_name in self.streams: if log_stream_name in self.streams:
@ -368,12 +364,12 @@ class LogGroup:
"storedBytes": sum(s.storedBytes for s in self.streams.values()), "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) # AWS only returns retentionInDays if a value is set for the log group (ie. not Never Expire)
if self.retentionInDays: if self.retention_in_days:
log_group["retentionInDays"] = self.retentionInDays log_group["retentionInDays"] = self.retention_in_days
return log_group return log_group
def set_retention_policy(self, retention_in_days): def set_retention_policy(self, retention_in_days):
self.retentionInDays = retention_in_days self.retention_in_days = retention_in_days
def list_tags(self): def list_tags(self):
return self.tags if self.tags else {} return self.tags if self.tags else {}
@ -401,10 +397,12 @@ class LogsBackend(BaseBackend):
self.__dict__ = {} self.__dict__ = {}
self.__init__(region_name) 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: if log_group_name in self.groups:
raise ResourceAlreadyExistsException() 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] return self.groups[log_group_name]
def ensure_log_group(self, log_group_name, tags): def ensure_log_group(self, log_group_name, tags):

View File

@ -2373,13 +2373,12 @@ def test_create_log_group_using_fntransform():
} }
cf_conn = boto3.client("cloudformation", "us-west-2") cf_conn = boto3.client("cloudformation", "us-west-2")
cf_conn.create_stack( cf_conn.create_stack(StackName="test_stack", TemplateBody=json.dumps(template))
StackName="test_stack", TemplateBody=json.dumps(template),
)
logs_conn = boto3.client("logs", region_name="us-west-2") logs_conn = boto3.client("logs", region_name="us-west-2")
log_group = logs_conn.describe_log_groups()["logGroups"][0] log_group = logs_conn.describe_log_groups()["logGroups"][0]
log_group["logGroupName"].should.equal("some-log-group") log_group["logGroupName"].should.equal("some-log-group")
log_group["retentionInDays"].should.be.equal(90)
@mock_cloudformation @mock_cloudformation
@ -2400,7 +2399,7 @@ def test_stack_events_create_rule_integration():
} }
cf_conn = boto3.client("cloudformation", "us-west-2") cf_conn = boto3.client("cloudformation", "us-west-2")
cf_conn.create_stack( 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() rules = boto3.client("events", "us-west-2").list_rules()
@ -2428,7 +2427,7 @@ def test_stack_events_delete_rule_integration():
} }
cf_conn = boto3.client("cloudformation", "us-west-2") cf_conn = boto3.client("cloudformation", "us-west-2")
cf_conn.create_stack( 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() rules = boto3.client("events", "us-west-2").list_rules()
@ -2457,8 +2456,45 @@ def test_stack_events_create_rule_without_name_integration():
} }
cf_conn = boto3.client("cloudformation", "us-west-2") cf_conn = boto3.client("cloudformation", "us-west-2")
cf_conn.create_stack( 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() rules = boto3.client("events", "us-west-2").list_rules()
rules["Rules"][0]["Name"].should.contain("test_stack-Event-") 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)

View File

@ -79,13 +79,23 @@ def generate_environment():
@mock_events @mock_events
def test_put_rule(): def test_put_rule():
client = boto3.client("events", "us-west-2") client = boto3.client("events", "us-west-2")
client.list_rules()["Rules"].should.have.length_of(0) 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.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 @mock_events

View File

@ -12,17 +12,14 @@ _logs_region = "us-east-1" if settings.TEST_SERVER_MODE else "us-west-2"
@mock_logs @mock_logs
def test_log_group_create(): def test_create_log_group():
conn = boto3.client("logs", "us-west-2") 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) response = conn.create_log_group(logGroupName="dummy")
assert len(response["logGroups"]) == 1 response = conn.describe_log_groups()
# AWS defaults to Never Expire for log group retention
assert response["logGroups"][0].get("retentionInDays") == None
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 @mock_logs