Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 35 additions & 4 deletions awscli/customizations/cloudformation/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,37 @@ class DeployCommand(BasicCommand):
'Amazon Simple Notification Service topic Amazon Resource Names'
' (ARNs) that AWS CloudFormation associates with the stack.'
)
}
},
{
'name': 'tags',
'action': 'store',
'required': False,
'default': [],
'schema': {
"type": "array",
"items": {
"type": "object",
"properties": {
"Key": {
"description": "The tag key.",
"type": "string",
"required": True
},
"Value": {
"description": "The tag value.",
"type": "string",
"required": True
}
}
}
},
'help_text': (
'Key-value pairs to associate with the stack,'
' that is created or updated by the changeset.'
' AWS CloudFormation also propagates these tags'
' to resources in the stack.'
)
},
]

def _run_main(self, parsed_args, parsed_globals):
Expand Down Expand Up @@ -181,18 +211,19 @@ def _run_main(self, parsed_args, parsed_globals):
return self.deploy(deployer, stack_name, template_str,
parameters, parsed_args.capabilities,
parsed_args.execute_changeset, parsed_args.role_arn,
parsed_args.notification_arns)
parsed_args.notification_arns, parsed_args.tags)

def deploy(self, deployer, stack_name, template_str,
parameters, capabilities, execute_changeset, role_arn,
notification_arns):
notification_arns, tags):
result = deployer.create_and_wait_for_changeset(
stack_name=stack_name,
cfn_template=template_str,
parameter_values=parameters,
capabilities=capabilities,
role_arn=role_arn,
notification_arns=notification_arns)
notification_arns=notification_arns,
tags=tags)

if execute_changeset:
deployer.execute_changeset(result.changeset_id, stack_name)
Expand Down
8 changes: 5 additions & 3 deletions awscli/customizations/cloudformation/deployer.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,15 @@ def has_stack(self, stack_name):

def create_changeset(self, stack_name, cfn_template,
parameter_values, capabilities, role_arn,
notification_arns):
notification_arns, tags):
"""
Call Cloudformation to create a changeset and wait for it to complete

:param stack_name: Name or ID of stack
:param cfn_template: CloudFormation template string
:param parameter_values: Template parameters object
:param capabilities: Array of capabilities passed to CloudFormation
:param tags: Array of tags passed to CloudFormation
:return:
"""

Expand All @@ -105,6 +106,7 @@ def create_changeset(self, stack_name, cfn_template,
'Parameters': parameter_values,
'Capabilities': capabilities,
'Description': description,
'Tags': tags,
}
# don't set these arguments if not specified to use existing values
if role_arn is not None:
Expand Down Expand Up @@ -193,11 +195,11 @@ def wait_for_execute(self, stack_name, changeset_type):

def create_and_wait_for_changeset(self, stack_name, cfn_template,
parameter_values, capabilities, role_arn,
notification_arns):
notification_arns, tags):

result = self.create_changeset(
stack_name, cfn_template, parameter_values, capabilities,
role_arn, notification_arns)
role_arn, notification_arns, tags)

self.wait_for_changeset(result.changeset_id, stack_name)

Expand Down
2 changes: 1 addition & 1 deletion awscli/examples/cloudformation/deploy.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ Following command deploys template named ``template.json`` to a stack named
``my-new-stack``::


aws cloudformation deploy --template-file /path_to_template/template.json --stack-name my-new-stack --parameter-overrides Key1=Value1 Key2=Value2
aws cloudformation deploy --template-file /path_to_template/template.json --stack-name my-new-stack --parameter-overrides Key1=Value1 Key2=Value2 --tags Key=key1,Value=value1,Key=key2,Value=Value2

26 changes: 17 additions & 9 deletions tests/unit/customizations/cloudformation/test_deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ def setUp(self):
execute_changeset=True,
capabilities=None,
role_arn=None,
notification_arns=[])
notification_arns=[],
tags=[{"Key": "key1", "Value": "val1"}])
self.parsed_globals = FakeArgs(region="us-east-1", endpoint_url=None,
verify_ssl=None)
self.deploy_command = DeployCommand(self.session)
Expand Down Expand Up @@ -109,7 +110,8 @@ def test_command_invoked(self, mock_yaml_parse):
None,
not self.parsed_args.no_execute_changeset,
None,
[])
[],
mock.ANY)

self.deploy_command.parse_parameter_arg.assert_called_once_with(
self.parsed_args.parameter_overrides)
Expand Down Expand Up @@ -140,7 +142,7 @@ def test_deploy_success(self):
changeset_type = "CREATE"
role_arn = "arn:aws:iam::1234567890:role"
notification_arns = ["arn:aws:sns:region:1234567890:notify"]

tags = [{"Key":"key1", "Value": "val1"}]

# Set the mock to return this fake changeset_id
self.deployer.create_and_wait_for_changeset.return_value = ChangeSetResult(changeset_id, changeset_type)
Expand All @@ -152,7 +154,8 @@ def test_deploy_success(self):
capabilities,
execute_changeset,
role_arn,
notification_arns)
notification_arns,
tags)
self.assertEqual(rc, 0)


Expand All @@ -161,7 +164,8 @@ def test_deploy_success(self):
parameter_values=parameters,
capabilities=capabilities,
role_arn=role_arn,
notification_arns=notification_arns)
notification_arns=notification_arns,
tags=tags)

# since execute_changeset is set to True, deploy() will execute changeset
self.deployer.execute_changeset.assert_called_once_with(changeset_id, stack_name)
Expand All @@ -177,6 +181,7 @@ def test_deploy_no_execute(self):
execute_changeset = False
role_arn = "arn:aws:iam::1234567890:role"
notification_arns = ["arn:aws:sns:region:1234567890:notify"]
tags = [{"Key":"key1", "Value": "val1"}]


