0

What is the correct command syntax for checking whether or not a specific call to aws cloudformation update-stack will result in any changes being made?

The problem we are having is that an automation program that runs an aws cloudformation update-stack --stack-name ourstackname --template-body file://c:\path\to\ourtemplate.json --parameters ParameterKey=someKey,ParameterValue=someValue ... command is failing with the following error:

An error occurred (ValidationError) when calling the UpdateStack operation: No updates are to be performed.

The result is a 254 http response code which we can tell from this documentation link means that a lot of possible problems could have occurred. So it would NOT help us to handle that 254 response code.

What aws cloudformation cli command syntax can we type instead to have the automation process receive a 0 response code in cases where no changes will be made? For example, a --flag added to the aws cloudformation update-stack ... command to return 0 when no changes are made.

Alternatively, if there were some preview command that returned 0 indicating that NO CHANGES WILL BE MADE, then our automation could simply refrain from calling aws cloudformation update-stack ... in that situation.

Terraform, for example defaults to simply succeeding while reporting that no changes have been made after a run when presented with this use case.

halfer
  • 19,824
  • 17
  • 99
  • 186
CodeMed
  • 9,527
  • 70
  • 212
  • 364
  • Can you provide any example of a template that you use? `ValidationError` suggest that the template itself has an issue, maybe its syntax. – Marcin Mar 19 '22 at 06:01
  • @CodeMed can you review if any of the answers helps you? – tyron Mar 22 '22 at 12:17
  • Please refrain from using a quote block (`>`) if the material within it is not a quote. A "quote" is something said by another person or material contained within a book, film, audio recording, script, periodical, etc. Quotes used merely as highlighters make posts harder to read, as readers need to make mental adjustments from the usual meaning of this formatting. – halfer Mar 27 '22 at 09:43

2 Answers2

2

Since you are asking to create a "preview", I suggest you try creating a Change Set, reviewing it's output, and then deciding if you want to execute it in case some changes are listed.

The commands below have been tested in bash/zsh, you might need to tweak it a bit in a Windows environment (unfortunately I have no way to test in a Windows machine right now).

# start the change-set and get its ID (note the extra change-set-name, output and query params)
myid=$(aws cloudformation create-change-set --change-set-name my-change --stack-name ourstackname --template-body file://ourtemplate.json --parameters ParameterKey=someKey,ParameterValue=someValue --output text --query Id)

# wait for your change-set to finish execution
aws cloudformation wait change-set-create-complete --change-set-name $myid 2> /dev/null

# get the result status in a variable
result_status=$(aws cloudformation describe-change-set --change-set-name $myid --output text --query Status)

# only executes the change-set if there were changes, aka Status is complete (if no changes, this will be FAILED)
[[ "$result_status" == "CREATE_COMPLETE" ]] && aws cloudformation execute-change-set --change-set-name $myid

# cleanup change-set afterwards if you want to re-use the name "my-change" from the 1st command, otherwise just leave it
aws cloudformation delete-change-set --change-set-name $myid

Update: author asked for a Python, more resilient version of the implementation:

from typing import Dict, Tuple
import boto3
from botocore.exceptions import ClientError, WaiterError


def main():
    template_file = "../s3-bucket.yaml"

    with open(template_file) as template_fileobj:
        template_data = template_fileobj.read()

    client = boto3.client('cloudformation')
    changeset_applied = _create_and_execute_changeset(
        client, 'my-stack', 'my-changeset', template_data)
    print(changeset_applied)


def _create_and_execute_changeset(client: boto3.client, stack_name: str, changeset_name: str, template_body: str) -> bool:
    client.validate_template(TemplateBody=template_body)

    response_create_changeset = client.create_change_set(
        StackName=stack_name,
        ChangeSetName=changeset_name,
        TemplateBody=template_body,
    )
    changeset_id = response_create_changeset['Id']
    apply_changeset = True

    waiter = client.get_waiter('change_set_create_complete')
    try:
        waiter.wait(ChangeSetName=changeset_id)
    except WaiterError as ex:
        if ex.last_response['Status'] == 'FAILED' and ex.last_response['StatusReason'].startswith('The submitted information didn\'t contain changes'):
            apply_changeset = False
        else:
            raise

    if apply_changeset:
        client.execute_change_set(ChangeSetName=changeset_id)
        # executed changesets cleanup automatically
    else:
        # cleanup changeset not executed
        client.delete_change_set(ChangeSetName=changeset_id)

    return apply_changeset


if __name__ == '__main__':
    main()
tyron
  • 3,715
  • 1
  • 22
  • 36
  • @CodeMed ha, great question! If you're asking this, then you might not know about a somewhat hidden boto3 feature: [`waiters`](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/cloudformation.html#waiters). I'll see if I can work in a version of this code running in Python, but for now I'm going to at least add this part in my answer above. – tyron Mar 22 '22 at 21:25
  • I'm not sure if I understand your request now... When you say plain python, are you using Python to call AWS cli instead of using libraries? Maybe you can provide samples of what you have there so we can understand better... – tyron Mar 23 '22 at 02:47
  • forgot to tag @CodeMed in my comment – tyron Mar 23 '22 at 17:26
  • @CodeMed what I am saying is that currently I'm having a hard time to understand what you are looking for. Your question has CLI commands on it. CLI is meant to be run on terminal: bash, PowerShell, some type, but still terminal. You just mentioned Python in a comment here. Calling AWS APIs from "python without libraries" is very hard, since you need to handle authentication. So I believe there is something in your workflow that you are not sharing. That's why I asked you to provide sample code of what/how you are running, otherwise it's getting hard to help you – tyron Mar 23 '22 at 21:43
  • "Please show a more resilient way to wait for the change set creation to complete" what is wrong with the current approach in the answer? – tyron Mar 23 '22 at 21:48
  • any of these commands will throw errors in python. your code (`aws cloudformation update-stack`) will throw an error, since aws is not defined. you are hiding some important details from your setup – tyron Mar 24 '22 at 00:52
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/243259/discussion-between-tyron-and-codemed). – tyron Mar 24 '22 at 00:53
  • @CodeMed I updated my answer again. I'll have to disagree about calling `subprocess` in Python as "running this code in python" (from your 1st comment here). As you said, you are really just invoking shell commands from Python, but could be from any language. If you want a resilient implementation, I will strongly encourage you to use native libraries (boto) and native Python code, so that you can parse and handle exceptions. I added a full working (and resilient) example of this code in Python. If this is not what you're looking for, I recommend you review your question and be more specific – tyron Mar 25 '22 at 00:47
  • We did not use your boto suggestion because we found a very resilient approach using plain Python that uses less code and which does not require our users to add boto. But we are marking yours as the accepted answer because you put a lot of time into it, and because we used your cli commands as a starting point to create what we actually ended up using. – CodeMed Mar 25 '22 at 19:58
  • 1
    @CodeMed thanks for acknowledging and I'm glad this has helped you. If you can, please add your own solution as an answer here, so that others might benefit if they need. – tyron Mar 25 '22 at 20:13
0

Instead of aws cloudformation update-stack, you can use the aws cloudformation deploy command.

Documentation for aws cloudformation deploy

Based on your exact requirements, the following two flags described in the linked documentation can be used:

--no-execute-changeset (boolean) Indicates whether to execute the change set. Specify this flag if you want to view your stack changes before executing the change set. The command creates an AWS CloudFormation change set and then exits without executing the change set. After you view the change set, execute it to implement your changes.

--fail-on-empty-changeset | --no-fail-on-empty-changeset (boolean) Specify if the CLI should return a non-zero exit code if there are no changes to be made to the stack. The default behavior is to return a zero exit code.

Another advantage of using the deploy command is that it can be used to create a stack as well as update the stack if it already exists. If you are interested specifically in differences between deploy and update-stack or create-stack, this answer to a previous question provides more details.

Kaustubh Khavnekar
  • 2,553
  • 2
  • 14
  • I think with a `deploy`, if you pass `--no-execute-changeset --no-fail-on-empty-changeset`, you won't know if there were no changes or if it would have completed successfully even with changes. And passing `--no-execute-changeset --fail-on-empty-changeset` would get to the same issue they're having today, error code without much information. And it's still up to you to execute the changeset later. In my experience, `deploy` is only really useful if you wanna close your eyes and execute it always, otherwise you have more granular control with other commands. – tyron Mar 20 '22 at 01:41