Repository: boto/boto

Issue:
Support for parameters-only update with CloudFormation (adding UsePreviousTemplate kwarg to update_stack)
For some time now CloudFormation has been supporting updates without the need to provide the template every time:
http://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_UpdateStack.html (search for `UsePreviousTemplate`)
Adding this to boto should be quick and it will be a big win as it will reduce the number of calls services that use CloudFomration need to make.



Discussion:
This would also provide a way to update stacks with templates originally sourced from S3 that are too large to be uploaded via `template_body`. Providing a `template_url` works, but it would be nice not to have to worry about unintended changes if the template in S3 has been modified since the stack was created.


Resolving patch:
diff --git a/boto/cloudformation/connection.py b/boto/cloudformation/connection.py
--- a/boto/cloudformation/connection.py
+++ b/boto/cloudformation/connection.py
@@ -98,7 +98,8 @@ def encode_bool(self, v):
     def _build_create_or_update_params(self, stack_name, template_body,
             template_url, parameters, disable_rollback, timeout_in_minutes,
             notification_arns, capabilities, on_failure, stack_policy_body,
-            stack_policy_url, tags, stack_policy_during_update_body=None,
+            stack_policy_url, tags, use_previous_template=None,
+            stack_policy_during_update_body=None,
             stack_policy_during_update_url=None):
         """
         Helper that creates JSON parameters needed by a Stack Create or
@@ -117,16 +118,20 @@ def _build_create_or_update_params(self, stack_name, template_body,
         :param template_body: Structure containing the template body. (For more
             information, go to `Template Anatomy`_ in the AWS CloudFormation
             User Guide.)
-        Conditional: You must pass `TemplateBody` or `TemplateURL`. If both are
-            passed, only `TemplateBody` is used.
+        Conditional: You must pass either `UsePreviousTemplate` or one of
+            `TemplateBody` or `TemplateUrl`. If both `TemplateBody` and
+            `TemplateUrl` are passed, only `TemplateBody` is used.
+            `TemplateBody`.
 
         :type template_url: string
         :param template_url: Location of file containing the template body. The
             URL must point to a template (max size: 307,200 bytes) located in
             an S3 bucket in the same region as the stack. For more information,
             go to the `Template Anatomy`_ in the AWS CloudFormation User Guide.
-        Conditional: You must pass `TemplateURL` or `TemplateBody`. If both are
-            passed, only `TemplateBody` is used.
+        Conditional: You must pass either `UsePreviousTemplate` or one of
+            `TemplateBody` or `TemplateUrl`. If both `TemplateBody` and
+            `TemplateUrl` are passed, only `TemplateBody` is used.
+            `TemplateBody`.
 
         :type parameters: list
         :param parameters: A list of key/value tuples that specify input
@@ -186,6 +191,13 @@ def _build_create_or_update_params(self, stack_name, template_body,
             propagated to EC2 resources that are created as part of the stack.
             A maximum number of 10 tags can be specified.
 
+        :type use_previous_template: boolean
+        :param use_previous_template: Set to `True` to use the previous
+            template instead of uploading a new one via `TemplateBody` or
+            `TemplateURL`.
+        Conditional: You must pass either `UsePreviousTemplate` or one of
+            `TemplateBody` or `TemplateUrl`.
+
         :type stack_policy_during_update_body: string
         :param stack_policy_during_update_body: Structure containing the
             temporary overriding stack policy body. If you pass
@@ -217,6 +229,8 @@ def _build_create_or_update_params(self, stack_name, template_body,
             params['TemplateBody'] = template_body
         if template_url:
             params['TemplateURL'] = template_url
+        if use_previous_template is not None:
+            params['UsePreviousTemplate'] = self.encode_bool(use_previous_template)
         if template_body and template_url:
             boto.log.warning("If both TemplateBody and TemplateURL are"
                 " specified, only TemplateBody will be honored by the API")
@@ -383,6 +397,7 @@ def create_stack(self, stack_name, template_body=None, template_url=None,
     def update_stack(self, stack_name, template_body=None, template_url=None,
             parameters=None, notification_arns=None, disable_rollback=False,
             timeout_in_minutes=None, capabilities=None, tags=None,
+            use_previous_template=None,
             stack_policy_during_update_body=None,
             stack_policy_during_update_url=None,
             stack_policy_body=None, stack_policy_url=None):
@@ -421,16 +436,26 @@ def update_stack(self, stack_name, template_body=None, template_url=None,
         :param template_body: Structure containing the template body. (For more
             information, go to `Template Anatomy`_ in the AWS CloudFormation
             User Guide.)
-        Conditional: You must pass `TemplateBody` or `TemplateURL`. If both are
-            passed, only `TemplateBody` is used.
+        Conditional: You must pass either `UsePreviousTemplate` or one of
+            `TemplateBody` or `TemplateUrl`. If both `TemplateBody` and
+            `TemplateUrl` are passed, only `TemplateBody` is used.
 
         :type template_url: string
         :param template_url: Location of file containing the template body. The
-            URL must point to a template located in an S3 bucket in the same
-            region as the stack. For more information, go to `Template
-            Anatomy`_ in the AWS CloudFormation User Guide.
-        Conditional: You must pass `TemplateURL` or `TemplateBody`. If both are
-            passed, only `TemplateBody` is used.
+            URL must point to a template (max size: 307,200 bytes) located in
+            an S3 bucket in the same region as the stack. For more information,
+            go to the `Template Anatomy`_ in the AWS CloudFormation User Guide.
+        Conditional: You must pass either `UsePreviousTemplate` or one of
+            `TemplateBody` or `TemplateUrl`. If both `TemplateBody` and
+            `TemplateUrl` are passed, only `TemplateBody` is used.
+            `TemplateBody`.
+
+        :type use_previous_template: boolean
+        :param use_previous_template: Set to `True` to use the previous
+            template instead of uploading a new one via `TemplateBody` or
+            `TemplateURL`.
+        Conditional: You must pass either `UsePreviousTemplate` or one of
+            `TemplateBody` or `TemplateUrl`.
 
         :type parameters: list
         :param parameters: A list of key/value tuples that specify input
@@ -497,8 +522,8 @@ def update_stack(self, stack_name, template_body=None, template_url=None,
         params = self._build_create_or_update_params(stack_name, template_body,
             template_url, parameters, disable_rollback, timeout_in_minutes,
             notification_arns, capabilities, None, stack_policy_body,
-            stack_policy_url, tags, stack_policy_during_update_body,
-            stack_policy_during_update_url)
+            stack_policy_url, tags, use_previous_template,
+            stack_policy_during_update_body, stack_policy_during_update_url)
         body = self._do_request('UpdateStack', params, '/', 'POST')
         return body['UpdateStackResponse']['UpdateStackResult']['StackId']
 


Test patch:
diff --git a/tests/unit/cloudformation/test_connection.py b/tests/unit/cloudformation/test_connection.py
--- a/tests/unit/cloudformation/test_connection.py
+++ b/tests/unit/cloudformation/test_connection.py
@@ -138,7 +138,8 @@ def test_update_stack_all_args(self):
             tags={'TagKey': 'TagValue'},
             notification_arns=['arn:notify1', 'arn:notify2'],
             disable_rollback=True,
-            timeout_in_minutes=20
+            timeout_in_minutes=20,
+            use_previous_template=True
         )
         self.assert_request_parameters({
             'Action': 'UpdateStack',
@@ -155,6 +156,7 @@ def test_update_stack_all_args(self):
             'TimeoutInMinutes': 20,
             'TemplateBody': SAMPLE_TEMPLATE,
             'TemplateURL': 'http://url',
+            'UsePreviousTemplate': 'true',
         })
 
     def test_update_stack_with_minimum_args(self):