self.deployer.create_and_wait_for_changeset.return_value = ChangeSetResult(changeset_id, "CREATE")
Expand All @@ -187,15 +192,17 @@ def test_deploy_no_execute(self):
capabilities,
execute_changeset,
role_arn,
notification_arns)
notification_arns,
tags)
self.assertEqual(rc, 0)

self.deployer.create_and_wait_for_changeset.assert_called_once_with(stack_name=stack_name,
cfn_template=template,
parameter_values=parameters,
capabilities=capabilities,
role_arn=role_arn,
notification_arns=notification_arns)
notification_arns=notification_arns,
tags=tags)

# since execute_changeset is set to True, deploy() will execute changeset
self.deployer.execute_changeset.assert_not_called()
Expand All @@ -210,7 +217,7 @@ def test_deploy_raise_exception(self):
execute_changeset = True
role_arn = "arn:aws:iam::1234567890:role"
notification_arns = ["arn:aws:sns:region:1234567890:notify"]

tags = [{"Key":"key1", "Value": "val1"}]

self.deployer.wait_for_execute.side_effect = RuntimeError("Some error")
with self.assertRaises(RuntimeError):
Expand All @@ -221,7 +228,8 @@ def test_deploy_raise_exception(self):
capabilities,
execute_changeset,
role_arn,
notification_arns)
notification_arns,
tags)


def test_parse_parameter_arg_success(self):
Expand Down
18 changes: 12 additions & 6 deletions tests/unit/customizations/cloudformation/test_deployer.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ def test_create_changeset_success(self):
role_arn = "arn:aws:iam::1234567890:role"
notification_arns = ["arn:aws:sns:region:1234567890:notify"]

tags = [{"Key":"key1", "Value": "val1"}]

# Case 1: Stack DOES NOT exist
self.deployer.has_stack = Mock()
self.deployer.has_stack.return_value = False
Expand All @@ -117,7 +119,8 @@ def test_create_changeset_success(self):
"Capabilities": capabilities,
"Description": botocore.stub.ANY,
"RoleARN": role_arn,
"NotificationARNs": notification_arns
"NotificationARNs": notification_arns,
"Tags": tags
}

response = {
Expand All @@ -129,7 +132,7 @@ def test_create_changeset_success(self):
with self.stub_client:
result = self.deployer.create_changeset(
stack_name, template, parameters, capabilities, role_arn,
notification_arns)
notification_arns, tags)
self.assertEquals(response["Id"], result.changeset_id)
self.assertEquals("CREATE", result.changeset_type)

Expand All @@ -142,7 +145,7 @@ def test_create_changeset_success(self):
with self.stub_client:
result = self.deployer.create_changeset(
stack_name, template, parameters, capabilities, role_arn,
notification_arns)
notification_arns, tags)
self.assertEquals(response["Id"], result.changeset_id)
self.assertEquals("UPDATE", result.changeset_type)

Expand All @@ -154,6 +157,7 @@ def test_create_changeset_exception(self):
capabilities = ["capabilities"]
role_arn = "arn:aws:iam::1234567890:role"
notification_arns = ["arn:aws:sns:region:1234567890:notify"]
tags = [{"Key":"key1", "Value": "val1"}]

self.deployer.has_stack = Mock()
self.deployer.has_stack.return_value = False
Expand All @@ -163,7 +167,7 @@ def test_create_changeset_exception(self):
with self.stub_client:
with self.assertRaises(botocore.exceptions.ClientError):
self.deployer.create_changeset(stack_name, template, parameters,
capabilities, role_arn, notification_arns)
capabilities, role_arn, notification_arns, tags)

def test_execute_changeset(self):
stack_name = "stack_name"
Expand Down Expand Up @@ -198,6 +202,7 @@ def test_create_and_wait_for_changeset_successful(self):
changeset_type = "changeset type"
role_arn = "arn:aws:iam::1234567890:role"
notification_arns = ["arn:aws:sns:region:1234567890:notify"]
tags = [{"Key":"key1", "Value": "val1"}]

self.deployer.create_changeset = Mock()
self.deployer.create_changeset.return_value = ChangeSetResult(changeset_id, changeset_type)
Expand All @@ -206,7 +211,7 @@ def test_create_and_wait_for_changeset_successful(self):

result = self.deployer.create_and_wait_for_changeset(
stack_name, template, parameters, capabilities, role_arn,
notification_arns)
notification_arns, tags)
self.assertEquals(result.changeset_id, changeset_id)
self.assertEquals(result.changeset_type, changeset_type)

Expand All @@ -220,6 +225,7 @@ def test_create_and_wait_for_changeset_error_waiting_for_changeset(self):
changeset_type = "changeset type"
role_arn = "arn:aws:iam::1234567890:role"
notification_arns = ["arn:aws:sns:region:1234567890:notify"]
tags = [{"Key":"key1", "Value": "val1"}]

self.deployer.create_changeset = Mock()
self.deployer.create_changeset.return_value = ChangeSetResult(changeset_id, changeset_type)
Expand All @@ -230,7 +236,7 @@ def test_create_and_wait_for_changeset_error_waiting_for_changeset(self):
with self.assertRaises(RuntimeError):
result = self.deployer.create_and_wait_for_changeset(
stack_name, template, parameters, capabilities, role_arn,
notification_arns)
notification_arns, tags)

def test_wait_for_changeset_no_changes(self):
stack_name = "stack_name"
Expand Down