aws-amplify / amplify-cli

The AWS Amplify CLI is a toolchain for simplifying serverless web and mobile development.
Apache License 2.0
2.81k stars 821 forks source link

custom cloudformation parameters for environments #3240

Open cyrfer opened 4 years ago

cyrfer commented 4 years ago

How can I pass custom cloudformation parameters whose values change for different environments? I'd like to leverage Amplify Environments but I don't know what I need to do to get environment specific CF parameters passed to each CF stack. Does the Amplify CI/CD Console help?

I discovered if I create the "parameters.json" file in my Function stack folder, the values are passed to CF deploy. What happens when I define another environment? Does the "parameters.json" file get replaced?

Also, I only discovered "parameters.json" on a wild guess. Is there a bug in the code generator for Functions that this file is not created automatically?

Which Category is your question related to?

I want to give my Function stack custom Cloudformation parameters that are specific to the environment. For example, my API may have a dependency URI different in DEV and PROD environments.

What AWS Services are you utilizing?

AWS AppSync, Lambda, Cognito, Amplify CI/CD, etc

Provide additional details e.g. code snippets

E.g. Sample code, versions of Amplify you are using

npm list -g --depth=0 | grep -i amplify
@aws-amplify/cli@4.12.0
Ashish-Nanda commented 4 years ago

Transferring to the CLI repo as the question seems related to CLI/console

kaustavghosh06 commented 4 years ago

I discovered if I create the "parameters.json" file in my Function stack folder, the values are passed to CF deploy. What happens when I define another environment? Does the "parameters.json" file get replaced?

Also, I only discovered "parameters.json" on a wild guess. Is there a bug in the code generator for Functions that this file is not created automatically?

The parameters.json file contains parameter's which are passed to the Cloudformation template and is agnostic to environment changes and is not replaced per environment. For a simple "Hello world" Lambda function Cloudformation template, you don't need to pass any parameters to the Cloudformation template - that's why the file is not generated.

Which Category is your question related to?

I want to give my Function stack custom Cloudformation parameters that are specific to the environment. For example, my API may have a dependency URI different in DEV and PROD environments.

For environment specific parameters, I would recommend injecting the values in the amplify/team-provider-info.json file. The format of the file per environmnet would be like the the following:


{
    "dev": {
        "awscloudformation": {
            "AuthRoleName": "xxxxxxxxxxx",
            "UnauthRoleArn": "xxxxxxxxxxx",
            "AuthRoleArn": " "xxxxxxxxxxx"",
            "Region": "xxxxxxxxxxx",
            "DeploymentBucketName":  "xxxxxxxxxxx",
            "UnauthRoleName":  "xxxxxxxxxxx"
            "StackName": "xxxxxxxxxxx",
            "StackId":  "xxxxxxxxxxx"
        },
        "categories": {
            "function": {
                "<function-resource-name>": {
                           <parameter-name>: <value>
                   }
            }
        }
    },
    "prod": {
        "awscloudformation": {
            "AuthRoleName": "xxxxxxxxxxx",
            "UnauthRoleArn": "xxxxxxxxxxx",
            "AuthRoleArn": " "xxxxxxxxxxx"",
            "Region": "xxxxxxxxxxx",
            "DeploymentBucketName":  "xxxxxxxxxxx",
            "UnauthRoleName":  "xxxxxxxxxxx"
            "StackName": "xxxxxxxxxxx",
            "StackId":  "xxxxxxxxxxx"
        },
        "categories": {
            "function": {
                "<function-resource-name>": {
                           <parameter-name>: <value>
                   }
            }
        }
    }
}

The parameters referred as <parameter-name> are auto-injected into the Cloudformation template and are environment specific.

@cyrfer Please let me know if you have any more questions around this.

kaustavghosh06 commented 4 years ago

Closing based on the last response. Please comment on this thread if you're still stuck and we'll re-open the issue.

cyrfer commented 4 years ago

@kaustavghosh06 this works great when i run amplify push from my local machine.

When I commit to codecommit and git push the amplify CICD build breaks saying my custom parameters were not provided.

EDIT:

I suspect the build spec (console makes it look like its using amplify.yml which is not checked into my repo) is not checking out the environment. My local system already has the amplify environment checked out, so maybe that's why I can deploy my backend from local using amplify push.

