We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Flag --no-fail-on-empty-changeset does not work
I am running into a issue where the --no-fail-on-empty-changeset flag doesn't make a difference. The flag is recognised by the CLI (at least it does not say Unknown option), but it still fails with the error message FAILED. Reason: No updates are to be performed. This issue is on aws cloudformation deploy. Version 1.14.32

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `awscli/customizations/cloudformation/deployer.py`
Content:
```
1 # Copyright 2012-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License"). You
4 # may not use this file except in compliance with the License. A copy of
5 # the License is located at
6 #
7 # http://aws.amazon.com/apache2.0/
8 #
9 # or in the "license" file accompanying this file. This file is
10 # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11 # ANY KIND, either express or implied. See the License for the specific
12 # language governing permissions and limitations under the License.
13 
14 import sys
15 import time
16 import logging
17 import botocore
18 import collections
19 
20 from awscli.customizations.cloudformation import exceptions
21 from awscli.customizations.cloudformation.artifact_exporter import mktempfile, parse_s3_url
22 
23 from datetime import datetime
24 
25 LOG = logging.getLogger(__name__)
26 
27 ChangeSetResult = collections.namedtuple(
28                 "ChangeSetResult", ["changeset_id", "changeset_type"])
29 
30 
31 class Deployer(object):
32 
33     def __init__(self, cloudformation_client,
34                  changeset_prefix="awscli-cloudformation-package-deploy-"):
35         self._client = cloudformation_client
36         self.changeset_prefix = changeset_prefix
37 
38     def has_stack(self, stack_name):
39         """
40         Checks if a CloudFormation stack with given name exists
41 
42         :param stack_name: Name or ID of the stack
43         :return: True if stack exists. False otherwise
44         """
45         try:
46             resp = self._client.describe_stacks(StackName=stack_name)
47             if len(resp["Stacks"]) != 1:
48                 return False
49 
50             # When you run CreateChangeSet on a a stack that does not exist,
51             # CloudFormation will create a stack and set it's status
52             # REVIEW_IN_PROGRESS. However this stack is cannot be manipulated
53             # by "update" commands. Under this circumstances, we treat like
54             # this stack does not exist and call CreateChangeSet will
55             # ChangeSetType set to CREATE and not UPDATE.
56             stack = resp["Stacks"][0]
57             return stack["StackStatus"] != "REVIEW_IN_PROGRESS"
58 
59         except botocore.exceptions.ClientError as e:
60             # If a stack does not exist, describe_stacks will throw an
61             # exception. Unfortunately we don't have a better way than parsing
62             # the exception msg to understand the nature of this exception.
63             msg = str(e)
64 
65             if "Stack with id {0} does not exist".format(stack_name) in msg:
66                 LOG.debug("Stack with id {0} does not exist".format(
67                     stack_name))
68                 return False
69             else:
70                 # We don't know anything about this exception. Don't handle
71                 LOG.debug("Unable to get stack details.", exc_info=e)
72                 raise e
73 
74     def create_changeset(self, stack_name, cfn_template,
75                          parameter_values, capabilities, role_arn,
76                          notification_arns, s3_uploader, tags):
77         """
78         Call Cloudformation to create a changeset and wait for it to complete
79 
80         :param stack_name: Name or ID of stack
81         :param cfn_template: CloudFormation template string
82         :param parameter_values: Template parameters object
83         :param capabilities: Array of capabilities passed to CloudFormation
84         :param tags: Array of tags passed to CloudFormation
85         :return:
86         """
87 
88         now = datetime.utcnow().isoformat()
89         description = "Created by AWS CLI at {0} UTC".format(now)
90 
91         # Each changeset will get a unique name based on time
92         changeset_name = self.changeset_prefix + str(int(time.time()))
93 
94         if not self.has_stack(stack_name):
95             changeset_type = "CREATE"
96             # When creating a new stack, UsePreviousValue=True is invalid.
97             # For such parameters, users should either override with new value,
98             # or set a Default value in template to successfully create a stack.
99             parameter_values = [x for x in parameter_values
100                                 if not x.get("UsePreviousValue", False)]
101         else:
102             changeset_type = "UPDATE"
103             # UsePreviousValue not valid if parameter is new
104             summary = self._client.get_template_summary(StackName=stack_name)
105             existing_parameters = [parameter['ParameterKey'] for parameter in \
106                                    summary['Parameters']]
107             parameter_values = [x for x in parameter_values
108                                 if not (x.get("UsePreviousValue", False) and \
109                                 x["ParameterKey"] not in existing_parameters)]
110 
111         kwargs = {
112             'ChangeSetName': changeset_name,
113             'StackName': stack_name,
114             'TemplateBody': cfn_template,
115             'ChangeSetType': changeset_type,
116             'Parameters': parameter_values,
117             'Capabilities': capabilities,
118             'Description': description,
119             'Tags': tags,
120         }
121 
122         # If an S3 uploader is available, use TemplateURL to deploy rather than
123         # TemplateBody. This is required for large templates.
124         if s3_uploader:
125             with mktempfile() as temporary_file:
126                 temporary_file.write(kwargs.pop('TemplateBody'))
127                 temporary_file.flush()
128                 url = s3_uploader.upload_with_dedup(
129                         temporary_file.name, "template")
130                 # TemplateUrl property requires S3 URL to be in path-style format
131                 parts = parse_s3_url(url, version_property="Version")
132                 kwargs['TemplateURL'] = s3_uploader.to_path_style_s3_url(parts["Key"], parts.get("Version", None))
133 
134         # don't set these arguments if not specified to use existing values
135         if role_arn is not None:
136             kwargs['RoleARN'] = role_arn
137         if notification_arns is not None:
138             kwargs['NotificationARNs'] = notification_arns
139         try:
140             resp = self._client.create_change_set(**kwargs)
141             return ChangeSetResult(resp["Id"], changeset_type)
142         except Exception as ex:
143             LOG.debug("Unable to create changeset", exc_info=ex)
144             raise ex
145 
146     def wait_for_changeset(self, changeset_id, stack_name):
147         """
148         Waits until the changeset creation completes
149 
150         :param changeset_id: ID or name of the changeset
151         :param stack_name:   Stack name
152         :return: Latest status of the create-change-set operation
153         """
154         sys.stdout.write("\nWaiting for changeset to be created..\n")
155         sys.stdout.flush()
156 
157         # Wait for changeset to be created
158         waiter = self._client.get_waiter("change_set_create_complete")
159         # Poll every 5 seconds. Changeset creation should be fast
160         waiter_config = {'Delay': 5}
161         try:
162             waiter.wait(ChangeSetName=changeset_id, StackName=stack_name,
163                         WaiterConfig=waiter_config)
164         except botocore.exceptions.WaiterError as ex:
165             LOG.debug("Create changeset waiter exception", exc_info=ex)
166 
167             resp = ex.last_response
168             status = resp["Status"]
169             reason = resp["StatusReason"]
170 
171             if status == "FAILED" and \
172                "The submitted information didn't contain changes." in reason:
173                     raise exceptions.ChangeEmptyError(stack_name=stack_name)
174 
175             raise RuntimeError("Failed to create the changeset: {0} "
176                                "Status: {1}. Reason: {2}"
177                                .format(ex, status, reason))
178 
179     def execute_changeset(self, changeset_id, stack_name):
180         """
181         Calls CloudFormation to execute changeset
182 
183         :param changeset_id: ID of the changeset
184         :param stack_name: Name or ID of the stack
185         :return: Response from execute-change-set call
186         """
187         return self._client.execute_change_set(
188                 ChangeSetName=changeset_id,
189                 StackName=stack_name)
190 
191     def wait_for_execute(self, stack_name, changeset_type):
192 
193         sys.stdout.write("Waiting for stack create/update to complete\n")
194         sys.stdout.flush()
195 
196         # Pick the right waiter
197         if changeset_type == "CREATE":
198             waiter = self._client.get_waiter("stack_create_complete")
199         elif changeset_type == "UPDATE":
200             waiter = self._client.get_waiter("stack_update_complete")
201         else:
202             raise RuntimeError("Invalid changeset type {0}"
203                                .format(changeset_type))
204 
205         # Poll every 5 seconds. Optimizing for the case when the stack has only
206         # minimal changes, such the Code for Lambda Function
207         waiter_config = {
208             'Delay': 5,
209             'MaxAttempts': 720,
210         }
211 
212         try:
213             waiter.wait(StackName=stack_name, WaiterConfig=waiter_config)
214         except botocore.exceptions.WaiterError as ex:
215             LOG.debug("Execute changeset waiter exception", exc_info=ex)
216 
217             raise exceptions.DeployFailedError(stack_name=stack_name)
218 
219     def create_and_wait_for_changeset(self, stack_name, cfn_template,
220                                       parameter_values, capabilities, role_arn,
221                                       notification_arns, s3_uploader, tags):
222 
223         result = self.create_changeset(
224                 stack_name, cfn_template, parameter_values, capabilities,
225                 role_arn, notification_arns, s3_uploader, tags)
226         self.wait_for_changeset(result.changeset_id, stack_name)
227 
228         return result
229
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/awscli/customizations/cloudformation/deployer.py b/awscli/customizations/cloudformation/deployer.py
--- a/awscli/customizations/cloudformation/deployer.py
+++ b/awscli/customizations/cloudformation/deployer.py
@@ -169,7 +169,8 @@
             reason = resp["StatusReason"]
 
             if status == "FAILED" and \
-               "The submitted information didn't contain changes." in reason:
+               "The submitted information didn't contain changes." in reason or \
+                            "No updates are to be performed" in reason:
                     raise exceptions.ChangeEmptyError(stack_name=stack_name)
 
             raise RuntimeError("Failed to create the changeset: {0} "

{"golden_diff": "diff --git a/awscli/customizations/cloudformation/deployer.py b/awscli/customizations/cloudformation/deployer.py\n--- a/awscli/customizations/cloudformation/deployer.py\n+++ b/awscli/customizations/cloudformation/deployer.py\n@@ -169,7 +169,8 @@\n             reason = resp[\"StatusReason\"]\n \n             if status == \"FAILED\" and \\\n-               \"The submitted information didn't contain changes.\" in reason:\n+               \"The submitted information didn't contain changes.\" in reason or \\\n+                            \"No updates are to be performed\" in reason:\n                     raise exceptions.ChangeEmptyError(stack_name=stack_name)\n \n             raise RuntimeError(\"Failed to create the changeset: {0} \"\n", "issue": "Flag --no-fail-on-empty-changeset does not work\nI am running into a issue where the --no-fail-on-empty-changeset flag doesn't make a difference. The flag is recognised by the CLI (at least it does not say Unknown option), but it still fails with the error message FAILED. Reason: No updates are to be performed. This issue is on aws cloudformation deploy. Version 1.14.32\n", "before_files": [{"content": "# Copyright 2012-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"). You\n# may not use this file except in compliance with the License. A copy of\n# the License is located at\n#\n# http://aws.amazon.com/apache2.0/\n#\n# or in the \"license\" file accompanying this file. This file is\n# distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF\n# ANY KIND, either express or implied. See the License for the specific\n# language governing permissions and limitations under the License.\n\nimport sys\nimport time\nimport logging\nimport botocore\nimport collections\n\nfrom awscli.customizations.cloudformation import exceptions\nfrom awscli.customizations.cloudformation.artifact_exporter import mktempfile, parse_s3_url\n\nfrom datetime import datetime\n\nLOG = logging.getLogger(__name__)\n\nChangeSetResult = collections.namedtuple(\n                \"ChangeSetResult\", [\"changeset_id\", \"changeset_type\"])\n\n\nclass Deployer(object):\n\n    def __init__(self, cloudformation_client,\n                 changeset_prefix=\"awscli-cloudformation-package-deploy-\"):\n        self._client = cloudformation_client\n        self.changeset_prefix = changeset_prefix\n\n    def has_stack(self, stack_name):\n        \"\"\"\n        Checks if a CloudFormation stack with given name exists\n\n        :param stack_name: Name or ID of the stack\n        :return: True if stack exists. False otherwise\n        \"\"\"\n        try:\n            resp = self._client.describe_stacks(StackName=stack_name)\n            if len(resp[\"Stacks\"]) != 1:\n                return False\n\n            # When you run CreateChangeSet on a a stack that does not exist,\n            # CloudFormation will create a stack and set it's status\n            # REVIEW_IN_PROGRESS. However this stack is cannot be manipulated\n            # by \"update\" commands. Under this circumstances, we treat like\n            # this stack does not exist and call CreateChangeSet will\n            # ChangeSetType set to CREATE and not UPDATE.\n            stack = resp[\"Stacks\"][0]\n            return stack[\"StackStatus\"] != \"REVIEW_IN_PROGRESS\"\n\n        except botocore.exceptions.ClientError as e:\n            # If a stack does not exist, describe_stacks will throw an\n            # exception. Unfortunately we don't have a better way than parsing\n            # the exception msg to understand the nature of this exception.\n            msg = str(e)\n\n            if \"Stack with id {0} does not exist\".format(stack_name) in msg:\n                LOG.debug(\"Stack with id {0} does not exist\".format(\n                    stack_name))\n                return False\n            else:\n                # We don't know anything about this exception. Don't handle\n                LOG.debug(\"Unable to get stack details.\", exc_info=e)\n                raise e\n\n    def create_changeset(self, stack_name, cfn_template,\n                         parameter_values, capabilities, role_arn,\n                         notification_arns, s3_uploader, tags):\n        \"\"\"\n        Call Cloudformation to create a changeset and wait for it to complete\n\n        :param stack_name: Name or ID of stack\n        :param cfn_template: CloudFormation template string\n        :param parameter_values: Template parameters object\n        :param capabilities: Array of capabilities passed to CloudFormation\n        :param tags: Array of tags passed to CloudFormation\n        :return:\n        \"\"\"\n\n        now = datetime.utcnow().isoformat()\n        description = \"Created by AWS CLI at {0} UTC\".format(now)\n\n        # Each changeset will get a unique name based on time\n        changeset_name = self.changeset_prefix + str(int(time.time()))\n\n        if not self.has_stack(stack_name):\n            changeset_type = \"CREATE\"\n            # When creating a new stack, UsePreviousValue=True is invalid.\n            # For such parameters, users should either override with new value,\n            # or set a Default value in template to successfully create a stack.\n            parameter_values = [x for x in parameter_values\n                                if not x.get(\"UsePreviousValue\", False)]\n        else:\n            changeset_type = \"UPDATE\"\n            # UsePreviousValue not valid if parameter is new\n            summary = self._client.get_template_summary(StackName=stack_name)\n            existing_parameters = [parameter['ParameterKey'] for parameter in \\\n                                   summary['Parameters']]\n            parameter_values = [x for x in parameter_values\n                                if not (x.get(\"UsePreviousValue\", False) and \\\n                                x[\"ParameterKey\"] not in existing_parameters)]\n\n        kwargs = {\n            'ChangeSetName': changeset_name,\n            'StackName': stack_name,\n            'TemplateBody': cfn_template,\n            'ChangeSetType': changeset_type,\n            'Parameters': parameter_values,\n            'Capabilities': capabilities,\n            'Description': description,\n            'Tags': tags,\n        }\n\n        # If an S3 uploader is available, use TemplateURL to deploy rather than\n        # TemplateBody. This is required for large templates.\n        if s3_uploader:\n            with mktempfile() as temporary_file:\n                temporary_file.write(kwargs.pop('TemplateBody'))\n                temporary_file.flush()\n                url = s3_uploader.upload_with_dedup(\n                        temporary_file.name, \"template\")\n                # TemplateUrl property requires S3 URL to be in path-style format\n                parts = parse_s3_url(url, version_property=\"Version\")\n                kwargs['TemplateURL'] = s3_uploader.to_path_style_s3_url(parts[\"Key\"], parts.get(\"Version\", None))\n\n        # don't set these arguments if not specified to use existing values\n        if role_arn is not None:\n            kwargs['RoleARN'] = role_arn\n        if notification_arns is not None:\n            kwargs['NotificationARNs'] = notification_arns\n        try:\n            resp = self._client.create_change_set(**kwargs)\n            return ChangeSetResult(resp[\"Id\"], changeset_type)\n        except Exception as ex:\n            LOG.debug(\"Unable to create changeset\", exc_info=ex)\n            raise ex\n\n    def wait_for_changeset(self, changeset_id, stack_name):\n        \"\"\"\n        Waits until the changeset creation completes\n\n        :param changeset_id: ID or name of the changeset\n        :param stack_name:   Stack name\n        :return: Latest status of the create-change-set operation\n        \"\"\"\n        sys.stdout.write(\"\\nWaiting for changeset to be created..\\n\")\n        sys.stdout.flush()\n\n        # Wait for changeset to be created\n        waiter = self._client.get_waiter(\"change_set_create_complete\")\n        # Poll every 5 seconds. Changeset creation should be fast\n        waiter_config = {'Delay': 5}\n        try:\n            waiter.wait(ChangeSetName=changeset_id, StackName=stack_name,\n                        WaiterConfig=waiter_config)\n        except botocore.exceptions.WaiterError as ex:\n            LOG.debug(\"Create changeset waiter exception\", exc_info=ex)\n\n            resp = ex.last_response\n            status = resp[\"Status\"]\n            reason = resp[\"StatusReason\"]\n\n            if status == \"FAILED\" and \\\n               \"The submitted information didn't contain changes.\" in reason:\n                    raise exceptions.ChangeEmptyError(stack_name=stack_name)\n\n            raise RuntimeError(\"Failed to create the changeset: {0} \"\n                               \"Status: {1}. Reason: {2}\"\n                               .format(ex, status, reason))\n\n    def execute_changeset(self, changeset_id, stack_name):\n        \"\"\"\n        Calls CloudFormation to execute changeset\n\n        :param changeset_id: ID of the changeset\n        :param stack_name: Name or ID of the stack\n        :return: Response from execute-change-set call\n        \"\"\"\n        return self._client.execute_change_set(\n                ChangeSetName=changeset_id,\n                StackName=stack_name)\n\n    def wait_for_execute(self, stack_name, changeset_type):\n\n        sys.stdout.write(\"Waiting for stack create/update to complete\\n\")\n        sys.stdout.flush()\n\n        # Pick the right waiter\n        if changeset_type == \"CREATE\":\n            waiter = self._client.get_waiter(\"stack_create_complete\")\n        elif changeset_type == \"UPDATE\":\n            waiter = self._client.get_waiter(\"stack_update_complete\")\n        else:\n            raise RuntimeError(\"Invalid changeset type {0}\"\n                               .format(changeset_type))\n\n        # Poll every 5 seconds. Optimizing for the case when the stack has only\n        # minimal changes, such the Code for Lambda Function\n        waiter_config = {\n            'Delay': 5,\n            'MaxAttempts': 720,\n        }\n\n        try:\n            waiter.wait(StackName=stack_name, WaiterConfig=waiter_config)\n        except botocore.exceptions.WaiterError as ex:\n            LOG.debug(\"Execute changeset waiter exception\", exc_info=ex)\n\n            raise exceptions.DeployFailedError(stack_name=stack_name)\n\n    def create_and_wait_for_changeset(self, stack_name, cfn_template,\n                                      parameter_values, capabilities, role_arn,\n                                      notification_arns, s3_uploader, tags):\n\n        result = self.create_changeset(\n                stack_name, cfn_template, parameter_values, capabilities,\n                role_arn, notification_arns, s3_uploader, tags)\n        self.wait_for_changeset(result.changeset_id, stack_name)\n\n        return result\n", "path": "awscli/customizations/cloudformation/deployer.py"}], "after_files": [{"content": "# Copyright 2012-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"). You\n# may not use this file except in compliance with the License. A copy of\n# the License is located at\n#\n# http://aws.amazon.com/apache2.0/\n#\n# or in the \"license\" file accompanying this file. This file is\n# distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF\n# ANY KIND, either express or implied. See the License for the specific\n# language governing permissions and limitations under the License.\n\nimport sys\nimport time\nimport logging\nimport botocore\nimport collections\n\nfrom awscli.customizations.cloudformation import exceptions\nfrom awscli.customizations.cloudformation.artifact_exporter import mktempfile, parse_s3_url\n\nfrom datetime import datetime\n\nLOG = logging.getLogger(__name__)\n\nChangeSetResult = collections.namedtuple(\n                \"ChangeSetResult\", [\"changeset_id\", \"changeset_type\"])\n\n\nclass Deployer(object):\n\n    def __init__(self, cloudformation_client,\n                 changeset_prefix=\"awscli-cloudformation-package-deploy-\"):\n        self._client = cloudformation_client\n        self.changeset_prefix = changeset_prefix\n\n    def has_stack(self, stack_name):\n        \"\"\"\n        Checks if a CloudFormation stack with given name exists\n\n        :param stack_name: Name or ID of the stack\n        :return: True if stack exists. False otherwise\n        \"\"\"\n        try:\n            resp = self._client.describe_stacks(StackName=stack_name)\n            if len(resp[\"Stacks\"]) != 1:\n                return False\n\n            # When you run CreateChangeSet on a a stack that does not exist,\n            # CloudFormation will create a stack and set it's status\n            # REVIEW_IN_PROGRESS. However this stack is cannot be manipulated\n            # by \"update\" commands. Under this circumstances, we treat like\n            # this stack does not exist and call CreateChangeSet will\n            # ChangeSetType set to CREATE and not UPDATE.\n            stack = resp[\"Stacks\"][0]\n            return stack[\"StackStatus\"] != \"REVIEW_IN_PROGRESS\"\n\n        except botocore.exceptions.ClientError as e:\n            # If a stack does not exist, describe_stacks will throw an\n            # exception. Unfortunately we don't have a better way than parsing\n            # the exception msg to understand the nature of this exception.\n            msg = str(e)\n\n            if \"Stack with id {0} does not exist\".format(stack_name) in msg:\n                LOG.debug(\"Stack with id {0} does not exist\".format(\n                    stack_name))\n                return False\n            else:\n                # We don't know anything about this exception. Don't handle\n                LOG.debug(\"Unable to get stack details.\", exc_info=e)\n                raise e\n\n    def create_changeset(self, stack_name, cfn_template,\n                         parameter_values, capabilities, role_arn,\n                         notification_arns, s3_uploader, tags):\n        \"\"\"\n        Call Cloudformation to create a changeset and wait for it to complete\n\n        :param stack_name: Name or ID of stack\n        :param cfn_template: CloudFormation template string\n        :param parameter_values: Template parameters object\n        :param capabilities: Array of capabilities passed to CloudFormation\n        :param tags: Array of tags passed to CloudFormation\n        :return:\n        \"\"\"\n\n        now = datetime.utcnow().isoformat()\n        description = \"Created by AWS CLI at {0} UTC\".format(now)\n\n        # Each changeset will get a unique name based on time\n        changeset_name = self.changeset_prefix + str(int(time.time()))\n\n        if not self.has_stack(stack_name):\n            changeset_type = \"CREATE\"\n            # When creating a new stack, UsePreviousValue=True is invalid.\n            # For such parameters, users should either override with new value,\n            # or set a Default value in template to successfully create a stack.\n            parameter_values = [x for x in parameter_values\n                                if not x.get(\"UsePreviousValue\", False)]\n        else:\n            changeset_type = \"UPDATE\"\n            # UsePreviousValue not valid if parameter is new\n            summary = self._client.get_template_summary(StackName=stack_name)\n            existing_parameters = [parameter['ParameterKey'] for parameter in \\\n                                   summary['Parameters']]\n            parameter_values = [x for x in parameter_values\n                                if not (x.get(\"UsePreviousValue\", False) and \\\n                                x[\"ParameterKey\"] not in existing_parameters)]\n\n        kwargs = {\n            'ChangeSetName': changeset_name,\n            'StackName': stack_name,\n            'TemplateBody': cfn_template,\n            'ChangeSetType': changeset_type,\n            'Parameters': parameter_values,\n            'Capabilities': capabilities,\n            'Description': description,\n            'Tags': tags,\n        }\n\n        # If an S3 uploader is available, use TemplateURL to deploy rather than\n        # TemplateBody. This is required for large templates.\n        if s3_uploader:\n            with mktempfile() as temporary_file:\n                temporary_file.write(kwargs.pop('TemplateBody'))\n                temporary_file.flush()\n                url = s3_uploader.upload_with_dedup(\n                        temporary_file.name, \"template\")\n                # TemplateUrl property requires S3 URL to be in path-style format\n                parts = parse_s3_url(url, version_property=\"Version\")\n                kwargs['TemplateURL'] = s3_uploader.to_path_style_s3_url(parts[\"Key\"], parts.get(\"Version\", None))\n\n        # don't set these arguments if not specified to use existing values\n        if role_arn is not None:\n            kwargs['RoleARN'] = role_arn\n        if notification_arns is not None:\n            kwargs['NotificationARNs'] = notification_arns\n        try:\n            resp = self._client.create_change_set(**kwargs)\n            return ChangeSetResult(resp[\"Id\"], changeset_type)\n        except Exception as ex:\n            LOG.debug(\"Unable to create changeset\", exc_info=ex)\n            raise ex\n\n    def wait_for_changeset(self, changeset_id, stack_name):\n        \"\"\"\n        Waits until the changeset creation completes\n\n        :param changeset_id: ID or name of the changeset\n        :param stack_name:   Stack name\n        :return: Latest status of the create-change-set operation\n        \"\"\"\n        sys.stdout.write(\"\\nWaiting for changeset to be created..\\n\")\n        sys.stdout.flush()\n\n        # Wait for changeset to be created\n        waiter = self._client.get_waiter(\"change_set_create_complete\")\n        # Poll every 5 seconds. Changeset creation should be fast\n        waiter_config = {'Delay': 5}\n        try:\n            waiter.wait(ChangeSetName=changeset_id, StackName=stack_name,\n                        WaiterConfig=waiter_config)\n        except botocore.exceptions.WaiterError as ex:\n            LOG.debug(\"Create changeset waiter exception\", exc_info=ex)\n\n            resp = ex.last_response\n            status = resp[\"Status\"]\n            reason = resp[\"StatusReason\"]\n\n            if status == \"FAILED\" and \\\n               \"The submitted information didn't contain changes.\" in reason or \\\n                            \"No updates are to be performed\" in reason:\n                    raise exceptions.ChangeEmptyError(stack_name=stack_name)\n\n            raise RuntimeError(\"Failed to create the changeset: {0} \"\n                               \"Status: {1}. Reason: {2}\"\n                               .format(ex, status, reason))\n\n    def execute_changeset(self, changeset_id, stack_name):\n        \"\"\"\n        Calls CloudFormation to execute changeset\n\n        :param changeset_id: ID of the changeset\n        :param stack_name: Name or ID of the stack\n        :return: Response from execute-change-set call\n        \"\"\"\n        return self._client.execute_change_set(\n                ChangeSetName=changeset_id,\n                StackName=stack_name)\n\n    def wait_for_execute(self, stack_name, changeset_type):\n\n        sys.stdout.write(\"Waiting for stack create/update to complete\\n\")\n        sys.stdout.flush()\n\n        # Pick the right waiter\n        if changeset_type == \"CREATE\":\n            waiter = self._client.get_waiter(\"stack_create_complete\")\n        elif changeset_type == \"UPDATE\":\n            waiter = self._client.get_waiter(\"stack_update_complete\")\n        else:\n            raise RuntimeError(\"Invalid changeset type {0}\"\n                               .format(changeset_type))\n\n        # Poll every 5 seconds. Optimizing for the case when the stack has only\n        # minimal changes, such the Code for Lambda Function\n        waiter_config = {\n            'Delay': 5,\n            'MaxAttempts': 720,\n        }\n\n        try:\n            waiter.wait(StackName=stack_name, WaiterConfig=waiter_config)\n        except botocore.exceptions.WaiterError as ex:\n            LOG.debug(\"Execute changeset waiter exception\", exc_info=ex)\n\n            raise exceptions.DeployFailedError(stack_name=stack_name)\n\n    def create_and_wait_for_changeset(self, stack_name, cfn_template,\n                                      parameter_values, capabilities, role_arn,\n                                      notification_arns, s3_uploader, tags):\n\n        result = self.create_changeset(\n                stack_name, cfn_template, parameter_values, capabilities,\n                role_arn, notification_arns, s3_uploader, tags)\n        self.wait_for_changeset(result.changeset_id, stack_name)\n\n        return result\n", "path": "awscli/customizations/cloudformation/deployer.py"}]}