Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add modes support for RestApi #2055

Merged
merged 7 commits into from
Jun 11, 2021
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
5 changes: 4 additions & 1 deletion bin/sam-translate.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,10 @@ def transform_template(input_file_path, output_file_path):
feature_toggle = FeatureToggle(
FeatureToggleLocalConfigProvider(
os.path.join(my_path, "..", "tests", "feature_toggle", "input", "feature_toggle_config.json")
)
),
stage=None,
account_id=None,
region=None,
)
cloud_formation_template = transform(sam_template, {}, ManagedPolicyLoader(iam_client), feature_toggle)
cloud_formation_template_prettified = json.dumps(cloud_formation_template, indent=2)
Expand Down
27 changes: 25 additions & 2 deletions integration/helpers/base_test.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import json
import logging
import os

Expand Down Expand Up @@ -146,6 +147,28 @@ def create_and_verify_stack(self, file_name, parameters=None):
self.deploy_stack(parameters)
self.verify_stack()

def update_and_verify_stack(self, file_name, parameters=None):
"""
Updates the Cloud Formation stack and verifies it against the expected
result

Parameters
----------
file_name : string
Template file name
parameters : list
List of parameters
"""
if not self.stack_name:
raise Exception("Stack not created.")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typed Execptions?

self.output_file_path = str(Path(self.output_dir, "cfn_" + file_name + ".yaml"))
self.expected_resource_path = str(Path(self.expected_dir, file_name + ".json"))

self._fill_template(file_name)
self.transform_template()
self.deploy_stack(parameters)
self.verify_stack(end_state="UPDATE_COMPLETE")

def transform_template(self):
transform_template(self.sub_input_file_path, self.output_file_path)

Expand Down Expand Up @@ -342,12 +365,12 @@ def deploy_stack(self, parameters=None):
self.stack_description = self.client_provider.cfn_client.describe_stacks(StackName=self.stack_name)
self.stack_resources = self.client_provider.cfn_client.list_stack_resources(StackName=self.stack_name)

