Merge branch 'master' into bugfix/1874
This commit is contained in:
commit
1fb844972f
@ -332,15 +332,16 @@ class ApiKey(BaseModel, dict):
|
|||||||
|
|
||||||
class UsagePlan(BaseModel, dict):
|
class UsagePlan(BaseModel, dict):
|
||||||
|
|
||||||
def __init__(self, name=None, description=None, apiStages=[],
|
def __init__(self, name=None, description=None, apiStages=None,
|
||||||
throttle=None, quota=None):
|
throttle=None, quota=None, tags=None):
|
||||||
super(UsagePlan, self).__init__()
|
super(UsagePlan, self).__init__()
|
||||||
self['id'] = create_id()
|
self['id'] = create_id()
|
||||||
self['name'] = name
|
self['name'] = name
|
||||||
self['description'] = description
|
self['description'] = description
|
||||||
self['apiStages'] = apiStages
|
self['apiStages'] = apiStages if apiStages else []
|
||||||
self['throttle'] = throttle
|
self['throttle'] = throttle
|
||||||
self['quota'] = quota
|
self['quota'] = quota
|
||||||
|
self['tags'] = tags
|
||||||
|
|
||||||
|
|
||||||
class UsagePlanKey(BaseModel, dict):
|
class UsagePlanKey(BaseModel, dict):
|
||||||
|
@ -235,7 +235,33 @@ class Item(BaseModel):
|
|||||||
value = re.sub(r'{0}\b'.format(k), v, value)
|
value = re.sub(r'{0}\b'.format(k), v, value)
|
||||||
|
|
||||||
if action == "REMOVE":
|
if action == "REMOVE":
|
||||||
|
key = value
|
||||||
|
if '.' not in key:
|
||||||
self.attrs.pop(value, None)
|
self.attrs.pop(value, None)
|
||||||
|
else:
|
||||||
|
# Handle nested dict updates
|
||||||
|
key_parts = key.split('.')
|
||||||
|
attr = key_parts.pop(0)
|
||||||
|
if attr not in self.attrs:
|
||||||
|
raise ValueError
|
||||||
|
|
||||||
|
last_val = self.attrs[attr].value
|
||||||
|
for key_part in key_parts[:-1]:
|
||||||
|
# Hack but it'll do, traverses into a dict
|
||||||
|
last_val_type = list(last_val.keys())
|
||||||
|
if last_val_type and last_val_type[0] == 'M':
|
||||||
|
last_val = last_val['M']
|
||||||
|
|
||||||
|
if key_part not in last_val:
|
||||||
|
last_val[key_part] = {'M': {}}
|
||||||
|
|
||||||
|
last_val = last_val[key_part]
|
||||||
|
|
||||||
|
last_val_type = list(last_val.keys())
|
||||||
|
if last_val_type and last_val_type[0] == 'M':
|
||||||
|
last_val['M'].pop(key_parts[-1], None)
|
||||||
|
else:
|
||||||
|
last_val.pop(key_parts[-1], None)
|
||||||
elif action == 'SET':
|
elif action == 'SET':
|
||||||
key, value = value.split("=", 1)
|
key, value = value.split("=", 1)
|
||||||
key = key.strip()
|
key = key.strip()
|
||||||
@ -1119,12 +1145,23 @@ class DynamoDBBackend(BaseBackend):
|
|||||||
item.update_with_attribute_updates(attribute_updates)
|
item.update_with_attribute_updates(attribute_updates)
|
||||||
return item
|
return item
|
||||||
|
|
||||||
def delete_item(self, table_name, keys):
|
def delete_item(self, table_name, key, expression_attribute_names=None, expression_attribute_values=None,
|
||||||
|
condition_expression=None):
|
||||||
table = self.get_table(table_name)
|
table = self.get_table(table_name)
|
||||||
if not table:
|
if not table:
|
||||||
return None
|
return None
|
||||||
hash_key, range_key = self.get_keys_value(table, keys)
|
|
||||||
return table.delete_item(hash_key, range_key)
|
hash_value, range_value = self.get_keys_value(table, key)
|
||||||
|
item = table.get_item(hash_value, range_value)
|
||||||
|
|
||||||
|
condition_op = get_filter_expression(
|
||||||
|
condition_expression,
|
||||||
|
expression_attribute_names,
|
||||||
|
expression_attribute_values)
|
||||||
|
if not condition_op.expr(item):
|
||||||
|
raise ValueError('The conditional request failed')
|
||||||
|
|
||||||
|
return table.delete_item(hash_value, range_value)
|
||||||
|
|
||||||
def update_ttl(self, table_name, ttl_spec):
|
def update_ttl(self, table_name, ttl_spec):
|
||||||
table = self.tables.get(table_name)
|
table = self.tables.get(table_name)
|
||||||
|
@ -582,7 +582,7 @@ class DynamoHandler(BaseResponse):
|
|||||||
|
|
||||||
def delete_item(self):
|
def delete_item(self):
|
||||||
name = self.body['TableName']
|
name = self.body['TableName']
|
||||||
keys = self.body['Key']
|
key = self.body['Key']
|
||||||
return_values = self.body.get('ReturnValues', 'NONE')
|
return_values = self.body.get('ReturnValues', 'NONE')
|
||||||
if return_values not in ('ALL_OLD', 'NONE'):
|
if return_values not in ('ALL_OLD', 'NONE'):
|
||||||
er = 'com.amazonaws.dynamodb.v20111205#ValidationException'
|
er = 'com.amazonaws.dynamodb.v20111205#ValidationException'
|
||||||
@ -593,7 +593,21 @@ class DynamoHandler(BaseResponse):
|
|||||||
er = 'com.amazonaws.dynamodb.v20120810#ConditionalCheckFailedException'
|
er = 'com.amazonaws.dynamodb.v20120810#ConditionalCheckFailedException'
|
||||||
return self.error(er, 'A condition specified in the operation could not be evaluated.')
|
return self.error(er, 'A condition specified in the operation could not be evaluated.')
|
||||||
|
|
||||||
item = self.dynamodb_backend.delete_item(name, keys)
|
# Attempt to parse simple ConditionExpressions into an Expected
|
||||||
|
# expression
|
||||||
|
condition_expression = self.body.get('ConditionExpression')
|
||||||
|
expression_attribute_names = self.body.get('ExpressionAttributeNames', {})
|
||||||
|
expression_attribute_values = self.body.get('ExpressionAttributeValues', {})
|
||||||
|
|
||||||
|
try:
|
||||||
|
item = self.dynamodb_backend.delete_item(
|
||||||
|
name, key, expression_attribute_names, expression_attribute_values,
|
||||||
|
condition_expression
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
er = 'com.amazonaws.dynamodb.v20111205#ConditionalCheckFailedException'
|
||||||
|
return self.error(er, 'A condition specified in the operation could not be evaluated.')
|
||||||
|
|
||||||
if item and return_values == 'ALL_OLD':
|
if item and return_values == 'ALL_OLD':
|
||||||
item_dict = item.to_json()
|
item_dict = item.to_json()
|
||||||
else:
|
else:
|
||||||
|
@ -329,7 +329,8 @@ class FakeGrant(BaseModel):
|
|||||||
|
|
||||||
class FakeAcl(BaseModel):
|
class FakeAcl(BaseModel):
|
||||||
|
|
||||||
def __init__(self, grants=[]):
|
def __init__(self, grants=None):
|
||||||
|
grants = grants or []
|
||||||
self.grants = grants
|
self.grants = grants
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -396,7 +397,7 @@ class FakeTag(BaseModel):
|
|||||||
class LifecycleFilter(BaseModel):
|
class LifecycleFilter(BaseModel):
|
||||||
|
|
||||||
def __init__(self, prefix=None, tag=None, and_filter=None):
|
def __init__(self, prefix=None, tag=None, and_filter=None):
|
||||||
self.prefix = prefix or ''
|
self.prefix = prefix
|
||||||
self.tag = tag
|
self.tag = tag
|
||||||
self.and_filter = and_filter
|
self.and_filter = and_filter
|
||||||
|
|
||||||
@ -404,7 +405,7 @@ class LifecycleFilter(BaseModel):
|
|||||||
class LifecycleAndFilter(BaseModel):
|
class LifecycleAndFilter(BaseModel):
|
||||||
|
|
||||||
def __init__(self, prefix=None, tags=None):
|
def __init__(self, prefix=None, tags=None):
|
||||||
self.prefix = prefix or ''
|
self.prefix = prefix
|
||||||
self.tags = tags
|
self.tags = tags
|
||||||
|
|
||||||
|
|
||||||
@ -478,6 +479,8 @@ class FakeBucket(BaseModel):
|
|||||||
self.logging = {}
|
self.logging = {}
|
||||||
self.notification_configuration = None
|
self.notification_configuration = None
|
||||||
self.accelerate_configuration = None
|
self.accelerate_configuration = None
|
||||||
|
self.payer = 'BucketOwner'
|
||||||
|
self.creation_date = datetime.datetime.utcnow()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def location(self):
|
def location(self):
|
||||||
@ -494,6 +497,11 @@ class FakeBucket(BaseModel):
|
|||||||
expiration = rule.get('Expiration')
|
expiration = rule.get('Expiration')
|
||||||
transition = rule.get('Transition')
|
transition = rule.get('Transition')
|
||||||
|
|
||||||
|
try:
|
||||||
|
top_level_prefix = rule['Prefix'] or '' # If it's `None` the set to the empty string
|
||||||
|
except KeyError:
|
||||||
|
top_level_prefix = None
|
||||||
|
|
||||||
nve_noncurrent_days = None
|
nve_noncurrent_days = None
|
||||||
if rule.get('NoncurrentVersionExpiration') is not None:
|
if rule.get('NoncurrentVersionExpiration') is not None:
|
||||||
if rule["NoncurrentVersionExpiration"].get('NoncurrentDays') is None:
|
if rule["NoncurrentVersionExpiration"].get('NoncurrentDays') is None:
|
||||||
@ -528,13 +536,22 @@ class FakeBucket(BaseModel):
|
|||||||
if rule.get("Filter"):
|
if rule.get("Filter"):
|
||||||
# Can't have both `Filter` and `Prefix` (need to check for the presence of the key):
|
# Can't have both `Filter` and `Prefix` (need to check for the presence of the key):
|
||||||
try:
|
try:
|
||||||
|
# 'Prefix' cannot be outside of a Filter:
|
||||||
if rule["Prefix"] or not rule["Prefix"]:
|
if rule["Prefix"] or not rule["Prefix"]:
|
||||||
raise MalformedXML()
|
raise MalformedXML()
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
filters = 0
|
||||||
|
try:
|
||||||
|
prefix_filter = rule['Filter']['Prefix'] or '' # If it's `None` the set to the empty string
|
||||||
|
filters += 1
|
||||||
|
except KeyError:
|
||||||
|
prefix_filter = None
|
||||||
|
|
||||||
and_filter = None
|
and_filter = None
|
||||||
if rule["Filter"].get("And"):
|
if rule["Filter"].get("And"):
|
||||||
|
filters += 1
|
||||||
and_tags = []
|
and_tags = []
|
||||||
if rule["Filter"]["And"].get("Tag"):
|
if rule["Filter"]["And"].get("Tag"):
|
||||||
if not isinstance(rule["Filter"]["And"]["Tag"], list):
|
if not isinstance(rule["Filter"]["And"]["Tag"], list):
|
||||||
@ -543,17 +560,34 @@ class FakeBucket(BaseModel):
|
|||||||
for t in rule["Filter"]["And"]["Tag"]:
|
for t in rule["Filter"]["And"]["Tag"]:
|
||||||
and_tags.append(FakeTag(t["Key"], t.get("Value", '')))
|
and_tags.append(FakeTag(t["Key"], t.get("Value", '')))
|
||||||
|
|
||||||
and_filter = LifecycleAndFilter(prefix=rule["Filter"]["And"]["Prefix"], tags=and_tags)
|
try:
|
||||||
|
and_prefix = rule["Filter"]["And"]["Prefix"] or '' # If it's `None` then set to the empty string
|
||||||
|
except KeyError:
|
||||||
|
and_prefix = None
|
||||||
|
|
||||||
|
and_filter = LifecycleAndFilter(prefix=and_prefix, tags=and_tags)
|
||||||
|
|
||||||
filter_tag = None
|
filter_tag = None
|
||||||
if rule["Filter"].get("Tag"):
|
if rule["Filter"].get("Tag"):
|
||||||
|
filters += 1
|
||||||
filter_tag = FakeTag(rule["Filter"]["Tag"]["Key"], rule["Filter"]["Tag"].get("Value", ''))
|
filter_tag = FakeTag(rule["Filter"]["Tag"]["Key"], rule["Filter"]["Tag"].get("Value", ''))
|
||||||
|
|
||||||
lc_filter = LifecycleFilter(prefix=rule["Filter"]["Prefix"], tag=filter_tag, and_filter=and_filter)
|
# Can't have more than 1 filter:
|
||||||
|
if filters > 1:
|
||||||
|
raise MalformedXML()
|
||||||
|
|
||||||
|
lc_filter = LifecycleFilter(prefix=prefix_filter, tag=filter_tag, and_filter=and_filter)
|
||||||
|
|
||||||
|
# If no top level prefix and no filter is present, then this is invalid:
|
||||||
|
if top_level_prefix is None:
|
||||||
|
try:
|
||||||
|
rule['Filter']
|
||||||
|
except KeyError:
|
||||||
|
raise MalformedXML()
|
||||||
|
|
||||||
self.rules.append(LifecycleRule(
|
self.rules.append(LifecycleRule(
|
||||||
id=rule.get('ID'),
|
id=rule.get('ID'),
|
||||||
prefix=rule.get('Prefix'),
|
prefix=top_level_prefix,
|
||||||
lc_filter=lc_filter,
|
lc_filter=lc_filter,
|
||||||
status=rule['Status'],
|
status=rule['Status'],
|
||||||
expiration_days=expiration.get('Days') if expiration else None,
|
expiration_days=expiration.get('Days') if expiration else None,
|
||||||
|
@ -1310,7 +1310,7 @@ S3_ALL_BUCKETS = """<ListAllMyBucketsResult xmlns="http://s3.amazonaws.com/doc/2
|
|||||||
{% for bucket in buckets %}
|
{% for bucket in buckets %}
|
||||||
<Bucket>
|
<Bucket>
|
||||||
<Name>{{ bucket.name }}</Name>
|
<Name>{{ bucket.name }}</Name>
|
||||||
<CreationDate>2006-02-03T16:45:09.000Z</CreationDate>
|
<CreationDate>{{ bucket.creation_date }}</CreationDate>
|
||||||
</Bucket>
|
</Bucket>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</Buckets>
|
</Buckets>
|
||||||
@ -1416,7 +1416,9 @@ S3_BUCKET_LIFECYCLE_CONFIGURATION = """<?xml version="1.0" encoding="UTF-8"?>
|
|||||||
<ID>{{ rule.id }}</ID>
|
<ID>{{ rule.id }}</ID>
|
||||||
{% if rule.filter %}
|
{% if rule.filter %}
|
||||||
<Filter>
|
<Filter>
|
||||||
|
{% if rule.filter.prefix != None %}
|
||||||
<Prefix>{{ rule.filter.prefix }}</Prefix>
|
<Prefix>{{ rule.filter.prefix }}</Prefix>
|
||||||
|
{% endif %}
|
||||||
{% if rule.filter.tag %}
|
{% if rule.filter.tag %}
|
||||||
<Tag>
|
<Tag>
|
||||||
<Key>{{ rule.filter.tag.key }}</Key>
|
<Key>{{ rule.filter.tag.key }}</Key>
|
||||||
@ -1425,7 +1427,9 @@ S3_BUCKET_LIFECYCLE_CONFIGURATION = """<?xml version="1.0" encoding="UTF-8"?>
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% if rule.filter.and_filter %}
|
{% if rule.filter.and_filter %}
|
||||||
<And>
|
<And>
|
||||||
|
{% if rule.filter.and_filter.prefix != None %}
|
||||||
<Prefix>{{ rule.filter.and_filter.prefix }}</Prefix>
|
<Prefix>{{ rule.filter.and_filter.prefix }}</Prefix>
|
||||||
|
{% endif %}
|
||||||
{% for tag in rule.filter.and_filter.tags %}
|
{% for tag in rule.filter.and_filter.tags %}
|
||||||
<Tag>
|
<Tag>
|
||||||
<Key>{{ tag.key }}</Key>
|
<Key>{{ tag.key }}</Key>
|
||||||
@ -1436,7 +1440,9 @@ S3_BUCKET_LIFECYCLE_CONFIGURATION = """<?xml version="1.0" encoding="UTF-8"?>
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</Filter>
|
</Filter>
|
||||||
{% else %}
|
{% else %}
|
||||||
<Prefix>{{ rule.prefix if rule.prefix != None }}</Prefix>
|
{% if rule.prefix != None %}
|
||||||
|
<Prefix>{{ rule.prefix }}</Prefix>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<Status>{{ rule.status }}</Status>
|
<Status>{{ rule.status }}</Status>
|
||||||
{% if rule.storage_class %}
|
{% if rule.storage_class %}
|
||||||
|
34
moto/ssm/exceptions.py
Normal file
34
moto/ssm/exceptions.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
from moto.core.exceptions import JsonRESTError
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidFilterKey(JsonRESTError):
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
def __init__(self, message):
|
||||||
|
super(InvalidFilterKey, self).__init__(
|
||||||
|
"InvalidFilterKey", message)
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidFilterOption(JsonRESTError):
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
def __init__(self, message):
|
||||||
|
super(InvalidFilterOption, self).__init__(
|
||||||
|
"InvalidFilterOption", message)
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidFilterValue(JsonRESTError):
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
def __init__(self, message):
|
||||||
|
super(InvalidFilterValue, self).__init__(
|
||||||
|
"InvalidFilterValue", message)
|
||||||
|
|
||||||
|
|
||||||
|
class ValidationException(JsonRESTError):
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
def __init__(self, message):
|
||||||
|
super(ValidationException, self).__init__(
|
||||||
|
"ValidationException", message)
|
@ -1,5 +1,6 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
from moto.core import BaseBackend, BaseModel
|
from moto.core import BaseBackend, BaseModel
|
||||||
@ -12,6 +13,8 @@ import time
|
|||||||
import uuid
|
import uuid
|
||||||
import itertools
|
import itertools
|
||||||
|
|
||||||
|
from .exceptions import ValidationException, InvalidFilterValue, InvalidFilterOption, InvalidFilterKey
|
||||||
|
|
||||||
|
|
||||||
class Parameter(BaseModel):
|
class Parameter(BaseModel):
|
||||||
def __init__(self, name, value, type, description, allowed_pattern, keyid,
|
def __init__(self, name, value, type, description, allowed_pattern, keyid,
|
||||||
@ -25,12 +28,15 @@ class Parameter(BaseModel):
|
|||||||
self.version = version
|
self.version = version
|
||||||
|
|
||||||
if self.type == 'SecureString':
|
if self.type == 'SecureString':
|
||||||
|
if not self.keyid:
|
||||||
|
self.keyid = 'alias/aws/ssm'
|
||||||
|
|
||||||
self.value = self.encrypt(value)
|
self.value = self.encrypt(value)
|
||||||
else:
|
else:
|
||||||
self.value = value
|
self.value = value
|
||||||
|
|
||||||
def encrypt(self, value):
|
def encrypt(self, value):
|
||||||
return 'kms:{}:'.format(self.keyid or 'default') + value
|
return 'kms:{}:'.format(self.keyid) + value
|
||||||
|
|
||||||
def decrypt(self, value):
|
def decrypt(self, value):
|
||||||
if self.type != 'SecureString':
|
if self.type != 'SecureString':
|
||||||
@ -217,6 +223,7 @@ class SimpleSystemManagerBackend(BaseBackend):
|
|||||||
self._parameters = {}
|
self._parameters = {}
|
||||||
self._resource_tags = defaultdict(lambda: defaultdict(dict))
|
self._resource_tags = defaultdict(lambda: defaultdict(dict))
|
||||||
self._commands = []
|
self._commands = []
|
||||||
|
self._errors = []
|
||||||
|
|
||||||
# figure out what region we're in
|
# figure out what region we're in
|
||||||
for region, backend in ssm_backends.items():
|
for region, backend in ssm_backends.items():
|
||||||
@ -239,6 +246,179 @@ class SimpleSystemManagerBackend(BaseBackend):
|
|||||||
pass
|
pass
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def describe_parameters(self, filters, parameter_filters):
|
||||||
|
if filters and parameter_filters:
|
||||||
|
raise ValidationException('You can use either Filters or ParameterFilters in a single request.')
|
||||||
|
|
||||||
|
self._validate_parameter_filters(parameter_filters, by_path=False)
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for param in self._parameters:
|
||||||
|
ssm_parameter = self._parameters[param]
|
||||||
|
if not self._match_filters(ssm_parameter, parameter_filters):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if filters:
|
||||||
|
for filter in filters:
|
||||||
|
if filter['Key'] == 'Name':
|
||||||
|
k = ssm_parameter.name
|
||||||
|
for v in filter['Values']:
|
||||||
|
if k.startswith(v):
|
||||||
|
result.append(ssm_parameter)
|
||||||
|
break
|
||||||
|
elif filter['Key'] == 'Type':
|
||||||
|
k = ssm_parameter.type
|
||||||
|
for v in filter['Values']:
|
||||||
|
if k == v:
|
||||||
|
result.append(ssm_parameter)
|
||||||
|
break
|
||||||
|
elif filter['Key'] == 'KeyId':
|
||||||
|
k = ssm_parameter.keyid
|
||||||
|
if k:
|
||||||
|
for v in filter['Values']:
|
||||||
|
if k == v:
|
||||||
|
result.append(ssm_parameter)
|
||||||
|
break
|
||||||
|
continue
|
||||||
|
|
||||||
|
result.append(ssm_parameter)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _validate_parameter_filters(self, parameter_filters, by_path):
|
||||||
|
for index, filter_obj in enumerate(parameter_filters or []):
|
||||||
|
key = filter_obj['Key']
|
||||||
|
values = filter_obj.get('Values', [])
|
||||||
|
|
||||||
|
if key == 'Path':
|
||||||
|
option = filter_obj.get('Option', 'OneLevel')
|
||||||
|
else:
|
||||||
|
option = filter_obj.get('Option', 'Equals')
|
||||||
|
|
||||||
|
if not re.match(r'^tag:.+|Name|Type|KeyId|Path|Label|Tier$', key):
|
||||||
|
self._errors.append(self._format_error(
|
||||||
|
key='parameterFilters.{index}.member.key'.format(index=(index + 1)),
|
||||||
|
value=key,
|
||||||
|
constraint='Member must satisfy regular expression pattern: tag:.+|Name|Type|KeyId|Path|Label|Tier',
|
||||||
|
))
|
||||||
|
|
||||||
|
if len(key) > 132:
|
||||||
|
self._errors.append(self._format_error(
|
||||||
|
key='parameterFilters.{index}.member.key'.format(index=(index + 1)),
|
||||||
|
value=key,
|
||||||
|
constraint='Member must have length less than or equal to 132',
|
||||||
|
))
|
||||||
|
|
||||||
|
if len(option) > 10:
|
||||||
|
self._errors.append(self._format_error(
|
||||||
|
key='parameterFilters.{index}.member.option'.format(index=(index + 1)),
|
||||||
|
value='over 10 chars',
|
||||||
|
constraint='Member must have length less than or equal to 10',
|
||||||
|
))
|
||||||
|
|
||||||
|
if len(values) > 50:
|
||||||
|
self._errors.append(self._format_error(
|
||||||
|
key='parameterFilters.{index}.member.values'.format(index=(index + 1)),
|
||||||
|
value=values,
|
||||||
|
constraint='Member must have length less than or equal to 50',
|
||||||
|
))
|
||||||
|
|
||||||
|
if any(len(value) > 1024 for value in values):
|
||||||
|
self._errors.append(self._format_error(
|
||||||
|
key='parameterFilters.{index}.member.values'.format(index=(index + 1)),
|
||||||
|
value=values,
|
||||||
|
constraint='[Member must have length less than or equal to 1024, Member must have length greater than or equal to 1]',
|
||||||
|
))
|
||||||
|
|
||||||
|
self._raise_errors()
|
||||||
|
|
||||||
|
filter_keys = []
|
||||||
|
for filter_obj in (parameter_filters or []):
|
||||||
|
key = filter_obj['Key']
|
||||||
|
values = filter_obj.get('Values')
|
||||||
|
|
||||||
|
if key == 'Path':
|
||||||
|
option = filter_obj.get('Option', 'OneLevel')
|
||||||
|
else:
|
||||||
|
option = filter_obj.get('Option', 'Equals')
|
||||||
|
|
||||||
|
if not by_path and key == 'Label':
|
||||||
|
raise InvalidFilterKey('The following filter key is not valid: Label. Valid filter keys include: [Path, Name, Type, KeyId, Tier].')
|
||||||
|
|
||||||
|
if not values:
|
||||||
|
raise InvalidFilterValue('The following filter values are missing : null for filter key Name.')
|
||||||
|
|
||||||
|
if key in filter_keys:
|
||||||
|
raise InvalidFilterKey(
|
||||||
|
'The following filter is duplicated in the request: Name. A request can contain only one occurrence of a specific filter.'
|
||||||
|
)
|
||||||
|
|
||||||
|
if key == 'Path':
|
||||||
|
if option not in ['Recursive', 'OneLevel']:
|
||||||
|
raise InvalidFilterOption(
|
||||||
|
'The following filter option is not valid: {option}. Valid options include: [Recursive, OneLevel].'.format(option=option)
|
||||||
|
)
|
||||||
|
if any(value.lower().startswith(('/aws', '/ssm')) for value in values):
|
||||||
|
raise ValidationException(
|
||||||
|
'Filters for common parameters can\'t be prefixed with "aws" or "ssm" (case-insensitive). '
|
||||||
|
'When using global parameters, please specify within a global namespace.'
|
||||||
|
)
|
||||||
|
for value in values:
|
||||||
|
if value.lower().startswith(('/aws', '/ssm')):
|
||||||
|
raise ValidationException(
|
||||||
|
'Filters for common parameters can\'t be prefixed with "aws" or "ssm" (case-insensitive). '
|
||||||
|
'When using global parameters, please specify within a global namespace.'
|
||||||
|
)
|
||||||
|
if ('//' in value or
|
||||||
|
not value.startswith('/') or
|
||||||
|
not re.match('^[a-zA-Z0-9_.-/]*$', value)):
|
||||||
|
raise ValidationException(
|
||||||
|
'The parameter doesn\'t meet the parameter name requirements. The parameter name must begin with a forward slash "/". '
|
||||||
|
'It can\'t be prefixed with \"aws\" or \"ssm\" (case-insensitive). '
|
||||||
|
'It must use only letters, numbers, or the following symbols: . (period), - (hyphen), _ (underscore). '
|
||||||
|
'Special characters are not allowed. All sub-paths, if specified, must use the forward slash symbol "/". '
|
||||||
|
'Valid example: /get/parameters2-/by1./path0_.'
|
||||||
|
)
|
||||||
|
|
||||||
|
if key == 'Tier':
|
||||||
|
for value in values:
|
||||||
|
if value not in ['Standard', 'Advanced', 'Intelligent-Tiering']:
|
||||||
|
raise InvalidFilterOption(
|
||||||
|
'The following filter value is not valid: {value}. Valid values include: [Standard, Advanced, Intelligent-Tiering].'.format(value=value)
|
||||||
|
)
|
||||||
|
|
||||||
|
if key == 'Type':
|
||||||
|
for value in values:
|
||||||
|
if value not in ['String', 'StringList', 'SecureString']:
|
||||||
|
raise InvalidFilterOption(
|
||||||
|
'The following filter value is not valid: {value}. Valid values include: [String, StringList, SecureString].'.format(value=value)
|
||||||
|
)
|
||||||
|
|
||||||
|
if key != 'Path' and option not in ['Equals', 'BeginsWith']:
|
||||||
|
raise InvalidFilterOption(
|
||||||
|
'The following filter option is not valid: {option}. Valid options include: [BeginsWith, Equals].'.format(option=option)
|
||||||
|
)
|
||||||
|
|
||||||
|
filter_keys.append(key)
|
||||||
|
|
||||||
|
def _format_error(self, key, value, constraint):
|
||||||
|
return 'Value "{value}" at "{key}" failed to satisfy constraint: {constraint}'.format(
|
||||||
|
constraint=constraint,
|
||||||
|
key=key,
|
||||||
|
value=value,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _raise_errors(self):
|
||||||
|
if self._errors:
|
||||||
|
count = len(self._errors)
|
||||||
|
plural = "s" if len(self._errors) > 1 else ""
|
||||||
|
errors = "; ".join(self._errors)
|
||||||
|
self._errors = [] # reset collected errors
|
||||||
|
|
||||||
|
raise ValidationException('{count} validation error{plural} detected: {errors}'.format(
|
||||||
|
count=count, plural=plural, errors=errors,
|
||||||
|
))
|
||||||
|
|
||||||
def get_all_parameters(self):
|
def get_all_parameters(self):
|
||||||
result = []
|
result = []
|
||||||
for k, _ in self._parameters.items():
|
for k, _ in self._parameters.items():
|
||||||
@ -269,25 +449,52 @@ class SimpleSystemManagerBackend(BaseBackend):
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@staticmethod
|
def _match_filters(self, parameter, filters=None):
|
||||||
def _match_filters(parameter, filters=None):
|
|
||||||
"""Return True if the given parameter matches all the filters"""
|
"""Return True if the given parameter matches all the filters"""
|
||||||
for filter_obj in (filters or []):
|
for filter_obj in (filters or []):
|
||||||
key = filter_obj['Key']
|
key = filter_obj['Key']
|
||||||
option = filter_obj.get('Option', 'Equals')
|
|
||||||
values = filter_obj.get('Values', [])
|
values = filter_obj.get('Values', [])
|
||||||
|
|
||||||
what = None
|
if key == 'Path':
|
||||||
if key == 'Type':
|
option = filter_obj.get('Option', 'OneLevel')
|
||||||
what = parameter.type
|
else:
|
||||||
elif key == 'KeyId':
|
option = filter_obj.get('Option', 'Equals')
|
||||||
what = parameter.keyid
|
|
||||||
|
|
||||||
if option == 'Equals'\
|
what = None
|
||||||
and not any(what == value for value in values):
|
if key == 'KeyId':
|
||||||
|
what = parameter.keyid
|
||||||
|
elif key == 'Name':
|
||||||
|
what = '/' + parameter.name.lstrip('/')
|
||||||
|
values = ['/' + value.lstrip('/') for value in values]
|
||||||
|
elif key == 'Path':
|
||||||
|
what = '/' + parameter.name.lstrip('/')
|
||||||
|
values = ['/' + value.strip('/') for value in values]
|
||||||
|
elif key == 'Type':
|
||||||
|
what = parameter.type
|
||||||
|
|
||||||
|
if what is None:
|
||||||
return False
|
return False
|
||||||
elif option == 'BeginsWith'\
|
elif (option == 'BeginsWith' and
|
||||||
and not any(what.startswith(value) for value in values):
|
not any(what.startswith(value) for value in values)):
|
||||||
|
return False
|
||||||
|
elif (option == 'Equals' and
|
||||||
|
not any(what == value for value in values)):
|
||||||
|
return False
|
||||||
|
elif option == 'OneLevel':
|
||||||
|
if any(value == '/' and len(what.split('/')) == 2 for value in values):
|
||||||
|
continue
|
||||||
|
elif any(value != '/' and
|
||||||
|
what.startswith(value + '/') and
|
||||||
|
len(what.split('/')) - 1 == len(value.split('/')) for value in values):
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
elif option == 'Recursive':
|
||||||
|
if any(value == '/' for value in values):
|
||||||
|
continue
|
||||||
|
elif any(what.startswith(value + '/') for value in values):
|
||||||
|
continue
|
||||||
|
else:
|
||||||
return False
|
return False
|
||||||
# True if no false match (or no filters at all)
|
# True if no false match (or no filters at all)
|
||||||
return True
|
return True
|
||||||
|
@ -104,6 +104,7 @@ class SimpleSystemManagerResponse(BaseResponse):
|
|||||||
def describe_parameters(self):
|
def describe_parameters(self):
|
||||||
page_size = 10
|
page_size = 10
|
||||||
filters = self._get_param('Filters')
|
filters = self._get_param('Filters')
|
||||||
|
parameter_filters = self._get_param('ParameterFilters')
|
||||||
token = self._get_param('NextToken')
|
token = self._get_param('NextToken')
|
||||||
if hasattr(token, 'strip'):
|
if hasattr(token, 'strip'):
|
||||||
token = token.strip()
|
token = token.strip()
|
||||||
@ -111,42 +112,17 @@ class SimpleSystemManagerResponse(BaseResponse):
|
|||||||
token = '0'
|
token = '0'
|
||||||
token = int(token)
|
token = int(token)
|
||||||
|
|
||||||
result = self.ssm_backend.get_all_parameters()
|
result = self.ssm_backend.describe_parameters(
|
||||||
|
filters, parameter_filters
|
||||||
|
)
|
||||||
|
|
||||||
response = {
|
response = {
|
||||||
'Parameters': [],
|
'Parameters': [],
|
||||||
}
|
}
|
||||||
|
|
||||||
end = token + page_size
|
end = token + page_size
|
||||||
for parameter in result[token:]:
|
for parameter in result[token:]:
|
||||||
param_data = parameter.describe_response_object(False)
|
response['Parameters'].append(parameter.describe_response_object(False))
|
||||||
add = False
|
|
||||||
|
|
||||||
if filters:
|
|
||||||
for filter in filters:
|
|
||||||
if filter['Key'] == 'Name':
|
|
||||||
k = param_data['Name']
|
|
||||||
for v in filter['Values']:
|
|
||||||
if k.startswith(v):
|
|
||||||
add = True
|
|
||||||
break
|
|
||||||
elif filter['Key'] == 'Type':
|
|
||||||
k = param_data['Type']
|
|
||||||
for v in filter['Values']:
|
|
||||||
if k == v:
|
|
||||||
add = True
|
|
||||||
break
|
|
||||||
elif filter['Key'] == 'KeyId':
|
|
||||||
k = param_data.get('KeyId')
|
|
||||||
if k:
|
|
||||||
for v in filter['Values']:
|
|
||||||
if k == v:
|
|
||||||
add = True
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
add = True
|
|
||||||
|
|
||||||
if add:
|
|
||||||
response['Parameters'].append(param_data)
|
|
||||||
|
|
||||||
token = token + 1
|
token = token + 1
|
||||||
if len(response['Parameters']) == page_size:
|
if len(response['Parameters']) == page_size:
|
||||||
|
@ -1029,20 +1029,27 @@ def test_usage_plans():
|
|||||||
usage_plan['name'].should.equal(usage_plan_name)
|
usage_plan['name'].should.equal(usage_plan_name)
|
||||||
usage_plan['apiStages'].should.equal([])
|
usage_plan['apiStages'].should.equal([])
|
||||||
|
|
||||||
usage_plan_name = 'TEST-PLAN-2'
|
payload = {
|
||||||
usage_plan_description = 'Description'
|
'name': 'TEST-PLAN-2',
|
||||||
usage_plan_quota = {'limit': 10, 'period': 'DAY', 'offset': 0}
|
'description': 'Description',
|
||||||
usage_plan_throttle = {'rateLimit': 2, 'burstLimit': 1}
|
'quota': {'limit': 10, 'period': 'DAY', 'offset': 0},
|
||||||
usage_plan_api_stages = [{'apiId': 'foo', 'stage': 'bar'}]
|
'throttle': {'rateLimit': 2, 'burstLimit': 1},
|
||||||
payload = {'name': usage_plan_name, 'description': usage_plan_description, 'quota': usage_plan_quota, 'throttle': usage_plan_throttle, 'apiStages': usage_plan_api_stages}
|
'apiStages': [{'apiId': 'foo', 'stage': 'bar'}],
|
||||||
|
'tags': {'tag_key': 'tag_value'},
|
||||||
|
}
|
||||||
response = client.create_usage_plan(**payload)
|
response = client.create_usage_plan(**payload)
|
||||||
usage_plan_id = response['id']
|
usage_plan_id = response['id']
|
||||||
usage_plan = client.get_usage_plan(usagePlanId=usage_plan_id)
|
usage_plan = client.get_usage_plan(usagePlanId=usage_plan_id)
|
||||||
usage_plan['name'].should.equal(usage_plan_name)
|
|
||||||
usage_plan['description'].should.equal(usage_plan_description)
|
# The payload should remain unchanged
|
||||||
usage_plan['apiStages'].should.equal(usage_plan_api_stages)
|
for key, value in payload.items():
|
||||||
usage_plan['throttle'].should.equal(usage_plan_throttle)
|
usage_plan.should.have.key(key).which.should.equal(value)
|
||||||
usage_plan['quota'].should.equal(usage_plan_quota)
|
|
||||||
|
# Status code should be 200
|
||||||
|
usage_plan['ResponseMetadata'].should.have.key('HTTPStatusCode').which.should.equal(200)
|
||||||
|
|
||||||
|
# An Id should've been generated
|
||||||
|
usage_plan.should.have.key('id').which.should_not.be.none
|
||||||
|
|
||||||
response = client.get_usage_plans()
|
response = client.get_usage_plans()
|
||||||
len(response['items']).should.equal(2)
|
len(response['items']).should.equal(2)
|
||||||
|
@ -1995,6 +1995,23 @@ def test_condition_expressions():
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
with assert_raises(client.exceptions.ConditionalCheckFailedException):
|
||||||
|
client.delete_item(
|
||||||
|
TableName = 'test1',
|
||||||
|
Key = {
|
||||||
|
'client': {'S': 'client1'},
|
||||||
|
'app': {'S': 'app1'},
|
||||||
|
},
|
||||||
|
ConditionExpression = 'attribute_not_exists(#existing)',
|
||||||
|
ExpressionAttributeValues = {
|
||||||
|
':match': {'S': 'match'}
|
||||||
|
},
|
||||||
|
ExpressionAttributeNames = {
|
||||||
|
'#existing': 'existing',
|
||||||
|
'#match': 'match',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@mock_dynamodb2
|
@mock_dynamodb2
|
||||||
def test_condition_expression__attr_doesnt_exist():
|
def test_condition_expression__attr_doesnt_exist():
|
||||||
|
@ -450,6 +450,70 @@ def test_update_item_remove():
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@mock_dynamodb2_deprecated
|
||||||
|
def test_update_item_nested_remove():
|
||||||
|
conn = boto.dynamodb2.connect_to_region("us-east-1")
|
||||||
|
table = Table.create('messages', schema=[
|
||||||
|
HashKey('username')
|
||||||
|
])
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'username': "steve",
|
||||||
|
'Meta': {
|
||||||
|
'FullName': 'Steve Urkel'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
table.put_item(data=data)
|
||||||
|
key_map = {
|
||||||
|
'username': {"S": "steve"}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Then remove the Meta.FullName field
|
||||||
|
conn.update_item("messages", key_map,
|
||||||
|
update_expression="REMOVE Meta.FullName")
|
||||||
|
|
||||||
|
returned_item = table.get_item(username="steve")
|
||||||
|
dict(returned_item).should.equal({
|
||||||
|
'username': "steve",
|
||||||
|
'Meta': {}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@mock_dynamodb2_deprecated
|
||||||
|
def test_update_item_double_nested_remove():
|
||||||
|
conn = boto.dynamodb2.connect_to_region("us-east-1")
|
||||||
|
table = Table.create('messages', schema=[
|
||||||
|
HashKey('username')
|
||||||
|
])
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'username': "steve",
|
||||||
|
'Meta': {
|
||||||
|
'Name': {
|
||||||
|
'First': 'Steve',
|
||||||
|
'Last': 'Urkel'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
table.put_item(data=data)
|
||||||
|
key_map = {
|
||||||
|
'username': {"S": "steve"}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Then remove the Meta.FullName field
|
||||||
|
conn.update_item("messages", key_map,
|
||||||
|
update_expression="REMOVE Meta.Name.First")
|
||||||
|
|
||||||
|
returned_item = table.get_item(username="steve")
|
||||||
|
dict(returned_item).should.equal({
|
||||||
|
'username': "steve",
|
||||||
|
'Meta': {
|
||||||
|
'Name': {
|
||||||
|
'Last': 'Urkel'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
@mock_dynamodb2_deprecated
|
@mock_dynamodb2_deprecated
|
||||||
def test_update_item_set():
|
def test_update_item_set():
|
||||||
conn = boto.dynamodb2.connect_to_region("us-east-1")
|
conn = boto.dynamodb2.connect_to_region("us-east-1")
|
||||||
|
@ -59,15 +59,43 @@ def test_lifecycle_with_filters():
|
|||||||
with assert_raises(KeyError):
|
with assert_raises(KeyError):
|
||||||
assert result["Rules"][0]["Prefix"]
|
assert result["Rules"][0]["Prefix"]
|
||||||
|
|
||||||
# With a tag:
|
# Without any prefixes and an empty filter (this is by default a prefix for the whole bucket):
|
||||||
lfc["Rules"][0]["Filter"]["Tag"] = {
|
lfc = {
|
||||||
"Key": "mytag",
|
"Rules": [
|
||||||
"Value": "mytagvalue"
|
{
|
||||||
|
"Expiration": {
|
||||||
|
"Days": 7
|
||||||
|
},
|
||||||
|
"ID": "wholebucket",
|
||||||
|
"Filter": {},
|
||||||
|
"Status": "Enabled"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
client.put_bucket_lifecycle_configuration(Bucket="bucket", LifecycleConfiguration=lfc)
|
client.put_bucket_lifecycle_configuration(Bucket="bucket", LifecycleConfiguration=lfc)
|
||||||
result = client.get_bucket_lifecycle_configuration(Bucket="bucket")
|
result = client.get_bucket_lifecycle_configuration(Bucket="bucket")
|
||||||
assert len(result["Rules"]) == 1
|
assert len(result["Rules"]) == 1
|
||||||
assert result["Rules"][0]["Filter"]["Prefix"] == ''
|
with assert_raises(KeyError):
|
||||||
|
assert result["Rules"][0]["Prefix"]
|
||||||
|
|
||||||
|
# If we remove the filter -- and don't specify a Prefix, then this is bad:
|
||||||
|
lfc['Rules'][0].pop('Filter')
|
||||||
|
with assert_raises(ClientError) as err:
|
||||||
|
client.put_bucket_lifecycle_configuration(Bucket="bucket", LifecycleConfiguration=lfc)
|
||||||
|
assert err.exception.response["Error"]["Code"] == "MalformedXML"
|
||||||
|
|
||||||
|
# With a tag:
|
||||||
|
lfc["Rules"][0]["Filter"] = {
|
||||||
|
'Tag': {
|
||||||
|
"Key": "mytag",
|
||||||
|
"Value": "mytagvalue"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
client.put_bucket_lifecycle_configuration(Bucket="bucket", LifecycleConfiguration=lfc)
|
||||||
|
result = client.get_bucket_lifecycle_configuration(Bucket="bucket")
|
||||||
|
assert len(result["Rules"]) == 1
|
||||||
|
with assert_raises(KeyError):
|
||||||
|
assert result["Rules"][0]["Filter"]['Prefix']
|
||||||
assert not result["Rules"][0]["Filter"].get("And")
|
assert not result["Rules"][0]["Filter"].get("And")
|
||||||
assert result["Rules"][0]["Filter"]["Tag"]["Key"] == "mytag"
|
assert result["Rules"][0]["Filter"]["Tag"]["Key"] == "mytag"
|
||||||
assert result["Rules"][0]["Filter"]["Tag"]["Value"] == "mytagvalue"
|
assert result["Rules"][0]["Filter"]["Tag"]["Value"] == "mytagvalue"
|
||||||
@ -75,7 +103,8 @@ def test_lifecycle_with_filters():
|
|||||||
assert result["Rules"][0]["Prefix"]
|
assert result["Rules"][0]["Prefix"]
|
||||||
|
|
||||||
# With And (single tag):
|
# With And (single tag):
|
||||||
lfc["Rules"][0]["Filter"]["And"] = {
|
lfc["Rules"][0]["Filter"] = {
|
||||||
|
"And": {
|
||||||
"Prefix": "some/prefix",
|
"Prefix": "some/prefix",
|
||||||
"Tags": [
|
"Tags": [
|
||||||
{
|
{
|
||||||
@ -84,16 +113,15 @@ def test_lifecycle_with_filters():
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
client.put_bucket_lifecycle_configuration(Bucket="bucket", LifecycleConfiguration=lfc)
|
client.put_bucket_lifecycle_configuration(Bucket="bucket", LifecycleConfiguration=lfc)
|
||||||
result = client.get_bucket_lifecycle_configuration(Bucket="bucket")
|
result = client.get_bucket_lifecycle_configuration(Bucket="bucket")
|
||||||
assert len(result["Rules"]) == 1
|
assert len(result["Rules"]) == 1
|
||||||
assert result["Rules"][0]["Filter"]["Prefix"] == ""
|
assert not result["Rules"][0]["Filter"].get("Prefix")
|
||||||
assert result["Rules"][0]["Filter"]["And"]["Prefix"] == "some/prefix"
|
assert result["Rules"][0]["Filter"]["And"]["Prefix"] == "some/prefix"
|
||||||
assert len(result["Rules"][0]["Filter"]["And"]["Tags"]) == 1
|
assert len(result["Rules"][0]["Filter"]["And"]["Tags"]) == 1
|
||||||
assert result["Rules"][0]["Filter"]["And"]["Tags"][0]["Key"] == "mytag"
|
assert result["Rules"][0]["Filter"]["And"]["Tags"][0]["Key"] == "mytag"
|
||||||
assert result["Rules"][0]["Filter"]["And"]["Tags"][0]["Value"] == "mytagvalue"
|
assert result["Rules"][0]["Filter"]["And"]["Tags"][0]["Value"] == "mytagvalue"
|
||||||
assert result["Rules"][0]["Filter"]["Tag"]["Key"] == "mytag"
|
|
||||||
assert result["Rules"][0]["Filter"]["Tag"]["Value"] == "mytagvalue"
|
|
||||||
with assert_raises(KeyError):
|
with assert_raises(KeyError):
|
||||||
assert result["Rules"][0]["Prefix"]
|
assert result["Rules"][0]["Prefix"]
|
||||||
|
|
||||||
@ -114,17 +142,39 @@ def test_lifecycle_with_filters():
|
|||||||
client.put_bucket_lifecycle_configuration(Bucket="bucket", LifecycleConfiguration=lfc)
|
client.put_bucket_lifecycle_configuration(Bucket="bucket", LifecycleConfiguration=lfc)
|
||||||
result = client.get_bucket_lifecycle_configuration(Bucket="bucket")
|
result = client.get_bucket_lifecycle_configuration(Bucket="bucket")
|
||||||
assert len(result["Rules"]) == 1
|
assert len(result["Rules"]) == 1
|
||||||
assert result["Rules"][0]["Filter"]["Prefix"] == ""
|
assert not result["Rules"][0]["Filter"].get("Prefix")
|
||||||
assert result["Rules"][0]["Filter"]["And"]["Prefix"] == "some/prefix"
|
assert result["Rules"][0]["Filter"]["And"]["Prefix"] == "some/prefix"
|
||||||
assert len(result["Rules"][0]["Filter"]["And"]["Tags"]) == 2
|
assert len(result["Rules"][0]["Filter"]["And"]["Tags"]) == 2
|
||||||
assert result["Rules"][0]["Filter"]["And"]["Tags"][0]["Key"] == "mytag"
|
assert result["Rules"][0]["Filter"]["And"]["Tags"][0]["Key"] == "mytag"
|
||||||
assert result["Rules"][0]["Filter"]["And"]["Tags"][0]["Value"] == "mytagvalue"
|
assert result["Rules"][0]["Filter"]["And"]["Tags"][0]["Value"] == "mytagvalue"
|
||||||
assert result["Rules"][0]["Filter"]["Tag"]["Key"] == "mytag"
|
|
||||||
assert result["Rules"][0]["Filter"]["Tag"]["Value"] == "mytagvalue"
|
|
||||||
assert result["Rules"][0]["Filter"]["And"]["Tags"][1]["Key"] == "mytag2"
|
assert result["Rules"][0]["Filter"]["And"]["Tags"][1]["Key"] == "mytag2"
|
||||||
assert result["Rules"][0]["Filter"]["And"]["Tags"][1]["Value"] == "mytagvalue2"
|
assert result["Rules"][0]["Filter"]["And"]["Tags"][1]["Value"] == "mytagvalue2"
|
||||||
assert result["Rules"][0]["Filter"]["Tag"]["Key"] == "mytag"
|
with assert_raises(KeyError):
|
||||||
assert result["Rules"][0]["Filter"]["Tag"]["Value"] == "mytagvalue"
|
assert result["Rules"][0]["Prefix"]
|
||||||
|
|
||||||
|
# And filter without Prefix but multiple Tags:
|
||||||
|
lfc["Rules"][0]["Filter"]["And"] = {
|
||||||
|
"Tags": [
|
||||||
|
{
|
||||||
|
"Key": "mytag",
|
||||||
|
"Value": "mytagvalue"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Key": "mytag2",
|
||||||
|
"Value": "mytagvalue2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
client.put_bucket_lifecycle_configuration(Bucket="bucket", LifecycleConfiguration=lfc)
|
||||||
|
result = client.get_bucket_lifecycle_configuration(Bucket="bucket")
|
||||||
|
assert len(result["Rules"]) == 1
|
||||||
|
with assert_raises(KeyError):
|
||||||
|
assert result["Rules"][0]["Filter"]["And"]["Prefix"]
|
||||||
|
assert len(result["Rules"][0]["Filter"]["And"]["Tags"]) == 2
|
||||||
|
assert result["Rules"][0]["Filter"]["And"]["Tags"][0]["Key"] == "mytag"
|
||||||
|
assert result["Rules"][0]["Filter"]["And"]["Tags"][0]["Value"] == "mytagvalue"
|
||||||
|
assert result["Rules"][0]["Filter"]["And"]["Tags"][1]["Key"] == "mytag2"
|
||||||
|
assert result["Rules"][0]["Filter"]["And"]["Tags"][1]["Value"] == "mytagvalue2"
|
||||||
with assert_raises(KeyError):
|
with assert_raises(KeyError):
|
||||||
assert result["Rules"][0]["Prefix"]
|
assert result["Rules"][0]["Prefix"]
|
||||||
|
|
||||||
@ -146,6 +196,73 @@ def test_lifecycle_with_filters():
|
|||||||
assert not result["Rules"][0].get("Filter")
|
assert not result["Rules"][0].get("Filter")
|
||||||
assert result["Rules"][0]["Prefix"] == "some/path"
|
assert result["Rules"][0]["Prefix"] == "some/path"
|
||||||
|
|
||||||
|
# Can't have Tag, Prefix, and And in a filter:
|
||||||
|
del lfc['Rules'][0]['Prefix']
|
||||||
|
lfc["Rules"][0]["Filter"] = {
|
||||||
|
"Prefix": "some/prefix",
|
||||||
|
"Tag": {
|
||||||
|
"Key": "mytag",
|
||||||
|
"Value": "mytagvalue"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
with assert_raises(ClientError) as err:
|
||||||
|
client.put_bucket_lifecycle_configuration(Bucket="bucket", LifecycleConfiguration=lfc)
|
||||||
|
assert err.exception.response["Error"]["Code"] == "MalformedXML"
|
||||||
|
|
||||||
|
lfc["Rules"][0]["Filter"] = {
|
||||||
|
"Tag": {
|
||||||
|
"Key": "mytag",
|
||||||
|
"Value": "mytagvalue"
|
||||||
|
},
|
||||||
|
"And": {
|
||||||
|
"Prefix": "some/prefix",
|
||||||
|
"Tags": [
|
||||||
|
{
|
||||||
|
"Key": "mytag",
|
||||||
|
"Value": "mytagvalue"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Key": "mytag2",
|
||||||
|
"Value": "mytagvalue2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
with assert_raises(ClientError) as err:
|
||||||
|
client.put_bucket_lifecycle_configuration(Bucket="bucket", LifecycleConfiguration=lfc)
|
||||||
|
assert err.exception.response["Error"]["Code"] == "MalformedXML"
|
||||||
|
|
||||||
|
# Make sure multiple rules work:
|
||||||
|
lfc = {
|
||||||
|
"Rules": [
|
||||||
|
{
|
||||||
|
"Expiration": {
|
||||||
|
"Days": 7
|
||||||
|
},
|
||||||
|
"ID": "wholebucket",
|
||||||
|
"Filter": {
|
||||||
|
"Prefix": ""
|
||||||
|
},
|
||||||
|
"Status": "Enabled"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Expiration": {
|
||||||
|
"Days": 10
|
||||||
|
},
|
||||||
|
"ID": "Tags",
|
||||||
|
"Filter": {
|
||||||
|
"Tag": {'Key': 'somekey', 'Value': 'somevalue'}
|
||||||
|
},
|
||||||
|
"Status": "Enabled"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
client.put_bucket_lifecycle_configuration(Bucket="bucket", LifecycleConfiguration=lfc)
|
||||||
|
result = client.get_bucket_lifecycle_configuration(Bucket="bucket")['Rules']
|
||||||
|
assert len(result) == 2
|
||||||
|
assert result[0]['ID'] == 'wholebucket'
|
||||||
|
assert result[1]['ID'] == 'Tags'
|
||||||
|
|
||||||
|
|
||||||
@mock_s3
|
@mock_s3
|
||||||
def test_lifecycle_with_eodm():
|
def test_lifecycle_with_eodm():
|
||||||
|
@ -7,7 +7,7 @@ import datetime
|
|||||||
import uuid
|
import uuid
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from botocore.exceptions import ClientError
|
from botocore.exceptions import ClientError, ParamValidationError
|
||||||
from nose.tools import assert_raises
|
from nose.tools import assert_raises
|
||||||
|
|
||||||
from moto import mock_ssm, mock_cloudformation
|
from moto import mock_ssm, mock_cloudformation
|
||||||
@ -336,10 +336,11 @@ def test_describe_parameters():
|
|||||||
|
|
||||||
response = client.describe_parameters()
|
response = client.describe_parameters()
|
||||||
|
|
||||||
len(response['Parameters']).should.equal(1)
|
parameters = response['Parameters']
|
||||||
response['Parameters'][0]['Name'].should.equal('test')
|
parameters.should.have.length_of(1)
|
||||||
response['Parameters'][0]['Type'].should.equal('String')
|
parameters[0]['Name'].should.equal('test')
|
||||||
response['Parameters'][0]['AllowedPattern'].should.equal(r'.*')
|
parameters[0]['Type'].should.equal('String')
|
||||||
|
parameters[0]['AllowedPattern'].should.equal(r'.*')
|
||||||
|
|
||||||
|
|
||||||
@mock_ssm
|
@mock_ssm
|
||||||
@ -354,28 +355,28 @@ def test_describe_parameters_paging():
|
|||||||
)
|
)
|
||||||
|
|
||||||
response = client.describe_parameters()
|
response = client.describe_parameters()
|
||||||
len(response['Parameters']).should.equal(10)
|
response['Parameters'].should.have.length_of(10)
|
||||||
response['NextToken'].should.equal('10')
|
response['NextToken'].should.equal('10')
|
||||||
|
|
||||||
response = client.describe_parameters(NextToken=response['NextToken'])
|
response = client.describe_parameters(NextToken=response['NextToken'])
|
||||||
len(response['Parameters']).should.equal(10)
|
response['Parameters'].should.have.length_of(10)
|
||||||
response['NextToken'].should.equal('20')
|
response['NextToken'].should.equal('20')
|
||||||
|
|
||||||
response = client.describe_parameters(NextToken=response['NextToken'])
|
response = client.describe_parameters(NextToken=response['NextToken'])
|
||||||
len(response['Parameters']).should.equal(10)
|
response['Parameters'].should.have.length_of(10)
|
||||||
response['NextToken'].should.equal('30')
|
response['NextToken'].should.equal('30')
|
||||||
|
|
||||||
response = client.describe_parameters(NextToken=response['NextToken'])
|
response = client.describe_parameters(NextToken=response['NextToken'])
|
||||||
len(response['Parameters']).should.equal(10)
|
response['Parameters'].should.have.length_of(10)
|
||||||
response['NextToken'].should.equal('40')
|
response['NextToken'].should.equal('40')
|
||||||
|
|
||||||
response = client.describe_parameters(NextToken=response['NextToken'])
|
response = client.describe_parameters(NextToken=response['NextToken'])
|
||||||
len(response['Parameters']).should.equal(10)
|
response['Parameters'].should.have.length_of(10)
|
||||||
response['NextToken'].should.equal('50')
|
response['NextToken'].should.equal('50')
|
||||||
|
|
||||||
response = client.describe_parameters(NextToken=response['NextToken'])
|
response = client.describe_parameters(NextToken=response['NextToken'])
|
||||||
len(response['Parameters']).should.equal(0)
|
response['Parameters'].should.have.length_of(0)
|
||||||
''.should.equal(response.get('NextToken', ''))
|
response.should_not.have.key('NextToken')
|
||||||
|
|
||||||
|
|
||||||
@mock_ssm
|
@mock_ssm
|
||||||
@ -399,10 +400,12 @@ def test_describe_parameters_filter_names():
|
|||||||
'Values': ['param-22']
|
'Values': ['param-22']
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
len(response['Parameters']).should.equal(1)
|
|
||||||
response['Parameters'][0]['Name'].should.equal('param-22')
|
parameters = response['Parameters']
|
||||||
response['Parameters'][0]['Type'].should.equal('String')
|
parameters.should.have.length_of(1)
|
||||||
''.should.equal(response.get('NextToken', ''))
|
parameters[0]['Name'].should.equal('param-22')
|
||||||
|
parameters[0]['Type'].should.equal('String')
|
||||||
|
response.should_not.have.key('NextToken')
|
||||||
|
|
||||||
|
|
||||||
@mock_ssm
|
@mock_ssm
|
||||||
@ -426,9 +429,11 @@ def test_describe_parameters_filter_type():
|
|||||||
'Values': ['SecureString']
|
'Values': ['SecureString']
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
len(response['Parameters']).should.equal(10)
|
|
||||||
response['Parameters'][0]['Type'].should.equal('SecureString')
|
parameters = response['Parameters']
|
||||||
'10'.should.equal(response.get('NextToken', ''))
|
parameters.should.have.length_of(10)
|
||||||
|
parameters[0]['Type'].should.equal('SecureString')
|
||||||
|
response.should.have.key('NextToken').which.should.equal('10')
|
||||||
|
|
||||||
|
|
||||||
@mock_ssm
|
@mock_ssm
|
||||||
@ -452,10 +457,508 @@ def test_describe_parameters_filter_keyid():
|
|||||||
'Values': ['key:10']
|
'Values': ['key:10']
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
len(response['Parameters']).should.equal(1)
|
|
||||||
response['Parameters'][0]['Name'].should.equal('param-10')
|
parameters = response['Parameters']
|
||||||
response['Parameters'][0]['Type'].should.equal('SecureString')
|
parameters.should.have.length_of(1)
|
||||||
''.should.equal(response.get('NextToken', ''))
|
parameters[0]['Name'].should.equal('param-10')
|
||||||
|
parameters[0]['Type'].should.equal('SecureString')
|
||||||
|
response.should_not.have.key('NextToken')
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ssm
|
||||||
|
def test_describe_parameters_with_parameter_filters_keyid():
|
||||||
|
client = boto3.client('ssm', region_name='us-east-1')
|
||||||
|
client.put_parameter(
|
||||||
|
Name='secure-param',
|
||||||
|
Value='secure-value',
|
||||||
|
Type='SecureString'
|
||||||
|
)
|
||||||
|
client.put_parameter(
|
||||||
|
Name='custom-secure-param',
|
||||||
|
Value='custom-secure-value',
|
||||||
|
Type='SecureString',
|
||||||
|
KeyId='alias/custom'
|
||||||
|
)
|
||||||
|
client.put_parameter(
|
||||||
|
Name = 'param',
|
||||||
|
Value = 'value',
|
||||||
|
Type = 'String'
|
||||||
|
)
|
||||||
|
|
||||||
|
response = client.describe_parameters(
|
||||||
|
ParameterFilters=[{
|
||||||
|
'Key': 'KeyId',
|
||||||
|
'Values': ['alias/aws/ssm']
|
||||||
|
}]
|
||||||
|
)
|
||||||
|
|
||||||
|
parameters = response['Parameters']
|
||||||
|
parameters.should.have.length_of(1)
|
||||||
|
parameters[0]['Name'].should.equal('secure-param')
|
||||||
|
parameters[0]['Type'].should.equal('SecureString')
|
||||||
|
response.should_not.have.key('NextToken')
|
||||||
|
|
||||||
|
response = client.describe_parameters(
|
||||||
|
ParameterFilters = [{
|
||||||
|
'Key': 'KeyId',
|
||||||
|
'Values': ['alias/custom']
|
||||||
|
}]
|
||||||
|
)
|
||||||
|
|
||||||
|
parameters = response['Parameters']
|
||||||
|
parameters.should.have.length_of(1)
|
||||||
|
parameters[0]['Name'].should.equal('custom-secure-param')
|
||||||
|
parameters[0]['Type'].should.equal('SecureString')
|
||||||
|
response.should_not.have.key('NextToken')
|
||||||
|
|
||||||
|
response = client.describe_parameters(
|
||||||
|
ParameterFilters = [{
|
||||||
|
'Key': 'KeyId',
|
||||||
|
'Option': 'BeginsWith',
|
||||||
|
'Values': ['alias']
|
||||||
|
}]
|
||||||
|
)
|
||||||
|
|
||||||
|
parameters = response['Parameters']
|
||||||
|
parameters.should.have.length_of(2)
|
||||||
|
response.should_not.have.key('NextToken')
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ssm
|
||||||
|
def test_describe_parameters_with_parameter_filters_name():
|
||||||
|
client = boto3.client('ssm', region_name='us-east-1')
|
||||||
|
client.put_parameter(
|
||||||
|
Name='param',
|
||||||
|
Value='value',
|
||||||
|
Type='String'
|
||||||
|
)
|
||||||
|
client.put_parameter(
|
||||||
|
Name = '/param-2',
|
||||||
|
Value = 'value-2',
|
||||||
|
Type = 'String'
|
||||||
|
)
|
||||||
|
|
||||||
|
response = client.describe_parameters(
|
||||||
|
ParameterFilters=[{
|
||||||
|
'Key': 'Name',
|
||||||
|
'Values': ['param']
|
||||||
|
}]
|
||||||
|
)
|
||||||
|
|
||||||
|
parameters = response['Parameters']
|
||||||
|
parameters.should.have.length_of(1)
|
||||||
|
parameters[0]['Name'].should.equal('param')
|
||||||
|
parameters[0]['Type'].should.equal('String')
|
||||||
|
response.should_not.have.key('NextToken')
|
||||||
|
|
||||||
|
response = client.describe_parameters(
|
||||||
|
ParameterFilters=[{
|
||||||
|
'Key': 'Name',
|
||||||
|
'Values': ['/param']
|
||||||
|
}]
|
||||||
|
)
|
||||||
|
|
||||||
|
parameters = response['Parameters']
|
||||||
|
parameters.should.have.length_of(1)
|
||||||
|
parameters[0]['Name'].should.equal('param')
|
||||||
|
parameters[0]['Type'].should.equal('String')
|
||||||
|
response.should_not.have.key('NextToken')
|
||||||
|
|
||||||
|
response = client.describe_parameters(
|
||||||
|
ParameterFilters=[{
|
||||||
|
'Key': 'Name',
|
||||||
|
'Values': ['param-2']
|
||||||
|
}]
|
||||||
|
)
|
||||||
|
|
||||||
|
parameters = response['Parameters']
|
||||||
|
parameters.should.have.length_of(1)
|
||||||
|
parameters[0]['Name'].should.equal('/param-2')
|
||||||
|
parameters[0]['Type'].should.equal('String')
|
||||||
|
response.should_not.have.key('NextToken')
|
||||||
|
|
||||||
|
response = client.describe_parameters(
|
||||||
|
ParameterFilters = [{
|
||||||
|
'Key': 'Name',
|
||||||
|
'Option': 'BeginsWith',
|
||||||
|
'Values': ['param']
|
||||||
|
}]
|
||||||
|
)
|
||||||
|
|
||||||
|
parameters = response['Parameters']
|
||||||
|
parameters.should.have.length_of(2)
|
||||||
|
response.should_not.have.key('NextToken')
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ssm
|
||||||
|
def test_describe_parameters_with_parameter_filters_path():
|
||||||
|
client = boto3.client('ssm', region_name='us-east-1')
|
||||||
|
client.put_parameter(
|
||||||
|
Name='/foo/name1',
|
||||||
|
Value='value1',
|
||||||
|
Type='String')
|
||||||
|
|
||||||
|
client.put_parameter(
|
||||||
|
Name='/foo/name2',
|
||||||
|
Value='value2',
|
||||||
|
Type='String')
|
||||||
|
|
||||||
|
client.put_parameter(
|
||||||
|
Name='/bar/name3',
|
||||||
|
Value='value3',
|
||||||
|
Type='String')
|
||||||
|
|
||||||
|
client.put_parameter(
|
||||||
|
Name='/bar/name3/name4',
|
||||||
|
Value='value4',
|
||||||
|
Type='String')
|
||||||
|
|
||||||
|
client.put_parameter(
|
||||||
|
Name='foo',
|
||||||
|
Value='bar',
|
||||||
|
Type='String')
|
||||||
|
|
||||||
|
response = client.describe_parameters(
|
||||||
|
ParameterFilters = [{
|
||||||
|
'Key': 'Path',
|
||||||
|
'Values': ['/fo']
|
||||||
|
}]
|
||||||
|
)
|
||||||
|
|
||||||
|
parameters = response['Parameters']
|
||||||
|
parameters.should.have.length_of(0)
|
||||||
|
response.should_not.have.key('NextToken')
|
||||||
|
|
||||||
|
response = client.describe_parameters(
|
||||||
|
ParameterFilters = [{
|
||||||
|
'Key': 'Path',
|
||||||
|
'Values': ['/']
|
||||||
|
}]
|
||||||
|
)
|
||||||
|
|
||||||
|
parameters = response['Parameters']
|
||||||
|
parameters.should.have.length_of(1)
|
||||||
|
parameters[0]['Name'].should.equal('foo')
|
||||||
|
parameters[0]['Type'].should.equal('String')
|
||||||
|
response.should_not.have.key('NextToken')
|
||||||
|
|
||||||
|
response = client.describe_parameters(
|
||||||
|
ParameterFilters = [{
|
||||||
|
'Key': 'Path',
|
||||||
|
'Values': ['/', '/foo']
|
||||||
|
}]
|
||||||
|
)
|
||||||
|
|
||||||
|
parameters = response['Parameters']
|
||||||
|
parameters.should.have.length_of(3)
|
||||||
|
{parameter['Name'] for parameter in response['Parameters']}.should.equal(
|
||||||
|
{'/foo/name1', '/foo/name2', 'foo'}
|
||||||
|
)
|
||||||
|
response.should_not.have.key('NextToken')
|
||||||
|
|
||||||
|
response = client.describe_parameters(
|
||||||
|
ParameterFilters = [{
|
||||||
|
'Key': 'Path',
|
||||||
|
'Values': ['/foo/']
|
||||||
|
}]
|
||||||
|
)
|
||||||
|
|
||||||
|
parameters = response['Parameters']
|
||||||
|
parameters.should.have.length_of(2)
|
||||||
|
{parameter['Name'] for parameter in response['Parameters']}.should.equal(
|
||||||
|
{'/foo/name1', '/foo/name2'}
|
||||||
|
)
|
||||||
|
response.should_not.have.key('NextToken')
|
||||||
|
|
||||||
|
response = client.describe_parameters(
|
||||||
|
ParameterFilters = [{
|
||||||
|
'Key': 'Path',
|
||||||
|
'Option': 'OneLevel',
|
||||||
|
'Values': ['/bar/name3']
|
||||||
|
}]
|
||||||
|
)
|
||||||
|
|
||||||
|
parameters = response['Parameters']
|
||||||
|
parameters.should.have.length_of(1)
|
||||||
|
parameters[0]['Name'].should.equal('/bar/name3/name4')
|
||||||
|
parameters[0]['Type'].should.equal('String')
|
||||||
|
response.should_not.have.key('NextToken')
|
||||||
|
|
||||||
|
|
||||||
|
response = client.describe_parameters(
|
||||||
|
ParameterFilters = [{
|
||||||
|
'Key': 'Path',
|
||||||
|
'Option': 'Recursive',
|
||||||
|
'Values': ['/fo']
|
||||||
|
}]
|
||||||
|
)
|
||||||
|
|
||||||
|
parameters = response['Parameters']
|
||||||
|
parameters.should.have.length_of(0)
|
||||||
|
response.should_not.have.key('NextToken')
|
||||||
|
|
||||||
|
response = client.describe_parameters(
|
||||||
|
ParameterFilters = [{
|
||||||
|
'Key': 'Path',
|
||||||
|
'Option': 'Recursive',
|
||||||
|
'Values': ['/']
|
||||||
|
}]
|
||||||
|
)
|
||||||
|
|
||||||
|
parameters = response['Parameters']
|
||||||
|
parameters.should.have.length_of(5)
|
||||||
|
response.should_not.have.key('NextToken')
|
||||||
|
|
||||||
|
response = client.describe_parameters(
|
||||||
|
ParameterFilters = [{
|
||||||
|
'Key': 'Path',
|
||||||
|
'Option': 'Recursive',
|
||||||
|
'Values': ['/foo', '/bar']
|
||||||
|
}]
|
||||||
|
)
|
||||||
|
|
||||||
|
parameters = response['Parameters']
|
||||||
|
parameters.should.have.length_of(4)
|
||||||
|
{parameter['Name'] for parameter in response['Parameters']}.should.equal(
|
||||||
|
{'/foo/name1', '/foo/name2', '/bar/name3', '/bar/name3/name4'}
|
||||||
|
)
|
||||||
|
response.should_not.have.key('NextToken')
|
||||||
|
|
||||||
|
response = client.describe_parameters(
|
||||||
|
ParameterFilters = [{
|
||||||
|
'Key': 'Path',
|
||||||
|
'Option': 'Recursive',
|
||||||
|
'Values': ['/foo/']
|
||||||
|
}]
|
||||||
|
)
|
||||||
|
|
||||||
|
parameters = response['Parameters']
|
||||||
|
parameters.should.have.length_of(2)
|
||||||
|
{parameter['Name'] for parameter in response['Parameters']}.should.equal(
|
||||||
|
{'/foo/name1', '/foo/name2'}
|
||||||
|
)
|
||||||
|
response.should_not.have.key('NextToken')
|
||||||
|
|
||||||
|
response = client.describe_parameters(
|
||||||
|
ParameterFilters = [{
|
||||||
|
'Key': 'Path',
|
||||||
|
'Option': 'Recursive',
|
||||||
|
'Values': ['/bar/name3']
|
||||||
|
}]
|
||||||
|
)
|
||||||
|
|
||||||
|
parameters = response['Parameters']
|
||||||
|
parameters.should.have.length_of(1)
|
||||||
|
parameters[0]['Name'].should.equal('/bar/name3/name4')
|
||||||
|
parameters[0]['Type'].should.equal('String')
|
||||||
|
response.should_not.have.key('NextToken')
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ssm
|
||||||
|
def test_describe_parameters_invalid_parameter_filters():
|
||||||
|
client = boto3.client('ssm', region_name='us-east-1')
|
||||||
|
|
||||||
|
client.describe_parameters.when.called_with(
|
||||||
|
Filters=[{
|
||||||
|
'Key': 'Name',
|
||||||
|
'Values': ['test']
|
||||||
|
}],
|
||||||
|
ParameterFilters=[{
|
||||||
|
'Key': 'Name',
|
||||||
|
'Values': ['test']
|
||||||
|
}]
|
||||||
|
).should.throw(
|
||||||
|
ClientError,
|
||||||
|
'You can use either Filters or ParameterFilters in a single request.'
|
||||||
|
)
|
||||||
|
|
||||||
|
client.describe_parameters.when.called_with(
|
||||||
|
ParameterFilters=[{}]
|
||||||
|
).should.throw(
|
||||||
|
ParamValidationError,
|
||||||
|
'Parameter validation failed:\nMissing required parameter in ParameterFilters[0]: "Key"'
|
||||||
|
)
|
||||||
|
|
||||||
|
client.describe_parameters.when.called_with(
|
||||||
|
ParameterFilters=[{
|
||||||
|
'Key': 'key',
|
||||||
|
}]
|
||||||
|
).should.throw(
|
||||||
|
ClientError,
|
||||||
|
'1 validation error detected: Value "key" at "parameterFilters.1.member.key" failed to satisfy constraint: '
|
||||||
|
'Member must satisfy regular expression pattern: tag:.+|Name|Type|KeyId|Path|Label|Tier'
|
||||||
|
)
|
||||||
|
|
||||||
|
long_key = 'tag:' + 't' * 129
|
||||||
|
client.describe_parameters.when.called_with(
|
||||||
|
ParameterFilters=[{
|
||||||
|
'Key': long_key,
|
||||||
|
}]
|
||||||
|
).should.throw(
|
||||||
|
ClientError,
|
||||||
|
'1 validation error detected: Value "{value}" at "parameterFilters.1.member.key" failed to satisfy constraint: '
|
||||||
|
'Member must have length less than or equal to 132'.format(value=long_key)
|
||||||
|
)
|
||||||
|
|
||||||
|
client.describe_parameters.when.called_with(
|
||||||
|
ParameterFilters=[{
|
||||||
|
'Key': 'Name',
|
||||||
|
'Option': 'over 10 chars'
|
||||||
|
}]
|
||||||
|
).should.throw(
|
||||||
|
ClientError,
|
||||||
|
'1 validation error detected: Value "over 10 chars" at "parameterFilters.1.member.option" failed to satisfy constraint: '
|
||||||
|
'Member must have length less than or equal to 10'
|
||||||
|
)
|
||||||
|
|
||||||
|
many_values = ['test'] * 51
|
||||||
|
client.describe_parameters.when.called_with(
|
||||||
|
ParameterFilters=[{
|
||||||
|
'Key': 'Name',
|
||||||
|
'Values': many_values
|
||||||
|
}]
|
||||||
|
).should.throw(
|
||||||
|
ClientError,
|
||||||
|
'1 validation error detected: Value "{value}" at "parameterFilters.1.member.values" failed to satisfy constraint: '
|
||||||
|
'Member must have length less than or equal to 50'.format(value=many_values)
|
||||||
|
)
|
||||||
|
|
||||||
|
long_value = ['t' * 1025]
|
||||||
|
client.describe_parameters.when.called_with(
|
||||||
|
ParameterFilters=[{
|
||||||
|
'Key': 'Name',
|
||||||
|
'Values': long_value
|
||||||
|
}]
|
||||||
|
).should.throw(
|
||||||
|
ClientError,
|
||||||
|
'1 validation error detected: Value "{value}" at "parameterFilters.1.member.values" failed to satisfy constraint: '
|
||||||
|
'[Member must have length less than or equal to 1024, Member must have length greater than or equal to 1]'.format(value=long_value)
|
||||||
|
)
|
||||||
|
|
||||||
|
client.describe_parameters.when.called_with(
|
||||||
|
ParameterFilters=[{
|
||||||
|
'Key': 'Name',
|
||||||
|
'Option': 'over 10 chars'
|
||||||
|
},{
|
||||||
|
'Key': 'key',
|
||||||
|
}]
|
||||||
|
).should.throw(
|
||||||
|
ClientError,
|
||||||
|
'2 validation errors detected: '
|
||||||
|
'Value "over 10 chars" at "parameterFilters.1.member.option" failed to satisfy constraint: '
|
||||||
|
'Member must have length less than or equal to 10; '
|
||||||
|
'Value "key" at "parameterFilters.2.member.key" failed to satisfy constraint: '
|
||||||
|
'Member must satisfy regular expression pattern: tag:.+|Name|Type|KeyId|Path|Label|Tier'
|
||||||
|
)
|
||||||
|
|
||||||
|
client.describe_parameters.when.called_with(
|
||||||
|
ParameterFilters=[{
|
||||||
|
'Key': 'Label',
|
||||||
|
}]
|
||||||
|
).should.throw(
|
||||||
|
ClientError,
|
||||||
|
'The following filter key is not valid: Label. Valid filter keys include: [Path, Name, Type, KeyId, Tier].'
|
||||||
|
)
|
||||||
|
|
||||||
|
client.describe_parameters.when.called_with(
|
||||||
|
ParameterFilters=[{
|
||||||
|
'Key': 'Name',
|
||||||
|
}]
|
||||||
|
).should.throw(
|
||||||
|
ClientError,
|
||||||
|
'The following filter values are missing : null for filter key Name.'
|
||||||
|
)
|
||||||
|
|
||||||
|
client.describe_parameters.when.called_with(
|
||||||
|
ParameterFilters=[{
|
||||||
|
'Key': 'Name',
|
||||||
|
'Values': []
|
||||||
|
}]
|
||||||
|
).should.throw(
|
||||||
|
ParamValidationError,
|
||||||
|
'Invalid length for parameter ParameterFilters[0].Values, value: 0, valid range: 1-inf'
|
||||||
|
)
|
||||||
|
|
||||||
|
client.describe_parameters.when.called_with(
|
||||||
|
ParameterFilters=[{
|
||||||
|
'Key': 'Name',
|
||||||
|
'Values': ['test']
|
||||||
|
},{
|
||||||
|
'Key': 'Name',
|
||||||
|
'Values': ['test test']
|
||||||
|
}]
|
||||||
|
).should.throw(
|
||||||
|
ClientError,
|
||||||
|
'The following filter is duplicated in the request: Name. A request can contain only one occurrence of a specific filter.'
|
||||||
|
)
|
||||||
|
|
||||||
|
for value in ['/###', '//', 'test']:
|
||||||
|
client.describe_parameters.when.called_with(
|
||||||
|
ParameterFilters=[{
|
||||||
|
'Key': 'Path',
|
||||||
|
'Values': [value]
|
||||||
|
}]
|
||||||
|
).should.throw(
|
||||||
|
ClientError,
|
||||||
|
'The parameter doesn\'t meet the parameter name requirements. The parameter name must begin with a forward slash "/". '
|
||||||
|
'It can\'t be prefixed with \"aws\" or \"ssm\" (case-insensitive). '
|
||||||
|
'It must use only letters, numbers, or the following symbols: . (period), - (hyphen), _ (underscore). '
|
||||||
|
'Special characters are not allowed. All sub-paths, if specified, must use the forward slash symbol "/". '
|
||||||
|
'Valid example: /get/parameters2-/by1./path0_.'
|
||||||
|
)
|
||||||
|
|
||||||
|
client.describe_parameters.when.called_with(
|
||||||
|
ParameterFilters=[{
|
||||||
|
'Key': 'Path',
|
||||||
|
'Values': ['/aws', '/ssm']
|
||||||
|
}]
|
||||||
|
).should.throw(
|
||||||
|
ClientError,
|
||||||
|
'Filters for common parameters can\'t be prefixed with "aws" or "ssm" (case-insensitive). '
|
||||||
|
'When using global parameters, please specify within a global namespace.'
|
||||||
|
)
|
||||||
|
|
||||||
|
client.describe_parameters.when.called_with(
|
||||||
|
ParameterFilters=[{
|
||||||
|
'Key': 'Path',
|
||||||
|
'Option': 'Equals',
|
||||||
|
'Values': ['test']
|
||||||
|
}]
|
||||||
|
).should.throw(
|
||||||
|
ClientError,
|
||||||
|
'The following filter option is not valid: Equals. Valid options include: [Recursive, OneLevel].'
|
||||||
|
)
|
||||||
|
|
||||||
|
client.describe_parameters.when.called_with(
|
||||||
|
ParameterFilters=[{
|
||||||
|
'Key': 'Tier',
|
||||||
|
'Values': ['test']
|
||||||
|
}]
|
||||||
|
).should.throw(
|
||||||
|
ClientError,
|
||||||
|
'The following filter value is not valid: test. Valid values include: [Standard, Advanced, Intelligent-Tiering]'
|
||||||
|
)
|
||||||
|
|
||||||
|
client.describe_parameters.when.called_with(
|
||||||
|
ParameterFilters=[{
|
||||||
|
'Key': 'Type',
|
||||||
|
'Values': ['test']
|
||||||
|
}]
|
||||||
|
).should.throw(
|
||||||
|
ClientError,
|
||||||
|
'The following filter value is not valid: test. Valid values include: [String, StringList, SecureString]'
|
||||||
|
)
|
||||||
|
|
||||||
|
client.describe_parameters.when.called_with(
|
||||||
|
ParameterFilters=[{
|
||||||
|
'Key': 'Name',
|
||||||
|
'Option': 'option',
|
||||||
|
'Values': ['test']
|
||||||
|
}]
|
||||||
|
).should.throw(
|
||||||
|
ClientError,
|
||||||
|
'The following filter option is not valid: option. Valid options include: [BeginsWith, Equals].'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@mock_ssm
|
@mock_ssm
|
||||||
@ -476,15 +979,17 @@ def test_describe_parameters_attributes():
|
|||||||
)
|
)
|
||||||
|
|
||||||
response = client.describe_parameters()
|
response = client.describe_parameters()
|
||||||
len(response['Parameters']).should.equal(2)
|
|
||||||
|
|
||||||
response['Parameters'][0]['Description'].should.equal('my description')
|
parameters = response['Parameters']
|
||||||
response['Parameters'][0]['Version'].should.equal(1)
|
parameters.should.have.length_of(2)
|
||||||
response['Parameters'][0]['LastModifiedDate'].should.be.a(datetime.date)
|
|
||||||
response['Parameters'][0]['LastModifiedUser'].should.equal('N/A')
|
|
||||||
|
|
||||||
response['Parameters'][1].get('Description').should.be.none
|
parameters[0]['Description'].should.equal('my description')
|
||||||
response['Parameters'][1]['Version'].should.equal(1)
|
parameters[0]['Version'].should.equal(1)
|
||||||
|
parameters[0]['LastModifiedDate'].should.be.a(datetime.date)
|
||||||
|
parameters[0]['LastModifiedUser'].should.equal('N/A')
|
||||||
|
|
||||||
|
parameters[1].should_not.have.key('Description')
|
||||||
|
parameters[1]['Version'].should.equal(1)
|
||||||
|
|
||||||
|
|
||||||
@mock_ssm
|
@mock_ssm
|
||||||
@ -519,7 +1024,7 @@ def test_put_parameter_secure_default_kms():
|
|||||||
|
|
||||||
len(response['Parameters']).should.equal(1)
|
len(response['Parameters']).should.equal(1)
|
||||||
response['Parameters'][0]['Name'].should.equal('test')
|
response['Parameters'][0]['Name'].should.equal('test')
|
||||||
response['Parameters'][0]['Value'].should.equal('kms:default:value')
|
response['Parameters'][0]['Value'].should.equal('kms:alias/aws/ssm:value')
|
||||||
response['Parameters'][0]['Type'].should.equal('SecureString')
|
response['Parameters'][0]['Type'].should.equal('SecureString')
|
||||||
|
|
||||||
response = client.get_parameters(
|
response = client.get_parameters(
|
||||||
|
Loading…
Reference in New Issue
Block a user