From 0d8167b22c4551cd836b02f889f4f2ba3ab0bc39 Mon Sep 17 00:00:00 2001 From: Keeton Hodgson Date: Thu, 13 Feb 2020 13:25:13 -0800 Subject: [PATCH 1/7] Add tagging to Http Api resource --- samtranslator/model/api/http_api_generator.py | 39 +++++++++++++------ samtranslator/open_api/open_api.py | 16 ++++++++ tests/translator/input/http_api_def_uri.yaml | 4 -- .../http_api_existing_openapi_conditions.yaml | 3 ++ .../output/aws-cn/http_api_def_uri.json | 32 ++------------- .../http_api_existing_openapi_conditions.json | 12 +++++- .../output/aws-us-gov/http_api_def_uri.json | 32 ++------------- .../http_api_existing_openapi_conditions.json | 12 +++++- tests/translator/output/http_api_def_uri.json | 32 ++------------- .../http_api_existing_openapi_conditions.json | 12 +++++- versions/2016-10-31.md | 1 + 11 files changed, 93 insertions(+), 102 deletions(-) diff --git a/samtranslator/model/api/http_api_generator.py b/samtranslator/model/api/http_api_generator.py index 0643db3322..15f2f005a7 100644 --- a/samtranslator/model/api/http_api_generator.py +++ b/samtranslator/model/api/http_api_generator.py @@ -11,6 +11,7 @@ AuthProperties = namedtuple("_AuthProperties", ["Authorizers", "DefaultAuthorizer"]) AuthProperties.__new__.__defaults__ = (None, None) DefaultStageName = "$default" +HttpApiTagName = "httpapi:createdBy" class HttpApiGenerator(object): @@ -70,6 +71,7 @@ def _construct_http_api(self): ) self._add_auth() + self._add_tags() if self.definition_uri: http_api.BodyS3Location = self._construct_body_s3_dict() @@ -83,9 +85,6 @@ def _construct_http_api(self): "add a 'HttpApi' event to an 'AWS::Serverless::Function'", ) - if self.tags is not None: - http_api.Tags = get_tag_list(self.tags) - return http_api def _add_auth(self): @@ -97,7 +96,7 @@ def _add_auth(self): if self.auth and not self.definition_body: raise InvalidResourceException( - self.logical_id, "Auth works only with inline Swagger specified in " "'DefinitionBody' property" + self.logical_id, "Auth works only with inline OpenApi specified in the 'DefinitionBody' property." ) # Make sure keys in the dict are recognized @@ -107,7 +106,7 @@ def _add_auth(self): if not OpenApiEditor.is_valid(self.definition_body): raise InvalidResourceException( self.logical_id, - "Unable to add Auth configuration because " "'DefinitionBody' does not contain a valid Swagger", + "Unable to add Auth configuration because 'DefinitionBody' does not contain a valid OpenApi definition.", ) open_api_editor = OpenApiEditor(self.definition_body) auth_properties = AuthProperties(**self.auth) @@ -120,6 +119,25 @@ def _add_auth(self): ) self.definition_body = open_api_editor.openapi + def _add_tags(self): + if self.tags and not self.definition_body: + raise InvalidResourceException( + self.logical_id, "Tags works only with inline OpenApi specified in the 'DefinitionBody' property." + ) + + if not self.definition_body: + return + + if not self.tags: + self.tags = {} + self.tags[HttpApiTagName] = "SAM" + + open_api_editor = OpenApiEditor(self.definition_body) + + # authorizers is guaranteed to return a value or raise an exception + open_api_editor.add_tags(self.tags) + self.definition_body = open_api_editor.openapi + def _set_default_authorizer(self, open_api_editor, authorizers, default_authorizer, api_authorizers): """ Sets the default authorizer if one is given in the template @@ -134,7 +152,9 @@ def _set_default_authorizer(self, open_api_editor, authorizers, default_authoriz if not authorizers.get(default_authorizer): raise InvalidResourceException( self.logical_id, - "Unable to set DefaultAuthorizer because '" + default_authorizer + "' was not defined in 'Authorizers'", + "Unable to set DefaultAuthorizer because '" + + default_authorizer + + "' was not defined in 'Authorizers'.", ) for path in open_api_editor.iter_on_path(): @@ -151,7 +171,7 @@ def _get_authorizers(self, authorizers_config, default_authorizer=None): authorizers = {} if not isinstance(authorizers_config, dict): - raise InvalidResourceException(self.logical_id, "Authorizers must be a dictionary") + raise InvalidResourceException(self.logical_id, "Authorizers must be a dictionary.") for authorizer_name, authorizer in authorizers_config.items(): if not isinstance(authorizer, dict): @@ -179,7 +199,7 @@ def _construct_body_s3_dict(self): if not self.definition_uri.get("Bucket", None) or not self.definition_uri.get("Key", None): # DefinitionUri is a dictionary but does not contain Bucket or Key property raise InvalidResourceException( - self.logical_id, "'DefinitionUri' requires Bucket and Key properties to be specified" + self.logical_id, "'DefinitionUri' requires Bucket and Key properties to be specified." ) s3_pointer = self.definition_uri @@ -226,9 +246,6 @@ def _construct_stage(self): stage.AccessLogSettings = self.access_log_settings stage.AutoDeploy = True - if self.tags is not None: - stage.Tags = get_tag_list(self.tags) - return stage def to_cloudformation(self): diff --git a/samtranslator/open_api/open_api.py b/samtranslator/open_api/open_api.py index 2d8ff46b4e..aace7a800c 100644 --- a/samtranslator/open_api/open_api.py +++ b/samtranslator/open_api/open_api.py @@ -16,6 +16,7 @@ class OpenApiEditor(object): """ _X_APIGW_INTEGRATION = "x-amazon-apigateway-integration" + _X_APIGW_TAG_VALUE = "x-amazon-apigateway-tag-value" _CONDITIONAL_IF = "Fn::If" _X_ANY_METHOD = "x-amazon-apigateway-any-method" _ALL_HTTP_METHODS = ["OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "PATCH"] @@ -39,6 +40,7 @@ def __init__(self, doc): self.paths = self._doc["paths"] self.security_schemes = self._doc.get("components", {}).get("securitySchemes", {}) self.definitions = self._doc.get("definitions", {}) + self.tags = self._doc.get("tags", []) def get_path(self, path): """ @@ -344,6 +346,17 @@ def _set_method_authorizer(self, path, method_name, authorizer_name, authorizers if security: method_definition["security"] = security + def add_tags(self, tags): + for name, value in tags.items(): + # find an existing tag with this name if it exists + existing_tag = next(existing_tag for existing_tag in self.tags if existing_tag.get("name") == name) + if existing_tag: + # overwrite tag value for an existing tag + existing_tag[self._X_APIGW_TAG_VALUE] = value + else: + tag = {"name": name, self._X_APIGW_TAG_VALUE: value} + self.tags.append(tag) + @property def openapi(self): """ @@ -355,6 +368,9 @@ def openapi(self): # Make sure any changes to the paths are reflected back in output self._doc["paths"] = self.paths + if self.tags: + self._doc["tags"] = self.tags + if self.security_schemes: self._doc.setdefault("components", {}) self._doc["components"]["securitySchemes"] = self.security_schemes diff --git a/tests/translator/input/http_api_def_uri.yaml b/tests/translator/input/http_api_def_uri.yaml index 4e6c80316d..ae1c901ff2 100644 --- a/tests/translator/input/http_api_def_uri.yaml +++ b/tests/translator/input/http_api_def_uri.yaml @@ -3,8 +3,6 @@ Resources: Type: AWS::Serverless::HttpApi Properties: DefinitionUri: s3://bucket/key - Tags: - Tag: value StageName: !Join ["", ["Stage", "Name"]] MyApi2: @@ -14,8 +12,6 @@ Resources: Bucket: bucket Key: key Version: version - Tags: - Tag: value Function: Type: AWS::Serverless::Function diff --git a/tests/translator/input/http_api_existing_openapi_conditions.yaml b/tests/translator/input/http_api_existing_openapi_conditions.yaml index 7a42bd4580..83118fcada 100644 --- a/tests/translator/input/http_api_existing_openapi_conditions.yaml +++ b/tests/translator/input/http_api_existing_openapi_conditions.yaml @@ -35,6 +35,9 @@ Resources: MyApi: Type: AWS::Serverless::HttpApi Properties: + Tags: + Tag1: value1 + Tag2: value2 Auth: Authorizers: OAuth2: diff --git a/tests/translator/output/aws-cn/http_api_def_uri.json b/tests/translator/output/aws-cn/http_api_def_uri.json index 2bb57f180c..044fd771ed 100644 --- a/tests/translator/output/aws-cn/http_api_def_uri.json +++ b/tests/translator/output/aws-cn/http_api_def_uri.json @@ -57,13 +57,7 @@ "Name" ] ] - }, - "Tags": [ - { - "Value": "value", - "Key": "Tag" - } - ] + } } }, "FunctionRole": { @@ -103,13 +97,7 @@ "Ref": "MyApi2" }, "AutoDeploy": true, - "StageName": "$default", - "Tags": [ - { - "Value": "value", - "Key": "Tag" - } - ] + "StageName": "$default" } }, "MyApi2": { @@ -119,13 +107,7 @@ "Version": "version", "Bucket": "bucket", "Key": "key" - }, - "Tags": [ - { - "Value": "value", - "Key": "Tag" - } - ] + } } }, "FunctionApi2Permission": { @@ -155,13 +137,7 @@ "BodyS3Location": { "Bucket": "bucket", "Key": "key" - }, - "Tags": [ - { - "Value": "value", - "Key": "Tag" - } - ] + } } } } diff --git a/tests/translator/output/aws-cn/http_api_existing_openapi_conditions.json b/tests/translator/output/aws-cn/http_api_existing_openapi_conditions.json index 07f4dd47aa..63a795dcc1 100644 --- a/tests/translator/output/aws-cn/http_api_existing_openapi_conditions.json +++ b/tests/translator/output/aws-cn/http_api_existing_openapi_conditions.json @@ -268,7 +268,17 @@ } } }, - "openapi": "3.0.1" + "openapi": "3.0.1", + "tags": [ + { + "name": "Tag1", + "x-amazon-apigateway-tag-value": "value1" + }, + { + "name": "Tag2", + "x-amazon-apigateway-tag-value": "value2" + } + ] } } } diff --git a/tests/translator/output/aws-us-gov/http_api_def_uri.json b/tests/translator/output/aws-us-gov/http_api_def_uri.json index c800370291..58af9f809f 100644 --- a/tests/translator/output/aws-us-gov/http_api_def_uri.json +++ b/tests/translator/output/aws-us-gov/http_api_def_uri.json @@ -57,13 +57,7 @@ "Name" ] ] - }, - "Tags": [ - { - "Value": "value", - "Key": "Tag" - } - ] + } } }, "FunctionRole": { @@ -103,13 +97,7 @@ "Ref": "MyApi2" }, "AutoDeploy": true, - "StageName": "$default", - "Tags": [ - { - "Value": "value", - "Key": "Tag" - } - ] + "StageName": "$default" } }, "MyApi2": { @@ -119,13 +107,7 @@ "Version": "version", "Bucket": "bucket", "Key": "key" - }, - "Tags": [ - { - "Value": "value", - "Key": "Tag" - } - ] + } } }, "FunctionApi2Permission": { @@ -155,13 +137,7 @@ "BodyS3Location": { "Bucket": "bucket", "Key": "key" - }, - "Tags": [ - { - "Value": "value", - "Key": "Tag" - } - ] + } } } } diff --git a/tests/translator/output/aws-us-gov/http_api_existing_openapi_conditions.json b/tests/translator/output/aws-us-gov/http_api_existing_openapi_conditions.json index 77517a7962..b224d75ef4 100644 --- a/tests/translator/output/aws-us-gov/http_api_existing_openapi_conditions.json +++ b/tests/translator/output/aws-us-gov/http_api_existing_openapi_conditions.json @@ -268,7 +268,17 @@ } } }, - "openapi": "3.0.1" + "openapi": "3.0.1", + "tags": [ + { + "name": "Tag1", + "x-amazon-apigateway-tag-value": "value1" + }, + { + "name": "Tag2", + "x-amazon-apigateway-tag-value": "value2" + } + ] } } } diff --git a/tests/translator/output/http_api_def_uri.json b/tests/translator/output/http_api_def_uri.json index ea5493bd81..3f5ad79f6a 100644 --- a/tests/translator/output/http_api_def_uri.json +++ b/tests/translator/output/http_api_def_uri.json @@ -57,13 +57,7 @@ "Name" ] ] - }, - "Tags": [ - { - "Value": "value", - "Key": "Tag" - } - ] + } } }, "FunctionRole": { @@ -103,13 +97,7 @@ "Ref": "MyApi2" }, "AutoDeploy": true, - "StageName": "$default", - "Tags": [ - { - "Value": "value", - "Key": "Tag" - } - ] + "StageName": "$default" } }, "MyApi2": { @@ -119,13 +107,7 @@ "Version": "version", "Bucket": "bucket", "Key": "key" - }, - "Tags": [ - { - "Value": "value", - "Key": "Tag" - } - ] + } } }, "FunctionApi2Permission": { @@ -155,13 +137,7 @@ "BodyS3Location": { "Bucket": "bucket", "Key": "key" - }, - "Tags": [ - { - "Value": "value", - "Key": "Tag" - } - ] + } } } } diff --git a/tests/translator/output/http_api_existing_openapi_conditions.json b/tests/translator/output/http_api_existing_openapi_conditions.json index 1c5adc9c26..8fff66cef7 100644 --- a/tests/translator/output/http_api_existing_openapi_conditions.json +++ b/tests/translator/output/http_api_existing_openapi_conditions.json @@ -268,7 +268,17 @@ } } }, - "openapi": "3.0.1" + "openapi": "3.0.1", + "tags": [ + { + "name": "Tag1", + "x-amazon-apigateway-tag-value": "value1" + }, + { + "name": "Tag2", + "x-amazon-apigateway-tag-value": "value2" + } + ] } } } diff --git a/versions/2016-10-31.md b/versions/2016-10-31.md index 4aaaa7d1a2..8ee07303a9 100644 --- a/versions/2016-10-31.md +++ b/versions/2016-10-31.md @@ -278,6 +278,7 @@ StageName | `string` | The name of the API stage. If a name is not given, SAM wi DefinitionUri | `string` | [S3 Location Object](#s3-location-object) | S3 URI or location to the Swagger document describing the API. If neither `DefinitionUri` nor `DefinitionBody` are specified, SAM will generate a `DefinitionBody` for you based on your template configuration. **Note** Intrinsic functions are not supported in external OpenApi files, instead use DefinitionBody to define OpenApi definition. DefinitionBody | `JSON or YAML Object` | OpenApi specification that describes your API. If neither `DefinitionUri` nor `DefinitionBody` are specified, SAM will generate a `DefinitionBody` for you based on your template configuration. Auth | [HTTP API Auth Object](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-httpapi-httpapiauth.html) | Configure authorization to control access to your API Gateway API. +Tags | Map of `string` to `string` | A map (string to string) that specifies the [tags](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-resource-tags.html) to be added to this HTTP API. When the stack is created, SAM will automatically add the following tag: `lambda:createdBy: SAM`. ##### Return values From 28b3739a9aad40d856a109f05b3d03f87bf3c684 Mon Sep 17 00:00:00 2001 From: Keeton Hodgson Date: Thu, 13 Feb 2020 14:26:11 -0800 Subject: [PATCH 2/7] Add docstrings --- samtranslator/model/api/http_api_generator.py | 3 +++ samtranslator/open_api/open_api.py | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/samtranslator/model/api/http_api_generator.py b/samtranslator/model/api/http_api_generator.py index 15f2f005a7..6a800fd5bf 100644 --- a/samtranslator/model/api/http_api_generator.py +++ b/samtranslator/model/api/http_api_generator.py @@ -120,6 +120,9 @@ def _add_auth(self): self.definition_body = open_api_editor.openapi def _add_tags(self): + """ + Adds tags to the Http Api, including a default SAM tag. + """ if self.tags and not self.definition_body: raise InvalidResourceException( self.logical_id, "Tags works only with inline OpenApi specified in the 'DefinitionBody' property." diff --git a/samtranslator/open_api/open_api.py b/samtranslator/open_api/open_api.py index aace7a800c..97a5a185e8 100644 --- a/samtranslator/open_api/open_api.py +++ b/samtranslator/open_api/open_api.py @@ -347,6 +347,11 @@ def _set_method_authorizer(self, path, method_name, authorizer_name, authorizers method_definition["security"] = security def add_tags(self, tags): + """ + Adds tags to the OpenApi definition using an ApiGateway extension for tag values. + + :param dict tags: dictionary of tagName:tagValue pairs. + """ for name, value in tags.items(): # find an existing tag with this name if it exists existing_tag = next(existing_tag for existing_tag in self.tags if existing_tag.get("name") == name) From b4305003cbe3d752a5f9bd0e86273780cfa5462d Mon Sep 17 00:00:00 2001 From: Keeton Hodgson Date: Thu, 20 Feb 2020 13:03:58 -0800 Subject: [PATCH 3/7] update --- samtranslator/open_api/open_api.py | 2 +- .../translator/output/aws-cn/explicit_http_api.json | 12 ++++++++++++ .../output/aws-cn/explicit_http_api_minimum.json | 12 ++++++++++++ .../output/aws-cn/http_api_existing_openapi.json | 6 ++++++ .../aws-cn/http_api_existing_openapi_conditions.json | 10 ++++++++++ .../output/aws-cn/http_api_explicit_stage.json | 8 +++++++- .../translator/output/aws-cn/implicit_http_api.json | 6 ++++++ .../implicit_http_api_auth_and_simple_case.json | 6 ++++++ .../implicit_http_api_with_many_conditions.json | 6 ++++++ .../output/aws-us-gov/explicit_http_api.json | 12 ++++++++++++ .../output/aws-us-gov/explicit_http_api_minimum.json | 12 ++++++++++++ .../output/aws-us-gov/http_api_existing_openapi.json | 6 ++++++ .../http_api_existing_openapi_conditions.json | 4 ++++ .../output/aws-us-gov/http_api_explicit_stage.json | 6 ++++++ .../output/aws-us-gov/implicit_http_api.json | 6 ++++++ .../implicit_http_api_auth_and_simple_case.json | 6 ++++++ .../implicit_http_api_with_many_conditions.json | 6 ++++++ tests/translator/output/explicit_http_api.json | 12 ++++++++++++ .../translator/output/explicit_http_api_minimum.json | 12 ++++++++++++ .../translator/output/http_api_existing_openapi.json | 6 ++++++ .../output/http_api_existing_openapi_conditions.json | 6 +++++- tests/translator/output/http_api_explicit_stage.json | 6 ++++++ tests/translator/output/implicit_http_api.json | 6 ++++++ .../implicit_http_api_auth_and_simple_case.json | 6 ++++++ .../implicit_http_api_with_many_conditions.json | 6 ++++++ 25 files changed, 183 insertions(+), 3 deletions(-) diff --git a/samtranslator/open_api/open_api.py b/samtranslator/open_api/open_api.py index 97a5a185e8..d137a0e9d0 100644 --- a/samtranslator/open_api/open_api.py +++ b/samtranslator/open_api/open_api.py @@ -354,7 +354,7 @@ def add_tags(self, tags): """ for name, value in tags.items(): # find an existing tag with this name if it exists - existing_tag = next(existing_tag for existing_tag in self.tags if existing_tag.get("name") == name) + existing_tag = next((existing_tag for existing_tag in self.tags if existing_tag.get("name") == name), None) if existing_tag: # overwrite tag value for an existing tag existing_tag[self._X_APIGW_TAG_VALUE] = value diff --git a/tests/translator/output/aws-cn/explicit_http_api.json b/tests/translator/output/aws-cn/explicit_http_api.json index d29519d7d0..f104793d30 100644 --- a/tests/translator/output/aws-cn/explicit_http_api.json +++ b/tests/translator/output/aws-cn/explicit_http_api.json @@ -74,6 +74,12 @@ "Ref": "AWS::StackName" } }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], "paths": { "$default": { "x-amazon-apigateway-any-method": { @@ -177,6 +183,12 @@ "Ref": "AWS::StackName" } }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], "paths": { "$default": { "x-amazon-apigateway-any-method": { diff --git a/tests/translator/output/aws-cn/explicit_http_api_minimum.json b/tests/translator/output/aws-cn/explicit_http_api_minimum.json index 992ca5675d..01e8424af4 100644 --- a/tests/translator/output/aws-cn/explicit_http_api_minimum.json +++ b/tests/translator/output/aws-cn/explicit_http_api_minimum.json @@ -54,6 +54,12 @@ "Ref": "AWS::StackName" } }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], "paths": {}, "openapi": "3.0.1" } @@ -99,6 +105,12 @@ "Ref": "AWS::StackName" } }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], "paths": { "$default": { "x-amazon-apigateway-any-method": { diff --git a/tests/translator/output/aws-cn/http_api_existing_openapi.json b/tests/translator/output/aws-cn/http_api_existing_openapi.json index 1f4be7b61c..3e77624b1d 100644 --- a/tests/translator/output/aws-cn/http_api_existing_openapi.json +++ b/tests/translator/output/aws-cn/http_api_existing_openapi.json @@ -95,6 +95,12 @@ "Ref": "AWS::StackName" } }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], "paths": { "/basic": { "post": { diff --git a/tests/translator/output/aws-cn/http_api_existing_openapi_conditions.json b/tests/translator/output/aws-cn/http_api_existing_openapi_conditions.json index 63a795dcc1..8e3363b94f 100644 --- a/tests/translator/output/aws-cn/http_api_existing_openapi_conditions.json +++ b/tests/translator/output/aws-cn/http_api_existing_openapi_conditions.json @@ -105,6 +105,12 @@ "Ref": "AWS::StackName" } }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], "paths": { "/basic": { "post": { @@ -270,6 +276,10 @@ }, "openapi": "3.0.1", "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + }, { "name": "Tag1", "x-amazon-apigateway-tag-value": "value1" diff --git a/tests/translator/output/aws-cn/http_api_explicit_stage.json b/tests/translator/output/aws-cn/http_api_explicit_stage.json index 857ca45f78..5c647d0366 100644 --- a/tests/translator/output/aws-cn/http_api_explicit_stage.json +++ b/tests/translator/output/aws-cn/http_api_explicit_stage.json @@ -110,7 +110,13 @@ } } }, - "openapi": "3.0.1" + "openapi": "3.0.1", + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] } } } diff --git a/tests/translator/output/aws-cn/implicit_http_api.json b/tests/translator/output/aws-cn/implicit_http_api.json index 6f589a57b5..72bb1a03d3 100644 --- a/tests/translator/output/aws-cn/implicit_http_api.json +++ b/tests/translator/output/aws-cn/implicit_http_api.json @@ -82,6 +82,12 @@ "Ref": "AWS::StackName" } }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], "paths": { "/basic2": { "post": { diff --git a/tests/translator/output/aws-cn/implicit_http_api_auth_and_simple_case.json b/tests/translator/output/aws-cn/implicit_http_api_auth_and_simple_case.json index 34b58b18ac..48f64be9c1 100644 --- a/tests/translator/output/aws-cn/implicit_http_api_auth_and_simple_case.json +++ b/tests/translator/output/aws-cn/implicit_http_api_auth_and_simple_case.json @@ -95,6 +95,12 @@ "Ref": "AWS::StackName" } }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], "paths": { "/scope3": { "post": { diff --git a/tests/translator/output/aws-cn/implicit_http_api_with_many_conditions.json b/tests/translator/output/aws-cn/implicit_http_api_with_many_conditions.json index 577ba4ed0d..519cb8083a 100644 --- a/tests/translator/output/aws-cn/implicit_http_api_with_many_conditions.json +++ b/tests/translator/output/aws-cn/implicit_http_api_with_many_conditions.json @@ -199,6 +199,12 @@ "Ref": "AWS::StackName" } }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], "paths": { "/hello/again": { "Fn::If": [ diff --git a/tests/translator/output/aws-us-gov/explicit_http_api.json b/tests/translator/output/aws-us-gov/explicit_http_api.json index 1da7c4eee3..50cb6158ad 100644 --- a/tests/translator/output/aws-us-gov/explicit_http_api.json +++ b/tests/translator/output/aws-us-gov/explicit_http_api.json @@ -74,6 +74,12 @@ "Ref": "AWS::StackName" } }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], "paths": { "$default": { "x-amazon-apigateway-any-method": { @@ -177,6 +183,12 @@ "Ref": "AWS::StackName" } }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], "paths": { "$default": { "x-amazon-apigateway-any-method": { diff --git a/tests/translator/output/aws-us-gov/explicit_http_api_minimum.json b/tests/translator/output/aws-us-gov/explicit_http_api_minimum.json index 7cce5345a2..0a35ee12b5 100644 --- a/tests/translator/output/aws-us-gov/explicit_http_api_minimum.json +++ b/tests/translator/output/aws-us-gov/explicit_http_api_minimum.json @@ -54,6 +54,12 @@ "Ref": "AWS::StackName" } }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], "paths": {}, "openapi": "3.0.1" } @@ -99,6 +105,12 @@ "Ref": "AWS::StackName" } }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], "paths": { "$default": { "x-amazon-apigateway-any-method": { diff --git a/tests/translator/output/aws-us-gov/http_api_existing_openapi.json b/tests/translator/output/aws-us-gov/http_api_existing_openapi.json index 3750d237cc..50ff3cb523 100644 --- a/tests/translator/output/aws-us-gov/http_api_existing_openapi.json +++ b/tests/translator/output/aws-us-gov/http_api_existing_openapi.json @@ -95,6 +95,12 @@ "Ref": "AWS::StackName" } }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], "paths": { "/basic": { "post": { diff --git a/tests/translator/output/aws-us-gov/http_api_existing_openapi_conditions.json b/tests/translator/output/aws-us-gov/http_api_existing_openapi_conditions.json index b224d75ef4..0223135e69 100644 --- a/tests/translator/output/aws-us-gov/http_api_existing_openapi_conditions.json +++ b/tests/translator/output/aws-us-gov/http_api_existing_openapi_conditions.json @@ -270,6 +270,10 @@ }, "openapi": "3.0.1", "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + }, { "name": "Tag1", "x-amazon-apigateway-tag-value": "value1" diff --git a/tests/translator/output/aws-us-gov/http_api_explicit_stage.json b/tests/translator/output/aws-us-gov/http_api_explicit_stage.json index 9c4fa4d5ac..6720fae181 100644 --- a/tests/translator/output/aws-us-gov/http_api_explicit_stage.json +++ b/tests/translator/output/aws-us-gov/http_api_explicit_stage.json @@ -94,6 +94,12 @@ "Ref": "AWS::StackName" } }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], "paths": { "$default": { "x-amazon-apigateway-any-method": { diff --git a/tests/translator/output/aws-us-gov/implicit_http_api.json b/tests/translator/output/aws-us-gov/implicit_http_api.json index 7cf122de5e..aaee74da6e 100644 --- a/tests/translator/output/aws-us-gov/implicit_http_api.json +++ b/tests/translator/output/aws-us-gov/implicit_http_api.json @@ -82,6 +82,12 @@ "Ref": "AWS::StackName" } }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], "paths": { "/basic2": { "post": { diff --git a/tests/translator/output/aws-us-gov/implicit_http_api_auth_and_simple_case.json b/tests/translator/output/aws-us-gov/implicit_http_api_auth_and_simple_case.json index 3d5bce2b86..4e4d6387f8 100644 --- a/tests/translator/output/aws-us-gov/implicit_http_api_auth_and_simple_case.json +++ b/tests/translator/output/aws-us-gov/implicit_http_api_auth_and_simple_case.json @@ -95,6 +95,12 @@ "Ref": "AWS::StackName" } }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], "paths": { "/scope3": { "post": { diff --git a/tests/translator/output/aws-us-gov/implicit_http_api_with_many_conditions.json b/tests/translator/output/aws-us-gov/implicit_http_api_with_many_conditions.json index b97e6db6b0..c5eea99b65 100644 --- a/tests/translator/output/aws-us-gov/implicit_http_api_with_many_conditions.json +++ b/tests/translator/output/aws-us-gov/implicit_http_api_with_many_conditions.json @@ -199,6 +199,12 @@ "Ref": "AWS::StackName" } }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], "paths": { "/hello/again": { "Fn::If": [ diff --git a/tests/translator/output/explicit_http_api.json b/tests/translator/output/explicit_http_api.json index 920023cbe2..8fc5d920d1 100644 --- a/tests/translator/output/explicit_http_api.json +++ b/tests/translator/output/explicit_http_api.json @@ -74,6 +74,12 @@ "Ref": "AWS::StackName" } }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], "paths": { "$default": { "x-amazon-apigateway-any-method": { @@ -177,6 +183,12 @@ "Ref": "AWS::StackName" } }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], "paths": { "$default": { "x-amazon-apigateway-any-method": { diff --git a/tests/translator/output/explicit_http_api_minimum.json b/tests/translator/output/explicit_http_api_minimum.json index ae96554bc0..6695f3404e 100644 --- a/tests/translator/output/explicit_http_api_minimum.json +++ b/tests/translator/output/explicit_http_api_minimum.json @@ -54,6 +54,12 @@ "Ref": "AWS::StackName" } }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], "paths": {}, "openapi": "3.0.1" } @@ -99,6 +105,12 @@ "Ref": "AWS::StackName" } }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], "paths": { "$default": { "x-amazon-apigateway-any-method": { diff --git a/tests/translator/output/http_api_existing_openapi.json b/tests/translator/output/http_api_existing_openapi.json index bf3ce13481..8076bf0ee8 100644 --- a/tests/translator/output/http_api_existing_openapi.json +++ b/tests/translator/output/http_api_existing_openapi.json @@ -95,6 +95,12 @@ "Ref": "AWS::StackName" } }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], "paths": { "/basic": { "post": { diff --git a/tests/translator/output/http_api_existing_openapi_conditions.json b/tests/translator/output/http_api_existing_openapi_conditions.json index 8fff66cef7..cd649a0991 100644 --- a/tests/translator/output/http_api_existing_openapi_conditions.json +++ b/tests/translator/output/http_api_existing_openapi_conditions.json @@ -104,7 +104,7 @@ "title": { "Ref": "AWS::StackName" } - }, + }, "paths": { "/basic": { "post": { @@ -270,6 +270,10 @@ }, "openapi": "3.0.1", "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + }, { "name": "Tag1", "x-amazon-apigateway-tag-value": "value1" diff --git a/tests/translator/output/http_api_explicit_stage.json b/tests/translator/output/http_api_explicit_stage.json index bc407fb3bc..db1b333dda 100644 --- a/tests/translator/output/http_api_explicit_stage.json +++ b/tests/translator/output/http_api_explicit_stage.json @@ -94,6 +94,12 @@ "Ref": "AWS::StackName" } }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], "paths": { "$default": { "x-amazon-apigateway-any-method": { diff --git a/tests/translator/output/implicit_http_api.json b/tests/translator/output/implicit_http_api.json index c2ba5006e3..6218d21173 100644 --- a/tests/translator/output/implicit_http_api.json +++ b/tests/translator/output/implicit_http_api.json @@ -82,6 +82,12 @@ "Ref": "AWS::StackName" } }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], "paths": { "/basic2": { "post": { diff --git a/tests/translator/output/implicit_http_api_auth_and_simple_case.json b/tests/translator/output/implicit_http_api_auth_and_simple_case.json index 6bbfafa11c..efcd8d3bbd 100644 --- a/tests/translator/output/implicit_http_api_auth_and_simple_case.json +++ b/tests/translator/output/implicit_http_api_auth_and_simple_case.json @@ -95,6 +95,12 @@ "Ref": "AWS::StackName" } }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], "paths": { "/scope3": { "post": { diff --git a/tests/translator/output/implicit_http_api_with_many_conditions.json b/tests/translator/output/implicit_http_api_with_many_conditions.json index 86e20884ef..ac14371a17 100644 --- a/tests/translator/output/implicit_http_api_with_many_conditions.json +++ b/tests/translator/output/implicit_http_api_with_many_conditions.json @@ -199,6 +199,12 @@ "Ref": "AWS::StackName" } }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], "paths": { "/hello/again": { "Fn::If": [ From bda77d24871ee373e0fb5b96010686e353fec139 Mon Sep 17 00:00:00 2001 From: Keeton Hodgson Date: Thu, 20 Feb 2020 14:12:00 -0800 Subject: [PATCH 4/7] Handle invalid definition body error --- samtranslator/model/api/http_api_generator.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/samtranslator/model/api/http_api_generator.py b/samtranslator/model/api/http_api_generator.py index 6a800fd5bf..5a96ea6e7f 100644 --- a/samtranslator/model/api/http_api_generator.py +++ b/samtranslator/model/api/http_api_generator.py @@ -131,6 +131,14 @@ def _add_tags(self): if not self.definition_body: return + if self.tags and not OpenApiEditor.is_valid(self.definition_body): + raise InvalidResourceException( + self.logical_id, + "Unable to add `Tags` because 'DefinitionBody' does not contain a valid OpenApi definition.", + ) + elif not OpenApiEditor.is_valid(self.definition_body): + return + if not self.tags: self.tags = {} self.tags[HttpApiTagName] = "SAM" From 317194b2a8f631fb5c1c43c83b991f9dab75c4ff Mon Sep 17 00:00:00 2001 From: Keeton Hodgson Date: Fri, 21 Feb 2020 09:10:32 -0800 Subject: [PATCH 5/7] Add test for an existing tag --- .../translator/input/http_api_existing_openapi_conditions.yaml | 3 +++ .../output/aws-cn/http_api_existing_openapi_conditions.json | 3 ++- .../aws-us-gov/http_api_existing_openapi_conditions.json | 3 ++- .../output/http_api_existing_openapi_conditions.json | 3 ++- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/translator/input/http_api_existing_openapi_conditions.yaml b/tests/translator/input/http_api_existing_openapi_conditions.yaml index 83118fcada..7b0b5fec14 100644 --- a/tests/translator/input/http_api_existing_openapi_conditions.yaml +++ b/tests/translator/input/http_api_existing_openapi_conditions.yaml @@ -109,6 +109,9 @@ Resources: - scope4 responses: {} openapi: 3.0.1 + tags: + - name: Tag1 + description: this tag exists, but doesn't have an amazon extension value components: securitySchemes: oauth2Auth: diff --git a/tests/translator/output/aws-cn/http_api_existing_openapi_conditions.json b/tests/translator/output/aws-cn/http_api_existing_openapi_conditions.json index 8e3363b94f..19b0491b02 100644 --- a/tests/translator/output/aws-cn/http_api_existing_openapi_conditions.json +++ b/tests/translator/output/aws-cn/http_api_existing_openapi_conditions.json @@ -282,7 +282,8 @@ }, { "name": "Tag1", - "x-amazon-apigateway-tag-value": "value1" + "x-amazon-apigateway-tag-value": "value1", + "description": "this tag exists, but doesn't have an amazon extension value" }, { "name": "Tag2", diff --git a/tests/translator/output/aws-us-gov/http_api_existing_openapi_conditions.json b/tests/translator/output/aws-us-gov/http_api_existing_openapi_conditions.json index 0223135e69..07e7c51583 100644 --- a/tests/translator/output/aws-us-gov/http_api_existing_openapi_conditions.json +++ b/tests/translator/output/aws-us-gov/http_api_existing_openapi_conditions.json @@ -276,7 +276,8 @@ }, { "name": "Tag1", - "x-amazon-apigateway-tag-value": "value1" + "x-amazon-apigateway-tag-value": "value1", + "description": "this tag exists, but doesn't have an amazon extension value" }, { "name": "Tag2", diff --git a/tests/translator/output/http_api_existing_openapi_conditions.json b/tests/translator/output/http_api_existing_openapi_conditions.json index cd649a0991..9dd6bcace4 100644 --- a/tests/translator/output/http_api_existing_openapi_conditions.json +++ b/tests/translator/output/http_api_existing_openapi_conditions.json @@ -276,7 +276,8 @@ }, { "name": "Tag1", - "x-amazon-apigateway-tag-value": "value1" + "x-amazon-apigateway-tag-value": "value1", + "description": "this tag exists, but doesn't have an amazon extension value" }, { "name": "Tag2", From 4a6e671b1eef77a8ed434e60fc119de017e60668 Mon Sep 17 00:00:00 2001 From: Keeton Hodgson Date: Fri, 21 Feb 2020 09:26:08 -0800 Subject: [PATCH 6/7] Add tests for error cases --- tests/translator/input/error_http_api_tags.yaml | 8 ++++++++ tests/translator/input/error_http_api_tags_def_uri.yaml | 7 +++++++ .../aws-cn/http_api_existing_openapi_conditions.json | 8 +------- tests/translator/output/error_http_api_tags.json | 8 ++++++++ tests/translator/output/error_http_api_tags_def_uri.json | 8 ++++++++ tests/translator/test_translator.py | 2 ++ 6 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 tests/translator/input/error_http_api_tags.yaml create mode 100644 tests/translator/input/error_http_api_tags_def_uri.yaml create mode 100644 tests/translator/output/error_http_api_tags.json create mode 100644 tests/translator/output/error_http_api_tags_def_uri.json diff --git a/tests/translator/input/error_http_api_tags.yaml b/tests/translator/input/error_http_api_tags.yaml new file mode 100644 index 0000000000..2af7574b4b --- /dev/null +++ b/tests/translator/input/error_http_api_tags.yaml @@ -0,0 +1,8 @@ +Resources: + Api: + Type: AWS::Serverless::HttpApi + Properties: + DefinitionBody: + invalid: def_body + Tags: + Tag: value \ No newline at end of file diff --git a/tests/translator/input/error_http_api_tags_def_uri.yaml b/tests/translator/input/error_http_api_tags_def_uri.yaml new file mode 100644 index 0000000000..dfe4cb642b --- /dev/null +++ b/tests/translator/input/error_http_api_tags_def_uri.yaml @@ -0,0 +1,7 @@ +Resources: + Api: + Type: AWS::Serverless::HttpApi + Properties: + DefinitionUri: s3://bucket/key + Tags: + Tag: value \ No newline at end of file diff --git a/tests/translator/output/aws-cn/http_api_existing_openapi_conditions.json b/tests/translator/output/aws-cn/http_api_existing_openapi_conditions.json index 19b0491b02..2e839b97a1 100644 --- a/tests/translator/output/aws-cn/http_api_existing_openapi_conditions.json +++ b/tests/translator/output/aws-cn/http_api_existing_openapi_conditions.json @@ -104,13 +104,7 @@ "title": { "Ref": "AWS::StackName" } - }, - "tags": [ - { - "name": "httpapi:createdBy", - "x-amazon-apigateway-tag-value": "SAM" - } - ], + }, "paths": { "/basic": { "post": { diff --git a/tests/translator/output/error_http_api_tags.json b/tests/translator/output/error_http_api_tags.json new file mode 100644 index 0000000000..97bd009e4e --- /dev/null +++ b/tests/translator/output/error_http_api_tags.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "errorMessage": "Resource with id [Api] is invalid. Unable to add `Tags` because 'DefinitionBody' does not contain a valid OpenApi definition." + } + ], + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [Api] is invalid. Unable to add `Tags` because 'DefinitionBody' does not contain a valid OpenApi definition." +} \ No newline at end of file diff --git a/tests/translator/output/error_http_api_tags_def_uri.json b/tests/translator/output/error_http_api_tags_def_uri.json new file mode 100644 index 0000000000..5b9eaa569a --- /dev/null +++ b/tests/translator/output/error_http_api_tags_def_uri.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "errorMessage": "Resource with id [Api] is invalid. Tags works only with inline OpenApi specified in the 'DefinitionBody' property." + } + ], + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [Api] is invalid. Tags works only with inline OpenApi specified in the 'DefinitionBody' property." +} \ No newline at end of file diff --git a/tests/translator/test_translator.py b/tests/translator/test_translator.py index 7a271f5076..e9d33c447c 100644 --- a/tests/translator/test_translator.py +++ b/tests/translator/test_translator.py @@ -602,6 +602,8 @@ def _generate_new_deployment_hash(self, logical_id, dict_to_hash, rest_api_to_sw "error_http_api_event_invalid_api", "error_http_api_invalid_auth", "error_http_api_invalid_openapi", + "error_http_api_tags", + "error_http_api_tags_def_uri", "error_implicit_http_api_method", "error_implicit_http_api_path", "error_http_api_event_multiple_same_path", From 4eeb2850013fe45622fb290b8bc8e9f9cb7b6f48 Mon Sep 17 00:00:00 2001 From: Keeton Hodgson Date: Fri, 21 Feb 2020 09:45:12 -0800 Subject: [PATCH 7/7] Update comments in open_api.py --- samtranslator/open_api/open_api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samtranslator/open_api/open_api.py b/samtranslator/open_api/open_api.py index d38815108a..76569e787f 100644 --- a/samtranslator/open_api/open_api.py +++ b/samtranslator/open_api/open_api.py @@ -27,8 +27,8 @@ def __init__(self, doc): Initialize the class with a swagger dictionary. This class creates a copy of the Swagger and performs all modifications on this copy. - :param dict doc: Swagger document as a dictionary - :raises ValueError: If the input Swagger document does not meet the basic Swagger requirements. + :param dict doc: OpenApi document as a dictionary + :raises ValueError: If the input OpenApi document does not meet the basic OpenApi requirements. """ if not OpenApiEditor.is_valid(doc): raise ValueError(