How can I verify the environment is actually "checked out" during the build process of Amplify CICD Console?

EDIT 2:

I tried this in another project and was able to do a little better, but I still have an issue when deploying from my local machine. In that project, I used the team file to successfully pass a resource value that is unique to each stack. Unfortunately I did not know the value when deploying the PROD stack, so I used a placeholder value "*" when deploying PROD. After the deploy succeeded, I determined the right value, plugged it into the team file in the PROD parameter section, and now when I run amplify push with my PROD env checked out locally, amplify push shows "no changes detected".

I am comfortable with Cloudformation but the deployment of amplify is still a mystery to me and others I am trying to evangelize it to.

cyrfer commented 4 years ago

This is linking the CICD question, which is related to the local CLI problems in EDIT 1 https://forums.aws.amazon.com/thread.jspa?threadID=318114

kaustavghosh06 commented 4 years ago

@cyrfer If you made changes to your team-provider.info file and want to do a push - you can install the latest version of the CLI - 4.16.1 and run amplify push --force - this would pickup your changes from the team-provider-info.json file and populate it into the Cloudformation tempalte.

Are you still stuck with the Amplify console build issue? Could you share the error stack from the Amplify console build step?

kaustavghosh06 commented 4 years ago

Closing this issue. If you're still stuck please comment on this thread.

cyrfer commented 4 years ago

@kaustavghosh06 yes, still stuck in amplify console. The build console output before the error was not that useful, but the downloadable logs were more useful.

Here is the Cloudformation event showing the real errors, obviously from the custom parameters that are still not being passed from the team file (attached):

Parameters: [InternalBucketName, MongoCollectionJobs, MongoUrl, MongoCollectionUsers, MongoDb, MongoUrlSecret, stateMachineArn] must have values
cyrfer commented 4 years ago

I want this tool to work for me but it really drives me crazy at times.

I wish I could get some insights at when the backend-config or team provider files are used in deployment. Today I found a critical feature that is barely documented, but I cant get it to work either.

@kaustavghosh06 you ask me to reply but you don't open the ticket. What should a developer do to get support with the CLI workflow since it is not supported by AWS Support?

jaga810 commented 4 years ago

How about using SSM Parameter Store to store and get CFn parameters. e.g. SSM Parameter Store Key: cognito-federation-access-id-${env name}

You can access amplify env name in CFn template. e.g. Parameters in auth category template

Parameters:
  env:
    Type: String
wizage commented 4 years ago

We found a semi work around for passing new environment variables to the functions:

Modifying the team-provider-info.json we were able to add a new section under function like this:

"dev": {
        "awscloudformation": {
           . . .
        },
        "categories": {
            "function": {
                "demowbd58adad4": {
                    "url": "dev.example.com
                }
            }
        }
    }

We then took went to the CloudFormation template for the function and added it to the parameters at the top and was able to !Ref it later in the file for environment variables. This seemed to work just fine when using amplify push.

Now the bug we found is around this: Next we need to create a new env for prod. So we run amplify env add and fill in the defaults for the new env.

Now we will update the team-provider-info.json with our environment variables that we added like above:

"prod": {
        "awscloudformation": {
           . . .
        },
        "categories": {
            "function": {
                "demowbd58adad4": {
                    "url": "prod.example.com
                }
            }
        }
    }

We decide not to run push and check out our dev environment again, the team-provider-info.json file deletes any variables put in the categories that was not synced. When checkout any env it will remove all the variables from all env even if they are pushed.

Other ways of it causing issues include:

Related tickets:

cyrfer commented 4 years ago

@jaga810 please, continue. How can I leverage that for specific configurations in each environment?

jaga810 commented 4 years ago
  1. Store specific configurations for each environment in Parameter Store using env name in key. image

  2. Refer configuration in Parameter Store in your CFn template using !Sub and resolve:ssm

Parameters:
  env:
    Default: production
    Type: String

Resources:
  S3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub '{{resolve:ssm:/your-project/${env}/s3-bucket-name:2}}'

If you store credentials, you should use resolve:ssm-secure instead of resolve:ssm

cyrfer commented 4 years ago

@jaga810 that's really cool. I did not know about resolve. I'll look for opportunities to apply it.

Unfortunately, I should have mentioned, some of the parameters I want to pass are resource ARNs that are created in other stacks of Amplify. So for an extra environment, there would be new ARNs that and I would not want to go around updating SSM Parameters.

