We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
aws cloudformation deploy command could not wait stack create/update more than 10 minutes
Hi,

I am doing some tests by my side and I've run the aws cli in debug mode trying to find why the aws cli commands hangs up on long CloudFormation create/update processes,  I could find that the 'aws cloudformation deploy' command checks the CloudFormation Stack status every 5 seconds and then, after approximately 10 minutes and 30 seconds the code returns the error:

--- Error ---
"WaiterError: Waiter StackCreateComplete failed: Max attempts exceeded"
--- Error ---

I think the check period could be greater, then the aws cli command could wait more time, or increase the number of max attempts, but I think the first option it's better. Could you please take a look at this?

tests:

> time aws cloudformation deploy --template-file cfn-rolling-deployment.yaml --stack-name rollindeployment-test --capabilities CAPABILITY_NAMED_IAM
Waiting for changeset to be created..
Waiting for stack create/update to complete

Failed to create/update the stack. Run the following command
to fetch the list of events leading up to the failure
aws cloudformation describe-stack-events --stack-name rollindeployment-test
aws cloudformation deploy --template-file cfn-rolling-deployment.yaml          0.90s user 0.13s system 0% cpu 10:29.37 total

---
Traceback:
---
2017-08-09 10:48:41,571 - MainThread - botocore.hooks - DEBUG - Event needs-retry.cloudformation.DescribeStacks: calling handler <botocore.retryhandler.RetryHandler object at 0x10e3ac550>
2017-08-09 10:48:41,571 - MainThread - botocore.retryhandler - DEBUG - No retry needed.
2017-08-09 10:48:41,571 - MainThread - awscli.customizations.cloudformation.deployer - DEBUG - Execute changeset waiter exception
Traceback (most recent call last):
  File "/usr/local/Cellar/awscli/1.11.129/libexec/lib/python2.7/site-packages/awscli/customizations/cloudformation/deployer.py", line 177, in wait_for_execute
    waiter.wait(StackName=stack_name)
  File "/usr/local/Cellar/awscli/1.11.129/libexec/lib/python2.7/site-packages/botocore/waiter.py", line 53, in wait
    Waiter.wait(self, **kwargs)
  File "/usr/local/Cellar/awscli/1.11.129/libexec/lib/python2.7/site-packages/botocore/waiter.py", line 327, in wait
    last_response=response
WaiterError: Waiter StackCreateComplete failed: Max attempts exceeded
2017-08-09 10:48:41,573 - MainThread - awscli.clidriver - DEBUG - Exception caught in main()
Traceback (most recent call last):
  File "/usr/local/Cellar/awscli/1.11.129/libexec/lib/python2.7/site-packages/awscli/clidriver.py", line 200, in main
    return command_table[parsed_args.command](remaining, parsed_args)
  File "/usr/local/Cellar/awscli/1.11.129/libexec/lib/python2.7/site-packages/awscli/clidriver.py", line 338, in __call__
    return command_table[parsed_args.operation](remaining, parsed_globals)
  File "/usr/local/Cellar/awscli/1.11.129/libexec/lib/python2.7/site-packages/awscli/customizations/commands.py", line 187, in __call__
    return self._run_main(parsed_args, parsed_globals)
  File "/usr/local/Cellar/awscli/1.11.129/libexec/lib/python2.7/site-packages/awscli/customizations/cloudformation/deploy.py", line 160, in _run_main
    parsed_args.execute_changeset)
  File "/usr/local/Cellar/awscli/1.11.129/libexec/lib/python2.7/site-packages/awscli/customizations/cloudformation/deploy.py", line 172, in deploy
    deployer.wait_for_execute(stack_name, result.changeset_type)
  File "/usr/local/Cellar/awscli/1.11.129/libexec/lib/python2.7/site-packages/awscli/customizations/cloudformation/deployer.py", line 181, in wait_for_execute
    raise exceptions.DeployFailedError(stack_name=stack_name)
DeployFailedError: Failed to create/update the stack. Run the following command
to fetch the list of events leading up to the failure
aws cloudformation describe-stack-events --stack-name rollindeployment-test2
2017-08-09 10:48:41,575 - MainThread - awscli.clidriver - DEBUG - Exiting with rc 255

