Carlos Jaime C. De Leon
Carlos Jaime C. De Leon

Reputation: 2896

Is there a way to specify only changed parameters using aws cloudformation update stack and avoid explicit UsePreviousValue for unchanged parameters?

I am trying to write a generic script in AWS Cloudformation CLI that will update the stacks' parameter AMI to a new value while leaving the rest of the parameters as is.

So far, I tried doing this like so:

aws cloudformation update-stack --stack-name asg-xxx-123 --use-previous-template --parameters ParameterKey=ApplicationName,UsePreviousValue=true  ParameterKey=ArtefactVersion,UsePreviousValue=true ParameterKey=MachineImage,ParameterValue=ami-123

Notice that there are 2 parameters who are just using UsePreviousValue=true and only the the value of ParameterKey=MachineImage is the one that needs to change - this works fine.

However, since I need it to be a generic script how can I handle the case where some stacks have more parameters than above (or even some have different parameters but still have ParameterKey=MachineImage)? Is there way to say only change value of ParameterKey=MachineImage and all the rest should be using previous value without explicitly listing in the --parameters?

Upvotes: 5

Views: 9933

Answers (4)

Akaisteph7
Akaisteph7

Reputation: 6476

Per @MolecularMan's comment, provided you have the template that was previously used, you can use the deploy command instead of update-stack:

aws cloudformation deploy \
--stack-name asg-xxx-123 \
--template-file /path/to/template.json \
--region us-east-1 \
--capabilities CAPABILITY_NAMED_IAM \
--parameter-overrides MachineImage=ami-123

Upvotes: 0

James Hrivnak
James Hrivnak

Reputation: 1

I appreciated this forum, I was able to strategize accordingly and was able to come up with a solution using DescribeStacks. In this node lambda implementation following, I have it responding to a file upload event to s3 as part of a devops artifact pipeline. The ssm.putParameter command is specific to my use case but the rest shows the basic idea- first fetch the existing parameters, then update the stack with those existing parameters as well as the new one. Then create the change set, wait for it to be created, and finally execute it.

Thanks everyone!

const AWS = require('aws-sdk');

AWS.config.update({ region: process.env.APPLICATION_REGION });

const ssm = new AWS.SSM();
const cfn = new AWS.CloudFormation();

exports.handler = async (event) => {
  try {
    await cfn
      .deleteChangeSet({
        StackName: 'cwd-best-stack',
        ChangeSetName: 'cwd-best-change-set',
      })
      .promise();
  } catch (err) {
    console.info('<<---<- no change set to delete! ->--->>');
  }

  const ssmPathInit = event.Records[0].s3.object.key.split('/')[0];
  const ssmParamName = '/cwd/objVersions/' + ssmPathInit;
  const ssmValue = event.Records[0].s3.object.versionId;

  const ssmParamsEvent = {
    Name: ssmParamName,
    Value: ssmValue,
    Overwrite: true,
    Tier: 'Standard',
    Type: 'String',
  };

  // Fetch Existing Parameters
  const {
    Stacks: [{ Parameters }],
  } = await cfn.describeStacks({ StackName: 'cwd-best-stack' }).promise();

  await ssm.putParameter(ssmParamsEvent).promise();

  const changeSetParamsEvent = {
    ChangeSetName: 'cwd-best-change-set',
    StackName: 'cwd-best-stack',
    ChangeSetType: 'UPDATE',
    Parameters: [
      ...Parameters,
      {
        ParameterKey: 'cwd' + ssmPathInit.toLowerCase() + 'version',
        ParameterValue: ssmValue,
      },
    ],
    UsePreviousTemplate: true,
    Capabilities: ['CAPABILITY_IAM'],
  };

  await cfn.createChangeSet(changeSetParamsEvent).promise();

  await new Promise((resolve) => {
    setInterval(async () => {
      const cfnDeets = await cfn
        .describeChangeSet({
          StackName: 'cwd-best-stack',
          ChangeSetName: 'cwd-best-change-set',
        })
        .promise();

      if (cfnDeets.ExecutionStatus === 'AVAILABLE') {
        resolve('ok');
      }
    }, 5000);
  });

  await cfn
    .executeChangeSet({
      ChangeSetName: 'cwd-best-change-set',
      StackName: 'cwd-best-stack',
    })
    .promise();
  console.info('executed change set');
};

Upvotes: 0

Carlos Jaime C. De Leon
Carlos Jaime C. De Leon

Reputation: 2896

I was able to write the unix script using the aws cli as per below:

curdate=`date +"%Y-%m-%d"`
newami=${1} 
for sname in $(aws cloudformation describe-stacks --query "Stacks[?contains(StackName,'prefix-') ].StackName" --output text) ;
do
    paramslist="--parameters ";
     
    for paramval in $(aws cloudformation describe-stacks --stack-name $sname --query "Stacks[].Parameters[].ParameterKey" --output text) ;
    do
        if [ $paramval == "MachineImg" ] || [ $paramval == "AMI" ]
        then
            paramslist+="ParameterKey=${paramval},ParameterValue=${newami} "; #use the ami from args
        else
            paramslist+="ParameterKey=${paramval},UsePreviousValue=true "; #else keep using UsePreviousValue=true
        fi
    done
     
    printf "aws cloudformation update-stack --stack-name ${sname} --use-previous-template ${paramslist};\n" >> "/tmp/ami-update-${curdate}.sh"
done

Which generates a new .sh file which contains the update commands, I then review the contents of that generated .sh and do a source to execute these :

source ./ami-update-2020-08-17.sh

Upvotes: 7

Marcin
Marcin

Reputation: 238169

Based on the comments.

The aws cloudformation update-stack does not provide desired functionality.

However, a possible solution is develop a custom wrapper around aws cloudformation update-stack. The wrapper would allow a user to provide only new/changed parameters. It would also use describe-stacks command to get the current values of existing stack parameters.

Having the current parameters from the stack, as well as the new/changed ones, the wrapper could construct construct the valid aws cloudformation update-stack command and execute the update.

Upvotes: 4

Related Questions