Add create change set endpoint (#1389)
This commit is contained in:
parent
9d087b0729
commit
2346e14e00
@ -48,3 +48,4 @@ Moto is written by Steve Pulec with contributions from:
|
||||
* [Guy Templeton](https://github.com/gjtempleton)
|
||||
* [Michael van Tellingen](https://github.com/mvantellingen)
|
||||
* [Jessie Nadler](https://github.com/nadlerjessie)
|
||||
* [Alex Morken](https://github.com/alexmorken)
|
||||
|
@ -329,10 +329,10 @@
|
||||
- [ ] update_schema
|
||||
- [ ] update_typed_link_facet
|
||||
|
||||
## cloudformation - 17% implemented
|
||||
## cloudformation - 20% implemented
|
||||
- [ ] cancel_update_stack
|
||||
- [ ] continue_update_rollback
|
||||
- [ ] create_change_set
|
||||
- [X] create_change_set
|
||||
- [X] create_stack
|
||||
- [ ] create_stack_instances
|
||||
- [ ] create_stack_set
|
||||
|
@ -9,13 +9,17 @@ from moto.compat import OrderedDict
|
||||
from moto.core import BaseBackend, BaseModel
|
||||
|
||||
from .parsing import ResourceMap, OutputMap
|
||||
from .utils import generate_stack_id, yaml_tag_constructor
|
||||
from .utils import (
|
||||
generate_changeset_id,
|
||||
generate_stack_id,
|
||||
yaml_tag_constructor,
|
||||
)
|
||||
from .exceptions import ValidationError
|
||||
|
||||
|
||||
class FakeStack(BaseModel):
|
||||
|
||||
def __init__(self, stack_id, name, template, parameters, region_name, notification_arns=None, tags=None, role_arn=None, cross_stack_resources=None):
|
||||
def __init__(self, stack_id, name, template, parameters, region_name, notification_arns=None, tags=None, role_arn=None, cross_stack_resources=None, create_change_set=False):
|
||||
self.stack_id = stack_id
|
||||
self.name = name
|
||||
self.template = template
|
||||
@ -26,6 +30,10 @@ class FakeStack(BaseModel):
|
||||
self.role_arn = role_arn
|
||||
self.tags = tags if tags else {}
|
||||
self.events = []
|
||||
if create_change_set:
|
||||
self._add_stack_event("REVIEW_IN_PROGRESS",
|
||||
resource_status_reason="User Initiated")
|
||||
else:
|
||||
self._add_stack_event("CREATE_IN_PROGRESS",
|
||||
resource_status_reason="User Initiated")
|
||||
|
||||
@ -138,8 +146,9 @@ class CloudFormationBackend(BaseBackend):
|
||||
self.stacks = OrderedDict()
|
||||
self.deleted_stacks = {}
|
||||
self.exports = OrderedDict()
|
||||
self.change_sets = OrderedDict()
|
||||
|
||||
def create_stack(self, name, template, parameters, region_name, notification_arns=None, tags=None, role_arn=None):
|
||||
def create_stack(self, name, template, parameters, region_name, notification_arns=None, tags=None, role_arn=None, create_change_set=False):
|
||||
stack_id = generate_stack_id(name)
|
||||
new_stack = FakeStack(
|
||||
stack_id=stack_id,
|
||||
@ -151,6 +160,7 @@ class CloudFormationBackend(BaseBackend):
|
||||
tags=tags,
|
||||
role_arn=role_arn,
|
||||
cross_stack_resources=self.exports,
|
||||
create_change_set=create_change_set,
|
||||
)
|
||||
self.stacks[stack_id] = new_stack
|
||||
self._validate_export_uniqueness(new_stack)
|
||||
@ -158,6 +168,26 @@ class CloudFormationBackend(BaseBackend):
|
||||
self.exports[export.name] = export
|
||||
return new_stack
|
||||
|
||||
def create_change_set(self, stack_name, change_set_name, template, parameters, region_name, change_set_type, notification_arns=None, tags=None, role_arn=None):
|
||||
if change_set_type == 'UPDATE':
|
||||
stacks = self.stacks.values()
|
||||
stack = None
|
||||
for s in stacks:
|
||||
if s.name == stack_name:
|
||||
stack = s
|
||||
if stack is None:
|
||||
raise ValidationError(stack_name)
|
||||
|
||||
else:
|
||||
stack = self.create_stack(stack_name, template, parameters,
|
||||
region_name, notification_arns, tags,
|
||||
role_arn, create_change_set=True)
|
||||
change_set_id = generate_changeset_id(change_set_name, region_name)
|
||||
self.stacks[change_set_name] = {'Id': change_set_id,
|
||||
'StackId': stack.stack_id}
|
||||
self.change_sets[change_set_id] = stack
|
||||
return change_set_id, stack.stack_id
|
||||
|
||||
def describe_stacks(self, name_or_stack_id):
|
||||
stacks = self.stacks.values()
|
||||
if name_or_stack_id:
|
||||
|
@ -4,6 +4,7 @@ import json
|
||||
from six.moves.urllib.parse import urlparse
|
||||
|
||||
from moto.core.responses import BaseResponse
|
||||
from moto.core.utils import amzn_request_id
|
||||
from moto.s3 import s3_backend
|
||||
from .models import cloudformation_backends
|
||||
from .exceptions import ValidationError
|
||||
@ -77,6 +78,46 @@ class CloudFormationResponse(BaseResponse):
|
||||
template = self.response_template(CREATE_STACK_RESPONSE_TEMPLATE)
|
||||
return template.render(stack=stack)
|
||||
|
||||
@amzn_request_id
|
||||
def create_change_set(self):
|
||||
stack_name = self._get_param('StackName')
|
||||
change_set_name = self._get_param('ChangeSetName')
|
||||
stack_body = self._get_param('TemplateBody')
|
||||
template_url = self._get_param('TemplateURL')
|
||||
role_arn = self._get_param('RoleARN')
|
||||
update_or_create = self._get_param('ChangeSetType', 'CREATE')
|
||||
parameters_list = self._get_list_prefix("Parameters.member")
|
||||
tags = {tag[0]: tag[1] for tag in self._get_list_prefix("Tags.member")}
|
||||
parameters = {param['parameter_key']: param['parameter_value']
|
||||
for param in parameters_list}
|
||||
if template_url:
|
||||
stack_body = self._get_stack_from_s3_url(template_url)
|
||||
stack_notification_arns = self._get_multi_param(
|
||||
'NotificationARNs.member')
|
||||
change_set_id, stack_id = self.cloudformation_backend.create_change_set(
|
||||
stack_name=stack_name,
|
||||
change_set_name=change_set_name,
|
||||
template=stack_body,
|
||||
parameters=parameters,
|
||||
region_name=self.region,
|
||||
notification_arns=stack_notification_arns,
|
||||
tags=tags,
|
||||
role_arn=role_arn,
|
||||
change_set_type=update_or_create,
|
||||
)
|
||||
if self.request_json:
|
||||
return json.dumps({
|
||||
'CreateChangeSetResponse': {
|
||||
'CreateChangeSetResult': {
|
||||
'Id': change_set_id,
|
||||
'StackId': stack_id,
|
||||
}
|
||||
}
|
||||
})
|
||||
else:
|
||||
template = self.response_template(CREATE_CHANGE_SET_RESPONSE_TEMPLATE)
|
||||
return template.render(stack_id=stack_id, change_set_id=change_set_id)
|
||||
|
||||
def describe_stacks(self):
|
||||
stack_name_or_id = None
|
||||
if self._get_param('StackName'):
|
||||
@ -250,6 +291,17 @@ UPDATE_STACK_RESPONSE_TEMPLATE = """<UpdateStackResponse xmlns="http://cloudform
|
||||
</UpdateStackResponse>
|
||||
"""
|
||||
|
||||
CREATE_CHANGE_SET_RESPONSE_TEMPLATE = """<CreateStackResponse>
|
||||
<CreateChangeSetResult>
|
||||
<Id>{{change_set_id}}</Id>
|
||||
<StackId>{{ stack_id }}</StackId>
|
||||
</CreateChangeSetResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>{{ request_id }}</RequestId>
|
||||
</ResponseMetadata>
|
||||
</CreateStackResponse>
|
||||
"""
|
||||
|
||||
DESCRIBE_STACKS_TEMPLATE = """<DescribeStacksResponse>
|
||||
<DescribeStacksResult>
|
||||
<Stacks>
|
||||
|
@ -10,6 +10,11 @@ def generate_stack_id(stack_name):
|
||||
return "arn:aws:cloudformation:us-east-1:123456789:stack/{0}/{1}".format(stack_name, random_id)
|
||||
|
||||
|
||||
def generate_changeset_id(changeset_name, region_name):
|
||||
random_id = uuid.uuid4()
|
||||
return 'arn:aws:cloudformation:{0}:123456789:changeSet/{1}/{2}'.format(region_name, changeset_name, random_id)
|
||||
|
||||
|
||||
def random_suffix():
|
||||
size = 12
|
||||
chars = list(range(10)) + ['A-Z']
|
||||
|
@ -310,6 +310,33 @@ def test_update_stack_from_s3_url():
|
||||
'TemplateBody'].should.equal(dummy_update_template)
|
||||
|
||||
|
||||
@mock_cloudformation
|
||||
@mock_s3
|
||||
def test_create_change_set_from_s3_url():
|
||||
s3 = boto3.client('s3')
|
||||
s3_conn = boto3.resource('s3')
|
||||
bucket = s3_conn.create_bucket(Bucket="foobar")
|
||||
|
||||
key = s3_conn.Object(
|
||||
'foobar', 'template-key').put(Body=dummy_template_json)
|
||||
key_url = s3.generate_presigned_url(
|
||||
ClientMethod='get_object',
|
||||
Params={
|
||||
'Bucket': 'foobar',
|
||||
'Key': 'template-key'
|
||||
}
|
||||
)
|
||||
cf_conn = boto3.client('cloudformation', region_name='us-west-1')
|
||||
response = cf_conn.create_change_set(
|
||||
StackName='NewStack',
|
||||
TemplateURL=key_url,
|
||||
ChangeSetName='NewChangeSet',
|
||||
ChangeSetType='CREATE',
|
||||
)
|
||||
assert 'arn:aws:cloudformation:us-west-1:123456789:changeSet/NewChangeSet/' in response['Id']
|
||||
assert 'arn:aws:cloudformation:us-east-1:123456789:stack/NewStack' in response['StackId']
|
||||
|
||||
|
||||
@mock_cloudformation
|
||||
def test_describe_stack_pagination():
|
||||
conn = boto3.client('cloudformation', region_name='us-east-1')
|
||||
|
Loading…
Reference in New Issue
Block a user