-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
Bug: SAM deploy tries to delete and recreate AWS::Serverless::API domain when switching away from Fn:If to hardcoded value #3007
Comments
Thanks for reporting this issue @Landon-Alo , transferring to SAMT repo.
|
@mndeveci ah apologies. Thank you for transferring. |
Do you encounter the issue if you add |
@hoffa, thanks for the response. I added the
Then I ran sam build and deploy with the
Without the
I then changed the
I then deleted the stack and rebuilt it, but this time with the Unfortunately I would like to avoid deleting a production stack and imposing downtime on critical services. Any thoughts for how I could do a migration with an existing stack without deleting it? Thanks. |
From the code, the logical ID of
There is no workaround available for now and we cannot change how the logical ID is generated in SAM to avoid backward compatibility issue. |
CFN doesn't support changing logical ID without replacing the resource (Create new one and delete the old resource). However, in theory this should work: Template A: the original SAM template that was deployed successfully
|
Thanks @aahung I'll work on figuring out the flow on my test sam app. I have a few clarifying questions. So if I understand correctly:
After here is where I start to get a little lost. How can I get the translated template without doing a full stack deployment? I think this is where I'm getting hung up on and is creating confusion for the later steps.
Is template B an entirely separate stack? Import seems to only be allowed when a stack has been fully initialized or when a stack is initially created. Should I be creating a new stack, minus the ApiGateway and LambdaFunctions (since they can't be built as they already exist) and then importing the resources into it? Or should I be modifying the processed JSON to not use the !If statements and use !Ref functions instead, and then creating a new stack and importing the resources at initialization? Or should I get a processed template that uses Thank you. |
There's a few ways. Get transformed template of deployed stackIf your stack is deployed, you can get the transformed template from the CloudFormation console (Template tab, enable View processed template). Or using the AWS CLI, assuming your stack is named aws cloudformation get-template --query TemplateBody --change-set-name "$(aws cloudformation describe-stacks --query 'Stacks[0].ChangeSetId' --output text --stack-name <my-stack>)" Transform template locallyIf you want to transform a template locally, you can use the script included in our repository: git clone https://github.com/aws/serverless-application-model.git
cd serverless-application-model
python3 -m venv .venv
source .venv/bin/activate
make init Then: bin/sam-translate.py --template-file template.yaml Note however that transforming using that script won't always work, as it assumes the input template is in same format as what Transform template without full deploymentIf you want a more faithful transformation, but without actually creating the resources in the template, you can create a change set (not execute it) and get the transformed template. If it's too tedious to do it through the console, you could whip up a script such as the following (untested, for inspiration only, not production-ready): import json
import sys
import uuid
import boto3
def transform(template: str) -> str:
cfn = boto3.client("cloudformation")
name = f"transform-{uuid.uuid4()}"
change_set = cfn.create_change_set(
TemplateBody=template,
StackName=name,
ChangeSetName=name,
ChangeSetType="CREATE",
Capabilities=[
"CAPABILITY_IAM",
"CAPABILITY_AUTO_EXPAND",
],
)
change_set_id = change_set["Id"]
waiter = cfn.get_waiter("change_set_create_complete")
waiter.wait(
ChangeSetName=change_set_id,
WaiterConfig={
"Delay": 1,
},
)
transformed = cfn.get_template(ChangeSetName=change_set_id)
cfn.delete_stack(StackName=name)
return json.dumps(transformed["TemplateBody"])
def main():
print(transform(sys.stdin.read()))
if __name__ == "__main__":
main() And then transform with: python transform.py < sam-template.yaml > cfn-template.json |
This one might not be needed. I think you can proceed the import without have it transformed first (to be confirmed in your test)
It is your original stack, as at that point, your original stack doesn't have the resources you want to rename.
Using AWS::LanguageExtensions might be a good choice as it avoid this issue in the future. See above, you might not need to process the template B manually. You can just import with template B and let CFN to handle the processing. Let me know if it works. |
Cool, thanks guys. I'll chip away at it and let you know if I have follow-up questions. |
So I think I worked out the flow but hit a hiccup with not being able to import The process
At this point I hit an error where it says Unfortunately I need to remove the RecordSetGroup from the stack as I use a Does this flow seem accurate? Any thoughts for how to get around the issue of not being able to import Thank you. |
If some resources do not support import, can they be excluded from those with |
@aahung this is a fair point and I actually think I can get away with doing that here. I was deleting resources from the stack by commenting them out of the untransformed template.yaml and then running build and deploy. This is a bit like taking a hammer to the problem. I'll revisit doing this work to the template directly. What is the best was to modify the transformed template directly? Should I download it, remove the necessary resources, then reimport it via the GUI? Thank you. |
Using CFN CreateChangeSet API is the method with closest output as normal SAM transform. Doing it locally with |
I went ahead and revisited this approach working with the transformed templates directly and trying to find a way to do the migration without having to touch The issue at hand is the For example here is the
Take note of the "RecordSets[Name]" key. Here is the RecordSetGroup resource after using the
You can see that the only change is Thus, I think I'm in a bit of a conundrum. Situation 1: Use This will change Situation 2: Use I don't think this would work either because I need to update the Conclusion What do you think? I'm not sure if there is a good way around this. My immediate reaction is that I would need to refactor |
I didn't realize For the current SAM-T, sorry, I don't see a nice workaround. Using language extension will definitely change the logical ID and will require you to delete the domain name first (causing downtime). An ugly workaroundI want to step back and know more about why you want to make change to the So you will have something like Conditions:
UseOldApiGatewayApi: ... (if the DomainName equals to your current production domain)
Resources:
...
ApiGatewayApi:
Condition: UseOldApiGatewayApi
Properties:
DomainName: !If [inDev, test.dev.hello.world, test.hello.world]
everything else stays the same
ApiGatewayApiNew:
Condition: UseApiGatewayApi
Properties:
DomainName: !Ref DomainName
everything else stays the same It is not pretty, will prevent you from adding language extensions in the future and may have cons. Another theoryIs it possible for you to not use the property Wait for SAM-T to have a new property to provide logical ID suffixI cannot guarantee this unfortunately but will look into whether a new property is possible. |
Thanks @aahung those are a few good ideas to go off of. I need to make it more dynamic because now the service needs to deploy to three different environments rather than two. Unfortunately, the way I had set it up locked me into a two environment setup. I'm currently testing an approach where I spin up a parallel version of the API that bypasses the I'll also play around with using a condition as you described, I didn't know you could do that! It may be ugly but it seems like that may be a good bandaid. If I have time, I'll also play around with using the native CFN resources and see if I can do a migration flow that way. I'll poke around with these solutions and follow-up. I feel like we're close to something. Thanks again. |
I came up with a flow that worked on my test application. Going to try it later this week on the main service. The process will be as follows:
At this point there should now be two DNS records for this service in each environment. Manually migrate traffic from the old DNS record to the new DNS record. Once the migration has completed in all environments do the following: Delete the old API from the SAM template This is similar in spirit to deconstructing the I'm going to give it a shot later this week and close this issue if I'm successful. |
Hello! Closing this out. The cloudfront approach didn't work either because we can't attach the alternate domain name to the distribution as long as the custom domain name for the API is up. Thus, we would have to take downtime while we delete the custom domain name and update the cloudfront distribution. At that point it's better to just take the downtime and rebuild the We could have likely used the conditional resource approach that was proposed in #3007 (comment), but we decided to manage the custom domain name, base path mapping, and route53 record in terraform instead. Thus, our flow was:
And that's it. In terms of the developers using the repository they'll be none the wiser to the change. The change largely impacts the infrastructure team and as such we've added documentation on how the custom domain name and associated elements are managed. Thanks again for you're help. I'm going to close this now. |
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:
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: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:
This is problematic for a couple of reasons.
I have also tried this by modifying the
AWS::Serverless::Api
resource just like so: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.
and here is the
samconfig.toml
. Note, that you don't need the parameter overrides to replicate the bug.After updating the
samconfig.toml
andtemplate.yaml
you'll need to do the following steps:DomainName
inAWS::Serverless::Api
to betest.dev.hello.world
(the same name it was deployed with before)Observed result:
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)
Thank you! Happy to answer any clarifying questions.
The text was updated successfully, but these errors were encountered: