Description
Description:
I have a lambda function with an API Gateway that is deployed to two environments. In each environment I want to specify a different Domain Name. To do so I used conditional statements within the AWS::Serverless::Api resource type:
ApiGatewayApi:
Type: AWS::Serverless::Api
Properties:
StageName: prod
Domain:
DomainName: !If [inDev, test.dev.hello.world, test.hello.world]
CertificateArn: !If [inDev, 123, 321]
EndpointConfiguration: EDGE
Route53:
HostedZoneId: !If [inDev, abcxyz, xyzabc]
This worked fine but then we were asked to set up the template.yaml to handle a third environment. To do this I decided to stop using the conditional and instead use parameters that are passed in via the parameter_overrides
option in samconfig.toml. This means that the above resource block now looks like:
ApiGatewayApi:
Type: AWS::Serverless::Api
Properties:
StageName: prod
Domain:
DomainName: !Ref DomainName
CertificateArn: !Ref EdgeCertificateArn
EndpointConfiguration: EDGE
Route53:
HostedZoneId: !Ref Route53HostedZoneId
Note that the domain name is unchanged for the two environments that already existed. I then try to deploy this to our dev environment via sam deploy --config-env dev --config-file ./samconfig.toml --tags createdby=awssam team=abc --resolve-image-repos --resolve-s3 --no-confirm-changeset --no-fail-on-empty-changeset
. Again, nothing is changed other than how I'm getting the data into the template.
What I expect to happen is that there will be no changes because I'm deploying using the dev config-env which already existed and for which I changed no values. I only moved values out of the conditional and into the parameter_overrides
.
What actually happens is the changeset reports the following:
CloudFormation stack changeset
-----------------------------------------------------------------------------------------------------------------------------------------
Operation LogicalResourceId ResourceType Replacement
-----------------------------------------------------------------------------------------------------------------------------------------
+ Add ApiGatewayApiDeployment567d98957 AWS::ApiGateway::Deployment N/A
0
+ Add ApiGatewayDomainName5a4c9e240d AWS::ApiGateway::DomainName N/A
* Modify ApiGatewayApiBasePathMapping AWS::ApiGateway::BasePathMapping True
* Modify ApiGatewayApiprodStage AWS::ApiGateway::Stage False
* Modify RecordSetGroup0d3ed29639 AWS::Route53::RecordSetGroup False
- Delete ApiGatewayApiDeploymentff19363ec AWS::ApiGateway::Deployment N/A
c
- Delete ApiGatewayDomainName4148406711 AWS::ApiGateway::DomainName N/A
-----------------------------------------------------------------------------------------------------------------------------------------
This is problematic for a couple of reasons.
- If this plan were to work it would involve downtime for our service since the domain would need to be deleted and recreated.
- The plan doesn't actually work because SAM will first try to create the custom domain, only to error out because it already exists.
I have also tried this by modifying the AWS::Serverless::Api
resource just like so:
ApiGatewayApi:
Type: AWS::Serverless::Api
Properties:
StageName: prod
Domain:
DomainName: test.dev.hello.world
CertificateArn: !If [inDev, 123, 321]
EndpointConfiguration: EDGE
Route53:
HostedZoneId: !If [inDev, abcxyz, xyzabc]
Where above I simply hardcode the DomainName (again, this is after having already deployed with the conditional setup prior). Even this setup will trigger the changeset above where it wants to delete the existing custom domain and create a new one.
Steps to reproduce:
I went ahead and replicated this behavior using the hello world SAM app with modification. What you'll need to do is initialize the hello world app and replace the template.yaml and samconfig.toml with the below code. Obviously, you'll need to update the Domain properties to actual values for your test case.
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
test-sam
Sample SAM Template for test-sam
Globals:
Function:
Timeout: 3
MemorySize: 128
Tracing: Active
Api:
TracingEnabled: true
Parameters:
Environment:
Type: String
Description: Name of environment
AllowedValues:
- dev
- prod
Conditions:
inDev:
!Equals [!Ref Environment, dev]
Resources:
#############################################################################
# API Gateway with Custom Domain Name
#############################################################################
ApiGatewayApi:
Type: AWS::Serverless::Api
Properties:
StageName: prod
Domain:
DomainName: !If [inDev, test.dev.hello.world, test.hello.world]
CertificateArn: !If [inDev, 123, 321]
EndpointConfiguration: EDGE
Route53:
HostedZoneId: !If [inDev, abcxyz, xyzabc]
#############################################################################
# Lambda for Service
#############################################################################
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello_world/
Handler: app.lambda_handler
Runtime: python3.9
Architectures:
- x86_64
Events:
HelloWorld:
Type: Api
Properties:
Path: /hello
Method: GET
RestApiId: !Ref ApiGatewayApi
and here is the samconfig.toml
. Note, that you don't need the parameter overrides to replicate the bug.
version = 0.1
[dev]
[dev.deploy]
[dev.deploy.parameters]
stack_name = "sam-test"
s3_prefix = "sam-test"
region = "us-east-1"
confirm_changeset = true
capabilities = ["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"]
parameter_overrides = [
"Environment=dev",
"DomainName=test.dev.hello.world",
"EdgeCertificateArn=123",
"Route53ZoneId=abcxyz",
]
[prod]
[prod.deploy]
[prod.deploy.parameters]
stack_name = "sam-test"
s3_prefix = "sam-test"
region = "us-east-1"
confirm_changeset = true
capabilities = ["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"]
parameter_overrides = [
"Environment=prod",
"DomainName=test.hello.world",
"EdgeCertificateArn=321",
"Route53ZoneId=xyabc",
]
After updating the samconfig.toml
and template.yaml
you'll need to do the following steps:
- Deploy the application to the dev environment.
- Change
DomainName
inAWS::Serverless::Api
to betest.dev.hello.world
(the same name it was deployed with before) - Build and try to deploy again
Observed result:
Initiating deployment
=====================
2023-03-08 14:09:49,686 | Collected default values for parameters: {}
2023-03-08 14:09:49,700 | Sam customer defined id is more priority than other IDs. Customer defined id for resource ApiGatewayApi is ApiGatewayApi
2023-03-08 14:09:49,700 | Sam customer defined id is more priority than other IDs. Customer defined id for resource HelloWorldFunction is HelloWorldFunction
2023-03-08 14:09:49,700 | 0 stacks found in the template
2023-03-08 14:09:49,700 | Collected default values for parameters: {}
2023-03-08 14:09:49,712 | Sam customer defined id is more priority than other IDs. Customer defined id for resource ApiGatewayApi is ApiGatewayApi
2023-03-08 14:09:49,712 | Sam customer defined id is more priority than other IDs. Customer defined id for resource HelloWorldFunction is HelloWorldFunction
2023-03-08 14:09:49,712 | 2 resources found in the stack
Uploading to sam-test/538be5ddbb1414e39664b8ea7dc96ed1.template 1609 / 1609 (100.00%)
Waiting for changeset to be created..
CloudFormation stack changeset
-----------------------------------------------------------------------------------------------------------------------------------------
Operation LogicalResourceId ResourceType Replacement
-----------------------------------------------------------------------------------------------------------------------------------------
+ Add ApiGatewayApiDeployment567d98957 AWS::ApiGateway::Deployment N/A
0
+ Add ApiGatewayDomainName5a4c9e240d AWS::ApiGateway::DomainName N/A
* Modify ApiGatewayApiBasePathMapping AWS::ApiGateway::BasePathMapping True
* Modify ApiGatewayApiprodStage AWS::ApiGateway::Stage False
* Modify RecordSetGroup0d3ed29639 AWS::Route53::RecordSetGroup False
- Delete ApiGatewayApiDeploymentff19363ec AWS::ApiGateway::Deployment N/A
c
- Delete ApiGatewayDomainName4148406711 AWS::ApiGateway::DomainName N/A
-----------------------------------------------------------------------------------------------------------------------------------------
Changeset created successfully. arn:aws:cloudformation:us-east-1:xxx:changeSet/samcli-deploy1678313390/cb3b2f9e-e8d0-469b-810d-d6c5c5731237
2023-03-08 14:10:03 - Waiting for stack create/update to complete
CloudFormation events from stack operations (refresh every 0.5 seconds)
-----------------------------------------------------------------------------------------------------------------------------------------
ResourceStatus ResourceType LogicalResourceId ResourceStatusReason
-----------------------------------------------------------------------------------------------------------------------------------------
CREATE_IN_PROGRESS AWS::ApiGateway::DomainName ApiGatewayDomainName5a4c9e240d -
CREATE_IN_PROGRESS AWS::ApiGateway::Deployment ApiGatewayApiDeployment567d98957 -
0
CREATE_FAILED AWS::ApiGateway::DomainName ApiGatewayDomainName5a4c9e240d test.dev.xxx.xxx already
exists in stack
arn:aws:cloudformation:us-
east-1:xxx:stack/sam-te
st/16f30000-bdf3-11ed-977a-12beb
d4450e9
CREATE_FAILED AWS::ApiGateway::Deployment ApiGatewayApiDeployment567d98957 Resource creation cancelled
0
UPDATE_ROLLBACK_IN_PROGRESS AWS::CloudFormation::Stack sam-test The following resource(s) failed
to create:
[ApiGatewayDomainName5a4c9e240d,
ApiGatewayApiDeployment567d98957
0].
UPDATE_ROLLBACK_COMPLETE_CLEANUP AWS::CloudFormation::Stack sam-test -
_IN_PROGRESS
DELETE_COMPLETE AWS::ApiGateway::DomainName ApiGatewayDomainName5a4c9e240d -
DELETE_COMPLETE AWS::ApiGateway::Deployment ApiGatewayApiDeployment567d98957 -
0
UPDATE_ROLLBACK_COMPLETE AWS::CloudFormation::Stack sam-test -
-----------------------------------------------------------------------------------------------------------------------------------------
2023-03-08 14:13:07,379 | Execute stack waiter exception
Traceback (most recent call last):
File "/opt/homebrew/Cellar/aws-sam-cli/1.76.0/libexec/lib/python3.8/site-packages/samcli/lib/deploy/deployer.py", line 502, in wait_for_execute
waiter.wait(StackName=stack_name, WaiterConfig=waiter_config)
File "/opt/homebrew/Cellar/aws-sam-cli/1.76.0/libexec/lib/python3.8/site-packages/botocore/waiter.py", line 55, in wait
Waiter.wait(self, **kwargs)
File "/opt/homebrew/Cellar/aws-sam-cli/1.76.0/libexec/lib/python3.8/site-packages/botocore/waiter.py", line 375, in wait
raise WaiterError(
botocore.exceptions.WaiterError: Waiter StackUpdateComplete failed: Waiter encountered a terminal failure state: For expression "Stacks[].StackStatus" we matched expected path: "UPDATE_ROLLBACK_COMPLETE" at least once
2023-03-08 14:13:07,384 | Telemetry endpoint configured to be https://aws-serverless-tools-telemetry.us-west-2.amazonaws.com/metrics
2023-03-08 14:13:07,475 | Sending Telemetry: {'metrics': [{'commandRun': {'requestId': '9543398a-8c4d-4d4f-befc-1c2ad451d024', 'installationId': 'dda226e3-9b79-4e59-84a4-1c7253bce103', 'sessionId': '6666cf63-41ac-47e3-9766-19f1b6d116df', 'executionEnvironment': 'CLI', 'ci': False, 'pyversion': '3.8.16', 'samcliVersion': '1.76.0', 'awsProfileProvided': True, 'debugFlagProvided': True, 'region': 'us-east-1', 'commandName': 'sam deploy', 'metricSpecificAttributes': {'projectType': 'CFN', 'gitOrigin': None, 'projectName': 'c705de491dcb53c849e84aa5634de3748cc3f96f7126d125eef2aa054399d24d', 'initialCommit': None}, 'duration': 200522, 'exitReason': 'DeployFailedError', 'exitCode': 1}}]}
2023-03-08 14:13:08,016 | Telemetry response: 200
Error: Failed to create/update the stack: sam-test, Waiter StackUpdateComplete failed: Waiter encountered a terminal failure state: For expression "Stacks[].StackStatus" we matched expected path: "UPDATE_ROLLBACK_COMPLETE" at least once
Expected result:
I expected that there would be no changes on the changeset because I am not changing values, only the way the values are passed into the template (hardcoded vs using a conditional statement)
Additional environment details (Ex: Windows, Mac, Amazon Linux etc)
{
"version": "1.76.0",
"system": {
"python": "3.8.16",
"os": "macOS-13.2-arm64-arm-64bit"
},
"additional_dependencies": {
"docker_engine": "20.10.23",
"aws_cdk": "Not available",
"terraform": "Not available"
}
}
Thank you! Happy to answer any clarifying questions.