Skip to content

Commit

Permalink
Serverless Apps Repo introduced a Resource called ServerlessApplicati…
Browse files Browse the repository at this point in the history
…on. Locally, this resource can point to a local template file to enable local development. The aws cloudformation package command will now support packaging of this resource's local template file and upload it to s3.
  • Loading branch information
Wujing Yao authored and stealthycoin committed Nov 29, 2018
1 parent 41b137f commit da5668f
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 4 deletions.
16 changes: 13 additions & 3 deletions awscli/customizations/cloudformation/artifact_exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,15 @@ def do_export(self, resource_id, resource_dict, parent_dir):
parts["Key"], parts.get("Version", None))


class ServerlessApplicationResource(CloudFormationStackResource):
"""
Represents Serverless::Application resource that can refer to a nested
app template via Location property.
"""
RESOURCE_TYPE = "AWS::Serverless::Application"
PROPERTY_NAME = "Location"


EXPORT_LIST = [
ServerlessFunctionResource,
ServerlessApiResource,
Expand All @@ -429,7 +438,8 @@ def do_export(self, resource_id, resource_dict, parent_dir):
ApiGatewayRestApiResource,
LambdaFunctionResource,
ElasticBeanstalkApplicationVersion,
CloudFormationStackResource
CloudFormationStackResource,
ServerlessApplicationResource
]

def include_transform_export_handler(template_dict, uploader):
Expand Down Expand Up @@ -474,9 +484,9 @@ def __init__(self, template_path, parent_dir, uploader,

def export_global_artifacts(self, template_dict):
"""
Template params such as AWS::Include transforms are not specific to
Template params such as AWS::Include transforms are not specific to
any resource type but contain artifacts that should be exported,
here we iterate through the template dict and export params with a
here we iterate through the template dict and export params with a
handler defined in GLOBAL_EXPORT_DICT
"""
for key, val in template_dict.items():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
ServerlessFunctionResource, GraphQLSchemaResource, \
LambdaFunctionResource, ApiGatewayRestApiResource, \
ElasticBeanstalkApplicationVersion, CloudFormationStackResource, \
ServerlessApplicationResource, \
copy_to_temp_dir, include_transform_export_handler, GLOBAL_EXPORT_DICT


Expand Down Expand Up @@ -707,6 +708,96 @@ def test_export_cloudformation_stack_no_upload_path_not_file(self):
stack_resource.export(resource_id, resource_dict, "dir")
self.s3_uploader_mock.upload_with_dedup.assert_not_called()

@patch("awscli.customizations.cloudformation.artifact_exporter.Template")
def test_export_serverless_application(self, TemplateMock):
stack_resource = ServerlessApplicationResource(self.s3_uploader_mock)

resource_id = "id"
property_name = stack_resource.PROPERTY_NAME
exported_template_dict = {"foo": "bar"}
result_s3_url = "s3://hello/world"
result_path_style_s3_url = "http://s3.amazonws.com/hello/world"

template_instance_mock = Mock()
TemplateMock.return_value = template_instance_mock
template_instance_mock.export.return_value = exported_template_dict

self.s3_uploader_mock.upload_with_dedup.return_value = result_s3_url
self.s3_uploader_mock.to_path_style_s3_url.return_value = result_path_style_s3_url

with tempfile.NamedTemporaryFile() as handle:
template_path = handle.name
resource_dict = {property_name: template_path}
parent_dir = tempfile.gettempdir()

stack_resource.export(resource_id, resource_dict, parent_dir)

self.assertEquals(resource_dict[property_name], result_path_style_s3_url)

TemplateMock.assert_called_once_with(template_path, parent_dir, self.s3_uploader_mock)
template_instance_mock.export.assert_called_once_with()
self.s3_uploader_mock.upload_with_dedup.assert_called_once_with(mock.ANY, "template")
self.s3_uploader_mock.to_path_style_s3_url.assert_called_once_with("world", None)

def test_export_serverless_application_no_upload_path_is_s3url(self):
stack_resource = ServerlessApplicationResource(self.s3_uploader_mock)
resource_id = "id"
property_name = stack_resource.PROPERTY_NAME
s3_url = "s3://hello/world"
resource_dict = {property_name: s3_url}

# Case 1: Path is already S3 url
stack_resource.export(resource_id, resource_dict, "dir")
self.assertEquals(resource_dict[property_name], s3_url)
self.s3_uploader_mock.upload_with_dedup.assert_not_called()

def test_export_serverless_application_no_upload_path_is_httpsurl(self):
stack_resource = ServerlessApplicationResource(self.s3_uploader_mock)
resource_id = "id"
property_name = stack_resource.PROPERTY_NAME
s3_url = "https://s3.amazonaws.com/hello/world"
resource_dict = {property_name: s3_url}

# Case 1: Path is already S3 url
stack_resource.export(resource_id, resource_dict, "dir")
self.assertEquals(resource_dict[property_name], s3_url)
self.s3_uploader_mock.upload_with_dedup.assert_not_called()

def test_export_serverless_application_no_upload_path_is_empty(self):
stack_resource = ServerlessApplicationResource(self.s3_uploader_mock)
resource_id = "id"
property_name = stack_resource.PROPERTY_NAME

# Case 2: Path is empty
resource_dict = {}
stack_resource.export(resource_id, resource_dict, "dir")
self.assertEquals(resource_dict, {})
self.s3_uploader_mock.upload_with_dedup.assert_not_called()

def test_export_serverless_application_no_upload_path_not_file(self):
stack_resource = ServerlessApplicationResource(self.s3_uploader_mock)
resource_id = "id"
property_name = stack_resource.PROPERTY_NAME

# Case 3: Path is not a file
with self.make_temp_dir() as dirname:
resource_dict = {property_name: dirname}
with self.assertRaises(exceptions.ExportFailedError):
stack_resource.export(resource_id, resource_dict, "dir")
self.s3_uploader_mock.upload_with_dedup.assert_not_called()

def test_export_serverless_application_no_upload_path_is_dictionary(self):
stack_resource = ServerlessApplicationResource(self.s3_uploader_mock)
resource_id = "id"
property_name = stack_resource.PROPERTY_NAME

# Case 4: Path is dictionary
location = {"ApplicationId": "id", "SemanticVersion": "1.0.1"}
resource_dict = {property_name: location}
stack_resource.export(resource_id, resource_dict, "dir")
self.assertEquals(resource_dict[property_name], location)
self.s3_uploader_mock.upload_with_dedup.assert_not_called()

@patch("awscli.customizations.cloudformation.artifact_exporter.yaml_parse")
def test_template_export(self, yaml_parse_mock):
parent_dir = os.path.sep
Expand Down Expand Up @@ -849,7 +940,7 @@ def test_include_transform_export_handler_non_local_file(self, is_local_file_moc
handler_output = include_transform_export_handler({"Name": "AWS::Include", "Parameters": {"Location": "http://foo.yaml"}}, self.s3_uploader_mock)
self.s3_uploader_mock.upload_with_dedup.assert_not_called()
self.assertEquals(handler_output, {"Name": "AWS::Include", "Parameters": {"Location": "http://foo.yaml"}})

@patch("awscli.customizations.cloudformation.artifact_exporter.is_local_file")
def test_include_transform_export_handler_non_include_transform(self, is_local_file_mock):
#ignores transform that is not aws::include
Expand Down

0 comments on commit da5668f

Please sign in to comment.