Luckily, for this part of the problem, I discovered and noted a solution over here. The file parameters.json supports a syntax gem that helps a ton. Anyone know where this is documented?

{
  "stateMachineArn": {
    "Fn::GetAtt": [
        "stateMachineaudio",
        "Outputs.StateMachineArn"
    ]
  }
}
cyrfer commented 4 years ago

I was able to use the CLI (version 4.19.0, with bug fix #4161) to deploy a 2nd environment including specifications for environment-specific resources and environment variables.

I did run into a small problem that I wrote a separate ticket for. https://github.com/aws-amplify/amplify-cli/issues/4335

Now I have only a couple concerns remaining about using Amplify for multiple environments,

  1. I have not yet (but will soon) tested deploying into a different account or region.
  2. Sometimes I need to deploy a piece of my application (async workflows) into multiple regions in order to avoid S3 egress charges, and I don't think I can deploy only a piece of an Amplify application.
kaustavghosh06 commented 4 years ago

@cyrfer Glad that you were able to deploy your environments. I responded to your issue #4335

Regarding multiple environments, you will be able to deploy your distinct environments in multiple regions or different accounts. The key out here is to have the right AWS profiles set and have that associated with the corresponding environments that you create (you make that association whenever you create a new env). Please feel free to comment on this thread if you have more questions. Closing this issue.

cyrfer commented 4 years ago

@kaustavghosh06 using the profiles makes sense for multiple accounts and regions.

For point 2, I think I need a subset of the app deployed, not the full app. For example, I need a State Machine in multiple regions, but my API in only one region.

cyrfer commented 4 years ago

@kaustavghosh06 this issue is not resolved. Custom parameters per environment are still a challenge. After seeing success passing parameters using team...json file, I see them overwritten when I:

  1. git clone MYREPO
  2. amplify init

The clone has my parameters, but running amplify init overwrites them. I must discard the change amplify init causes for my parameters to work.

Only the parameters for the function resource categories were affected. The hosting and api categories were not overwritten.

$ amplify -v
4.19.0
kaustavghosh06 commented 4 years ago

@cyrfer If you're sharing your project with your team or cloning it on a different machine it is recommended to remove the team-provider-info.json file from your .gitignore file - that way you'll not loose your team-provider-info.json file. We've documented this out here as well - https://docs.amplify.aws/cli/teams/shared#sharing-projects-outside-the-team

cyrfer commented 4 years ago

@kaustavghosh06 I did. Like I said, some categories were not affected.

To be clear,

The clone has my parameters

and

running amplify init overwrites them

I don't understand why anyone would add the team...json to .gitignore. It's required for passing environment specific parameters.

kaustavghosh06 commented 4 years ago

@cyrfer The overwriting of these parameters was fixed in #4161 (and is a part of the latest version of the CLI) and you'd previously acknowledged of that the fix worked for you. Is that still an issue?

This is the reason why anyone would add the team-provier-info.json file to the .gitignore file - https://docs.amplify.aws/cli/teams/shared#sharing-projects-outside-the-team

cyrfer commented 4 years ago

and you'd previously acknowledged of that the fix worked for you. Is that still an issue?

I acknowledged that CLI 4.19.0 allowed me to successfully deploy one environment with env-specific parameters, specified in the team.json file, per working with @wizage offline.

Deploying to one environment is no longer an issue.

It would be cruel to the other team if you remove that file. I can imagine omitting things, but removing means they would need to reverse engineer what keys need to be in the team...json file for each environment.

@kaustavghosh06

cyrfer commented 4 years ago

@kaustavghosh06 please keep this ticket open until this is resolved. https://github.com/aws-amplify/amplify-cli/issues/3240#issuecomment-634109040

kaustavghosh06 commented 4 years ago

Okay - so the request out here is to store the team-provider-info.json file in the cloud as well to share across team-members since there is hesitation that git isn't the right way to share this file within the team? Let me know if that's the request out here? I'll change the GitHub issue title since this is a different request altogether than the original bug filed initially which we fixed.

cyrfer commented 4 years ago

Not even close. :(

kaustavghosh06 commented 4 years ago

@cyrfer Sorry, could you please provide more details? The request is not clear based on your last comment.

cyrfer commented 4 years ago

I don't know how to say it any clearer than I did here. https://github.com/aws-amplify/amplify-cli/issues/3240#issuecomment-634109040

Mentioum commented 4 years ago

Okay - so the request out here is to store the team-provider-info.json file in the cloud as well

This is very close to what Terraform does with its .tfstate files. Big fan of it tbh along with state locking when deploying using DynamoDB. docs here

That aside. Having read through this thread I do think that the docs could benefit from an end to end guide on how to add custom variables to Lambda functions from beginning to end. If this already exists I'd really appreciate a link :pray:

cyrfer commented 4 years ago

@Mentioum

Here is the current guide on using Custom CF, which provides enough info that one can use it for custom environment variables in Lambda Functions. https://docs.amplify.aws/cli/usage/customcf

To be clear, I am not asking for help with custom environment variables for a Lambda. I'm comfortable customizing the CF templates generated by the Amplify CLI, and passing CF template parameters as Function environment variables.

Mentioum commented 4 years ago

@Mentioum

Here is the current guide on using Custom CF, which provides enough info that one can use it for custom environment variables in Lambda Functions. https://docs.amplify.aws/cli/usage/customcf

To be clear, I am not asking for help with custom environment variables for a Lambda. I'm comfortable customizing the CF templates generated by the Amplify CLI, and passing CF template parameters as Function environment variables.

Thanks for the link. Yep I understand what you're looking for re multienv and I'll want that too eventually. At the moment I'm just adding All envs to the CF template and then using them conditionally based on the ENV environment variable.

Unideal and obviously not as secure as I'd like in the longer run. But ok... for now.

Looking forward to multienv env support 💪, until then it looks looks like KMS is the best option... bit of a pain.

BennettSmith commented 3 years ago

Storing environment specific properties in the team-provider-info.json file breaks down when using AWS Console PR previews. Deployments fail in CF with “variable must contain a value” errors because the synthesized environment names for each preview are not in the team-provider-info.json file.

Is there any workable solution to that issue?

rob-didio commented 2 years ago

I'm having a problem with this as well, specific to user pool groups. When I perform the steps provided by @kaustavghosh06 I can't seem to get it to work. It appears before the push the entire template is overwritten by my user group precedences which bars me from adding custom params? That's my assumption anyway. Would like clarification if this is what is actually occurring.

Background on use-case: Using Amplify Video and would like to give per environment access to the buckets to my admins. I'm also planning to be able to access buckets outside of my account in the future as well. Having custom params available for userpool group policies in my toolbelt would be nice. I don't believe this specific issue is tied to Amplify Video. More in line with getting custom params down which should line up with an issue on amplify cli.

josefaidt commented 2 years ago

cc @edwardfoyle @swaminator for visibility on state management rework

neekey commented 2 years ago

I'm having a related question around amplify parameters usage in my template.json for my hosting.

I'm trying to add a "Comment" property to my CloudFrontDistribution resource, and I want it to include both env name and the stack name, but unfortunately i havent not found a way to reference to any other variables other than env.

Is there any way i can reference to the keys in awscloudformation section from team-provider-info.json?

ghost commented 2 years ago

i figured out a couple of helpful tips and tricks with regards to custom cf services in amplify. for starts, i can reformat the auto generated cf json script to yaml, and it still works. i then add all my general aws cf requirements into amplify custom services. and everything is once again in one space (instead of having CF scripts in git repos pipelines etc). i also figured out how to pass parameters into the custom CF scripts, by using the parameters.json file (this has to remain a json file though). but - my question (and i think others had the same question, yet no answers).... how can I set up ENV variables in amplify, and pass those env variables down into the amplify / cf-cli runtime so that i can then feed my amplify custom cf scripts with env variables which were created in the amplify console?

P.S.

  1. alternative is to use ssmparms - this is not an option, since we even use CF to create ssm parameters
  2. alternative is to use CF mappings / conditions based on the amplify "env" variable - this is clumsy and tuff to maintain in a large setup
  3. alternative is to execute aws cloudformation cli directly from the amplify pipeline - once again this feels counterproductive since amplify gave us this nice custom service feature

and one last question: how does amplify assign a value to the "env" variable which is a standard variable in all the cf scripts? i tried to trace this back through all the json files to see how this gets an assigned value, but could not find how it is done