Merge branch 'master' into master
This commit is contained in:
commit
a6f7f06c30
@ -477,12 +477,12 @@
|
|||||||
- [X] create_stack
|
- [X] create_stack
|
||||||
- [X] create_stack_instances
|
- [X] create_stack_instances
|
||||||
- [X] create_stack_set
|
- [X] create_stack_set
|
||||||
- [ ] delete_change_set
|
- [X] delete_change_set
|
||||||
- [X] delete_stack
|
- [X] delete_stack
|
||||||
- [X] delete_stack_instances
|
- [X] delete_stack_instances
|
||||||
- [X] delete_stack_set
|
- [X] delete_stack_set
|
||||||
- [ ] describe_account_limits
|
- [ ] describe_account_limits
|
||||||
- [ ] describe_change_set
|
- [X] describe_change_set
|
||||||
- [ ] describe_stack_events
|
- [ ] describe_stack_events
|
||||||
- [X] describe_stack_instance
|
- [X] describe_stack_instance
|
||||||
- [ ] describe_stack_resource
|
- [ ] describe_stack_resource
|
||||||
@ -495,7 +495,7 @@
|
|||||||
- [ ] get_stack_policy
|
- [ ] get_stack_policy
|
||||||
- [ ] get_template
|
- [ ] get_template
|
||||||
- [ ] get_template_summary
|
- [ ] get_template_summary
|
||||||
- [ ] list_change_sets
|
- [X] list_change_sets
|
||||||
- [X] list_exports
|
- [X] list_exports
|
||||||
- [ ] list_imports
|
- [ ] list_imports
|
||||||
- [X] list_stack_instances
|
- [X] list_stack_instances
|
||||||
@ -2208,7 +2208,7 @@
|
|||||||
- [ ] describe_event_types
|
- [ ] describe_event_types
|
||||||
- [ ] describe_events
|
- [ ] describe_events
|
||||||
|
|
||||||
## iam - 48% implemented
|
## iam - 62% implemented
|
||||||
- [ ] add_client_id_to_open_id_connect_provider
|
- [ ] add_client_id_to_open_id_connect_provider
|
||||||
- [X] add_role_to_instance_profile
|
- [X] add_role_to_instance_profile
|
||||||
- [X] add_user_to_group
|
- [X] add_user_to_group
|
||||||
@ -2247,7 +2247,7 @@
|
|||||||
- [X] delete_server_certificate
|
- [X] delete_server_certificate
|
||||||
- [ ] delete_service_linked_role
|
- [ ] delete_service_linked_role
|
||||||
- [ ] delete_service_specific_credential
|
- [ ] delete_service_specific_credential
|
||||||
- [ ] delete_signing_certificate
|
- [X] delete_signing_certificate
|
||||||
- [ ] delete_ssh_public_key
|
- [ ] delete_ssh_public_key
|
||||||
- [X] delete_user
|
- [X] delete_user
|
||||||
- [X] delete_user_policy
|
- [X] delete_user_policy
|
||||||
@ -2279,7 +2279,7 @@
|
|||||||
- [ ] get_ssh_public_key
|
- [ ] get_ssh_public_key
|
||||||
- [X] get_user
|
- [X] get_user
|
||||||
- [X] get_user_policy
|
- [X] get_user_policy
|
||||||
- [ ] list_access_keys
|
- [X] list_access_keys
|
||||||
- [X] list_account_aliases
|
- [X] list_account_aliases
|
||||||
- [X] list_attached_group_policies
|
- [X] list_attached_group_policies
|
||||||
- [X] list_attached_role_policies
|
- [X] list_attached_role_policies
|
||||||
@ -2287,19 +2287,21 @@
|
|||||||
- [ ] list_entities_for_policy
|
- [ ] list_entities_for_policy
|
||||||
- [X] list_group_policies
|
- [X] list_group_policies
|
||||||
- [X] list_groups
|
- [X] list_groups
|
||||||
- [ ] list_groups_for_user
|
- [X] list_groups_for_user
|
||||||
- [ ] list_instance_profiles
|
- [X] list_instance_profiles
|
||||||
- [ ] list_instance_profiles_for_role
|
- [X] list_instance_profiles_for_role
|
||||||
- [X] list_mfa_devices
|
- [X] list_mfa_devices
|
||||||
- [ ] list_open_id_connect_providers
|
- [ ] list_open_id_connect_providers
|
||||||
- [X] list_policies
|
- [X] list_policies
|
||||||
- [X] list_policy_versions
|
- [X] list_policy_versions
|
||||||
- [X] list_role_policies
|
- [X] list_role_policies
|
||||||
- [ ] list_roles
|
- [X] list_roles
|
||||||
|
- [X] list_role_tags
|
||||||
|
- [ ] list_user_tags
|
||||||
- [X] list_saml_providers
|
- [X] list_saml_providers
|
||||||
- [ ] list_server_certificates
|
- [X] list_server_certificates
|
||||||
- [ ] list_service_specific_credentials
|
- [ ] list_service_specific_credentials
|
||||||
- [ ] list_signing_certificates
|
- [X] list_signing_certificates
|
||||||
- [ ] list_ssh_public_keys
|
- [ ] list_ssh_public_keys
|
||||||
- [X] list_user_policies
|
- [X] list_user_policies
|
||||||
- [X] list_users
|
- [X] list_users
|
||||||
@ -2315,6 +2317,10 @@
|
|||||||
- [ ] set_default_policy_version
|
- [ ] set_default_policy_version
|
||||||
- [ ] simulate_custom_policy
|
- [ ] simulate_custom_policy
|
||||||
- [ ] simulate_principal_policy
|
- [ ] simulate_principal_policy
|
||||||
|
- [X] tag_role
|
||||||
|
- [ ] tag_user
|
||||||
|
- [X] untag_role
|
||||||
|
- [ ] untag_user
|
||||||
- [X] update_access_key
|
- [X] update_access_key
|
||||||
- [ ] update_account_password_policy
|
- [ ] update_account_password_policy
|
||||||
- [ ] update_assume_role_policy
|
- [ ] update_assume_role_policy
|
||||||
@ -2326,11 +2332,11 @@
|
|||||||
- [X] update_saml_provider
|
- [X] update_saml_provider
|
||||||
- [ ] update_server_certificate
|
- [ ] update_server_certificate
|
||||||
- [ ] update_service_specific_credential
|
- [ ] update_service_specific_credential
|
||||||
- [ ] update_signing_certificate
|
- [X] update_signing_certificate
|
||||||
- [ ] update_ssh_public_key
|
- [ ] update_ssh_public_key
|
||||||
- [ ] update_user
|
- [ ] update_user
|
||||||
- [ ] upload_server_certificate
|
- [X] upload_server_certificate
|
||||||
- [ ] upload_signing_certificate
|
- [X] upload_signing_certificate
|
||||||
- [ ] upload_ssh_public_key
|
- [ ] upload_ssh_public_key
|
||||||
|
|
||||||
## importexport - 0% implemented
|
## importexport - 0% implemented
|
||||||
@ -3542,7 +3548,7 @@
|
|||||||
- [ ] get_bucket_inventory_configuration
|
- [ ] get_bucket_inventory_configuration
|
||||||
- [ ] get_bucket_lifecycle
|
- [ ] get_bucket_lifecycle
|
||||||
- [ ] get_bucket_lifecycle_configuration
|
- [ ] get_bucket_lifecycle_configuration
|
||||||
- [ ] get_bucket_location
|
- [X] get_bucket_location
|
||||||
- [ ] get_bucket_logging
|
- [ ] get_bucket_logging
|
||||||
- [ ] get_bucket_metrics_configuration
|
- [ ] get_bucket_metrics_configuration
|
||||||
- [ ] get_bucket_notification
|
- [ ] get_bucket_notification
|
||||||
|
@ -271,6 +271,49 @@ class FakeStack(BaseModel):
|
|||||||
self.status = "DELETE_COMPLETE"
|
self.status = "DELETE_COMPLETE"
|
||||||
|
|
||||||
|
|
||||||
|
class FakeChange(BaseModel):
|
||||||
|
|
||||||
|
def __init__(self, action, logical_resource_id, resource_type):
|
||||||
|
self.action = action
|
||||||
|
self.logical_resource_id = logical_resource_id
|
||||||
|
self.resource_type = resource_type
|
||||||
|
|
||||||
|
|
||||||
|
class FakeChangeSet(FakeStack):
|
||||||
|
|
||||||
|
def __init__(self, stack_id, stack_name, stack_template, change_set_id, change_set_name, template, parameters, region_name, notification_arns=None, tags=None, role_arn=None, cross_stack_resources=None):
|
||||||
|
super(FakeChangeSet, self).__init__(
|
||||||
|
stack_id,
|
||||||
|
stack_name,
|
||||||
|
stack_template,
|
||||||
|
parameters,
|
||||||
|
region_name,
|
||||||
|
notification_arns=notification_arns,
|
||||||
|
tags=tags,
|
||||||
|
role_arn=role_arn,
|
||||||
|
cross_stack_resources=cross_stack_resources,
|
||||||
|
create_change_set=True,
|
||||||
|
)
|
||||||
|
self.stack_name = stack_name
|
||||||
|
self.change_set_id = change_set_id
|
||||||
|
self.change_set_name = change_set_name
|
||||||
|
self.changes = self.diff(template=template, parameters=parameters)
|
||||||
|
|
||||||
|
def diff(self, template, parameters=None):
|
||||||
|
self.template = template
|
||||||
|
self._parse_template()
|
||||||
|
changes = []
|
||||||
|
resources_by_action = self.resource_map.diff(self.template_dict, parameters)
|
||||||
|
for action, resources in resources_by_action.items():
|
||||||
|
for resource_name, resource in resources.items():
|
||||||
|
changes.append(FakeChange(
|
||||||
|
action=action,
|
||||||
|
logical_resource_id=resource_name,
|
||||||
|
resource_type=resource['ResourceType'],
|
||||||
|
))
|
||||||
|
return changes
|
||||||
|
|
||||||
|
|
||||||
class FakeEvent(BaseModel):
|
class FakeEvent(BaseModel):
|
||||||
|
|
||||||
def __init__(self, stack_id, stack_name, logical_resource_id, physical_resource_id, resource_type, resource_status, resource_status_reason=None, resource_properties=None):
|
def __init__(self, stack_id, stack_name, logical_resource_id, physical_resource_id, resource_type, resource_status, resource_status_reason=None, resource_properties=None):
|
||||||
@ -377,24 +420,62 @@ class CloudFormationBackend(BaseBackend):
|
|||||||
return new_stack
|
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):
|
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):
|
||||||
|
stack_id = None
|
||||||
|
stack_template = None
|
||||||
if change_set_type == 'UPDATE':
|
if change_set_type == 'UPDATE':
|
||||||
stacks = self.stacks.values()
|
stacks = self.stacks.values()
|
||||||
stack = None
|
stack = None
|
||||||
for s in stacks:
|
for s in stacks:
|
||||||
if s.name == stack_name:
|
if s.name == stack_name:
|
||||||
stack = s
|
stack = s
|
||||||
|
stack_id = stack.stack_id
|
||||||
|
stack_template = stack.template
|
||||||
if stack is None:
|
if stack is None:
|
||||||
raise ValidationError(stack_name)
|
raise ValidationError(stack_name)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
stack = self.create_stack(stack_name, template, parameters,
|
stack_id = generate_stack_id(stack_name)
|
||||||
region_name, notification_arns, tags,
|
stack_template = template
|
||||||
role_arn, create_change_set=True)
|
|
||||||
change_set_id = generate_changeset_id(change_set_name, region_name)
|
change_set_id = generate_changeset_id(change_set_name, region_name)
|
||||||
self.stacks[change_set_name] = {'Id': change_set_id,
|
new_change_set = FakeChangeSet(
|
||||||
'StackId': stack.stack_id}
|
stack_id=stack_id,
|
||||||
self.change_sets[change_set_id] = stack
|
stack_name=stack_name,
|
||||||
return change_set_id, stack.stack_id
|
stack_template=stack_template,
|
||||||
|
change_set_id=change_set_id,
|
||||||
|
change_set_name=change_set_name,
|
||||||
|
template=template,
|
||||||
|
parameters=parameters,
|
||||||
|
region_name=region_name,
|
||||||
|
notification_arns=notification_arns,
|
||||||
|
tags=tags,
|
||||||
|
role_arn=role_arn,
|
||||||
|
cross_stack_resources=self.exports
|
||||||
|
)
|
||||||
|
self.change_sets[change_set_id] = new_change_set
|
||||||
|
self.stacks[stack_id] = new_change_set
|
||||||
|
return change_set_id, stack_id
|
||||||
|
|
||||||
|
def delete_change_set(self, change_set_name, stack_name=None):
|
||||||
|
if change_set_name in self.change_sets:
|
||||||
|
# This means arn was passed in
|
||||||
|
del self.change_sets[change_set_name]
|
||||||
|
else:
|
||||||
|
for cs in self.change_sets:
|
||||||
|
if self.change_sets[cs].change_set_name == change_set_name:
|
||||||
|
del self.change_sets[cs]
|
||||||
|
|
||||||
|
def describe_change_set(self, change_set_name, stack_name=None):
|
||||||
|
change_set = None
|
||||||
|
if change_set_name in self.change_sets:
|
||||||
|
# This means arn was passed in
|
||||||
|
change_set = self.change_sets[change_set_name]
|
||||||
|
else:
|
||||||
|
for cs in self.change_sets:
|
||||||
|
if self.change_sets[cs].change_set_name == change_set_name:
|
||||||
|
change_set = self.change_sets[cs]
|
||||||
|
if change_set is None:
|
||||||
|
raise ValidationError(change_set_name)
|
||||||
|
return change_set
|
||||||
|
|
||||||
def execute_change_set(self, change_set_name, stack_name=None):
|
def execute_change_set(self, change_set_name, stack_name=None):
|
||||||
stack = None
|
stack = None
|
||||||
@ -403,7 +484,7 @@ class CloudFormationBackend(BaseBackend):
|
|||||||
stack = self.change_sets[change_set_name]
|
stack = self.change_sets[change_set_name]
|
||||||
else:
|
else:
|
||||||
for cs in self.change_sets:
|
for cs in self.change_sets:
|
||||||
if self.change_sets[cs].name == change_set_name:
|
if self.change_sets[cs].change_set_name == change_set_name:
|
||||||
stack = self.change_sets[cs]
|
stack = self.change_sets[cs]
|
||||||
if stack is None:
|
if stack is None:
|
||||||
raise ValidationError(stack_name)
|
raise ValidationError(stack_name)
|
||||||
@ -429,6 +510,9 @@ class CloudFormationBackend(BaseBackend):
|
|||||||
else:
|
else:
|
||||||
return list(stacks)
|
return list(stacks)
|
||||||
|
|
||||||
|
def list_change_sets(self):
|
||||||
|
return self.change_sets.values()
|
||||||
|
|
||||||
def list_stacks(self):
|
def list_stacks(self):
|
||||||
return [
|
return [
|
||||||
v for v in self.stacks.values()
|
v for v in self.stacks.values()
|
||||||
|
@ -465,36 +465,70 @@ class ResourceMap(collections.Mapping):
|
|||||||
ec2_models.ec2_backends[self._region_name].create_tags(
|
ec2_models.ec2_backends[self._region_name].create_tags(
|
||||||
[self[resource].physical_resource_id], self.tags)
|
[self[resource].physical_resource_id], self.tags)
|
||||||
|
|
||||||
def update(self, template, parameters=None):
|
def diff(self, template, parameters=None):
|
||||||
if parameters:
|
if parameters:
|
||||||
self.input_parameters = parameters
|
self.input_parameters = parameters
|
||||||
self.load_mapping()
|
self.load_mapping()
|
||||||
self.load_parameters()
|
self.load_parameters()
|
||||||
self.load_conditions()
|
self.load_conditions()
|
||||||
|
|
||||||
|
old_template = self._resource_json_map
|
||||||
|
new_template = template['Resources']
|
||||||
|
|
||||||
|
resource_names_by_action = {
|
||||||
|
'Add': set(new_template) - set(old_template),
|
||||||
|
'Modify': set(name for name in new_template if name in old_template and new_template[
|
||||||
|
name] != old_template[name]),
|
||||||
|
'Remove': set(old_template) - set(new_template)
|
||||||
|
}
|
||||||
|
resources_by_action = {
|
||||||
|
'Add': {},
|
||||||
|
'Modify': {},
|
||||||
|
'Remove': {},
|
||||||
|
}
|
||||||
|
|
||||||
|
for resource_name in resource_names_by_action['Add']:
|
||||||
|
resources_by_action['Add'][resource_name] = {
|
||||||
|
'LogicalResourceId': resource_name,
|
||||||
|
'ResourceType': new_template[resource_name]['Type']
|
||||||
|
}
|
||||||
|
|
||||||
|
for resource_name in resource_names_by_action['Modify']:
|
||||||
|
resources_by_action['Modify'][resource_name] = {
|
||||||
|
'LogicalResourceId': resource_name,
|
||||||
|
'ResourceType': new_template[resource_name]['Type']
|
||||||
|
}
|
||||||
|
|
||||||
|
for resource_name in resource_names_by_action['Remove']:
|
||||||
|
resources_by_action['Remove'][resource_name] = {
|
||||||
|
'LogicalResourceId': resource_name,
|
||||||
|
'ResourceType': old_template[resource_name]['Type']
|
||||||
|
}
|
||||||
|
|
||||||
|
return resources_by_action
|
||||||
|
|
||||||
|
def update(self, template, parameters=None):
|
||||||
|
resources_by_action = self.diff(template, parameters)
|
||||||
|
|
||||||
old_template = self._resource_json_map
|
old_template = self._resource_json_map
|
||||||
new_template = template['Resources']
|
new_template = template['Resources']
|
||||||
self._resource_json_map = new_template
|
self._resource_json_map = new_template
|
||||||
|
|
||||||
new_resource_names = set(new_template) - set(old_template)
|
for resource_name, resource in resources_by_action['Add'].items():
|
||||||
for resource_name in new_resource_names:
|
|
||||||
resource_json = new_template[resource_name]
|
resource_json = new_template[resource_name]
|
||||||
new_resource = parse_and_create_resource(
|
new_resource = parse_and_create_resource(
|
||||||
resource_name, resource_json, self, self._region_name)
|
resource_name, resource_json, self, self._region_name)
|
||||||
self._parsed_resources[resource_name] = new_resource
|
self._parsed_resources[resource_name] = new_resource
|
||||||
|
|
||||||
removed_resource_names = set(old_template) - set(new_template)
|
for resource_name, resource in resources_by_action['Remove'].items():
|
||||||
for resource_name in removed_resource_names:
|
|
||||||
resource_json = old_template[resource_name]
|
resource_json = old_template[resource_name]
|
||||||
parse_and_delete_resource(
|
parse_and_delete_resource(
|
||||||
resource_name, resource_json, self, self._region_name)
|
resource_name, resource_json, self, self._region_name)
|
||||||
self._parsed_resources.pop(resource_name)
|
self._parsed_resources.pop(resource_name)
|
||||||
|
|
||||||
resources_to_update = set(name for name in new_template if name in old_template and new_template[
|
|
||||||
name] != old_template[name])
|
|
||||||
tries = 1
|
tries = 1
|
||||||
while resources_to_update and tries < 5:
|
while resources_by_action['Modify'] and tries < 5:
|
||||||
for resource_name in resources_to_update.copy():
|
for resource_name, resource in resources_by_action['Modify'].copy().items():
|
||||||
resource_json = new_template[resource_name]
|
resource_json = new_template[resource_name]
|
||||||
try:
|
try:
|
||||||
changed_resource = parse_and_update_resource(
|
changed_resource = parse_and_update_resource(
|
||||||
@ -505,7 +539,7 @@ class ResourceMap(collections.Mapping):
|
|||||||
last_exception = e
|
last_exception = e
|
||||||
else:
|
else:
|
||||||
self._parsed_resources[resource_name] = changed_resource
|
self._parsed_resources[resource_name] = changed_resource
|
||||||
resources_to_update.remove(resource_name)
|
del resources_by_action['Modify'][resource_name]
|
||||||
tries += 1
|
tries += 1
|
||||||
if tries == 5:
|
if tries == 5:
|
||||||
raise last_exception
|
raise last_exception
|
||||||
|
@ -120,6 +120,31 @@ class CloudFormationResponse(BaseResponse):
|
|||||||
template = self.response_template(CREATE_CHANGE_SET_RESPONSE_TEMPLATE)
|
template = self.response_template(CREATE_CHANGE_SET_RESPONSE_TEMPLATE)
|
||||||
return template.render(stack_id=stack_id, change_set_id=change_set_id)
|
return template.render(stack_id=stack_id, change_set_id=change_set_id)
|
||||||
|
|
||||||
|
def delete_change_set(self):
|
||||||
|
stack_name = self._get_param('StackName')
|
||||||
|
change_set_name = self._get_param('ChangeSetName')
|
||||||
|
|
||||||
|
self.cloudformation_backend.delete_change_set(change_set_name=change_set_name, stack_name=stack_name)
|
||||||
|
if self.request_json:
|
||||||
|
return json.dumps({
|
||||||
|
'DeleteChangeSetResponse': {
|
||||||
|
'DeleteChangeSetResult': {},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
template = self.response_template(DELETE_CHANGE_SET_RESPONSE_TEMPLATE)
|
||||||
|
return template.render()
|
||||||
|
|
||||||
|
def describe_change_set(self):
|
||||||
|
stack_name = self._get_param('StackName')
|
||||||
|
change_set_name = self._get_param('ChangeSetName')
|
||||||
|
change_set = self.cloudformation_backend.describe_change_set(
|
||||||
|
change_set_name=change_set_name,
|
||||||
|
stack_name=stack_name,
|
||||||
|
)
|
||||||
|
template = self.response_template(DESCRIBE_CHANGE_SET_RESPONSE_TEMPLATE)
|
||||||
|
return template.render(change_set=change_set)
|
||||||
|
|
||||||
@amzn_request_id
|
@amzn_request_id
|
||||||
def execute_change_set(self):
|
def execute_change_set(self):
|
||||||
stack_name = self._get_param('StackName')
|
stack_name = self._get_param('StackName')
|
||||||
@ -187,6 +212,11 @@ class CloudFormationResponse(BaseResponse):
|
|||||||
template = self.response_template(DESCRIBE_STACK_EVENTS_RESPONSE)
|
template = self.response_template(DESCRIBE_STACK_EVENTS_RESPONSE)
|
||||||
return template.render(stack=stack)
|
return template.render(stack=stack)
|
||||||
|
|
||||||
|
def list_change_sets(self):
|
||||||
|
change_sets = self.cloudformation_backend.list_change_sets()
|
||||||
|
template = self.response_template(LIST_CHANGE_SETS_RESPONSE)
|
||||||
|
return template.render(change_sets=change_sets)
|
||||||
|
|
||||||
def list_stacks(self):
|
def list_stacks(self):
|
||||||
stacks = self.cloudformation_backend.list_stacks()
|
stacks = self.cloudformation_backend.list_stacks()
|
||||||
template = self.response_template(LIST_STACKS_RESPONSE)
|
template = self.response_template(LIST_STACKS_RESPONSE)
|
||||||
@ -523,6 +553,66 @@ CREATE_CHANGE_SET_RESPONSE_TEMPLATE = """<CreateStackResponse>
|
|||||||
</CreateStackResponse>
|
</CreateStackResponse>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
DELETE_CHANGE_SET_RESPONSE_TEMPLATE = """<DeleteChangeSetResponse>
|
||||||
|
<DeleteChangeSetResult>
|
||||||
|
</DeleteChangeSetResult>
|
||||||
|
<ResponseMetadata>
|
||||||
|
<RequestId>3d3200a1-810e-3023-6cc3-example</RequestId>
|
||||||
|
</ResponseMetadata>
|
||||||
|
</DeleteChangeSetResponse>
|
||||||
|
"""
|
||||||
|
|
||||||
|
DESCRIBE_CHANGE_SET_RESPONSE_TEMPLATE = """<DescribeChangeSetResponse>
|
||||||
|
<DescribeChangeSetResult>
|
||||||
|
<ChangeSetId>{{ change_set.change_set_id }}</ChangeSetId>
|
||||||
|
<ChangeSetName>{{ change_set.change_set_name }}</ChangeSetName>
|
||||||
|
<StackId>{{ change_set.stack_id }}</StackId>
|
||||||
|
<StackName>{{ change_set.stack_name }}</StackName>
|
||||||
|
<Description>{{ change_set.description }}</Description>
|
||||||
|
<Parameters>
|
||||||
|
{% for param_name, param_value in change_set.stack_parameters.items() %}
|
||||||
|
<member>
|
||||||
|
<ParameterKey>{{ param_name }}</ParameterKey>
|
||||||
|
<ParameterValue>{{ param_value }}</ParameterValue>
|
||||||
|
</member>
|
||||||
|
{% endfor %}
|
||||||
|
</Parameters>
|
||||||
|
<CreationTime>2011-05-23T15:47:44Z</CreationTime>
|
||||||
|
<ExecutionStatus>{{ change_set.execution_status }}</ExecutionStatus>
|
||||||
|
<Status>{{ change_set.status }}</Status>
|
||||||
|
<StatusReason>{{ change_set.status_reason }}</StatusReason>
|
||||||
|
{% if change_set.notification_arns %}
|
||||||
|
<NotificationARNs>
|
||||||
|
{% for notification_arn in change_set.notification_arns %}
|
||||||
|
<member>{{ notification_arn }}</member>
|
||||||
|
{% endfor %}
|
||||||
|
</NotificationARNs>
|
||||||
|
{% else %}
|
||||||
|
<NotificationARNs/>
|
||||||
|
{% endif %}
|
||||||
|
{% if change_set.role_arn %}
|
||||||
|
<RoleARN>{{ change_set.role_arn }}</RoleARN>
|
||||||
|
{% endif %}
|
||||||
|
{% if change_set.changes %}
|
||||||
|
<Changes>
|
||||||
|
{% for change in change_set.changes %}
|
||||||
|
<member>
|
||||||
|
<Type>Resource</Type>
|
||||||
|
<ResourceChange>
|
||||||
|
<Action>{{ change.action }}</Action>
|
||||||
|
<LogicalResourceId>{{ change.logical_resource_id }}</LogicalResourceId>
|
||||||
|
<ResourceType>{{ change.resource_type }}</ResourceType>
|
||||||
|
</ResourceChange>
|
||||||
|
</member>
|
||||||
|
{% endfor %}
|
||||||
|
</Changes>
|
||||||
|
{% endif %}
|
||||||
|
{% if next_token %}
|
||||||
|
<NextToken>{{ next_token }}</NextToken>
|
||||||
|
{% endif %}
|
||||||
|
</DescribeChangeSetResult>
|
||||||
|
</DescribeChangeSetResponse>"""
|
||||||
|
|
||||||
EXECUTE_CHANGE_SET_RESPONSE_TEMPLATE = """<ExecuteChangeSetResponse>
|
EXECUTE_CHANGE_SET_RESPONSE_TEMPLATE = """<ExecuteChangeSetResponse>
|
||||||
<ExecuteChangeSetResult>
|
<ExecuteChangeSetResult>
|
||||||
<ExecuteChangeSetResult/>
|
<ExecuteChangeSetResult/>
|
||||||
@ -648,6 +738,27 @@ DESCRIBE_STACK_EVENTS_RESPONSE = """<DescribeStackEventsResponse xmlns="http://c
|
|||||||
</DescribeStackEventsResponse>"""
|
</DescribeStackEventsResponse>"""
|
||||||
|
|
||||||
|
|
||||||
|
LIST_CHANGE_SETS_RESPONSE = """<ListChangeSetsResponse>
|
||||||
|
<ListChangeSetsResult>
|
||||||
|
<Summaries>
|
||||||
|
{% for change_set in change_sets %}
|
||||||
|
<member>
|
||||||
|
<StackId>{{ change_set.stack_id }}</StackId>
|
||||||
|
<StackName>{{ change_set.stack_name }}</StackName>
|
||||||
|
<ChangeSetId>{{ change_set.change_set_id }}</ChangeSetId>
|
||||||
|
<ChangeSetName>{{ change_set.change_set_name }}</ChangeSetName>
|
||||||
|
<ExecutionStatus>{{ change_set.execution_status }}</ExecutionStatus>
|
||||||
|
<Status>{{ change_set.status }}</Status>
|
||||||
|
<StatusReason>{{ change_set.status_reason }}</StatusReason>
|
||||||
|
<CreationTime>2011-05-23T15:47:44Z</CreationTime>
|
||||||
|
<Description>{{ change_set.description }}</Description>
|
||||||
|
</member>
|
||||||
|
{% endfor %}
|
||||||
|
</Summaries>
|
||||||
|
</ListChangeSetsResult>
|
||||||
|
</ListChangeSetsResponse>"""
|
||||||
|
|
||||||
|
|
||||||
LIST_STACKS_RESPONSE = """<ListStacksResponse>
|
LIST_STACKS_RESPONSE = """<ListStacksResponse>
|
||||||
<ListStacksResult>
|
<ListStacksResult>
|
||||||
<StackSummaries>
|
<StackSummaries>
|
||||||
|
@ -66,6 +66,8 @@ class DynamoType(object):
|
|||||||
return int(self.value)
|
return int(self.value)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return float(self.value)
|
return float(self.value)
|
||||||
|
elif self.is_set():
|
||||||
|
return set(self.value)
|
||||||
else:
|
else:
|
||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
@ -509,15 +511,12 @@ class Table(BaseModel):
|
|||||||
elif 'Value' in val and DynamoType(val['Value']).value != current_attr[key].value:
|
elif 'Value' in val and DynamoType(val['Value']).value != current_attr[key].value:
|
||||||
raise ValueError("The conditional request failed")
|
raise ValueError("The conditional request failed")
|
||||||
elif 'ComparisonOperator' in val:
|
elif 'ComparisonOperator' in val:
|
||||||
comparison_func = get_comparison_func(
|
|
||||||
val['ComparisonOperator'])
|
|
||||||
dynamo_types = [
|
dynamo_types = [
|
||||||
DynamoType(ele) for ele in
|
DynamoType(ele) for ele in
|
||||||
val.get("AttributeValueList", [])
|
val.get("AttributeValueList", [])
|
||||||
]
|
]
|
||||||
for t in dynamo_types:
|
if not current_attr[key].compare(val['ComparisonOperator'], dynamo_types):
|
||||||
if not comparison_func(current_attr[key].value, t.value):
|
raise ValueError('The conditional request failed')
|
||||||
raise ValueError('The conditional request failed')
|
|
||||||
if range_value:
|
if range_value:
|
||||||
self.items[hash_value][range_value] = item
|
self.items[hash_value][range_value] = item
|
||||||
else:
|
else:
|
||||||
@ -946,15 +945,12 @@ class DynamoDBBackend(BaseBackend):
|
|||||||
elif 'Value' in val and DynamoType(val['Value']).value != item_attr[key].value:
|
elif 'Value' in val and DynamoType(val['Value']).value != item_attr[key].value:
|
||||||
raise ValueError("The conditional request failed")
|
raise ValueError("The conditional request failed")
|
||||||
elif 'ComparisonOperator' in val:
|
elif 'ComparisonOperator' in val:
|
||||||
comparison_func = get_comparison_func(
|
|
||||||
val['ComparisonOperator'])
|
|
||||||
dynamo_types = [
|
dynamo_types = [
|
||||||
DynamoType(ele) for ele in
|
DynamoType(ele) for ele in
|
||||||
val.get("AttributeValueList", [])
|
val.get("AttributeValueList", [])
|
||||||
]
|
]
|
||||||
for t in dynamo_types:
|
if not item_attr[key].compare(val['ComparisonOperator'], dynamo_types):
|
||||||
if not comparison_func(item_attr[key].value, t.value):
|
raise ValueError('The conditional request failed')
|
||||||
raise ValueError('The conditional request failed')
|
|
||||||
|
|
||||||
# Update does not fail on new items, so create one
|
# Update does not fail on new items, so create one
|
||||||
if item is None:
|
if item is None:
|
||||||
|
55
moto/ec2/models.py
Executable file → Normal file
55
moto/ec2/models.py
Executable file → Normal file
@ -2464,7 +2464,7 @@ class SubnetBackend(object):
|
|||||||
default_for_az, map_public_ip_on_launch)
|
default_for_az, map_public_ip_on_launch)
|
||||||
|
|
||||||
# AWS associates a new subnet with the default Network ACL
|
# AWS associates a new subnet with the default Network ACL
|
||||||
self.associate_default_network_acl_with_subnet(subnet_id)
|
self.associate_default_network_acl_with_subnet(subnet_id, vpc_id)
|
||||||
self.subnets[availability_zone][subnet_id] = subnet
|
self.subnets[availability_zone][subnet_id] = subnet
|
||||||
return subnet
|
return subnet
|
||||||
|
|
||||||
@ -2879,7 +2879,7 @@ class SpotInstanceRequest(BotoSpotRequest, TaggedEC2Resource):
|
|||||||
def __init__(self, ec2_backend, spot_request_id, price, image_id, type,
|
def __init__(self, ec2_backend, spot_request_id, price, image_id, type,
|
||||||
valid_from, valid_until, launch_group, availability_zone_group,
|
valid_from, valid_until, launch_group, availability_zone_group,
|
||||||
key_name, security_groups, user_data, instance_type, placement,
|
key_name, security_groups, user_data, instance_type, placement,
|
||||||
kernel_id, ramdisk_id, monitoring_enabled, subnet_id, spot_fleet_id,
|
kernel_id, ramdisk_id, monitoring_enabled, subnet_id, tags, spot_fleet_id,
|
||||||
**kwargs):
|
**kwargs):
|
||||||
super(SpotInstanceRequest, self).__init__(**kwargs)
|
super(SpotInstanceRequest, self).__init__(**kwargs)
|
||||||
ls = LaunchSpecification()
|
ls = LaunchSpecification()
|
||||||
@ -2903,6 +2903,7 @@ class SpotInstanceRequest(BotoSpotRequest, TaggedEC2Resource):
|
|||||||
ls.monitored = monitoring_enabled
|
ls.monitored = monitoring_enabled
|
||||||
ls.subnet_id = subnet_id
|
ls.subnet_id = subnet_id
|
||||||
self.spot_fleet_id = spot_fleet_id
|
self.spot_fleet_id = spot_fleet_id
|
||||||
|
self.tags = tags
|
||||||
|
|
||||||
if security_groups:
|
if security_groups:
|
||||||
for group_name in security_groups:
|
for group_name in security_groups:
|
||||||
@ -2936,6 +2937,7 @@ class SpotInstanceRequest(BotoSpotRequest, TaggedEC2Resource):
|
|||||||
security_group_names=[],
|
security_group_names=[],
|
||||||
security_group_ids=self.launch_specification.groups,
|
security_group_ids=self.launch_specification.groups,
|
||||||
spot_fleet_id=self.spot_fleet_id,
|
spot_fleet_id=self.spot_fleet_id,
|
||||||
|
tags=self.tags,
|
||||||
)
|
)
|
||||||
instance = reservation.instances[0]
|
instance = reservation.instances[0]
|
||||||
return instance
|
return instance
|
||||||
@ -2951,15 +2953,16 @@ class SpotRequestBackend(object):
|
|||||||
valid_until, launch_group, availability_zone_group,
|
valid_until, launch_group, availability_zone_group,
|
||||||
key_name, security_groups, user_data,
|
key_name, security_groups, user_data,
|
||||||
instance_type, placement, kernel_id, ramdisk_id,
|
instance_type, placement, kernel_id, ramdisk_id,
|
||||||
monitoring_enabled, subnet_id, spot_fleet_id=None):
|
monitoring_enabled, subnet_id, tags=None, spot_fleet_id=None):
|
||||||
requests = []
|
requests = []
|
||||||
|
tags = tags or {}
|
||||||
for _ in range(count):
|
for _ in range(count):
|
||||||
spot_request_id = random_spot_request_id()
|
spot_request_id = random_spot_request_id()
|
||||||
request = SpotInstanceRequest(self,
|
request = SpotInstanceRequest(self,
|
||||||
spot_request_id, price, image_id, type, valid_from, valid_until,
|
spot_request_id, price, image_id, type, valid_from, valid_until,
|
||||||
launch_group, availability_zone_group, key_name, security_groups,
|
launch_group, availability_zone_group, key_name, security_groups,
|
||||||
user_data, instance_type, placement, kernel_id, ramdisk_id,
|
user_data, instance_type, placement, kernel_id, ramdisk_id,
|
||||||
monitoring_enabled, subnet_id, spot_fleet_id)
|
monitoring_enabled, subnet_id, tags, spot_fleet_id)
|
||||||
self.spot_instance_requests[spot_request_id] = request
|
self.spot_instance_requests[spot_request_id] = request
|
||||||
requests.append(request)
|
requests.append(request)
|
||||||
return requests
|
return requests
|
||||||
@ -2979,8 +2982,8 @@ class SpotRequestBackend(object):
|
|||||||
|
|
||||||
class SpotFleetLaunchSpec(object):
|
class SpotFleetLaunchSpec(object):
|
||||||
def __init__(self, ebs_optimized, group_set, iam_instance_profile, image_id,
|
def __init__(self, ebs_optimized, group_set, iam_instance_profile, image_id,
|
||||||
instance_type, key_name, monitoring, spot_price, subnet_id, user_data,
|
instance_type, key_name, monitoring, spot_price, subnet_id, tag_specifications,
|
||||||
weighted_capacity):
|
user_data, weighted_capacity):
|
||||||
self.ebs_optimized = ebs_optimized
|
self.ebs_optimized = ebs_optimized
|
||||||
self.group_set = group_set
|
self.group_set = group_set
|
||||||
self.iam_instance_profile = iam_instance_profile
|
self.iam_instance_profile = iam_instance_profile
|
||||||
@ -2990,6 +2993,7 @@ class SpotFleetLaunchSpec(object):
|
|||||||
self.monitoring = monitoring
|
self.monitoring = monitoring
|
||||||
self.spot_price = spot_price
|
self.spot_price = spot_price
|
||||||
self.subnet_id = subnet_id
|
self.subnet_id = subnet_id
|
||||||
|
self.tag_specifications = tag_specifications
|
||||||
self.user_data = user_data
|
self.user_data = user_data
|
||||||
self.weighted_capacity = float(weighted_capacity)
|
self.weighted_capacity = float(weighted_capacity)
|
||||||
|
|
||||||
@ -3020,6 +3024,7 @@ class SpotFleetRequest(TaggedEC2Resource):
|
|||||||
monitoring=spec.get('monitoring._enabled'),
|
monitoring=spec.get('monitoring._enabled'),
|
||||||
spot_price=spec.get('spot_price', self.spot_price),
|
spot_price=spec.get('spot_price', self.spot_price),
|
||||||
subnet_id=spec['subnet_id'],
|
subnet_id=spec['subnet_id'],
|
||||||
|
tag_specifications=self._parse_tag_specifications(spec),
|
||||||
user_data=spec.get('user_data'),
|
user_data=spec.get('user_data'),
|
||||||
weighted_capacity=spec['weighted_capacity'],
|
weighted_capacity=spec['weighted_capacity'],
|
||||||
)
|
)
|
||||||
@ -3102,6 +3107,7 @@ class SpotFleetRequest(TaggedEC2Resource):
|
|||||||
monitoring_enabled=launch_spec.monitoring,
|
monitoring_enabled=launch_spec.monitoring,
|
||||||
subnet_id=launch_spec.subnet_id,
|
subnet_id=launch_spec.subnet_id,
|
||||||
spot_fleet_id=self.id,
|
spot_fleet_id=self.id,
|
||||||
|
tags=launch_spec.tag_specifications,
|
||||||
)
|
)
|
||||||
self.spot_requests.extend(requests)
|
self.spot_requests.extend(requests)
|
||||||
self.fulfilled_capacity += added_weight
|
self.fulfilled_capacity += added_weight
|
||||||
@ -3124,6 +3130,25 @@ class SpotFleetRequest(TaggedEC2Resource):
|
|||||||
self.spot_requests = [req for req in self.spot_requests if req.instance.id not in instance_ids]
|
self.spot_requests = [req for req in self.spot_requests if req.instance.id not in instance_ids]
|
||||||
self.ec2_backend.terminate_instances(instance_ids)
|
self.ec2_backend.terminate_instances(instance_ids)
|
||||||
|
|
||||||
|
def _parse_tag_specifications(self, spec):
|
||||||
|
try:
|
||||||
|
tag_spec_num = max([int(key.split('.')[1]) for key in spec if key.startswith("tag_specification_set")])
|
||||||
|
except ValueError: # no tag specifications
|
||||||
|
return {}
|
||||||
|
|
||||||
|
tag_specifications = {}
|
||||||
|
for si in range(1, tag_spec_num + 1):
|
||||||
|
resource_type = spec["tag_specification_set.{si}._resource_type".format(si=si)]
|
||||||
|
|
||||||
|
tags = [key for key in spec if key.startswith("tag_specification_set.{si}._tag".format(si=si))]
|
||||||
|
tag_num = max([int(key.split('.')[3]) for key in tags])
|
||||||
|
tag_specifications[resource_type] = dict((
|
||||||
|
spec["tag_specification_set.{si}._tag.{ti}._key".format(si=si, ti=ti)],
|
||||||
|
spec["tag_specification_set.{si}._tag.{ti}._value".format(si=si, ti=ti)],
|
||||||
|
) for ti in range(1, tag_num + 1))
|
||||||
|
|
||||||
|
return tag_specifications
|
||||||
|
|
||||||
|
|
||||||
class SpotFleetBackend(object):
|
class SpotFleetBackend(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -3560,8 +3585,22 @@ class NetworkAclBackend(object):
|
|||||||
self.get_vpc(vpc_id)
|
self.get_vpc(vpc_id)
|
||||||
network_acl = NetworkAcl(self, network_acl_id, vpc_id, default)
|
network_acl = NetworkAcl(self, network_acl_id, vpc_id, default)
|
||||||
self.network_acls[network_acl_id] = network_acl
|
self.network_acls[network_acl_id] = network_acl
|
||||||
|
if default:
|
||||||
|
self.add_default_entries(network_acl_id)
|
||||||
return network_acl
|
return network_acl
|
||||||
|
|
||||||
|
def add_default_entries(self, network_acl_id):
|
||||||
|
default_acl_entries = [
|
||||||
|
{'rule_number': 100, 'rule_action': 'allow', 'egress': 'true'},
|
||||||
|
{'rule_number': 32767, 'rule_action': 'deny', 'egress': 'true'},
|
||||||
|
{'rule_number': 100, 'rule_action': 'allow', 'egress': 'false'},
|
||||||
|
{'rule_number': 32767, 'rule_action': 'deny', 'egress': 'false'}
|
||||||
|
]
|
||||||
|
for entry in default_acl_entries:
|
||||||
|
self.create_network_acl_entry(network_acl_id=network_acl_id, rule_number=entry['rule_number'], protocol='-1',
|
||||||
|
rule_action=entry['rule_action'], egress=entry['egress'], cidr_block='0.0.0.0/0',
|
||||||
|
icmp_code=None, icmp_type=None, port_range_from=None, port_range_to=None)
|
||||||
|
|
||||||
def get_all_network_acls(self, network_acl_ids=None, filters=None):
|
def get_all_network_acls(self, network_acl_ids=None, filters=None):
|
||||||
network_acls = self.network_acls.values()
|
network_acls = self.network_acls.values()
|
||||||
|
|
||||||
@ -3636,9 +3675,9 @@ class NetworkAclBackend(object):
|
|||||||
new_acl.associations[new_assoc_id] = association
|
new_acl.associations[new_assoc_id] = association
|
||||||
return association
|
return association
|
||||||
|
|
||||||
def associate_default_network_acl_with_subnet(self, subnet_id):
|
def associate_default_network_acl_with_subnet(self, subnet_id, vpc_id):
|
||||||
association_id = random_network_acl_subnet_association_id()
|
association_id = random_network_acl_subnet_association_id()
|
||||||
acl = next(acl for acl in self.network_acls.values() if acl.default)
|
acl = next(acl for acl in self.network_acls.values() if acl.default and acl.vpc_id == vpc_id)
|
||||||
acl.associations[association_id] = NetworkAclAssociation(self, association_id,
|
acl.associations[association_id] = NetworkAclAssociation(self, association_id,
|
||||||
subnet_id, acl.id)
|
subnet_id, acl.id)
|
||||||
|
|
||||||
|
@ -107,6 +107,21 @@ DESCRIBE_SPOT_FLEET_TEMPLATE = """<DescribeSpotFleetRequestsResponse xmlns="http
|
|||||||
</item>
|
</item>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</groupSet>
|
</groupSet>
|
||||||
|
<tagSpecificationSet>
|
||||||
|
{% for resource_type in launch_spec.tag_specifications %}
|
||||||
|
<item>
|
||||||
|
<resourceType>{{ resource_type }}</resourceType>
|
||||||
|
<tag>
|
||||||
|
{% for key, value in launch_spec.tag_specifications[resource_type].items() %}
|
||||||
|
<item>
|
||||||
|
<key>{{ key }}</key>
|
||||||
|
<value>{{ value }}</value>
|
||||||
|
</item>
|
||||||
|
{% endfor %}
|
||||||
|
</tag>
|
||||||
|
</item>
|
||||||
|
{% endfor %}
|
||||||
|
</tagSpecificationSet>
|
||||||
</item>
|
</item>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</launchSpecifications>
|
</launchSpecifications>
|
||||||
|
@ -3,6 +3,7 @@ from .responses import ECRResponse
|
|||||||
|
|
||||||
url_bases = [
|
url_bases = [
|
||||||
"https?://ecr.(.+).amazonaws.com",
|
"https?://ecr.(.+).amazonaws.com",
|
||||||
|
"https?://api.ecr.(.+).amazonaws.com",
|
||||||
]
|
]
|
||||||
|
|
||||||
url_paths = {
|
url_paths = {
|
||||||
|
@ -32,3 +32,48 @@ class MalformedCertificate(RESTError):
|
|||||||
def __init__(self, cert):
|
def __init__(self, cert):
|
||||||
super(MalformedCertificate, self).__init__(
|
super(MalformedCertificate, self).__init__(
|
||||||
'MalformedCertificate', 'Certificate {cert} is malformed'.format(cert=cert))
|
'MalformedCertificate', 'Certificate {cert} is malformed'.format(cert=cert))
|
||||||
|
|
||||||
|
|
||||||
|
class DuplicateTags(RESTError):
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(DuplicateTags, self).__init__(
|
||||||
|
'InvalidInput', 'Duplicate tag keys found. Please note that Tag keys are case insensitive.')
|
||||||
|
|
||||||
|
|
||||||
|
class TagKeyTooBig(RESTError):
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
def __init__(self, tag, param='tags.X.member.key'):
|
||||||
|
super(TagKeyTooBig, self).__init__(
|
||||||
|
'ValidationError', "1 validation error detected: Value '{}' at '{}' failed to satisfy "
|
||||||
|
"constraint: Member must have length less than or equal to 128.".format(tag, param))
|
||||||
|
|
||||||
|
|
||||||
|
class TagValueTooBig(RESTError):
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
def __init__(self, tag):
|
||||||
|
super(TagValueTooBig, self).__init__(
|
||||||
|
'ValidationError', "1 validation error detected: Value '{}' at 'tags.X.member.value' failed to satisfy "
|
||||||
|
"constraint: Member must have length less than or equal to 256.".format(tag))
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidTagCharacters(RESTError):
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
def __init__(self, tag, param='tags.X.member.key'):
|
||||||
|
message = "1 validation error detected: Value '{}' at '{}' failed to satisfy ".format(tag, param)
|
||||||
|
message += "constraint: Member must satisfy regular expression pattern: [\\p{L}\\p{Z}\\p{N}_.:/=+\\-@]+"
|
||||||
|
|
||||||
|
super(InvalidTagCharacters, self).__init__('ValidationError', message)
|
||||||
|
|
||||||
|
|
||||||
|
class TooManyTags(RESTError):
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
def __init__(self, tags, param='tags'):
|
||||||
|
super(TooManyTags, self).__init__(
|
||||||
|
'ValidationError', "1 validation error detected: Value '{}' at '{}' failed to satisfy "
|
||||||
|
"constraint: Member must have length less than or equal to 50.".format(tags, param))
|
||||||
|
@ -3,6 +3,7 @@ import base64
|
|||||||
import sys
|
import sys
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import json
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
from cryptography import x509
|
from cryptography import x509
|
||||||
from cryptography.hazmat.backends import default_backend
|
from cryptography.hazmat.backends import default_backend
|
||||||
@ -12,7 +13,8 @@ from moto.core import BaseBackend, BaseModel
|
|||||||
from moto.core.utils import iso_8601_datetime_without_milliseconds
|
from moto.core.utils import iso_8601_datetime_without_milliseconds
|
||||||
|
|
||||||
from .aws_managed_policies import aws_managed_policies_data
|
from .aws_managed_policies import aws_managed_policies_data
|
||||||
from .exceptions import IAMNotFoundException, IAMConflictException, IAMReportNotPresentException, MalformedCertificate
|
from .exceptions import IAMNotFoundException, IAMConflictException, IAMReportNotPresentException, MalformedCertificate, \
|
||||||
|
DuplicateTags, TagKeyTooBig, InvalidTagCharacters, TooManyTags, TagValueTooBig
|
||||||
from .utils import random_access_key, random_alphanumeric, random_resource_id, random_policy_id
|
from .utils import random_access_key, random_alphanumeric, random_resource_id, random_policy_id
|
||||||
|
|
||||||
ACCOUNT_ID = 123456789012
|
ACCOUNT_ID = 123456789012
|
||||||
@ -32,7 +34,6 @@ class MFADevice(object):
|
|||||||
|
|
||||||
|
|
||||||
class Policy(BaseModel):
|
class Policy(BaseModel):
|
||||||
|
|
||||||
is_attachable = False
|
is_attachable = False
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
@ -132,6 +133,7 @@ class Role(BaseModel):
|
|||||||
self.policies = {}
|
self.policies = {}
|
||||||
self.managed_policies = {}
|
self.managed_policies = {}
|
||||||
self.create_date = datetime.now(pytz.utc)
|
self.create_date = datetime.now(pytz.utc)
|
||||||
|
self.tags = {}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
|
def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
|
||||||
@ -175,6 +177,9 @@ class Role(BaseModel):
|
|||||||
raise NotImplementedError('"Fn::GetAtt" : [ "{0}" , "Arn" ]"')
|
raise NotImplementedError('"Fn::GetAtt" : [ "{0}" , "Arn" ]"')
|
||||||
raise UnformattedGetAttTemplateException()
|
raise UnformattedGetAttTemplateException()
|
||||||
|
|
||||||
|
def get_tags(self):
|
||||||
|
return [self.tags[tag] for tag in self.tags]
|
||||||
|
|
||||||
|
|
||||||
class InstanceProfile(BaseModel):
|
class InstanceProfile(BaseModel):
|
||||||
|
|
||||||
@ -614,6 +619,86 @@ class IAMBackend(BaseBackend):
|
|||||||
role = self.get_role(role_name)
|
role = self.get_role(role_name)
|
||||||
return role.policies.keys()
|
return role.policies.keys()
|
||||||
|
|
||||||
|
def _validate_tag_key(self, tag_key, exception_param='tags.X.member.key'):
|
||||||
|
"""Validates the tag key.
|
||||||
|
|
||||||
|
:param all_tags: Dict to check if there is a duplicate tag.
|
||||||
|
:param tag_key: The tag key to check against.
|
||||||
|
:param exception_param: The exception parameter to send over to help format the message. This is to reflect
|
||||||
|
the difference between the tag and untag APIs.
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
# Validate that the key length is correct:
|
||||||
|
if len(tag_key) > 128:
|
||||||
|
raise TagKeyTooBig(tag_key, param=exception_param)
|
||||||
|
|
||||||
|
# Validate that the tag key fits the proper Regex:
|
||||||
|
# [\w\s_.:/=+\-@]+ SHOULD be the same as the Java regex on the AWS documentation: [\p{L}\p{Z}\p{N}_.:/=+\-@]+
|
||||||
|
match = re.findall(r'[\w\s_.:/=+\-@]+', tag_key)
|
||||||
|
# Kudos if you can come up with a better way of doing a global search :)
|
||||||
|
if not len(match) or len(match[0]) < len(tag_key):
|
||||||
|
raise InvalidTagCharacters(tag_key, param=exception_param)
|
||||||
|
|
||||||
|
def _check_tag_duplicate(self, all_tags, tag_key):
|
||||||
|
"""Validates that a tag key is not a duplicate
|
||||||
|
|
||||||
|
:param all_tags: Dict to check if there is a duplicate tag.
|
||||||
|
:param tag_key: The tag key to check against.
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if tag_key in all_tags:
|
||||||
|
raise DuplicateTags()
|
||||||
|
|
||||||
|
def list_role_tags(self, role_name, marker, max_items=100):
|
||||||
|
role = self.get_role(role_name)
|
||||||
|
|
||||||
|
max_items = int(max_items)
|
||||||
|
tag_index = sorted(role.tags)
|
||||||
|
start_idx = int(marker) if marker else 0
|
||||||
|
|
||||||
|
tag_index = tag_index[start_idx:start_idx + max_items]
|
||||||
|
|
||||||
|
if len(role.tags) <= (start_idx + max_items):
|
||||||
|
marker = None
|
||||||
|
else:
|
||||||
|
marker = str(start_idx + max_items)
|
||||||
|
|
||||||
|
# Make the tag list of dict's:
|
||||||
|
tags = [role.tags[tag] for tag in tag_index]
|
||||||
|
|
||||||
|
return tags, marker
|
||||||
|
|
||||||
|
def tag_role(self, role_name, tags):
|
||||||
|
if len(tags) > 50:
|
||||||
|
raise TooManyTags(tags)
|
||||||
|
|
||||||
|
role = self.get_role(role_name)
|
||||||
|
|
||||||
|
tag_keys = {}
|
||||||
|
for tag in tags:
|
||||||
|
# Need to index by the lowercase tag key since the keys are case insensitive, but their case is retained.
|
||||||
|
ref_key = tag['Key'].lower()
|
||||||
|
self._check_tag_duplicate(tag_keys, ref_key)
|
||||||
|
self._validate_tag_key(tag['Key'])
|
||||||
|
if len(tag['Value']) > 256:
|
||||||
|
raise TagValueTooBig(tag['Value'])
|
||||||
|
|
||||||
|
tag_keys[ref_key] = tag
|
||||||
|
|
||||||
|
role.tags.update(tag_keys)
|
||||||
|
|
||||||
|
def untag_role(self, role_name, tag_keys):
|
||||||
|
if len(tag_keys) > 50:
|
||||||
|
raise TooManyTags(tag_keys, param='tagKeys')
|
||||||
|
|
||||||
|
role = self.get_role(role_name)
|
||||||
|
|
||||||
|
for key in tag_keys:
|
||||||
|
ref_key = key.lower()
|
||||||
|
self._validate_tag_key(key, exception_param='tagKeys')
|
||||||
|
|
||||||
|
role.tags.pop(ref_key, None)
|
||||||
|
|
||||||
def create_policy_version(self, policy_arn, policy_document, set_as_default):
|
def create_policy_version(self, policy_arn, policy_document, set_as_default):
|
||||||
policy = self.get_policy(policy_arn)
|
policy = self.get_policy(policy_arn)
|
||||||
if not policy:
|
if not policy:
|
||||||
|
@ -554,7 +554,8 @@ class IamResponse(BaseResponse):
|
|||||||
policies=account_details['managed_policies'],
|
policies=account_details['managed_policies'],
|
||||||
users=account_details['users'],
|
users=account_details['users'],
|
||||||
groups=account_details['groups'],
|
groups=account_details['groups'],
|
||||||
roles=account_details['roles']
|
roles=account_details['roles'],
|
||||||
|
get_groups_for_user=iam_backend.get_groups_for_user
|
||||||
)
|
)
|
||||||
|
|
||||||
def create_saml_provider(self):
|
def create_saml_provider(self):
|
||||||
@ -625,6 +626,34 @@ class IamResponse(BaseResponse):
|
|||||||
template = self.response_template(LIST_SIGNING_CERTIFICATES_TEMPLATE)
|
template = self.response_template(LIST_SIGNING_CERTIFICATES_TEMPLATE)
|
||||||
return template.render(user_name=user_name, certificates=certs)
|
return template.render(user_name=user_name, certificates=certs)
|
||||||
|
|
||||||
|
def list_role_tags(self):
|
||||||
|
role_name = self._get_param('RoleName')
|
||||||
|
marker = self._get_param('Marker')
|
||||||
|
max_items = self._get_param('MaxItems', 100)
|
||||||
|
|
||||||
|
tags, marker = iam_backend.list_role_tags(role_name, marker, max_items)
|
||||||
|
|
||||||
|
template = self.response_template(LIST_ROLE_TAG_TEMPLATE)
|
||||||
|
return template.render(tags=tags, marker=marker)
|
||||||
|
|
||||||
|
def tag_role(self):
|
||||||
|
role_name = self._get_param('RoleName')
|
||||||
|
tags = self._get_multi_param('Tags.member')
|
||||||
|
|
||||||
|
iam_backend.tag_role(role_name, tags)
|
||||||
|
|
||||||
|
template = self.response_template(TAG_ROLE_TEMPLATE)
|
||||||
|
return template.render()
|
||||||
|
|
||||||
|
def untag_role(self):
|
||||||
|
role_name = self._get_param('RoleName')
|
||||||
|
tag_keys = self._get_multi_param('TagKeys.member')
|
||||||
|
|
||||||
|
iam_backend.untag_role(role_name, tag_keys)
|
||||||
|
|
||||||
|
template = self.response_template(UNTAG_ROLE_TEMPLATE)
|
||||||
|
return template.render()
|
||||||
|
|
||||||
|
|
||||||
ATTACH_ROLE_POLICY_TEMPLATE = """<AttachRolePolicyResponse>
|
ATTACH_ROLE_POLICY_TEMPLATE = """<AttachRolePolicyResponse>
|
||||||
<ResponseMetadata>
|
<ResponseMetadata>
|
||||||
@ -878,6 +907,16 @@ GET_ROLE_TEMPLATE = """<GetRoleResponse xmlns="https://iam.amazonaws.com/doc/201
|
|||||||
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
|
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
|
||||||
<CreateDate>{{ role.create_date }}</CreateDate>
|
<CreateDate>{{ role.create_date }}</CreateDate>
|
||||||
<RoleId>{{ role.id }}</RoleId>
|
<RoleId>{{ role.id }}</RoleId>
|
||||||
|
{% if role.tags %}
|
||||||
|
<Tags>
|
||||||
|
{% for tag in role.get_tags() %}
|
||||||
|
<member>
|
||||||
|
<Key>{{ tag['Key'] }}</Key>
|
||||||
|
<Value>{{ tag['Value'] }}</Value>
|
||||||
|
</member>
|
||||||
|
{% endfor %}
|
||||||
|
</Tags>
|
||||||
|
{% endif %}
|
||||||
</Role>
|
</Role>
|
||||||
</GetRoleResult>
|
</GetRoleResult>
|
||||||
<ResponseMetadata>
|
<ResponseMetadata>
|
||||||
@ -1461,8 +1500,19 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """<GetAccountAuthorizationDetailsR
|
|||||||
<UserDetailList>
|
<UserDetailList>
|
||||||
{% for user in users %}
|
{% for user in users %}
|
||||||
<member>
|
<member>
|
||||||
<GroupList />
|
<GroupList>
|
||||||
<AttachedManagedPolicies/>
|
{% for group in get_groups_for_user(user.name) %}
|
||||||
|
<member>{{ group.name }}</member>
|
||||||
|
{% endfor %}
|
||||||
|
</GroupList>
|
||||||
|
<AttachedManagedPolicies>
|
||||||
|
{% for policy in user.managed_policies %}
|
||||||
|
<member>
|
||||||
|
<PolicyName>{{ user.managed_policies[policy].name }}</PolicyName>
|
||||||
|
<PolicyArn>{{ policy }}</PolicyArn>
|
||||||
|
</member>
|
||||||
|
{% endfor %}
|
||||||
|
</AttachedManagedPolicies>
|
||||||
<UserId>{{ user.id }}</UserId>
|
<UserId>{{ user.id }}</UserId>
|
||||||
<Path>{{ user.path }}</Path>
|
<Path>{{ user.path }}</Path>
|
||||||
<UserName>{{ user.name }}</UserName>
|
<UserName>{{ user.name }}</UserName>
|
||||||
@ -1476,33 +1526,55 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """<GetAccountAuthorizationDetailsR
|
|||||||
<member>
|
<member>
|
||||||
<GroupId>{{ group.id }}</GroupId>
|
<GroupId>{{ group.id }}</GroupId>
|
||||||
<AttachedManagedPolicies>
|
<AttachedManagedPolicies>
|
||||||
{% for policy in group.managed_policies %}
|
{% for policy_arn in group.managed_policies %}
|
||||||
<member>
|
<member>
|
||||||
<PolicyName>{{ policy.name }}</PolicyName>
|
<PolicyName>{{ group.managed_policies[policy_arn].name }}</PolicyName>
|
||||||
<PolicyArn>{{ policy.arn }}</PolicyArn>
|
<PolicyArn>{{ policy_arn }}</PolicyArn>
|
||||||
</member>
|
</member>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</AttachedManagedPolicies>
|
</AttachedManagedPolicies>
|
||||||
<GroupName>{{ group.name }}</GroupName>
|
<GroupName>{{ group.name }}</GroupName>
|
||||||
<Path>{{ group.path }}</Path>
|
<Path>{{ group.path }}</Path>
|
||||||
<Arn>{{ group.arn }}</Arn>
|
<Arn>{{ group.arn }}</Arn>
|
||||||
<CreateDate>{{ group.create_date }}</CreateDate>
|
<CreateDate>{{ group.create_date }}</CreateDate>
|
||||||
<GroupPolicyList/>
|
<GroupPolicyList>
|
||||||
|
{% for policy in group.policies %}
|
||||||
|
<member>
|
||||||
|
<PolicyName>{{ policy }}</PolicyName>
|
||||||
|
<PolicyDocument>{{ group.get_policy(policy) }}</PolicyDocument>
|
||||||
|
</member>
|
||||||
|
{% endfor %}
|
||||||
|
</GroupPolicyList>
|
||||||
</member>
|
</member>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</GroupDetailList>
|
</GroupDetailList>
|
||||||
<RoleDetailList>
|
<RoleDetailList>
|
||||||
{% for role in roles %}
|
{% for role in roles %}
|
||||||
<member>
|
<member>
|
||||||
<RolePolicyList/>
|
<RolePolicyList>
|
||||||
<AttachedManagedPolicies>
|
{% for inline_policy in role.policies %}
|
||||||
{% for policy in role.managed_policies %}
|
|
||||||
<member>
|
<member>
|
||||||
<PolicyName>{{ policy.name }}</PolicyName>
|
<PolicyName>{{ inline_policy }}</PolicyName>
|
||||||
<PolicyArn>{{ policy.arn }}</PolicyArn>
|
<PolicyDocument>{{ role.policies[inline_policy] }}</PolicyDocument>
|
||||||
|
</member>
|
||||||
|
{% endfor %}
|
||||||
|
</RolePolicyList>
|
||||||
|
<AttachedManagedPolicies>
|
||||||
|
{% for policy_arn in role.managed_policies %}
|
||||||
|
<member>
|
||||||
|
<PolicyName>{{ role.managed_policies[policy_arn].name }}</PolicyName>
|
||||||
|
<PolicyArn>{{ policy_arn }}</PolicyArn>
|
||||||
</member>
|
</member>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</AttachedManagedPolicies>
|
</AttachedManagedPolicies>
|
||||||
|
<Tags>
|
||||||
|
{% for tag in role.get_tags() %}
|
||||||
|
<member>
|
||||||
|
<Key>{{ tag['Key'] }}</Key>
|
||||||
|
<Value>{{ tag['Value'] }}</Value>
|
||||||
|
</member>
|
||||||
|
{% endfor %}
|
||||||
|
</Tags>
|
||||||
<InstanceProfileList>
|
<InstanceProfileList>
|
||||||
{% for profile in instance_profiles %}
|
{% for profile in instance_profiles %}
|
||||||
<member>
|
<member>
|
||||||
@ -1543,19 +1615,14 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """<GetAccountAuthorizationDetailsR
|
|||||||
<PolicyId>{{ policy.id }}</PolicyId>
|
<PolicyId>{{ policy.id }}</PolicyId>
|
||||||
<Path>{{ policy.path }}</Path>
|
<Path>{{ policy.path }}</Path>
|
||||||
<PolicyVersionList>
|
<PolicyVersionList>
|
||||||
|
{% for policy_version in policy.versions %}
|
||||||
<member>
|
<member>
|
||||||
<Document>
|
<Document>{{ policy_version.document }}</Document>
|
||||||
{"Version":"2012-10-17","Statement":{"Effect":"Allow",
|
<IsDefaultVersion>{{ policy_version.is_default }}</IsDefaultVersion>
|
||||||
"Action":["iam:CreatePolicy","iam:CreatePolicyVersion",
|
<VersionId>{{ policy_version.version_id }}</VersionId>
|
||||||
"iam:DeletePolicy","iam:DeletePolicyVersion","iam:GetPolicy",
|
<CreateDate>{{ policy_version.create_datetime }}</CreateDate>
|
||||||
"iam:GetPolicyVersion","iam:ListPolicies",
|
|
||||||
"iam:ListPolicyVersions","iam:SetDefaultPolicyVersion"],
|
|
||||||
"Resource":"*"}}
|
|
||||||
</Document>
|
|
||||||
<IsDefaultVersion>true</IsDefaultVersion>
|
|
||||||
<VersionId>v1</VersionId>
|
|
||||||
<CreateDate>2012-05-09T16:27:11Z</CreateDate>
|
|
||||||
</member>
|
</member>
|
||||||
|
{% endfor %}
|
||||||
</PolicyVersionList>
|
</PolicyVersionList>
|
||||||
<Arn>{{ policy.arn }}</Arn>
|
<Arn>{{ policy.arn }}</Arn>
|
||||||
<AttachmentCount>1</AttachmentCount>
|
<AttachmentCount>1</AttachmentCount>
|
||||||
@ -1671,3 +1738,38 @@ LIST_SIGNING_CERTIFICATES_TEMPLATE = """<ListSigningCertificatesResponse>
|
|||||||
<RequestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</RequestId>
|
<RequestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</RequestId>
|
||||||
</ResponseMetadata>
|
</ResponseMetadata>
|
||||||
</ListSigningCertificatesResponse>"""
|
</ListSigningCertificatesResponse>"""
|
||||||
|
|
||||||
|
|
||||||
|
TAG_ROLE_TEMPLATE = """<TagRoleResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
|
||||||
|
<ResponseMetadata>
|
||||||
|
<RequestId>EXAMPLE8-90ab-cdef-fedc-ba987EXAMPLE</RequestId>
|
||||||
|
</ResponseMetadata>
|
||||||
|
</TagRoleResponse>"""
|
||||||
|
|
||||||
|
|
||||||
|
LIST_ROLE_TAG_TEMPLATE = """<ListRoleTagsResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
|
||||||
|
<ListRoleTagsResult>
|
||||||
|
<IsTruncated>{{ 'true' if marker else 'false' }}</IsTruncated>
|
||||||
|
{% if marker %}
|
||||||
|
<Marker>{{ marker }}</Marker>
|
||||||
|
{% endif %}
|
||||||
|
<Tags>
|
||||||
|
{% for tag in tags %}
|
||||||
|
<member>
|
||||||
|
<Key>{{ tag['Key'] }}</Key>
|
||||||
|
<Value>{{ tag['Value'] }}</Value>
|
||||||
|
</member>
|
||||||
|
{% endfor %}
|
||||||
|
</Tags>
|
||||||
|
</ListRoleTagsResult>
|
||||||
|
<ResponseMetadata>
|
||||||
|
<RequestId>EXAMPLE8-90ab-cdef-fedc-ba987EXAMPLE</RequestId>
|
||||||
|
</ResponseMetadata>
|
||||||
|
</ListRoleTagsResponse>"""
|
||||||
|
|
||||||
|
|
||||||
|
UNTAG_ROLE_TEMPLATE = """<UntagRoleResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
|
||||||
|
<ResponseMetadata>
|
||||||
|
<RequestId>EXAMPLE8-90ab-cdef-fedc-ba987EXAMPLE</RequestId>
|
||||||
|
</ResponseMetadata>
|
||||||
|
</UntagRoleResponse>"""
|
||||||
|
4
setup.py
4
setup.py
@ -21,8 +21,8 @@ def read(*parts):
|
|||||||
install_requires = [
|
install_requires = [
|
||||||
"Jinja2>=2.7.3",
|
"Jinja2>=2.7.3",
|
||||||
"boto>=2.36.0",
|
"boto>=2.36.0",
|
||||||
"boto3>=1.6.16",
|
"boto3>=1.9.86",
|
||||||
"botocore>=1.12.13",
|
"botocore>=1.12.86",
|
||||||
"cryptography>=2.3.0",
|
"cryptography>=2.3.0",
|
||||||
"requests>=2.5",
|
"requests>=2.5",
|
||||||
"xmltodict",
|
"xmltodict",
|
||||||
|
@ -816,6 +816,32 @@ def test_create_change_set_from_s3_url():
|
|||||||
assert 'arn:aws:cloudformation:us-east-1:123456789:stack/NewStack' in response['StackId']
|
assert 'arn:aws:cloudformation:us-east-1:123456789:stack/NewStack' in response['StackId']
|
||||||
|
|
||||||
|
|
||||||
|
@mock_cloudformation
|
||||||
|
def test_describe_change_set():
|
||||||
|
cf_conn = boto3.client('cloudformation', region_name='us-east-1')
|
||||||
|
cf_conn.create_change_set(
|
||||||
|
StackName='NewStack',
|
||||||
|
TemplateBody=dummy_template_json,
|
||||||
|
ChangeSetName='NewChangeSet',
|
||||||
|
ChangeSetType='CREATE',
|
||||||
|
)
|
||||||
|
|
||||||
|
stack = cf_conn.describe_change_set(ChangeSetName="NewChangeSet")
|
||||||
|
stack['ChangeSetName'].should.equal('NewChangeSet')
|
||||||
|
stack['StackName'].should.equal('NewStack')
|
||||||
|
|
||||||
|
cf_conn.create_change_set(
|
||||||
|
StackName='NewStack',
|
||||||
|
TemplateBody=dummy_update_template_json,
|
||||||
|
ChangeSetName='NewChangeSet2',
|
||||||
|
ChangeSetType='UPDATE',
|
||||||
|
)
|
||||||
|
stack = cf_conn.describe_change_set(ChangeSetName="NewChangeSet2")
|
||||||
|
stack['ChangeSetName'].should.equal('NewChangeSet2')
|
||||||
|
stack['StackName'].should.equal('NewStack')
|
||||||
|
stack['Changes'].should.have.length_of(2)
|
||||||
|
|
||||||
|
|
||||||
@mock_cloudformation
|
@mock_cloudformation
|
||||||
def test_execute_change_set_w_arn():
|
def test_execute_change_set_w_arn():
|
||||||
cf_conn = boto3.client('cloudformation', region_name='us-east-1')
|
cf_conn = boto3.client('cloudformation', region_name='us-east-1')
|
||||||
@ -837,7 +863,7 @@ def test_execute_change_set_w_name():
|
|||||||
ChangeSetName='NewChangeSet',
|
ChangeSetName='NewChangeSet',
|
||||||
ChangeSetType='CREATE',
|
ChangeSetType='CREATE',
|
||||||
)
|
)
|
||||||
cf_conn.execute_change_set(ChangeSetName='NewStack', StackName='NewStack')
|
cf_conn.execute_change_set(ChangeSetName='NewChangeSet', StackName='NewStack')
|
||||||
|
|
||||||
|
|
||||||
@mock_cloudformation
|
@mock_cloudformation
|
||||||
@ -906,6 +932,20 @@ def test_describe_stack_by_stack_id():
|
|||||||
stack_by_id['StackName'].should.equal("test_stack")
|
stack_by_id['StackName'].should.equal("test_stack")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_cloudformation
|
||||||
|
def test_list_change_sets():
|
||||||
|
cf_conn = boto3.client('cloudformation', region_name='us-east-1')
|
||||||
|
cf_conn.create_change_set(
|
||||||
|
StackName='NewStack2',
|
||||||
|
TemplateBody=dummy_template_json,
|
||||||
|
ChangeSetName='NewChangeSet2',
|
||||||
|
ChangeSetType='CREATE',
|
||||||
|
)
|
||||||
|
change_set = cf_conn.list_change_sets(StackName='NewStack2')['Summaries'][0]
|
||||||
|
change_set['StackName'].should.equal('NewStack2')
|
||||||
|
change_set['ChangeSetName'].should.equal('NewChangeSet2')
|
||||||
|
|
||||||
|
|
||||||
@mock_cloudformation
|
@mock_cloudformation
|
||||||
def test_list_stacks():
|
def test_list_stacks():
|
||||||
cf = boto3.resource('cloudformation', region_name='us-east-1')
|
cf = boto3.resource('cloudformation', region_name='us-east-1')
|
||||||
@ -938,6 +978,22 @@ def test_delete_stack_from_resource():
|
|||||||
list(cf.stacks.all()).should.have.length_of(0)
|
list(cf.stacks.all()).should.have.length_of(0)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_cloudformation
|
||||||
|
@mock_ec2
|
||||||
|
def test_delete_change_set():
|
||||||
|
cf_conn = boto3.client('cloudformation', region_name='us-east-1')
|
||||||
|
cf_conn.create_change_set(
|
||||||
|
StackName='NewStack',
|
||||||
|
TemplateBody=dummy_template_json,
|
||||||
|
ChangeSetName='NewChangeSet',
|
||||||
|
ChangeSetType='CREATE',
|
||||||
|
)
|
||||||
|
|
||||||
|
cf_conn.list_change_sets(StackName='NewStack')['Summaries'].should.have.length_of(1)
|
||||||
|
cf_conn.delete_change_set(ChangeSetName='NewChangeSet', StackName='NewStack')
|
||||||
|
cf_conn.list_change_sets(StackName='NewStack')['Summaries'].should.have.length_of(0)
|
||||||
|
|
||||||
|
|
||||||
@mock_cloudformation
|
@mock_cloudformation
|
||||||
@mock_ec2
|
@mock_ec2
|
||||||
def test_delete_stack_by_name():
|
def test_delete_stack_by_name():
|
||||||
|
@ -750,6 +750,47 @@ def test_boto3_update_item_conditions_pass_because_expect_exists_by_compare_to_n
|
|||||||
returned_item = table.get_item(Key={'username': 'johndoe'})
|
returned_item = table.get_item(Key={'username': 'johndoe'})
|
||||||
assert dict(returned_item)['Item']['foo'].should.equal("baz")
|
assert dict(returned_item)['Item']['foo'].should.equal("baz")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_dynamodb2
|
||||||
|
def test_boto3_update_settype_item_with_conditions():
|
||||||
|
class OrderedSet(set):
|
||||||
|
"""A set with predictable iteration order"""
|
||||||
|
def __init__(self, values):
|
||||||
|
super(OrderedSet, self).__init__(values)
|
||||||
|
self.__ordered_values = values
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self.__ordered_values)
|
||||||
|
|
||||||
|
table = _create_user_table()
|
||||||
|
table.put_item(Item={'username': 'johndoe'})
|
||||||
|
table.update_item(
|
||||||
|
Key={'username': 'johndoe'},
|
||||||
|
UpdateExpression='SET foo=:new_value',
|
||||||
|
ExpressionAttributeValues={
|
||||||
|
':new_value': OrderedSet(['hello', 'world']),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
table.update_item(
|
||||||
|
Key={'username': 'johndoe'},
|
||||||
|
UpdateExpression='SET foo=:new_value',
|
||||||
|
ExpressionAttributeValues={
|
||||||
|
':new_value': set(['baz']),
|
||||||
|
},
|
||||||
|
Expected={
|
||||||
|
'foo': {
|
||||||
|
'ComparisonOperator': 'EQ',
|
||||||
|
'AttributeValueList': [
|
||||||
|
OrderedSet(['world', 'hello']), # Opposite order to original
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
returned_item = table.get_item(Key={'username': 'johndoe'})
|
||||||
|
assert dict(returned_item)['Item']['foo'].should.equal(set(['baz']))
|
||||||
|
|
||||||
|
|
||||||
@mock_dynamodb2
|
@mock_dynamodb2
|
||||||
def test_boto3_put_item_conditions_pass():
|
def test_boto3_put_item_conditions_pass():
|
||||||
table = _create_user_table()
|
table = _create_user_table()
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import boto
|
import boto
|
||||||
|
import boto3
|
||||||
import sure # noqa
|
import sure # noqa
|
||||||
|
|
||||||
from moto import mock_ec2_deprecated
|
from moto import mock_ec2_deprecated, mock_ec2
|
||||||
|
|
||||||
|
|
||||||
@mock_ec2_deprecated
|
@mock_ec2_deprecated
|
||||||
@ -173,3 +174,43 @@ def test_network_acl_tagging():
|
|||||||
if na.id == network_acl.id)
|
if na.id == network_acl.id)
|
||||||
test_network_acl.tags.should.have.length_of(1)
|
test_network_acl.tags.should.have.length_of(1)
|
||||||
test_network_acl.tags["a key"].should.equal("some value")
|
test_network_acl.tags["a key"].should.equal("some value")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ec2
|
||||||
|
def test_new_subnet_in_new_vpc_associates_with_default_network_acl():
|
||||||
|
ec2 = boto3.resource('ec2', region_name='us-west-1')
|
||||||
|
new_vpc = ec2.create_vpc(CidrBlock='10.0.0.0/16')
|
||||||
|
new_vpc.reload()
|
||||||
|
|
||||||
|
subnet = ec2.create_subnet(VpcId=new_vpc.id, CidrBlock='10.0.0.0/24')
|
||||||
|
subnet.reload()
|
||||||
|
|
||||||
|
new_vpcs_default_network_acl = next(iter(new_vpc.network_acls.all()), None)
|
||||||
|
new_vpcs_default_network_acl.reload()
|
||||||
|
new_vpcs_default_network_acl.vpc_id.should.equal(new_vpc.id)
|
||||||
|
new_vpcs_default_network_acl.associations.should.have.length_of(1)
|
||||||
|
new_vpcs_default_network_acl.associations[0]['SubnetId'].should.equal(subnet.id)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ec2
|
||||||
|
def test_default_network_acl_default_entries():
|
||||||
|
ec2 = boto3.resource('ec2', region_name='us-west-1')
|
||||||
|
default_network_acl = next(iter(ec2.network_acls.all()), None)
|
||||||
|
default_network_acl.is_default.should.be.ok
|
||||||
|
|
||||||
|
default_network_acl.entries.should.have.length_of(4)
|
||||||
|
unique_entries = []
|
||||||
|
for entry in default_network_acl.entries:
|
||||||
|
entry['CidrBlock'].should.equal('0.0.0.0/0')
|
||||||
|
entry['Protocol'].should.equal('-1')
|
||||||
|
entry['RuleNumber'].should.be.within([100, 32767])
|
||||||
|
entry['RuleAction'].should.be.within(['allow', 'deny'])
|
||||||
|
assert type(entry['Egress']) is bool
|
||||||
|
if entry['RuleAction'] == 'allow':
|
||||||
|
entry['RuleNumber'].should.be.equal(100)
|
||||||
|
else:
|
||||||
|
entry['RuleNumber'].should.be.equal(32767)
|
||||||
|
if entry not in unique_entries:
|
||||||
|
unique_entries.append(entry)
|
||||||
|
|
||||||
|
unique_entries.should.have.length_of(4)
|
||||||
|
@ -54,7 +54,7 @@ def spot_config(subnet_id, allocation_strategy="lowestPrice"):
|
|||||||
},
|
},
|
||||||
'EbsOptimized': False,
|
'EbsOptimized': False,
|
||||||
'WeightedCapacity': 2.0,
|
'WeightedCapacity': 2.0,
|
||||||
'SpotPrice': '0.13'
|
'SpotPrice': '0.13',
|
||||||
}, {
|
}, {
|
||||||
'ImageId': 'ami-123',
|
'ImageId': 'ami-123',
|
||||||
'KeyName': 'my-key',
|
'KeyName': 'my-key',
|
||||||
@ -148,6 +148,48 @@ def test_create_diversified_spot_fleet():
|
|||||||
instances[0]['InstanceId'].should.contain("i-")
|
instances[0]['InstanceId'].should.contain("i-")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ec2
|
||||||
|
def test_create_spot_fleet_request_with_tag_spec():
|
||||||
|
conn = boto3.client("ec2", region_name='us-west-2')
|
||||||
|
subnet_id = get_subnet_id(conn)
|
||||||
|
|
||||||
|
tag_spec = [
|
||||||
|
{
|
||||||
|
'ResourceType': 'instance',
|
||||||
|
'Tags': [
|
||||||
|
{
|
||||||
|
'Key': 'tag-1',
|
||||||
|
'Value': 'foo',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Key': 'tag-2',
|
||||||
|
'Value': 'bar',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
config = spot_config(subnet_id)
|
||||||
|
config['LaunchSpecifications'][0]['TagSpecifications'] = tag_spec
|
||||||
|
spot_fleet_res = conn.request_spot_fleet(
|
||||||
|
SpotFleetRequestConfig=config
|
||||||
|
)
|
||||||
|
spot_fleet_id = spot_fleet_res['SpotFleetRequestId']
|
||||||
|
spot_fleet_requests = conn.describe_spot_fleet_requests(
|
||||||
|
SpotFleetRequestIds=[spot_fleet_id])['SpotFleetRequestConfigs']
|
||||||
|
spot_fleet_config = spot_fleet_requests[0]['SpotFleetRequestConfig']
|
||||||
|
spot_fleet_config['LaunchSpecifications'][0]['TagSpecifications'][0][
|
||||||
|
'ResourceType'].should.equal('instance')
|
||||||
|
for tag in tag_spec[0]['Tags']:
|
||||||
|
spot_fleet_config['LaunchSpecifications'][0]['TagSpecifications'][0]['Tags'].should.contain(tag)
|
||||||
|
|
||||||
|
instance_res = conn.describe_spot_fleet_instances(
|
||||||
|
SpotFleetRequestId=spot_fleet_id)
|
||||||
|
instances = conn.describe_instances(InstanceIds=[i['InstanceId'] for i in instance_res['ActiveInstances']])
|
||||||
|
for instance in instances['Reservations'][0]['Instances']:
|
||||||
|
for tag in tag_spec[0]['Tags']:
|
||||||
|
instance['Tags'].should.contain(tag)
|
||||||
|
|
||||||
|
|
||||||
@mock_ec2
|
@mock_ec2
|
||||||
def test_cancel_spot_fleet_request():
|
def test_cancel_spot_fleet_request():
|
||||||
conn = boto3.client("ec2", region_name='us-west-2')
|
conn = boto3.client("ec2", region_name='us-west-2')
|
||||||
|
@ -306,6 +306,7 @@ def test_create_policy_versions():
|
|||||||
PolicyDocument='{"some":"policy"}')
|
PolicyDocument='{"some":"policy"}')
|
||||||
version.get('PolicyVersion').get('Document').should.equal({'some': 'policy'})
|
version.get('PolicyVersion').get('Document').should.equal({'some': 'policy'})
|
||||||
|
|
||||||
|
|
||||||
@mock_iam
|
@mock_iam
|
||||||
def test_get_policy():
|
def test_get_policy():
|
||||||
conn = boto3.client('iam', region_name='us-east-1')
|
conn = boto3.client('iam', region_name='us-east-1')
|
||||||
@ -579,6 +580,7 @@ def test_get_credential_report():
|
|||||||
'get_credential_report_result']['content'].encode('ascii')).decode('ascii')
|
'get_credential_report_result']['content'].encode('ascii')).decode('ascii')
|
||||||
report.should.match(r'.*my-user.*')
|
report.should.match(r'.*my-user.*')
|
||||||
|
|
||||||
|
|
||||||
@mock_iam
|
@mock_iam
|
||||||
def test_boto3_get_credential_report():
|
def test_boto3_get_credential_report():
|
||||||
conn = boto3.client('iam', region_name='us-east-1')
|
conn = boto3.client('iam', region_name='us-east-1')
|
||||||
@ -757,6 +759,17 @@ def test_get_access_key_last_used():
|
|||||||
@mock_iam
|
@mock_iam
|
||||||
def test_get_account_authorization_details():
|
def test_get_account_authorization_details():
|
||||||
import json
|
import json
|
||||||
|
test_policy = json.dumps({
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Action": "s3:ListBucket",
|
||||||
|
"Resource": "*",
|
||||||
|
"Effect": "Allow",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
conn = boto3.client('iam', region_name='us-east-1')
|
conn = boto3.client('iam', region_name='us-east-1')
|
||||||
conn.create_role(RoleName="my-role", AssumeRolePolicyDocument="some policy", Path="/my-path/")
|
conn.create_role(RoleName="my-role", AssumeRolePolicyDocument="some policy", Path="/my-path/")
|
||||||
conn.create_user(Path='/', UserName='testUser')
|
conn.create_user(Path='/', UserName='testUser')
|
||||||
@ -764,21 +777,34 @@ def test_get_account_authorization_details():
|
|||||||
conn.create_policy(
|
conn.create_policy(
|
||||||
PolicyName='testPolicy',
|
PolicyName='testPolicy',
|
||||||
Path='/',
|
Path='/',
|
||||||
PolicyDocument=json.dumps({
|
PolicyDocument=test_policy,
|
||||||
"Version": "2012-10-17",
|
|
||||||
"Statement": [
|
|
||||||
{
|
|
||||||
"Action": "s3:ListBucket",
|
|
||||||
"Resource": "*",
|
|
||||||
"Effect": "Allow",
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}),
|
|
||||||
Description='Test Policy'
|
Description='Test Policy'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Attach things to the user and group:
|
||||||
|
conn.put_user_policy(UserName='testUser', PolicyName='testPolicy', PolicyDocument=test_policy)
|
||||||
|
conn.put_group_policy(GroupName='testGroup', PolicyName='testPolicy', PolicyDocument=test_policy)
|
||||||
|
|
||||||
|
conn.attach_user_policy(UserName='testUser', PolicyArn='arn:aws:iam::123456789012:policy/testPolicy')
|
||||||
|
conn.attach_group_policy(GroupName='testGroup', PolicyArn='arn:aws:iam::123456789012:policy/testPolicy')
|
||||||
|
|
||||||
|
conn.add_user_to_group(UserName='testUser', GroupName='testGroup')
|
||||||
|
|
||||||
|
# Add things to the role:
|
||||||
conn.create_instance_profile(InstanceProfileName='ipn')
|
conn.create_instance_profile(InstanceProfileName='ipn')
|
||||||
conn.add_role_to_instance_profile(InstanceProfileName='ipn', RoleName='my-role')
|
conn.add_role_to_instance_profile(InstanceProfileName='ipn', RoleName='my-role')
|
||||||
|
conn.tag_role(RoleName='my-role', Tags=[
|
||||||
|
{
|
||||||
|
'Key': 'somekey',
|
||||||
|
'Value': 'somevalue'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Key': 'someotherkey',
|
||||||
|
'Value': 'someothervalue'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
conn.put_role_policy(RoleName='my-role', PolicyName='test-policy', PolicyDocument=test_policy)
|
||||||
|
conn.attach_role_policy(RoleName='my-role', PolicyArn='arn:aws:iam::123456789012:policy/testPolicy')
|
||||||
|
|
||||||
result = conn.get_account_authorization_details(Filter=['Role'])
|
result = conn.get_account_authorization_details(Filter=['Role'])
|
||||||
assert len(result['RoleDetailList']) == 1
|
assert len(result['RoleDetailList']) == 1
|
||||||
@ -786,24 +812,41 @@ def test_get_account_authorization_details():
|
|||||||
assert len(result['GroupDetailList']) == 0
|
assert len(result['GroupDetailList']) == 0
|
||||||
assert len(result['Policies']) == 0
|
assert len(result['Policies']) == 0
|
||||||
assert len(result['RoleDetailList'][0]['InstanceProfileList']) == 1
|
assert len(result['RoleDetailList'][0]['InstanceProfileList']) == 1
|
||||||
|
assert len(result['RoleDetailList'][0]['Tags']) == 2
|
||||||
|
assert len(result['RoleDetailList'][0]['RolePolicyList']) == 1
|
||||||
|
assert len(result['RoleDetailList'][0]['AttachedManagedPolicies']) == 1
|
||||||
|
assert result['RoleDetailList'][0]['AttachedManagedPolicies'][0]['PolicyName'] == 'testPolicy'
|
||||||
|
assert result['RoleDetailList'][0]['AttachedManagedPolicies'][0]['PolicyArn'] == \
|
||||||
|
'arn:aws:iam::123456789012:policy/testPolicy'
|
||||||
|
|
||||||
result = conn.get_account_authorization_details(Filter=['User'])
|
result = conn.get_account_authorization_details(Filter=['User'])
|
||||||
assert len(result['RoleDetailList']) == 0
|
assert len(result['RoleDetailList']) == 0
|
||||||
assert len(result['UserDetailList']) == 1
|
assert len(result['UserDetailList']) == 1
|
||||||
|
assert len(result['UserDetailList'][0]['GroupList']) == 1
|
||||||
|
assert len(result['UserDetailList'][0]['AttachedManagedPolicies']) == 1
|
||||||
assert len(result['GroupDetailList']) == 0
|
assert len(result['GroupDetailList']) == 0
|
||||||
assert len(result['Policies']) == 0
|
assert len(result['Policies']) == 0
|
||||||
|
assert result['UserDetailList'][0]['AttachedManagedPolicies'][0]['PolicyName'] == 'testPolicy'
|
||||||
|
assert result['UserDetailList'][0]['AttachedManagedPolicies'][0]['PolicyArn'] == \
|
||||||
|
'arn:aws:iam::123456789012:policy/testPolicy'
|
||||||
|
|
||||||
result = conn.get_account_authorization_details(Filter=['Group'])
|
result = conn.get_account_authorization_details(Filter=['Group'])
|
||||||
assert len(result['RoleDetailList']) == 0
|
assert len(result['RoleDetailList']) == 0
|
||||||
assert len(result['UserDetailList']) == 0
|
assert len(result['UserDetailList']) == 0
|
||||||
assert len(result['GroupDetailList']) == 1
|
assert len(result['GroupDetailList']) == 1
|
||||||
|
assert len(result['GroupDetailList'][0]['GroupPolicyList']) == 1
|
||||||
|
assert len(result['GroupDetailList'][0]['AttachedManagedPolicies']) == 1
|
||||||
assert len(result['Policies']) == 0
|
assert len(result['Policies']) == 0
|
||||||
|
assert result['GroupDetailList'][0]['AttachedManagedPolicies'][0]['PolicyName'] == 'testPolicy'
|
||||||
|
assert result['GroupDetailList'][0]['AttachedManagedPolicies'][0]['PolicyArn'] == \
|
||||||
|
'arn:aws:iam::123456789012:policy/testPolicy'
|
||||||
|
|
||||||
result = conn.get_account_authorization_details(Filter=['LocalManagedPolicy'])
|
result = conn.get_account_authorization_details(Filter=['LocalManagedPolicy'])
|
||||||
assert len(result['RoleDetailList']) == 0
|
assert len(result['RoleDetailList']) == 0
|
||||||
assert len(result['UserDetailList']) == 0
|
assert len(result['UserDetailList']) == 0
|
||||||
assert len(result['GroupDetailList']) == 0
|
assert len(result['GroupDetailList']) == 0
|
||||||
assert len(result['Policies']) == 1
|
assert len(result['Policies']) == 1
|
||||||
|
assert len(result['Policies'][0]['PolicyVersionList']) == 1
|
||||||
|
|
||||||
# Check for greater than 1 since this should always be greater than one but might change.
|
# Check for greater than 1 since this should always be greater than one but might change.
|
||||||
# See iam/aws_managed_policies.py
|
# See iam/aws_managed_policies.py
|
||||||
@ -872,6 +915,7 @@ def test_signing_certs():
|
|||||||
with assert_raises(ClientError):
|
with assert_raises(ClientError):
|
||||||
client.delete_signing_certificate(UserName='notauser', CertificateId=cert_id)
|
client.delete_signing_certificate(UserName='notauser', CertificateId=cert_id)
|
||||||
|
|
||||||
|
|
||||||
@mock_iam()
|
@mock_iam()
|
||||||
def test_create_saml_provider():
|
def test_create_saml_provider():
|
||||||
conn = boto3.client('iam', region_name='us-east-1')
|
conn = boto3.client('iam', region_name='us-east-1')
|
||||||
@ -881,6 +925,7 @@ def test_create_saml_provider():
|
|||||||
)
|
)
|
||||||
response['SAMLProviderArn'].should.equal("arn:aws:iam::123456789012:saml-provider/TestSAMLProvider")
|
response['SAMLProviderArn'].should.equal("arn:aws:iam::123456789012:saml-provider/TestSAMLProvider")
|
||||||
|
|
||||||
|
|
||||||
@mock_iam()
|
@mock_iam()
|
||||||
def test_get_saml_provider():
|
def test_get_saml_provider():
|
||||||
conn = boto3.client('iam', region_name='us-east-1')
|
conn = boto3.client('iam', region_name='us-east-1')
|
||||||
@ -893,6 +938,7 @@ def test_get_saml_provider():
|
|||||||
)
|
)
|
||||||
response['SAMLMetadataDocument'].should.equal('a' * 1024)
|
response['SAMLMetadataDocument'].should.equal('a' * 1024)
|
||||||
|
|
||||||
|
|
||||||
@mock_iam()
|
@mock_iam()
|
||||||
def test_list_saml_providers():
|
def test_list_saml_providers():
|
||||||
conn = boto3.client('iam', region_name='us-east-1')
|
conn = boto3.client('iam', region_name='us-east-1')
|
||||||
@ -903,6 +949,7 @@ def test_list_saml_providers():
|
|||||||
response = conn.list_saml_providers()
|
response = conn.list_saml_providers()
|
||||||
response['SAMLProviderList'][0]['Arn'].should.equal("arn:aws:iam::123456789012:saml-provider/TestSAMLProvider")
|
response['SAMLProviderList'][0]['Arn'].should.equal("arn:aws:iam::123456789012:saml-provider/TestSAMLProvider")
|
||||||
|
|
||||||
|
|
||||||
@mock_iam()
|
@mock_iam()
|
||||||
def test_delete_saml_provider():
|
def test_delete_saml_provider():
|
||||||
conn = boto3.client('iam', region_name='us-east-1')
|
conn = boto3.client('iam', region_name='us-east-1')
|
||||||
@ -929,3 +976,178 @@ def test_delete_saml_provider():
|
|||||||
# Verify that it's not in the list:
|
# Verify that it's not in the list:
|
||||||
resp = conn.list_signing_certificates(UserName='testing')
|
resp = conn.list_signing_certificates(UserName='testing')
|
||||||
assert not resp['Certificates']
|
assert not resp['Certificates']
|
||||||
|
|
||||||
|
|
||||||
|
@mock_iam()
|
||||||
|
def test_tag_role():
|
||||||
|
"""Tests both the tag_role and get_role_tags capability"""
|
||||||
|
conn = boto3.client('iam', region_name='us-east-1')
|
||||||
|
conn.create_role(RoleName="my-role", AssumeRolePolicyDocument="{}")
|
||||||
|
|
||||||
|
# Get without tags:
|
||||||
|
role = conn.get_role(RoleName='my-role')['Role']
|
||||||
|
assert not role.get('Tags')
|
||||||
|
|
||||||
|
# With proper tag values:
|
||||||
|
conn.tag_role(RoleName='my-role', Tags=[
|
||||||
|
{
|
||||||
|
'Key': 'somekey',
|
||||||
|
'Value': 'somevalue'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Key': 'someotherkey',
|
||||||
|
'Value': 'someothervalue'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
# Get role:
|
||||||
|
role = conn.get_role(RoleName='my-role')['Role']
|
||||||
|
assert len(role['Tags']) == 2
|
||||||
|
assert role['Tags'][0]['Key'] == 'somekey'
|
||||||
|
assert role['Tags'][0]['Value'] == 'somevalue'
|
||||||
|
assert role['Tags'][1]['Key'] == 'someotherkey'
|
||||||
|
assert role['Tags'][1]['Value'] == 'someothervalue'
|
||||||
|
|
||||||
|
# Same -- but for list_role_tags:
|
||||||
|
tags = conn.list_role_tags(RoleName='my-role')
|
||||||
|
assert len(tags['Tags']) == 2
|
||||||
|
assert role['Tags'][0]['Key'] == 'somekey'
|
||||||
|
assert role['Tags'][0]['Value'] == 'somevalue'
|
||||||
|
assert role['Tags'][1]['Key'] == 'someotherkey'
|
||||||
|
assert role['Tags'][1]['Value'] == 'someothervalue'
|
||||||
|
assert not tags['IsTruncated']
|
||||||
|
assert not tags.get('Marker')
|
||||||
|
|
||||||
|
# Test pagination:
|
||||||
|
tags = conn.list_role_tags(RoleName='my-role', MaxItems=1)
|
||||||
|
assert len(tags['Tags']) == 1
|
||||||
|
assert tags['IsTruncated']
|
||||||
|
assert tags['Tags'][0]['Key'] == 'somekey'
|
||||||
|
assert tags['Tags'][0]['Value'] == 'somevalue'
|
||||||
|
assert tags['Marker'] == '1'
|
||||||
|
|
||||||
|
tags = conn.list_role_tags(RoleName='my-role', Marker=tags['Marker'])
|
||||||
|
assert len(tags['Tags']) == 1
|
||||||
|
assert tags['Tags'][0]['Key'] == 'someotherkey'
|
||||||
|
assert tags['Tags'][0]['Value'] == 'someothervalue'
|
||||||
|
assert not tags['IsTruncated']
|
||||||
|
assert not tags.get('Marker')
|
||||||
|
|
||||||
|
# Test updating an existing tag:
|
||||||
|
conn.tag_role(RoleName='my-role', Tags=[
|
||||||
|
{
|
||||||
|
'Key': 'somekey',
|
||||||
|
'Value': 'somenewvalue'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
tags = conn.list_role_tags(RoleName='my-role')
|
||||||
|
assert len(tags['Tags']) == 2
|
||||||
|
assert tags['Tags'][0]['Key'] == 'somekey'
|
||||||
|
assert tags['Tags'][0]['Value'] == 'somenewvalue'
|
||||||
|
|
||||||
|
# Empty is good:
|
||||||
|
conn.tag_role(RoleName='my-role', Tags=[
|
||||||
|
{
|
||||||
|
'Key': 'somekey',
|
||||||
|
'Value': ''
|
||||||
|
}
|
||||||
|
])
|
||||||
|
tags = conn.list_role_tags(RoleName='my-role')
|
||||||
|
assert len(tags['Tags']) == 2
|
||||||
|
assert tags['Tags'][0]['Key'] == 'somekey'
|
||||||
|
assert tags['Tags'][0]['Value'] == ''
|
||||||
|
|
||||||
|
# Test creating tags with invalid values:
|
||||||
|
# With more than 50 tags:
|
||||||
|
with assert_raises(ClientError) as ce:
|
||||||
|
too_many_tags = list(map(lambda x: {'Key': str(x), 'Value': str(x)}, range(0, 51)))
|
||||||
|
conn.tag_role(RoleName='my-role', Tags=too_many_tags)
|
||||||
|
assert 'failed to satisfy constraint: Member must have length less than or equal to 50.' \
|
||||||
|
in ce.exception.response['Error']['Message']
|
||||||
|
|
||||||
|
# With a duplicate tag:
|
||||||
|
with assert_raises(ClientError) as ce:
|
||||||
|
conn.tag_role(RoleName='my-role', Tags=[{'Key': '0', 'Value': ''}, {'Key': '0', 'Value': ''}])
|
||||||
|
assert 'Duplicate tag keys found. Please note that Tag keys are case insensitive.' \
|
||||||
|
in ce.exception.response['Error']['Message']
|
||||||
|
|
||||||
|
# Duplicate tag with different casing:
|
||||||
|
with assert_raises(ClientError) as ce:
|
||||||
|
conn.tag_role(RoleName='my-role', Tags=[{'Key': 'a', 'Value': ''}, {'Key': 'A', 'Value': ''}])
|
||||||
|
assert 'Duplicate tag keys found. Please note that Tag keys are case insensitive.' \
|
||||||
|
in ce.exception.response['Error']['Message']
|
||||||
|
|
||||||
|
# With a really big key:
|
||||||
|
with assert_raises(ClientError) as ce:
|
||||||
|
conn.tag_role(RoleName='my-role', Tags=[{'Key': '0' * 129, 'Value': ''}])
|
||||||
|
assert 'Member must have length less than or equal to 128.' in ce.exception.response['Error']['Message']
|
||||||
|
|
||||||
|
# With a really big value:
|
||||||
|
with assert_raises(ClientError) as ce:
|
||||||
|
conn.tag_role(RoleName='my-role', Tags=[{'Key': '0', 'Value': '0' * 257}])
|
||||||
|
assert 'Member must have length less than or equal to 256.' in ce.exception.response['Error']['Message']
|
||||||
|
|
||||||
|
# With an invalid character:
|
||||||
|
with assert_raises(ClientError) as ce:
|
||||||
|
conn.tag_role(RoleName='my-role', Tags=[{'Key': 'NOWAY!', 'Value': ''}])
|
||||||
|
assert 'Member must satisfy regular expression pattern: [\\p{L}\\p{Z}\\p{N}_.:/=+\\-@]+' \
|
||||||
|
in ce.exception.response['Error']['Message']
|
||||||
|
|
||||||
|
# With a role that doesn't exist:
|
||||||
|
with assert_raises(ClientError):
|
||||||
|
conn.tag_role(RoleName='notarole', Tags=[{'Key': 'some', 'Value': 'value'}])
|
||||||
|
|
||||||
|
|
||||||
|
@mock_iam
|
||||||
|
def test_untag_role():
|
||||||
|
conn = boto3.client('iam', region_name='us-east-1')
|
||||||
|
conn.create_role(RoleName="my-role", AssumeRolePolicyDocument="{}")
|
||||||
|
|
||||||
|
# With proper tag values:
|
||||||
|
conn.tag_role(RoleName='my-role', Tags=[
|
||||||
|
{
|
||||||
|
'Key': 'somekey',
|
||||||
|
'Value': 'somevalue'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Key': 'someotherkey',
|
||||||
|
'Value': 'someothervalue'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
# Remove them:
|
||||||
|
conn.untag_role(RoleName='my-role', TagKeys=['somekey'])
|
||||||
|
tags = conn.list_role_tags(RoleName='my-role')
|
||||||
|
assert len(tags['Tags']) == 1
|
||||||
|
assert tags['Tags'][0]['Key'] == 'someotherkey'
|
||||||
|
assert tags['Tags'][0]['Value'] == 'someothervalue'
|
||||||
|
|
||||||
|
# And again:
|
||||||
|
conn.untag_role(RoleName='my-role', TagKeys=['someotherkey'])
|
||||||
|
tags = conn.list_role_tags(RoleName='my-role')
|
||||||
|
assert not tags['Tags']
|
||||||
|
|
||||||
|
# Test removing tags with invalid values:
|
||||||
|
# With more than 50 tags:
|
||||||
|
with assert_raises(ClientError) as ce:
|
||||||
|
conn.untag_role(RoleName='my-role', TagKeys=[str(x) for x in range(0, 51)])
|
||||||
|
assert 'failed to satisfy constraint: Member must have length less than or equal to 50.' \
|
||||||
|
in ce.exception.response['Error']['Message']
|
||||||
|
assert 'tagKeys' in ce.exception.response['Error']['Message']
|
||||||
|
|
||||||
|
# With a really big key:
|
||||||
|
with assert_raises(ClientError) as ce:
|
||||||
|
conn.untag_role(RoleName='my-role', TagKeys=['0' * 129])
|
||||||
|
assert 'Member must have length less than or equal to 128.' in ce.exception.response['Error']['Message']
|
||||||
|
assert 'tagKeys' in ce.exception.response['Error']['Message']
|
||||||
|
|
||||||
|
# With an invalid character:
|
||||||
|
with assert_raises(ClientError) as ce:
|
||||||
|
conn.untag_role(RoleName='my-role', TagKeys=['NOWAY!'])
|
||||||
|
assert 'Member must satisfy regular expression pattern: [\\p{L}\\p{Z}\\p{N}_.:/=+\\-@]+' \
|
||||||
|
in ce.exception.response['Error']['Message']
|
||||||
|
assert 'tagKeys' in ce.exception.response['Error']['Message']
|
||||||
|
|
||||||
|
# With a role that doesn't exist:
|
||||||
|
with assert_raises(ClientError):
|
||||||
|
conn.untag_role(RoleName='notarole', TagKeys=['somevalue'])
|
||||||
|
Loading…
Reference in New Issue
Block a user