Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 39 additions & 11 deletions samtranslator/model/api/http_api_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
AuthProperties = namedtuple("_AuthProperties", ["Authorizers", "DefaultAuthorizer"])
AuthProperties.__new__.__defaults__ = (None, None)
DefaultStageName = "$default"
HttpApiTagName = "httpapi:createdBy"


class HttpApiGenerator(object):
Expand Down Expand Up @@ -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()
Expand All @@ -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):
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -120,6 +119,36 @@ 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."
)

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"

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
Expand All @@ -134,7 +163,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():
Expand All @@ -151,7 +182,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):
Expand Down Expand Up @@ -179,7 +210,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

Expand Down Expand Up @@ -226,9 +257,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):
Expand Down
25 changes: 23 additions & 2 deletions samtranslator/open_api/open_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand All @@ -26,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(
Expand All @@ -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):
"""
Expand Down Expand Up @@ -358,6 +360,22 @@ def _set_method_authorizer(self, path, method_name, authorizer_name, authorizers
if security:
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), None)
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):
"""
Expand All @@ -369,6 +387,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
Expand Down
8 changes: 8 additions & 0 deletions tests/translator/input/error_http_api_tags.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Resources:
Api:
Type: AWS::Serverless::HttpApi
Properties:
DefinitionBody:
invalid: def_body
Tags:
Tag: value
7 changes: 7 additions & 0 deletions tests/translator/input/error_http_api_tags_def_uri.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Resources:
Api:
Type: AWS::Serverless::HttpApi
Properties:
DefinitionUri: s3://bucket/key
Tags:
Tag: value
4 changes: 0 additions & 4 deletions tests/translator/input/http_api_def_uri.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ Resources:
Type: AWS::Serverless::HttpApi
Properties:
DefinitionUri: s3://bucket/key
Tags:
Tag: value
StageName: !Join ["", ["Stage", "Name"]]

MyApi2:
Expand All @@ -14,8 +12,6 @@ Resources:
Bucket: bucket
Key: key
Version: version
Tags:
Tag: value

Function:
Type: AWS::Serverless::Function
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ Resources:
MyApi:
Type: AWS::Serverless::HttpApi
Properties:
Tags:
Tag1: value1
Tag2: value2
Auth:
Authorizers:
OAuth2:
Expand Down Expand Up @@ -106,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:
Expand Down
12 changes: 12 additions & 0 deletions tests/translator/output/aws-cn/explicit_http_api.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@
"Ref": "AWS::StackName"
}
},
"tags": [
{
"name": "httpapi:createdBy",
"x-amazon-apigateway-tag-value": "SAM"
}
],
"paths": {
"$default": {
"x-amazon-apigateway-any-method": {
Expand Down Expand Up @@ -177,6 +183,12 @@
"Ref": "AWS::StackName"
}
},
"tags": [
{
"name": "httpapi:createdBy",
"x-amazon-apigateway-tag-value": "SAM"
}
],
"paths": {
"$default": {
"x-amazon-apigateway-any-method": {
Expand Down
12 changes: 12 additions & 0 deletions tests/translator/output/aws-cn/explicit_http_api_minimum.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@
"Ref": "AWS::StackName"
}
},
"tags": [
{
"name": "httpapi:createdBy",
"x-amazon-apigateway-tag-value": "SAM"
}
],
"paths": {},
"openapi": "3.0.1"
}
Expand Down Expand Up @@ -99,6 +105,12 @@
"Ref": "AWS::StackName"
}
},
"tags": [
{
"name": "httpapi:createdBy",
"x-amazon-apigateway-tag-value": "SAM"
}
],
"paths": {
"$default": {
"x-amazon-apigateway-any-method": {
Expand Down
32 changes: 4 additions & 28 deletions tests/translator/output/aws-cn/http_api_def_uri.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,7 @@
"Name"
]
]
},
"Tags": [
{
"Value": "value",
"Key": "Tag"
}
]
}
}
},
"FunctionRole": {
Expand Down Expand Up @@ -103,13 +97,7 @@
"Ref": "MyApi2"
},
"AutoDeploy": true,
"StageName": "$default",
"Tags": [
{
"Value": "value",
"Key": "Tag"
}
]
"StageName": "$default"
}
},
"MyApi2": {
Expand All @@ -119,13 +107,7 @@
"Version": "version",
"Bucket": "bucket",
"Key": "key"
},
"Tags": [
{
"Value": "value",
"Key": "Tag"
}
]
}
}
},
"FunctionApi2Permission": {
Expand Down Expand Up @@ -155,13 +137,7 @@
"BodyS3Location": {
"Bucket": "bucket",
"Key": "key"
},
"Tags": [
{
"Value": "value",
"Key": "Tag"
}
]
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@
"Ref": "AWS::StackName"
}
},
"tags": [
{
"name": "httpapi:createdBy",
"x-amazon-apigateway-tag-value": "SAM"
}
],
"paths": {
"/basic": {
"post": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@
"title": {
"Ref": "AWS::StackName"
}
},
},
"paths": {
"/basic": {
"post": {
Expand Down Expand Up @@ -268,7 +268,22 @@
}
}
},
"openapi": "3.0.1"
"openapi": "3.0.1",
"tags": [
{
"name": "httpapi:createdBy",
"x-amazon-apigateway-tag-value": "SAM"
},
{
"name": "Tag1",
"x-amazon-apigateway-tag-value": "value1",
"description": "this tag exists, but doesn't have an amazon extension value"
},
{
"name": "Tag2",
"x-amazon-apigateway-tag-value": "value2"
}
]
}
}
}
Expand Down
Loading