Skip to content

Commit f7969bf

Browse files
authored
chore: merge develop into release/v1.11.0 (#880)
2 parents 83d5d36 + 7b16fbb commit f7969bf

28 files changed

+330
-67
lines changed

bin/sam-translate.py

Lines changed: 87 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,42 +5,91 @@
55
Known limitations: cannot transform CodeUri pointing at local directory.
66
77
Usage:
8-
sam-translate.py --input-file=sam-template.yaml [--output-file=<o>]
8+
sam-translate.py --template-file=sam-template.yaml [--verbose] [--output-template=<o>]
9+
sam-translate.py package --template-file=sam-template.yaml --s3-bucket=my-bucket [--verbose] [--output-template=<o>]
10+
sam-translate.py deploy --template-file=sam-template.yaml --s3-bucket=my-bucket --capabilities=CAPABILITY_NAMED_IAM --stack-name=my-stack [--verbose] [--output-template=<o>]
911
1012
Options:
11-
--input-file=<i> Location of SAM template to transform.
12-
--output-file=<o> Location to store resulting CloudFormation template [default: cfn-template.json].
13+
--template-file=<i> Location of SAM template to transform [default: template.yaml].
14+
--output-template=<o> Location to store resulting CloudFormation template [default: transformed-template.json].
15+
--s3-bucket=<s> S3 bucket to use for SAM artifacts when using the `package` command
16+
--capabilities=<c> Capabilities
17+
--stack-name=<n> Unique name for your CloudFormation Stack
18+
--verbose Enables verbose logging
1319
1420
"""
1521
import json
22+
import logging
1623
import os
24+
import platform
25+
import subprocess
26+
import sys
1727

1828
import boto3
1929
from docopt import docopt
2030

31+
my_path = os.path.dirname(os.path.abspath(__file__))
32+
sys.path.insert(0, my_path + '/..')
33+
2134
from samtranslator.public.translator import ManagedPolicyLoader
2235
from samtranslator.translator.transform import transform
2336
from samtranslator.yaml_helper import yaml_parse
2437
from samtranslator.model.exceptions import InvalidDocumentException
2538

26-
39+
LOG = logging.getLogger(__name__)
2740
cli_options = docopt(__doc__)
2841
iam_client = boto3.client('iam')
2942
cwd = os.getcwd()
3043

44+
if cli_options.get('--verbose'):
45+
logging.basicConfig(level=logging.DEBUG)
46+
else:
47+
logging.basicConfig()
48+
49+
def execute_command(command, args):
50+
try:
51+
aws_cmd = 'aws' if platform.system().lower() != 'windows' else 'aws.cmd'
52+
command_with_args = [aws_cmd, 'cloudformation', command] + list(args)
53+
54+
LOG.debug("Executing command: %s", command_with_args)
55+
56+
subprocess.check_call(command_with_args)
57+
58+
LOG.debug("Command successful")
59+
except subprocess.CalledProcessError as e:
60+
# Underlying aws command will print the exception to the user
61+
LOG.debug("Exception: %s", e)
62+
sys.exit(e.returncode)
63+
3164

3265
def get_input_output_file_paths():
33-
input_file_option = cli_options.get('--input-file')
34-
output_file_option = cli_options.get('--output-file')
66+
input_file_option = cli_options.get('--template-file')
67+
output_file_option = cli_options.get('--output-template')
3568
input_file_path = os.path.join(cwd, input_file_option)
3669
output_file_path = os.path.join(cwd, output_file_option)
3770

3871
return input_file_path, output_file_path
3972

4073

41-
def main():
42-
input_file_path, output_file_path = get_input_output_file_paths()
74+
def package(input_file_path, output_file_path):
75+
template_file = input_file_path
76+
package_output_template_file = input_file_path + '._sam_packaged_.yaml'
77+
s3_bucket = cli_options.get('--s3-bucket')
78+
args = [
79+
'--template-file',
80+
template_file,
81+
'--output-template-file',
82+
package_output_template_file,
83+
'--s3-bucket',
84+
s3_bucket
85+
]
4386

87+
execute_command('package', args)
88+
89+
return package_output_template_file
90+
91+
92+
def transform_template(input_file_path, output_file_path):
4493
with open(input_file_path, 'r') as f:
4594
sam_template = yaml_parse(f)
4695

@@ -56,10 +105,37 @@ def main():
56105
print('Wrote transformed CloudFormation template to: ' + output_file_path)
57106
except InvalidDocumentException as e:
58107
errorMessage = reduce(lambda message, error: message + ' ' + error.message, e.causes, e.message)
59-
print(errorMessage)
108+
LOG.error(errorMessage)
60109
errors = map(lambda cause: cause.message, e.causes)
61-
print(errors)
110+
LOG.error(errors)
111+
112+
113+
def deploy(template_file):
114+
capabilities = cli_options.get('--capabilities')
115+
stack_name = cli_options.get('--stack-name')
116+
args = [
117+
'--template-file',
118+
template_file,
119+
'--capabilities',
120+
capabilities,
121+
'--stack-name',
122+
stack_name
123+
]
124+
125+
execute_command('deploy', args)
126+
127+
return package_output_template_file
62128

63129

64130
if __name__ == '__main__':
65-
main()
131+
input_file_path, output_file_path = get_input_output_file_paths()
132+
133+
if cli_options.get('package'):
134+
package_output_template_file = package(input_file_path, output_file_path)
135+
transform_template(package_output_template_file, output_file_path)
136+
elif cli_options.get('deploy'):
137+
package_output_template_file = package(input_file_path, output_file_path)
138+
transform_template(package_output_template_file, output_file_path)
139+
deploy(output_file_path)
140+
else:
141+
transform_template(input_file_path, output_file_path)

examples/2016-10-31/api_gateway_responses/src/index.js

Lines changed: 0 additions & 10 deletions
This file was deleted.

examples/2016-10-31/api_gateway_responses/template.yaml

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,46 +3,31 @@ Transform: AWS::Serverless-2016-10-31
33
Description: Simple webservice deomnstrating gateway responses.
44

55
Resources:
6-
ExplicitApi:
6+
MyApi:
77
Type: AWS::Serverless::Api
88
Properties:
9-
Auth:
10-
Authorizers:
11-
Authorizer:
12-
FunctionArn: !GetAtt AuthorizerFunction.Arn
13-
Identity:
14-
ValidationExpression: "^Bearer +[-0-9a-zA-Z\\._]*$"
15-
ReauthorizeEvery: 300
9+
StageName: Prod
1610
GatewayResponses:
17-
UNAUTHORIZED:
11+
DEFAULT_4xx:
1812
ResponseParameters:
1913
Headers:
2014
Access-Control-Expose-Headers: "'WWW-Authenticate'"
2115
Access-Control-Allow-Origin: "'*'"
22-
WWW-Authenticate: >-
23-
'Bearer realm="admin"'
16+
2417
GetFunction:
2518
Type: AWS::Serverless::Function
2619
Properties:
2720
Handler: index.get
2821
Runtime: nodejs6.10
29-
CodeUri: src/
22+
InlineCode: module.exports = async () => throw new Error('Check out the response headers!')
3023
Events:
3124
GetResource:
3225
Type: Api
3326
Properties:
34-
Path: /resource/{resourceId}
27+
Path: /error
3528
Method: get
36-
Auth:
37-
Authorizer: Authorizer
38-
RestApiId: !Ref ExplicitApi
39-
AuthorizerFunction:
40-
Type: AWS::Serverless::Function
41-
Properties:
42-
Handler: index.auth
43-
Runtime: nodejs6.10
44-
CodeUri: src/
29+
RestApiId: !Ref MyApi
4530
Outputs:
4631
ApiURL:
4732
Description: "API endpoint URL for Prod environment"
48-
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/resource/"
33+
Value: !Sub "https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/error/"

examples/2016-10-31/api_swagger_cors/swagger.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ paths:
2424
Fn::Sub: "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaFunction.Arn}/invocations"
2525

2626
passthroughBehavior: when_no_match
27-
httpMethod: POST
27+
httpMethod: POST # Keep "POST" when the API definition method is not POST. This "httpMethod" is used to call Lambda.
2828
type: aws_proxy
2929
/{proxy+}:
3030
x-amazon-apigateway-any-method:
@@ -41,7 +41,7 @@ paths:
4141
x-amazon-apigateway-integration:
4242
uri:
4343
Fn::Sub: "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaFunction.Arn}/invocations"
44-
httpMethod: POST
44+
httpMethod: POST # Keep "POST" when the API definition method is not POST. This "httpMethod" is used to call Lambda.
4545
type: aws_proxy
4646
definitions:
4747
Empty:

samtranslator/intrinsics/actions.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import re
22

33
from six import string_types
4-
from samtranslator.model.exceptions import InvalidTemplateException
4+
from samtranslator.model.exceptions import InvalidTemplateException, InvalidDocumentException
55

66

77
class Action(object):
@@ -427,6 +427,11 @@ def resolve_resource_refs(self, input_dict, supported_resource_refs):
427427
if not isinstance(value, list) or len(value) < 2:
428428
return input_dict
429429

430+
if (not all(isinstance(entry, string_types) for entry in value)):
431+
raise InvalidDocumentException(
432+
[InvalidTemplateException('Invalid GetAtt value {}. GetAtt expects an array with 2 strings.'
433+
.format(value))])
434+
430435
# Value of GetAtt is an array. It can contain any number of elements, with first being the LogicalId of
431436
# resource and rest being the attributes. In a SAM template, a reference to a resource can be used in the
432437
# first parameter. However tools like AWS CLI might break them down as well. So let's just concatenate
@@ -529,8 +534,9 @@ def resolve_parameter_refs(self, input_dict, parameters):
529534

530535
# FindInMap expects an array with 3 values
531536
if not isinstance(value, list) or len(value) != 3:
532-
raise InvalidTemplateException('Invalid FindInMap value {}. FindInMap expects an array with 3 values.'
533-
.format(value))
537+
raise InvalidDocumentException(
538+
[InvalidTemplateException('Invalid FindInMap value {}. FindInMap expects an array with 3 values.'
539+
.format(value))])
534540

535541
map_name = self.resolve_parameter_refs(value[0], parameters)
536542
top_level_key = self.resolve_parameter_refs(value[1], parameters)

samtranslator/model/eventsources/push.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from samtranslator.model.events import EventsRule
1313
from samtranslator.model.iot import IotTopicRule
1414
from samtranslator.translator.arn_generator import ArnGenerator
15-
from samtranslator.model.exceptions import InvalidEventException
15+
from samtranslator.model.exceptions import InvalidEventException, InvalidResourceException
1616
from samtranslator.swagger.swagger import SwaggerEditor
1717

1818
CONDITION = 'Condition'
@@ -419,6 +419,8 @@ def resources_to_link(self, resources):
419419

420420
# Stage could be a intrinsic, in which case leave the suffix to default value
421421
if isinstance(permitted_stage, string_types):
422+
if not permitted_stage:
423+
raise InvalidResourceException(rest_api_id, 'StageName cannot be empty.')
422424
stage_suffix = permitted_stage
423425
else:
424426
stage_suffix = "Stage"

samtranslator/model/sam_resources.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ def _construct_alias(self, name, function, version):
381381
"""
382382

383383
if not name:
384-
raise ValueError("Alias name is required to create an alias")
384+
raise InvalidResourceException(self.logical_id, "Alias name is required to create an alias")
385385

386386
logical_id = "{id}Alias{suffix}".format(id=function.logical_id, suffix=name)
387387
alias = LambdaAlias(logical_id=logical_id, attributes=self.get_passthrough_resource_attributes())

samtranslator/parser/parser.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
from samtranslator.model.exceptions import InvalidDocumentException, InvalidTemplateException
1+
from samtranslator.model.exceptions import InvalidDocumentException, InvalidTemplateException, InvalidResourceException
22
from samtranslator.validator.validator import SamTemplateValidator
33
from samtranslator.plugins import LifeCycleEvents
4+
from samtranslator.public.sdk.template import SamTemplate
45

56

67
class Parser:
@@ -26,11 +27,24 @@ def _validate(self, sam_template, parameter_values):
2627
raise InvalidDocumentException(
2728
[InvalidTemplateException("'Resources' section is required")])
2829

29-
if (not all(isinstance(value, dict) for value in sam_template["Resources"].values())):
30+
if (not all(isinstance(sam_resource, dict) for sam_resource in sam_template["Resources"].values())):
3031
raise InvalidDocumentException(
3132
[InvalidTemplateException(
3233
"All 'Resources' must be Objects. If you're using YAML, this may be an "
3334
"indentation issue."
3435
)])
3536

37+
sam_template_instance = SamTemplate(sam_template)
38+
39+
for resource_logical_id, sam_resource in sam_template_instance.iterate():
40+
# NOTE: Properties isn't required for SimpleTable, so we can't check
41+
# `not isinstance(sam_resources.get("Properties"), dict)` as this would be a breaking change.
42+
# sam_resource.properties defaults to {} in SamTemplate init
43+
if (not isinstance(sam_resource.properties, dict)):
44+
raise InvalidDocumentException(
45+
[InvalidResourceException(resource_logical_id,
46+
"All 'Resources' must be Objects and have a 'Properties' Object. If "
47+
"you're using YAML, this may be an indentation issue."
48+
)])
49+
3650
SamTemplateValidator.validate(sam_template)

samtranslator/plugins/application/serverless_app_plugin.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import boto3
2+
import json
23
from botocore.exceptions import ClientError, EndpointConnectionError
34
import logging
45
from time import sleep, time
@@ -86,10 +87,17 @@ def on_before_transform_template(self, template_dict):
8687

8788
app_id = self._replace_value(app.properties[self.LOCATION_KEY],
8889
self.APPLICATION_ID_KEY, intrinsic_resolvers)
90+
8991
semver = self._replace_value(app.properties[self.LOCATION_KEY],
9092
self.SEMANTIC_VERSION_KEY, intrinsic_resolvers)
9193

94+
if isinstance(app_id, dict) or isinstance(semver, dict):
95+
key = (json.dumps(app_id), json.dumps(semver))
96+
self._applications[key] = False
97+
continue
98+
9299
key = (app_id, semver)
100+
93101
if key not in self._applications:
94102
try:
95103
# Lazy initialization of the client- create it when it is needed
@@ -211,11 +219,23 @@ def on_before_transform_resource(self, logical_id, resource_type, resource_prope
211219
[self.APPLICATION_ID_KEY, self.SEMANTIC_VERSION_KEY])
212220

213221
app_id = resource_properties[self.LOCATION_KEY].get(self.APPLICATION_ID_KEY)
222+
214223
if not app_id:
215224
raise InvalidResourceException(logical_id, "Property 'ApplicationId' cannot be blank.")
225+
226+
if isinstance(app_id, dict):
227+
raise InvalidResourceException(logical_id, "Property 'ApplicationId' cannot be resolved. Only FindInMap "
228+
"and Ref intrinsic functions are supported.")
229+
216230
semver = resource_properties[self.LOCATION_KEY].get(self.SEMANTIC_VERSION_KEY)
231+
217232
if not semver:
218-
raise InvalidResourceException(logical_id, "Property 'SemanticVersion cannot be blank.")
233+
raise InvalidResourceException(logical_id, "Property 'SemanticVersion' cannot be blank.")
234+
235+
if isinstance(semver, dict):
236+
raise InvalidResourceException(logical_id, "Property 'SemanticVersion' cannot be resolved. Only FindInMap "
237+
"and Ref intrinsic functions are supported.")
238+
219239
key = (app_id, semver)
220240

221241
# Throw any resource exceptions saved from the before_transform_template event

samtranslator/swagger/swagger.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from samtranslator.model.intrinsics import ref
55
from samtranslator.model.intrinsics import make_conditional
6+
from samtranslator.model.exceptions import InvalidDocumentException, InvalidTemplateException
67

78

89
class SwaggerEditor(object):
@@ -124,7 +125,9 @@ def add_path(self, path, method=None):
124125

125126
if not isinstance(path_dict, dict):
126127
# Either customers has provided us an invalid Swagger, or this class has messed it somehow
127-
raise ValueError("Value of '{}' path must be a dictionary according to Swagger spec".format(path))
128+
raise InvalidDocumentException(
129+
[InvalidTemplateException("Value of '{}' path must be a dictionary according to Swagger spec."
130+
.format(path))])
128131

129132
if self._CONDITIONAL_IF in path_dict:
130133
path_dict = path_dict[self._CONDITIONAL_IF][1]

0 commit comments

Comments
 (0)