Failed to create/update the stack. Run the following command
to fetch the list of events leading up to the failure
aws cloudformation describe-stack-events --stack-name rollindeployment-test2
aws cloudformation deploy --template-file cfn-rolling-deployment.yaml      0   1.21s user 0.16s system 0% cpu 10:31.42 total

Changed waiter.config.delay from 5 seconds to 10 seconds
The documentation below mentions the following with regards to: aws cloudformation wait stack-create-complete [1]
[1] http://docs.aws.amazon.com/cli/latest/reference/cloudformation/wait/stack-create-complete.html

"Wait until stack status is CREATE_COMPLETE. It will poll every 30 seconds until a successful state has been reached. 
This will exit with a return code of 255 after 120 failed checks."

This should be technically be 30 seconds not 5 seconds or 10 seconds because: 
120 calls at 30 seconds intervals will equal 3600 seconds/1 hour until the "Max attempts exceeded" which to confirms with CloudFormations 1 hour* timeout.

I understand that it is a fine line between performance (speed) and consistency, but unfortunately some services like CloudFront take time to build.

--- 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 datetime import datetime
22 
23 LOG = logging.getLogger(__name__)
24 
25 ChangeSetResult = collections.namedtuple(
26                 "ChangeSetResult", ["changeset_id", "changeset_type"])
27 
28 
29 class Deployer(object):
30 
31     def __init__(self, cloudformation_client,
32                  changeset_prefix="awscli-cloudformation-package-deploy-"):
33         self._client = cloudformation_client
34         self.changeset_prefix = changeset_prefix
35 
36     def has_stack(self, stack_name):
37         """
38         Checks if a CloudFormation stack with given name exists
39 
40         :param stack_name: Name or ID of the stack
41         :return: True if stack exists. False otherwise
42         """
43         try:
44             resp = self._client.describe_stacks(StackName=stack_name)
45             if len(resp["Stacks"]) != 1:
46                 return False
47 
48             # When you run CreateChangeSet on a a stack that does not exist,
49             # CloudFormation will create a stack and set it's status
50             # REVIEW_IN_PROGRESS. However this stack is cannot be manipulated
51             # by "update" commands. Under this circumstances, we treat like
52             # this stack does not exist and call CreateChangeSet will
53             # ChangeSetType set to CREATE and not UPDATE.
54             stack = resp["Stacks"][0]
55             return stack["StackStatus"] != "REVIEW_IN_PROGRESS"
56 
57         except botocore.exceptions.ClientError as e:
58             # If a stack does not exist, describe_stacks will throw an
59             # exception. Unfortunately we don't have a better way than parsing
60             # the exception msg to understand the nature of this exception.
61             msg = str(e)
62 
63             if "Stack with id {0} does not exist".format(stack_name) in msg:
64                 LOG.debug("Stack with id {0} does not exist".format(
65                     stack_name))
66                 return False
67             else:
68                 # We don't know anything about this exception. Don't handle
69                 LOG.debug("Unable to get stack details.", exc_info=e)
70                 raise e
71 
72     def create_changeset(self, stack_name, cfn_template,
73                          parameter_values, capabilities, role_arn,
74                          notification_arns):
75         """
76         Call Cloudformation to create a changeset and wait for it to complete
77 
78         :param stack_name: Name or ID of stack
79         :param cfn_template: CloudFormation template string
80         :param parameter_values: Template parameters object
81         :param capabilities: Array of capabilities passed to CloudFormation
82         :return:
83         """
84 
85         now = datetime.utcnow().isoformat()
86         description = "Created by AWS CLI at {0} UTC".format(now)
87 
88         # Each changeset will get a unique name based on time
89         changeset_name = self.changeset_prefix + str(int(time.time()))
90 
91         changeset_type = "UPDATE"
92         if not self.has_stack(stack_name):
93             changeset_type = "CREATE"
94             # When creating a new stack, UsePreviousValue=True is invalid.
95             # For such parameters, users should either override with new value,
96             # or set a Default value in template to successfully create a stack.
97             parameter_values = [x for x in parameter_values
98                                 if not x.get("UsePreviousValue", False)]
99 
100         kwargs = {
101             'ChangeSetName': changeset_name,
102             'StackName': stack_name,
103             'TemplateBody': cfn_template,
104             'ChangeSetType': changeset_type,
105             'Parameters': parameter_values,
106             'Capabilities': capabilities,
107             'Description': description,
108         }
109         # don't set these arguments if not specified to use existing values
110         if role_arn is not None:
111             kwargs['RoleARN'] = role_arn
112         if notification_arns is not None:
113             kwargs['NotificationARNs'] = notification_arns
114         try:
115             resp = self._client.create_change_set(**kwargs)
116             return ChangeSetResult(resp["Id"], changeset_type)
117         except Exception as ex:
118             LOG.debug("Unable to create changeset", exc_info=ex)
119             raise ex
120 
121     def wait_for_changeset(self, changeset_id, stack_name):
122         """
123         Waits until the changeset creation completes
124 
125         :param changeset_id: ID or name of the changeset
126         :param stack_name:   Stack name
127         :return: Latest status of the create-change-set operation
128         """
129         sys.stdout.write("Waiting for changeset to be created..\n")
130         sys.stdout.flush()
131 
132         # Wait for changeset to be created
133         waiter = self._client.get_waiter("change_set_create_complete")
134         # Poll every 5 seconds. Changeset creation should be fast
135         waiter.config.delay = 5
136         try:
137             waiter.wait(ChangeSetName=changeset_id, StackName=stack_name)
138         except botocore.exceptions.WaiterError as ex:
139             LOG.debug("Create changeset waiter exception", exc_info=ex)
140 
141             resp = ex.last_response
142             status = resp["Status"]
143             reason = resp["StatusReason"]
144 
145             if status == "FAILED" and \
146                "No updates are to be performed" in reason:
147                     raise exceptions.ChangeEmptyError(stack_name=stack_name)
148 
149             raise RuntimeError("Failed to create the changeset: {0} "
150                                "Status: {1}. Reason: {2}"
151                                .format(ex, status, reason))
152 
153     def execute_changeset(self, changeset_id, stack_name):
154         """
155         Calls CloudFormation to execute changeset
156 
157         :param changeset_id: ID of the changeset
158         :param stack_name: Name or ID of the stack
159         :return: Response from execute-change-set call
160         """
161         return self._client.execute_change_set(
162                 ChangeSetName=changeset_id,
163                 StackName=stack_name)
164 
165     def wait_for_execute(self, stack_name, changeset_type):
166 
167         sys.stdout.write("Waiting for stack create/update to complete\n")
168         sys.stdout.flush()
169 
170         # Pick the right waiter
171         if changeset_type == "CREATE":
172             waiter = self._client.get_waiter("stack_create_complete")
173         elif changeset_type == "UPDATE":
174             waiter = self._client.get_waiter("stack_update_complete")
175         else:
176             raise RuntimeError("Invalid changeset type {0}"
177                                .format(changeset_type))
178 
179         # Poll every 5 seconds. Optimizing for the case when the stack has only
180         # minimal changes, such the Code for Lambda Function
181         waiter.config.delay = 5
182 
183         try:
184             waiter.wait(StackName=stack_name)
185         except botocore.exceptions.WaiterError as ex:
186             LOG.debug("Execute changeset waiter exception", exc_info=ex)
187 
188             raise exceptions.DeployFailedError(stack_name=stack_name)
189 
190     def create_and_wait_for_changeset(self, stack_name, cfn_template,
191                                       parameter_values, capabilities, role_arn,
192                                       notification_arns):
193 
194         result = self.create_changeset(
195                 stack_name, cfn_template, parameter_values, capabilities,
196                 role_arn, notification_arns)
197 
198         self.wait_for_changeset(result.changeset_id, stack_name)
199 
200         return result
201
```
--- 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
@@ -132,9 +132,10 @@
         # Wait for changeset to be created
         waiter = self._client.get_waiter("change_set_create_complete")
         # Poll every 5 seconds. Changeset creation should be fast
-        waiter.config.delay = 5
+        waiter_config = {'Delay': 5}
         try:
-            waiter.wait(ChangeSetName=changeset_id, StackName=stack_name)
+            waiter.wait(ChangeSetName=changeset_id, StackName=stack_name,
+                        WaiterConfig=waiter_config)
         except botocore.exceptions.WaiterError as ex:
             LOG.debug("Create changeset waiter exception", exc_info=ex)
 
@@ -178,10 +179,13 @@
 
         # Poll every 5 seconds. Optimizing for the case when the stack has only
         # minimal changes, such the Code for Lambda Function
-        waiter.config.delay = 5
+        waiter_config = {
+            'Delay': 5,
+            'MaxAttempts': 720,
+        }
 
         try:
-            waiter.wait(StackName=stack_name)
+            waiter.wait(StackName=stack_name, WaiterConfig=waiter_config)
         except botocore.exceptions.WaiterError as ex:
             LOG.debug("Execute changeset waiter exception", exc_info=ex)

{"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@@ -132,9 +132,10 @@\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+        waiter_config = {'Delay': 5}\n         try:\n-            waiter.wait(ChangeSetName=changeset_id, StackName=stack_name)\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@@ -178,10 +179,13 @@\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.delay = 5\n+        waiter_config = {\n+            'Delay': 5,\n+            'MaxAttempts': 720,\n+        }\n \n         try:\n-            waiter.wait(StackName=stack_name)\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", "issue": "aws cloudformation deploy command could not wait stack create/update more than 10 minutes\nHi,\r\n\r\nI am doing some tests by my side and I've run the aws cli in debug mode trying to find why the aws cli commands hangs up on long CloudFormation create/update processes,  I could find that the 'aws cloudformation deploy' command checks the CloudFormation Stack status every 5 seconds and then, after approximately 10 minutes and 30 seconds the code returns the error:\r\n\r\n--- Error ---\r\n\"WaiterError: Waiter StackCreateComplete failed: Max attempts exceeded\"\r\n--- Error ---\r\n\r\nI think the check period could be greater, then the aws cli command could wait more time, or increase the number of max attempts, but I think the first option it's better. Could you please take a look at this?\r\n\r\ntests:\r\n\r\n> time aws cloudformation deploy --template-file cfn-rolling-deployment.yaml --stack-name rollindeployment-test --capabilities CAPABILITY_NAMED_IAM\r\nWaiting for changeset to be created..\r\nWaiting for stack create/update to complete\r\n\r\nFailed to create/update the stack. Run the following command\r\nto fetch the list of events leading up to the failure\r\naws cloudformation describe-stack-events --stack-name rollindeployment-test\r\naws cloudformation deploy --template-file cfn-rolling-deployment.yaml          0.90s user 0.13s system 0% cpu 10:29.37 total\r\n\r\n---\r\nTraceback:\r\n---\r\n2017-08-09 10:48:41,571 - MainThread - botocore.hooks - DEBUG - Event needs-retry.cloudformation.DescribeStacks: calling handler <botocore.retryhandler.RetryHandler object at 0x10e3ac550>\r\n2017-08-09 10:48:41,571 - MainThread - botocore.retryhandler - DEBUG - No retry needed.\r\n2017-08-09 10:48:41,571 - MainThread - awscli.customizations.cloudformation.deployer - DEBUG - Execute changeset waiter exception\r\nTraceback (most recent call last):\r\n  File \"/usr/local/Cellar/awscli/1.11.129/libexec/lib/python2.7/site-packages/awscli/customizations/cloudformation/deployer.py\", line 177, in wait_for_execute\r\n    waiter.wait(StackName=stack_name)\r\n  File \"/usr/local/Cellar/awscli/1.11.129/libexec/lib/python2.7/site-packages/botocore/waiter.py\", line 53, in wait\r\n    Waiter.wait(self, **kwargs)\r\n  File \"/usr/local/Cellar/awscli/1.11.129/libexec/lib/python2.7/site-packages/botocore/waiter.py\", line 327, in wait\r\n    last_response=response\r\nWaiterError: Waiter StackCreateComplete failed: Max attempts exceeded\r\n2017-08-09 10:48:41,573 - MainThread - awscli.clidriver - DEBUG - Exception caught in main()\r\nTraceback (most recent call last):\r\n  File \"/usr/local/Cellar/awscli/1.11.129/libexec/lib/python2.7/site-packages/awscli/clidriver.py\", line 200, in main\r\n    return command_table[parsed_args.command](remaining, parsed_args)\r\n  File \"/usr/local/Cellar/awscli/1.11.129/libexec/lib/python2.7/site-packages/awscli/clidriver.py\", line 338, in __call__\r\n    return command_table[parsed_args.operation](remaining, parsed_globals)\r\n  File \"/usr/local/Cellar/awscli/1.11.129/libexec/lib/python2.7/site-packages/awscli/customizations/commands.py\", line 187, in __call__\r\n    return self._run_main(parsed_args, parsed_globals)\r\n  File \"/usr/local/Cellar/awscli/1.11.129/libexec/lib/python2.7/site-packages/awscli/customizations/cloudformation/deploy.py\", line 160, in _run_main\r\n    parsed_args.execute_changeset)\r\n  File \"/usr/local/Cellar/awscli/1.11.129/libexec/lib/python2.7/site-packages/awscli/customizations/cloudformation/deploy.py\", line 172, in deploy\r\n    deployer.wait_for_execute(stack_name, result.changeset_type)\r\n  File \"/usr/local/Cellar/awscli/1.11.129/libexec/lib/python2.7/site-packages/awscli/customizations/cloudformation/deployer.py\", line 181, in wait_for_execute\r\n    raise exceptions.DeployFailedError(stack_name=stack_name)\r\nDeployFailedError: Failed to create/update the stack. Run the following command\r\nto fetch the list of events leading up to the failure\r\naws cloudformation describe-stack-events --stack-name rollindeployment-test2\r\n2017-08-09 10:48:41,575 - MainThread - awscli.clidriver - DEBUG - Exiting with rc 255\r\n\r\nFailed to create/update the stack. Run the following command\r\nto fetch the list of events leading up to the failure\r\naws cloudformation describe-stack-events --stack-name rollindeployment-test2\r\naws cloudformation deploy --template-file cfn-rolling-deployment.yaml      0   1.21s user 0.16s system 0% cpu 10:31.42 total\r\n\nChanged waiter.config.delay from 5 seconds to 10 seconds\nThe documentation below mentions the following with regards to: aws cloudformation wait stack-create-complete [1]\r\n[1] http://docs.aws.amazon.com/cli/latest/reference/cloudformation/wait/stack-create-complete.html\r\n\r\n\"Wait until stack status is CREATE_COMPLETE. It will poll every 30 seconds until a successful state has been reached. \r\nThis will exit with a return code of 255 after 120 failed checks.\"\r\n\r\nThis should be technically be 30 seconds not 5 seconds or 10 seconds because: \r\n120 calls at 30 seconds intervals will equal 3600 seconds/1 hour until the \"Max attempts exceeded\" which to confirms with CloudFormations 1 hour* timeout.\r\n\r\nI understand that it is a fine line between performance (speed) and consistency, but unfortunately some services like CloudFront take time to build.\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 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):\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        :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        changeset_type = \"UPDATE\"\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\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        }\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(\"Waiting 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        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               \"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.delay = 5\n\n        try:\n            waiter.wait(StackName=stack_name)\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):\n\n        result = self.create_changeset(\n                stack_name, cfn_template, parameter_values, capabilities,\n                role_arn, notification_arns)\n\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 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):\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        :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        changeset_type = \"UPDATE\"\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\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        }\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(\"Waiting 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               \"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):\n\n        result = self.create_changeset(\n                stack_name, cfn_template, parameter_values, capabilities,\n                role_arn, notification_arns)\n\n        self.wait_for_changeset(result.changeset_id, stack_name)\n\n        return result\n", "path": "awscli/customizations/cloudformation/deployer.py"}]}