def verify_stack(self):
def verify_stack(self, end_state="CREATE_COMPLETE"):
"""
Gets and compares the Cloud Formation stack against the expect result file
"""
# verify if the stack was successfully created
self.assertEqual(self.stack_description["Stacks"][0]["StackStatus"], "CREATE_COMPLETE")
self.assertEqual(self.stack_description["Stacks"][0]["StackStatus"], end_state)
# verify if the stack contains the expected resources
error = verify_stack_resources(self.expected_resource_path, self.stack_resources)
if error:
Expand Down
11 changes: 11 additions & 0 deletions integration/resources/expected/single/basic_api_with_mode.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[
{"LogicalResourceId": "MyApi", "ResourceType": "AWS::ApiGateway::RestApi"},
{"LogicalResourceId": "MyApiDeploymenta808f15210", "ResourceType": "AWS::ApiGateway::Deployment"},
{"LogicalResourceId": "MyApiMyNewStageNameStage", "ResourceType": "AWS::ApiGateway::Stage"},
{"LogicalResourceId": "TestFunction", "ResourceType": "AWS::Lambda::Function"},
{"LogicalResourceId": "TestFunctionAliaslive", "ResourceType": "AWS::Lambda::Alias"},
{"LogicalResourceId": "TestFunctionGetPermissionMyNewStageName", "ResourceType": "AWS::Lambda::Permission"},
{"LogicalResourceId": "TestFunctionPutPermissionMyNewStageName", "ResourceType": "AWS::Lambda::Permission"},
{"LogicalResourceId": "TestFunctionRole", "ResourceType": "AWS::IAM::Role"},
{"LogicalResourceId": "TestFunctionVersione9898fd501", "ResourceType": "AWS::Lambda::Version"}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[
{"LogicalResourceId": "MyApi", "ResourceType": "AWS::ApiGateway::RestApi"},
{"LogicalResourceId": "MyApiDeploymentada889e3ac", "ResourceType": "AWS::ApiGateway::Deployment"},
{"LogicalResourceId": "MyApiMyNewStageNameStage", "ResourceType": "AWS::ApiGateway::Stage"},
{"LogicalResourceId": "TestFunction", "ResourceType": "AWS::Lambda::Function"},
{"LogicalResourceId": "TestFunctionAliaslive", "ResourceType": "AWS::Lambda::Alias"},
{"LogicalResourceId": "TestFunctionPutPermissionMyNewStageName", "ResourceType": "AWS::Lambda::Permission"},
{"LogicalResourceId": "TestFunctionRole", "ResourceType": "AWS::IAM::Role"},
{"LogicalResourceId": "TestFunctionVersion847aaa5fc1", "ResourceType": "AWS::Lambda::Version"}
]
34 changes: 34 additions & 0 deletions integration/resources/templates/single/basic_api_with_mode.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
Resources:
MyApi:
Type: AWS::Serverless::Api
Properties:
StageName: MyNewStageName
Mode: overwrite

TestFunction:
Type: 'AWS::Serverless::Function'
Properties:
Handler: index.handler
Runtime: python3.6
AutoPublishAlias: live
InlineCode: |
import json
def handler(event, context):
return {'statusCode': 200, 'body': json.dumps('Hello World!')}
Events:
Get:
Type: Api
Properties:
Path: /get
Method: get
RestApiId: !Ref MyApi
Put:
Type: Api
Properties:
Path: /put
Method: put
RestApiId: !Ref MyApi

Outputs:
ApiEndpoint:
Value: !Sub "https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/MyNewStageName"
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
Resources:
MyApi:
Type: AWS::Serverless::Api
Properties:
StageName: MyNewStageName
Mode: overwrite

TestFunction:
Type: 'AWS::Serverless::Function'
Properties:
Handler: index.handler
Runtime: python3.6
AutoPublishAlias: live
InlineCode: |
def handler(event, context):
print("Hello, world!")
Events:
Put:
Type: Api
Properties:
Path: /put
Method: put
RestApiId: !Ref MyApi

19 changes: 19 additions & 0 deletions integration/single/test_basic_api.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from integration.helpers.base_test import BaseTest
import requests


class TestBasicApi(BaseTest):
Expand All @@ -24,6 +25,24 @@ def test_basic_api(self):

self.assertEqual(len(set(first_dep_ids).intersection(second_dep_ids)), 0)

def test_basic_api_with_mode(self):
"""
Creates an API and updates its DefinitionUri
"""
# Create an API with get and put
self.create_and_verify_stack("basic_api_with_mode")

stack_output = self.get_stack_outputs()
api_endpoint = stack_output.get("ApiEndpoint")
response = requests.get(f"{api_endpoint}/get")
self.assertEqual(response.status_code, 200)

# Removes get from the API
self.update_and_verify_stack("basic_api_with_mode_update")
response = requests.get(f"{api_endpoint}/get")
# API Gateway by default returns 403 if a path do not exist
self.assertEqual(response.status_code, 403)

def test_basic_api_inline_openapi(self):
"""
Creates an API with and inline OpenAPI and updates its DefinitionBody basePath
Expand Down
5 changes: 5 additions & 0 deletions samtranslator/model/api/api_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ def __init__(
models=None,
domain=None,
description=None,
mode=None,
):
"""Constructs an API Generator class that generates API Gateway resources

Expand Down Expand Up @@ -230,6 +231,7 @@ def __init__(
self.description = description
self.shared_api_usage_plan = shared_api_usage_plan
self.template_conditions = template_conditions
self.mode = mode

def _construct_rest_api(self):
"""Constructs and returns the ApiGateway RestApi.
Expand Down Expand Up @@ -283,6 +285,9 @@ def _construct_rest_api(self):
if self.description:
rest_api.Description = self.description

if self.mode:
rest_api.Mode = self.mode

return rest_api

def _construct_body_s3_dict(self):
Expand Down
1 change: 1 addition & 0 deletions samtranslator/model/apigateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class ApiGatewayRestApi(Resource):
"EndpointConfiguration": PropertyType(False, is_type(dict)),
"BinaryMediaTypes": PropertyType(False, is_type(list)),
"MinimumCompressionSize": PropertyType(False, is_type(int)),
"Mode": PropertyType(False, is_str()),
}

runtime_attrs = {"rest_api_id": lambda self: ref(self.logical_id)}
Expand Down
2 changes: 2 additions & 0 deletions samtranslator/model/sam_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,7 @@ class SamApi(SamResourceMacro):
"Models": PropertyType(False, is_type(dict)),
"Domain": PropertyType(False, is_type(dict)),
"Description": PropertyType(False, is_str()),
"Mode": PropertyType(False, is_str()),
}

referable_properties = {
Expand Down Expand Up @@ -893,6 +894,7 @@ def to_cloudformation(self, **kwargs):
models=self.Models,
domain=self.Domain,
description=self.Description,
mode=self.Mode,
)

(
Expand Down
22 changes: 22 additions & 0 deletions tests/translator/input/api_with_mode.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Resources:
Function:
Type: AWS::Serverless::Function
Properties:
CodeUri: s3://sam-demo-bucket/member_portal.zip
Handler: index.gethtml
Runtime: nodejs12.x
Events:
GetHtml:
Type: Api
Properties:
RestApiId: Api
Path: /
Method: get

Api:
Type: AWS::Serverless::Api
Properties:
StageName: Prod
DefinitionUri: s3://sam-demo-bucket/webpage_swagger.json
Description: my description
Mode: overwrite
109 changes: 109 additions & 0 deletions tests/translator/output/api_with_mode.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
{
"Resources": {
"FunctionRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"ManagedPolicyArns": [
"arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
],
"Tags": [
{
"Value": "SAM",
"Key": "lambda:createdBy"
}
],
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"sts:AssumeRole"
],
"Effect": "Allow",
"Principal": {
"Service": [
"lambda.amazonaws.com"
]
}
}
]
}
}
},
"ApiProdStage": {
"Type": "AWS::ApiGateway::Stage",
"Properties": {
"DeploymentId": {
"Ref": "ApiDeploymentf117c932f7"
},
"RestApiId": {
"Ref": "Api"
},
"StageName": "Prod"
}
},
"FunctionGetHtmlPermissionProd": {
"Type": "AWS::Lambda::Permission",
"Properties": {
"Action": "lambda:InvokeFunction",
"Principal": "apigateway.amazonaws.com",
"FunctionName": {
"Ref": "Function"
},
"SourceArn": {
"Fn::Sub": [
"arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/",
{
"__Stage__": "*",
"__ApiId__": "Api"
}
]
}
}
},
"ApiDeploymentf117c932f7": {
"Type": "AWS::ApiGateway::Deployment",
"Properties": {
"RestApiId": {
"Ref": "Api"
},
"Description": "RestApi deployment id: f117c932f75cfa87d23dfed64e9430d0081ef289",
"StageName": "Stage"
}
},
"Api": {
"Type": "AWS::ApiGateway::RestApi",
"Properties": {
"BodyS3Location": {
"Bucket": "sam-demo-bucket",
"Key": "webpage_swagger.json"
},
"Description": "my description",
"Mode": "overwrite"
}
},
"Function": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Code": {
"S3Bucket": "sam-demo-bucket",
"S3Key": "member_portal.zip"
},
"Handler": "index.gethtml",
"Role": {
"Fn::GetAtt": [
"FunctionRole",
"Arn"
]
},
"Runtime": "nodejs12.x",
"Tags": [
{
"Value": "SAM",
"Key": "lambda:createdBy"
}
]
}
}
}
}
Loading