Merge pull request #2821 from bblommers/feature/2255
CloudFormation - Add Support for FN:Transform and AWS:Include
This commit is contained in:
commit
94f85902bc
@ -1,5 +1,6 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import functools
|
import functools
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import copy
|
import copy
|
||||||
import warnings
|
import warnings
|
||||||
@ -24,7 +25,8 @@ from moto.rds import models as rds_models
|
|||||||
from moto.rds2 import models as rds2_models
|
from moto.rds2 import models as rds2_models
|
||||||
from moto.redshift import models as redshift_models
|
from moto.redshift import models as redshift_models
|
||||||
from moto.route53 import models as route53_models
|
from moto.route53 import models as route53_models
|
||||||
from moto.s3 import models as s3_models
|
from moto.s3 import models as s3_models, s3_backend
|
||||||
|
from moto.s3.utils import bucket_and_name_from_url
|
||||||
from moto.sns import models as sns_models
|
from moto.sns import models as sns_models
|
||||||
from moto.sqs import models as sqs_models
|
from moto.sqs import models as sqs_models
|
||||||
from moto.core import ACCOUNT_ID
|
from moto.core import ACCOUNT_ID
|
||||||
@ -150,7 +152,10 @@ def clean_json(resource_json, resources_map):
|
|||||||
map_path = resource_json["Fn::FindInMap"][1:]
|
map_path = resource_json["Fn::FindInMap"][1:]
|
||||||
result = resources_map[map_name]
|
result = resources_map[map_name]
|
||||||
for path in map_path:
|
for path in map_path:
|
||||||
result = result[clean_json(path, resources_map)]
|
if "Fn::Transform" in result:
|
||||||
|
result = resources_map[clean_json(path, resources_map)]
|
||||||
|
else:
|
||||||
|
result = result[clean_json(path, resources_map)]
|
||||||
return result
|
return result
|
||||||
|
|
||||||
if "Fn::GetAtt" in resource_json:
|
if "Fn::GetAtt" in resource_json:
|
||||||
@ -470,6 +475,17 @@ class ResourceMap(collections_abc.Mapping):
|
|||||||
def load_mapping(self):
|
def load_mapping(self):
|
||||||
self._parsed_resources.update(self._template.get("Mappings", {}))
|
self._parsed_resources.update(self._template.get("Mappings", {}))
|
||||||
|
|
||||||
|
def transform_mapping(self):
|
||||||
|
for k, v in self._template.get("Mappings", {}).items():
|
||||||
|
if "Fn::Transform" in v:
|
||||||
|
name = v["Fn::Transform"]["Name"]
|
||||||
|
params = v["Fn::Transform"]["Parameters"]
|
||||||
|
if name == "AWS::Include":
|
||||||
|
location = params["Location"]
|
||||||
|
bucket_name, name = bucket_and_name_from_url(location)
|
||||||
|
key = s3_backend.get_key(bucket_name, name)
|
||||||
|
self._parsed_resources.update(json.loads(key.value))
|
||||||
|
|
||||||
def load_parameters(self):
|
def load_parameters(self):
|
||||||
parameter_slots = self._template.get("Parameters", {})
|
parameter_slots = self._template.get("Parameters", {})
|
||||||
for parameter_name, parameter in parameter_slots.items():
|
for parameter_name, parameter in parameter_slots.items():
|
||||||
@ -515,6 +531,7 @@ class ResourceMap(collections_abc.Mapping):
|
|||||||
|
|
||||||
def create(self):
|
def create(self):
|
||||||
self.load_mapping()
|
self.load_mapping()
|
||||||
|
self.transform_mapping()
|
||||||
self.load_parameters()
|
self.load_parameters()
|
||||||
self.load_conditions()
|
self.load_conditions()
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ from boto3 import Session
|
|||||||
from moto.core.utils import iso_8601_datetime_without_milliseconds
|
from moto.core.utils import iso_8601_datetime_without_milliseconds
|
||||||
from moto.core import BaseBackend, BaseModel
|
from moto.core import BaseBackend, BaseModel
|
||||||
from moto.core.exceptions import RESTError
|
from moto.core.exceptions import RESTError
|
||||||
|
from moto.logs import logs_backends
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from dateutil.tz import tzutc
|
from dateutil.tz import tzutc
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
@ -428,12 +429,9 @@ class LogGroup(BaseModel):
|
|||||||
cls, resource_name, cloudformation_json, region_name
|
cls, resource_name, cloudformation_json, region_name
|
||||||
):
|
):
|
||||||
properties = cloudformation_json["Properties"]
|
properties = cloudformation_json["Properties"]
|
||||||
spec = {"LogGroupName": properties["LogGroupName"]}
|
log_group_name = properties["LogGroupName"]
|
||||||
optional_properties = "Tags".split()
|
tags = properties.get("Tags", {})
|
||||||
for prop in optional_properties:
|
return logs_backends[region_name].create_log_group(log_group_name, tags)
|
||||||
if prop in properties:
|
|
||||||
spec[prop] = properties[prop]
|
|
||||||
return LogGroup(spec)
|
|
||||||
|
|
||||||
|
|
||||||
cloudwatch_backends = {}
|
cloudwatch_backends = {}
|
||||||
|
@ -405,6 +405,7 @@ class LogsBackend(BaseBackend):
|
|||||||
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)
|
||||||
|
return self.groups[log_group_name]
|
||||||
|
|
||||||
def ensure_log_group(self, log_group_name, tags):
|
def ensure_log_group(self, log_group_name, tags):
|
||||||
if log_group_name in self.groups:
|
if log_group_name in self.groups:
|
||||||
|
@ -35,6 +35,17 @@ def bucket_name_from_url(url):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# 'owi-common-cf', 'snippets/test.json' = bucket_and_name_from_url('s3://owi-common-cf/snippets/test.json')
|
||||||
|
def bucket_and_name_from_url(url):
|
||||||
|
prefix = "s3://"
|
||||||
|
if url.startswith(prefix):
|
||||||
|
bucket_name = url[len(prefix) : url.index("/", len(prefix))]
|
||||||
|
key = url[url.index("/", len(prefix)) + 1 :]
|
||||||
|
return bucket_name, key
|
||||||
|
else:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
REGION_URL_REGEX = re.compile(
|
REGION_URL_REGEX = re.compile(
|
||||||
r"^https?://(s3[-\.](?P<region1>.+)\.amazonaws\.com/(.+)|"
|
r"^https?://(s3[-\.](?P<region1>.+)\.amazonaws\.com/(.+)|"
|
||||||
r"(.+)\.s3[-\.](?P<region2>.+)\.amazonaws\.com)/?"
|
r"(.+)\.s3[-\.](?P<region2>.+)\.amazonaws\.com)/?"
|
||||||
|
@ -32,12 +32,14 @@ from moto import (
|
|||||||
mock_iam_deprecated,
|
mock_iam_deprecated,
|
||||||
mock_kms,
|
mock_kms,
|
||||||
mock_lambda,
|
mock_lambda,
|
||||||
|
mock_logs,
|
||||||
mock_rds_deprecated,
|
mock_rds_deprecated,
|
||||||
mock_rds2,
|
mock_rds2,
|
||||||
mock_rds2_deprecated,
|
mock_rds2_deprecated,
|
||||||
mock_redshift,
|
mock_redshift,
|
||||||
mock_redshift_deprecated,
|
mock_redshift_deprecated,
|
||||||
mock_route53_deprecated,
|
mock_route53_deprecated,
|
||||||
|
mock_s3,
|
||||||
mock_sns_deprecated,
|
mock_sns_deprecated,
|
||||||
mock_sqs,
|
mock_sqs,
|
||||||
mock_sqs_deprecated,
|
mock_sqs_deprecated,
|
||||||
@ -2332,3 +2334,48 @@ def test_stack_dynamodb_resources_integration():
|
|||||||
response["Item"]["Sales"].should.equal(Decimal("10"))
|
response["Item"]["Sales"].should.equal(Decimal("10"))
|
||||||
response["Item"]["NumberOfSongs"].should.equal(Decimal("5"))
|
response["Item"]["NumberOfSongs"].should.equal(Decimal("5"))
|
||||||
response["Item"]["Album"].should.equal("myAlbum")
|
response["Item"]["Album"].should.equal("myAlbum")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_cloudformation
|
||||||
|
@mock_logs
|
||||||
|
@mock_s3
|
||||||
|
def test_create_log_group_using_fntransform():
|
||||||
|
s3_resource = boto3.resource("s3")
|
||||||
|
s3_resource.create_bucket(
|
||||||
|
Bucket="owi-common-cf",
|
||||||
|
CreateBucketConfiguration={"LocationConstraint": "us-west-2"},
|
||||||
|
)
|
||||||
|
s3_resource.Object("owi-common-cf", "snippets/test.json").put(
|
||||||
|
Body=json.dumps({"lgname": {"name": "some-log-group"}})
|
||||||
|
)
|
||||||
|
template = {
|
||||||
|
"AWSTemplateFormatVersion": "2010-09-09",
|
||||||
|
"Mappings": {
|
||||||
|
"EnvironmentMapping": {
|
||||||
|
"Fn::Transform": {
|
||||||
|
"Name": "AWS::Include",
|
||||||
|
"Parameters": {"Location": "s3://owi-common-cf/snippets/test.json"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Resources": {
|
||||||
|
"LogGroup": {
|
||||||
|
"Properties": {
|
||||||
|
"LogGroupName": {
|
||||||
|
"Fn::FindInMap": ["EnvironmentMapping", "lgname", "name"]
|
||||||
|
},
|
||||||
|
"RetentionInDays": 90,
|
||||||
|
},
|
||||||
|
"Type": "AWS::Logs::LogGroup",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cf_conn = boto3.client("cloudformation", "us-west-2")
|
||||||
|
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]
|
||||||
|
log_group["logGroupName"].should.equal("some-log-group")
|
||||||
|
Loading…
Reference in New Issue
Block a user