Support all ELBv2 ListenerRule condition types (#4066)
* ELBv2 - ListenerRule condition validation - ListenerRule condition model now uses upper case field names that match input params for boto and CloudFormation. - BaseResponse._get_params() introduced to make it easier to deal with the querystring input params.
This commit is contained in:
parent
54b98d4749
commit
85dc52bd84
@ -557,6 +557,81 @@ class BaseResponse(_TemplateEnvironmentMixin, ActionAuthenticatorMixin):
|
|||||||
]
|
]
|
||||||
return params
|
return params
|
||||||
|
|
||||||
|
def _get_params(self):
|
||||||
|
"""
|
||||||
|
Given a querystring of
|
||||||
|
{
|
||||||
|
'Action': ['CreatRule'],
|
||||||
|
'Conditions.member.1.Field': ['http-header'],
|
||||||
|
'Conditions.member.1.HttpHeaderConfig.HttpHeaderName': ['User-Agent'],
|
||||||
|
'Conditions.member.1.HttpHeaderConfig.Values.member.1': ['curl'],
|
||||||
|
'Actions.member.1.FixedResponseConfig.StatusCode': ['200'],
|
||||||
|
'Actions.member.1.FixedResponseConfig.ContentType': ['text/plain'],
|
||||||
|
'Actions.member.1.Type': ['fixed-response']
|
||||||
|
}
|
||||||
|
|
||||||
|
returns
|
||||||
|
{
|
||||||
|
'Action': 'CreatRule',
|
||||||
|
'Conditions': [
|
||||||
|
{
|
||||||
|
'Field': 'http-header',
|
||||||
|
'HttpHeaderConfig': {
|
||||||
|
'HttpHeaderName': 'User-Agent',
|
||||||
|
'Values': ['curl']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'Actions': [
|
||||||
|
{
|
||||||
|
'Type': 'fixed-response',
|
||||||
|
'FixedResponseConfig': {
|
||||||
|
'StatusCode': '200',
|
||||||
|
'ContentType': 'text/plain'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
params = {}
|
||||||
|
for k, v in sorted(self.querystring.items()):
|
||||||
|
self._parse_param(k, v[0], params)
|
||||||
|
return params
|
||||||
|
|
||||||
|
def _parse_param(self, key, value, params):
|
||||||
|
keylist = key.split(".")
|
||||||
|
obj = params
|
||||||
|
for i, key in enumerate(keylist[:-1]):
|
||||||
|
if key in obj:
|
||||||
|
# step into
|
||||||
|
parent = obj
|
||||||
|
obj = obj[key]
|
||||||
|
else:
|
||||||
|
if key == "member":
|
||||||
|
if not isinstance(obj, list):
|
||||||
|
# initialize list
|
||||||
|
# reset parent
|
||||||
|
obj = []
|
||||||
|
parent[keylist[i - 1]] = obj
|
||||||
|
elif key.isdigit():
|
||||||
|
index = int(key) - 1
|
||||||
|
if len(obj) <= index:
|
||||||
|
# initialize list element
|
||||||
|
obj.insert(index, {})
|
||||||
|
# step into
|
||||||
|
parent = obj
|
||||||
|
obj = obj[index]
|
||||||
|
else:
|
||||||
|
# initialize dict
|
||||||
|
obj[key] = {}
|
||||||
|
# step into
|
||||||
|
parent = obj
|
||||||
|
obj = obj[key]
|
||||||
|
if isinstance(obj, list):
|
||||||
|
obj.append(value)
|
||||||
|
else:
|
||||||
|
obj[keylist[-1]] = value
|
||||||
|
|
||||||
def _get_list_prefix(self, param_prefix):
|
def _get_list_prefix(self, param_prefix):
|
||||||
"""
|
"""
|
||||||
Given a query dict like
|
Given a query dict like
|
||||||
|
@ -326,7 +326,6 @@ class FakeListenerRule(CloudFormationModel):
|
|||||||
conditions = properties.get("Conditions")
|
conditions = properties.get("Conditions")
|
||||||
|
|
||||||
actions = elbv2_backend.convert_and_validate_action_properties(properties)
|
actions = elbv2_backend.convert_and_validate_action_properties(properties)
|
||||||
conditions = elbv2_backend.convert_and_validate_condition_properties(properties)
|
|
||||||
listener_rule = elbv2_backend.create_rule(
|
listener_rule = elbv2_backend.create_rule(
|
||||||
listener_arn, conditions, priority, actions
|
listener_arn, conditions, priority, actions
|
||||||
)
|
)
|
||||||
@ -343,7 +342,6 @@ class FakeListenerRule(CloudFormationModel):
|
|||||||
conditions = properties.get("Conditions")
|
conditions = properties.get("Conditions")
|
||||||
|
|
||||||
actions = elbv2_backend.convert_and_validate_action_properties(properties)
|
actions = elbv2_backend.convert_and_validate_action_properties(properties)
|
||||||
conditions = elbv2_backend.convert_and_validate_condition_properties(properties)
|
|
||||||
listener_rule = elbv2_backend.modify_rule(
|
listener_rule = elbv2_backend.modify_rule(
|
||||||
original_resource.arn, conditions, actions
|
original_resource.arn, conditions, actions
|
||||||
)
|
)
|
||||||
@ -660,15 +658,6 @@ class ELBv2Backend(BaseBackend):
|
|||||||
raise InvalidActionTypeError(action_type, i + 1)
|
raise InvalidActionTypeError(action_type, i + 1)
|
||||||
return default_actions
|
return default_actions
|
||||||
|
|
||||||
def convert_and_validate_condition_properties(self, properties):
|
|
||||||
|
|
||||||
conditions = []
|
|
||||||
for i, condition in enumerate(properties["Conditions"]):
|
|
||||||
conditions.append(
|
|
||||||
{"field": condition["Field"], "values": condition["Values"],}
|
|
||||||
)
|
|
||||||
return conditions
|
|
||||||
|
|
||||||
def create_rule(self, listener_arn, conditions, priority, actions):
|
def create_rule(self, listener_arn, conditions, priority, actions):
|
||||||
actions = [FakeAction(action) for action in actions]
|
actions = [FakeAction(action) for action in actions]
|
||||||
listeners = self.describe_listeners(None, [listener_arn])
|
listeners = self.describe_listeners(None, [listener_arn])
|
||||||
@ -677,22 +666,7 @@ class ELBv2Backend(BaseBackend):
|
|||||||
listener = listeners[0]
|
listener = listeners[0]
|
||||||
|
|
||||||
# validate conditions
|
# validate conditions
|
||||||
for condition in conditions:
|
self._validate_conditions(conditions)
|
||||||
if "field" in condition:
|
|
||||||
field = condition["field"]
|
|
||||||
if field not in ["path-pattern", "host-header"]:
|
|
||||||
raise InvalidConditionFieldError(field)
|
|
||||||
|
|
||||||
values = condition["values"]
|
|
||||||
if len(values) == 0:
|
|
||||||
raise InvalidConditionValueError(
|
|
||||||
"A condition value must be specified"
|
|
||||||
)
|
|
||||||
if len(values) > 1:
|
|
||||||
raise InvalidConditionValueError(
|
|
||||||
"The '%s' field contains too many values; the limit is '1'"
|
|
||||||
% field
|
|
||||||
)
|
|
||||||
|
|
||||||
# TODO: check pattern of value for 'host-header'
|
# TODO: check pattern of value for 'host-header'
|
||||||
# TODO: check pattern of value for 'path-pattern'
|
# TODO: check pattern of value for 'path-pattern'
|
||||||
@ -714,6 +688,136 @@ class ELBv2Backend(BaseBackend):
|
|||||||
listener.register(arn, rule)
|
listener.register(arn, rule)
|
||||||
return rule
|
return rule
|
||||||
|
|
||||||
|
def _validate_conditions(self, conditions):
|
||||||
|
for condition in conditions:
|
||||||
|
if "Field" in condition:
|
||||||
|
field = condition["Field"]
|
||||||
|
if field not in [
|
||||||
|
"host-header",
|
||||||
|
"http-header",
|
||||||
|
"http-request-method",
|
||||||
|
"path-pattern",
|
||||||
|
"query-string",
|
||||||
|
"source-ip",
|
||||||
|
]:
|
||||||
|
raise InvalidConditionFieldError(field)
|
||||||
|
if "Values" in condition and field not in [
|
||||||
|
"host-header",
|
||||||
|
"path-pattern",
|
||||||
|
]:
|
||||||
|
raise InvalidConditionValueError(
|
||||||
|
"The 'Values' field is not compatible with '%s'" % field
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
method_name = "_validate_" + field.replace("-", "_") + "_condition"
|
||||||
|
func = getattr(self, method_name)
|
||||||
|
func(condition)
|
||||||
|
|
||||||
|
def _validate_host_header_condition(self, condition):
|
||||||
|
values = None
|
||||||
|
if "HostHeaderConfig" in condition:
|
||||||
|
values = condition["HostHeaderConfig"]["Values"]
|
||||||
|
elif "Values" in condition:
|
||||||
|
values = condition["Values"]
|
||||||
|
if len(values) > 1:
|
||||||
|
raise InvalidConditionValueError(
|
||||||
|
"The 'host-header' field contains too many values; the limit is '1'"
|
||||||
|
)
|
||||||
|
if values is None or len(values) == 0:
|
||||||
|
raise InvalidConditionValueError("A condition value must be specified")
|
||||||
|
for value in values:
|
||||||
|
if len(value) > 128:
|
||||||
|
raise InvalidConditionValueError(
|
||||||
|
"The 'host-header' value is too long; the limit is '128'"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _validate_http_header_condition(self, condition):
|
||||||
|
if "HttpHeaderConfig" in condition:
|
||||||
|
config = condition["HttpHeaderConfig"]
|
||||||
|
name = config.get("HttpHeaderName")
|
||||||
|
if len(name) > 40:
|
||||||
|
raise InvalidConditionValueError(
|
||||||
|
"The 'HttpHeaderName' value is too long; the limit is '40'"
|
||||||
|
)
|
||||||
|
values = config["Values"]
|
||||||
|
for value in values:
|
||||||
|
if len(value) > 128:
|
||||||
|
raise InvalidConditionValueError(
|
||||||
|
"The 'http-header' value is too long; the limit is '128'"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise InvalidConditionValueError(
|
||||||
|
"A 'HttpHeaderConfig' must be specified with 'http-header'"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _validate_http_request_method_condition(self, condition):
|
||||||
|
if "HttpRequestMethodConfig" in condition:
|
||||||
|
for value in condition["HttpRequestMethodConfig"]["Values"]:
|
||||||
|
if len(value) > 40:
|
||||||
|
raise InvalidConditionValueError(
|
||||||
|
"The 'http-request-method' value is too long; the limit is '40'"
|
||||||
|
)
|
||||||
|
if not re.match("[A-Z_-]+", value):
|
||||||
|
raise InvalidConditionValueError(
|
||||||
|
"The 'http-request-method' value is invalid; the allowed characters are A-Z, hyphen and underscore"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise InvalidConditionValueError(
|
||||||
|
"A 'HttpRequestMethodConfig' must be specified with 'http-request-method'"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _validate_path_pattern_condition(self, condition):
|
||||||
|
values = None
|
||||||
|
if "PathPatternConfig" in condition:
|
||||||
|
values = condition["PathPatternConfig"]["Values"]
|
||||||
|
elif "Values" in condition:
|
||||||
|
values = condition["Values"]
|
||||||
|
if len(values) > 1:
|
||||||
|
raise InvalidConditionValueError(
|
||||||
|
"The 'path-pattern' field contains too many values; the limit is '1'"
|
||||||
|
)
|
||||||
|
if values is None or len(values) == 0:
|
||||||
|
raise InvalidConditionValueError("A condition value must be specified")
|
||||||
|
for value in values:
|
||||||
|
if len(value) > 128:
|
||||||
|
raise InvalidConditionValueError(
|
||||||
|
"The 'path-pattern' value is too long; the limit is '128'"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _validate_source_ip_condition(self, condition):
|
||||||
|
if "SourceIpConfig" in condition:
|
||||||
|
values = condition["SourceIpConfig"].get("Values", [])
|
||||||
|
if len(values) == 0:
|
||||||
|
raise InvalidConditionValueError(
|
||||||
|
"A 'source-ip' value must be specified"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise InvalidConditionValueError(
|
||||||
|
"A 'SourceIpConfig' must be specified with 'source-ip'"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _validate_query_string_condition(self, condition):
|
||||||
|
if "QueryStringConfig" in condition:
|
||||||
|
config = condition["QueryStringConfig"]
|
||||||
|
values = config["Values"]
|
||||||
|
for value in values:
|
||||||
|
if "Value" not in value:
|
||||||
|
raise InvalidConditionValueError(
|
||||||
|
"A 'Value' must be specified in 'QueryStringKeyValuePair'"
|
||||||
|
)
|
||||||
|
if "Key" in value and len(value["Key"]) > 128:
|
||||||
|
raise InvalidConditionValueError(
|
||||||
|
"The 'Key' value is too long; the limit is '128'"
|
||||||
|
)
|
||||||
|
if len(value["Value"]) > 128:
|
||||||
|
raise InvalidConditionValueError(
|
||||||
|
"The 'Value' value is too long; the limit is '128'"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise InvalidConditionValueError(
|
||||||
|
"A 'QueryStringConfig' must be specified with 'query-string'"
|
||||||
|
)
|
||||||
|
|
||||||
def _validate_actions(self, actions):
|
def _validate_actions(self, actions):
|
||||||
# validate Actions
|
# validate Actions
|
||||||
target_group_arns = [
|
target_group_arns = [
|
||||||
@ -1063,24 +1167,9 @@ Member must satisfy regular expression pattern: {}".format(
|
|||||||
raise RuleNotFoundError()
|
raise RuleNotFoundError()
|
||||||
rule = rules[0]
|
rule = rules[0]
|
||||||
|
|
||||||
if conditions:
|
self._validate_conditions(conditions)
|
||||||
for condition in conditions:
|
# TODO: check pattern of value for 'host-header'
|
||||||
field = condition["field"]
|
# TODO: check pattern of value for 'path-pattern'
|
||||||
if field not in ["path-pattern", "host-header"]:
|
|
||||||
raise InvalidConditionFieldError(field)
|
|
||||||
|
|
||||||
values = condition["values"]
|
|
||||||
if len(values) == 0:
|
|
||||||
raise InvalidConditionValueError(
|
|
||||||
"A condition value must be specified"
|
|
||||||
)
|
|
||||||
if len(values) > 1:
|
|
||||||
raise InvalidConditionValueError(
|
|
||||||
"The '%s' field contains too many values; the limit is '1'"
|
|
||||||
% field
|
|
||||||
)
|
|
||||||
# TODO: check pattern of value for 'host-header'
|
|
||||||
# TODO: check pattern of value for 'path-pattern'
|
|
||||||
|
|
||||||
# validate Actions
|
# validate Actions
|
||||||
self._validate_actions(actions)
|
self._validate_actions(actions)
|
||||||
|
@ -151,24 +151,12 @@ class ELBV2Response(BaseResponse):
|
|||||||
|
|
||||||
@amzn_request_id
|
@amzn_request_id
|
||||||
def create_rule(self):
|
def create_rule(self):
|
||||||
listener_arn = self._get_param("ListenerArn")
|
params = self._get_params()
|
||||||
_conditions = self._get_list_prefix("Conditions.member")
|
|
||||||
conditions = []
|
|
||||||
for _condition in _conditions:
|
|
||||||
condition = {}
|
|
||||||
condition["field"] = _condition["field"]
|
|
||||||
values = sorted(
|
|
||||||
[e for e in _condition.items() if "values.member" in e[0]],
|
|
||||||
key=lambda x: x[0],
|
|
||||||
)
|
|
||||||
condition["values"] = [e[1] for e in values]
|
|
||||||
conditions.append(condition)
|
|
||||||
priority = self._get_int_param("Priority")
|
|
||||||
actions = self._get_list_prefix("Actions.member")
|
actions = self._get_list_prefix("Actions.member")
|
||||||
rules = self.elbv2_backend.create_rule(
|
rules = self.elbv2_backend.create_rule(
|
||||||
listener_arn=listener_arn,
|
listener_arn=params["ListenerArn"],
|
||||||
conditions=conditions,
|
conditions=params["Conditions"],
|
||||||
priority=priority,
|
priority=params["Priority"],
|
||||||
actions=actions,
|
actions=actions,
|
||||||
)
|
)
|
||||||
template = self.response_template(CREATE_RULE_TEMPLATE)
|
template = self.response_template(CREATE_RULE_TEMPLATE)
|
||||||
@ -350,17 +338,8 @@ class ELBV2Response(BaseResponse):
|
|||||||
@amzn_request_id
|
@amzn_request_id
|
||||||
def modify_rule(self):
|
def modify_rule(self):
|
||||||
rule_arn = self._get_param("RuleArn")
|
rule_arn = self._get_param("RuleArn")
|
||||||
_conditions = self._get_list_prefix("Conditions.member")
|
params = self._get_params()
|
||||||
conditions = []
|
conditions = params["Conditions"]
|
||||||
for _condition in _conditions:
|
|
||||||
condition = {}
|
|
||||||
condition["field"] = _condition["field"]
|
|
||||||
values = sorted(
|
|
||||||
[e for e in _condition.items() if "values.member" in e[0]],
|
|
||||||
key=lambda x: x[0],
|
|
||||||
)
|
|
||||||
condition["values"] = [e[1] for e in values]
|
|
||||||
conditions.append(condition)
|
|
||||||
actions = self._get_list_prefix("Actions.member")
|
actions = self._get_list_prefix("Actions.member")
|
||||||
rules = self.elbv2_backend.modify_rule(
|
rules = self.elbv2_backend.modify_rule(
|
||||||
rule_arn=rule_arn, conditions=conditions, actions=actions
|
rule_arn=rule_arn, conditions=conditions, actions=actions
|
||||||
@ -725,12 +704,72 @@ CREATE_RULE_TEMPLATE = """<CreateRuleResponse xmlns="http://elasticloadbalancing
|
|||||||
<Conditions>
|
<Conditions>
|
||||||
{% for condition in rules.conditions %}
|
{% for condition in rules.conditions %}
|
||||||
<member>
|
<member>
|
||||||
<Field>{{ condition["field"] }}</Field>
|
<Field>{{ condition["Field"] }}</Field>
|
||||||
|
{% if "Values" in condition %}
|
||||||
<Values>
|
<Values>
|
||||||
{% for value in condition["values"] %}
|
{% for value in condition["Values"] %}
|
||||||
<member>{{ value }}</member>
|
<member>{{ value }}</member>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</Values>
|
</Values>
|
||||||
|
{% endif %}
|
||||||
|
{% if "HttpHeaderConfig" in condition %}
|
||||||
|
<HttpHeaderConfig>
|
||||||
|
<HttpHeaderName>{{ condition["HttpHeaderConfig"]["HttpHeaderName"] }}</HttpHeaderName>
|
||||||
|
<Values>
|
||||||
|
{% for value in condition["HttpHeaderConfig"]["Values"] %}
|
||||||
|
<member>{{ value }}</member>
|
||||||
|
{% endfor %}
|
||||||
|
</Values>
|
||||||
|
</HttpHeaderConfig>
|
||||||
|
{% endif %}
|
||||||
|
{% if "HttpRequestMethodConfig" in condition %}
|
||||||
|
<HttpRequestMethodConfig>
|
||||||
|
<Values>
|
||||||
|
{% for value in condition["HttpRequestMethodConfig"]["Values"] %}
|
||||||
|
<member>{{ value }}</member>
|
||||||
|
{% endfor %}
|
||||||
|
</Values>
|
||||||
|
</HttpRequestMethodConfig>
|
||||||
|
{% endif %}
|
||||||
|
{% if "QueryStringConfig" in condition %}
|
||||||
|
<QueryStringConfig>
|
||||||
|
<Values>
|
||||||
|
{% for value in condition["QueryStringConfig"]["Values"] %}
|
||||||
|
<member>
|
||||||
|
<Key>{{ value["Key"] }}</Key>
|
||||||
|
<Value>{{ value["Value"] }}</Value>
|
||||||
|
</member>
|
||||||
|
{% endfor %}
|
||||||
|
</Values>
|
||||||
|
</QueryStringConfig>
|
||||||
|
{% endif %}
|
||||||
|
{% if "SourceIpConfig" in condition %}
|
||||||
|
<SourceIpConfig>
|
||||||
|
<Values>
|
||||||
|
{% for value in condition["SourceIpConfig"]["Values"] %}
|
||||||
|
<member>{{ value }}</member>
|
||||||
|
{% endfor %}
|
||||||
|
</Values>
|
||||||
|
</SourceIpConfig>
|
||||||
|
{% endif %}
|
||||||
|
{% if "PathPatternConfig" in condition %}
|
||||||
|
<PathPatternConfig>
|
||||||
|
<Values>
|
||||||
|
{% for value in condition["PathPatternConfig"]["Values"] %}
|
||||||
|
<member>{{ value }}</member>
|
||||||
|
{% endfor %}
|
||||||
|
</Values>
|
||||||
|
</PathPatternConfig>
|
||||||
|
{% endif %}
|
||||||
|
{% if "HostHeaderConfig" in condition %}
|
||||||
|
<HostHeaderConfig>
|
||||||
|
<Values>
|
||||||
|
{% for value in condition["HostHeaderConfig"]["Values"] %}
|
||||||
|
<member>{{ value }}</member>
|
||||||
|
{% endfor %}
|
||||||
|
</Values>
|
||||||
|
</HostHeaderConfig>
|
||||||
|
{% endif %}
|
||||||
</member>
|
</member>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</Conditions>
|
</Conditions>
|
||||||
@ -912,12 +951,72 @@ DESCRIBE_RULES_TEMPLATE = """<DescribeRulesResponse xmlns="http://elasticloadbal
|
|||||||
<Conditions>
|
<Conditions>
|
||||||
{% for condition in rule.conditions %}
|
{% for condition in rule.conditions %}
|
||||||
<member>
|
<member>
|
||||||
<Field>{{ condition["field"] }}</Field>
|
<Field>{{ condition["Field"] }}</Field>
|
||||||
|
{% if "HttpHeaderConfig" in condition %}
|
||||||
|
<HttpHeaderConfig>
|
||||||
|
<HttpHeaderName>{{ condition["HttpHeaderConfig"]["HttpHeaderName"] }}</HttpHeaderName>
|
||||||
|
<Values>
|
||||||
|
{% for value in condition["HttpHeaderConfig"]["Values"] %}
|
||||||
|
<member>{{ value }}</member>
|
||||||
|
{% endfor %}
|
||||||
|
</Values>
|
||||||
|
</HttpHeaderConfig>
|
||||||
|
{% endif %}
|
||||||
|
{% if "HttpRequestMethodConfig" in condition %}
|
||||||
|
<HttpRequestMethodConfig>
|
||||||
|
<Values>
|
||||||
|
{% for value in condition["HttpRequestMethodConfig"]["Values"] %}
|
||||||
|
<member>{{ value }}</member>
|
||||||
|
{% endfor %}
|
||||||
|
</Values>
|
||||||
|
</HttpRequestMethodConfig>
|
||||||
|
{% endif %}
|
||||||
|
{% if "QueryStringConfig" in condition %}
|
||||||
|
<QueryStringConfig>
|
||||||
|
<Values>
|
||||||
|
{% for value in condition["QueryStringConfig"]["Values"] %}
|
||||||
|
<member>
|
||||||
|
<Key>{{ value["Key"] }}</Key>
|
||||||
|
<Value>{{ value["Value"] }}</Value>
|
||||||
|
</member>
|
||||||
|
{% endfor %}
|
||||||
|
</Values>
|
||||||
|
</QueryStringConfig>
|
||||||
|
{% endif %}
|
||||||
|
{% if "SourceIpConfig" in condition %}
|
||||||
|
<SourceIpConfig>
|
||||||
|
<Values>
|
||||||
|
{% for value in condition["SourceIpConfig"]["Values"] %}
|
||||||
|
<member>{{ value }}</member>
|
||||||
|
{% endfor %}
|
||||||
|
</Values>
|
||||||
|
</SourceIpConfig>
|
||||||
|
{% endif %}
|
||||||
|
{% if "PathPatternConfig" in condition %}
|
||||||
|
<PathPatternConfig>
|
||||||
|
<Values>
|
||||||
|
{% for value in condition["PathPatternConfig"]["Values"] %}
|
||||||
|
<member>{{ value }}</member>
|
||||||
|
{% endfor %}
|
||||||
|
</Values>
|
||||||
|
</PathPatternConfig>
|
||||||
|
{% endif %}
|
||||||
|
{% if "HostHeaderConfig" in condition %}
|
||||||
|
<HostHeaderConfig>
|
||||||
|
<Values>
|
||||||
|
{% for value in condition["HostHeaderConfig"]["Values"] %}
|
||||||
|
<member>{{ value }}</member>
|
||||||
|
{% endfor %}
|
||||||
|
</Values>
|
||||||
|
</HostHeaderConfig>
|
||||||
|
{% endif %}
|
||||||
|
{% if "Values" in condition %}
|
||||||
<Values>
|
<Values>
|
||||||
{% for value in condition["values"] %}
|
{% for value in condition["Values"] %}
|
||||||
<member>{{ value }}</member>
|
<member>{{ value }}</member>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</Values>
|
</Values>
|
||||||
|
{% endif %}
|
||||||
</member>
|
</member>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</Conditions>
|
</Conditions>
|
||||||
@ -1053,12 +1152,72 @@ MODIFY_RULE_TEMPLATE = """<ModifyRuleResponse xmlns="http://elasticloadbalancing
|
|||||||
<Conditions>
|
<Conditions>
|
||||||
{% for condition in rules.conditions %}
|
{% for condition in rules.conditions %}
|
||||||
<member>
|
<member>
|
||||||
<Field>{{ condition["field"] }}</Field>
|
<Field>{{ condition["Field"] }}</Field>
|
||||||
|
{% if "PathPatternConfig" in condition %}
|
||||||
|
<PathPatternConfig>
|
||||||
|
<Values>
|
||||||
|
{% for value in condition["PathPatternConfig"]["Values"] %}
|
||||||
|
<member>{{ value }}</member>
|
||||||
|
{% endfor %}
|
||||||
|
</Values>
|
||||||
|
</PathPatternConfig>
|
||||||
|
{% endif %}
|
||||||
|
{% if "HostHeaderConfig" in condition %}
|
||||||
|
<HostHeaderConfig>
|
||||||
|
<Values>
|
||||||
|
{% for value in condition["HostHeaderConfig"]["Values"] %}
|
||||||
|
<member>{{ value }}</member>
|
||||||
|
{% endfor %}
|
||||||
|
</Values>
|
||||||
|
</HostHeaderConfig>
|
||||||
|
{% endif %}
|
||||||
|
{% if "HttpHeaderConfig" in condition %}
|
||||||
|
<HttpHeaderConfig>
|
||||||
|
<HttpHeaderName>{{ condition["HttpHeaderConfig"]["HttpHeaderName"] }}</HttpHeaderName>
|
||||||
|
<Values>
|
||||||
|
{% for value in condition["HttpHeaderConfig"]["Values"] %}
|
||||||
|
<member>{{ value }}</member>
|
||||||
|
{% endfor %}
|
||||||
|
</Values>
|
||||||
|
</HttpHeaderConfig>
|
||||||
|
{% endif %}
|
||||||
|
{% if "HttpRequestMethodConfig" in condition %}
|
||||||
|
<HttpRequestMethodConfig>
|
||||||
|
<Values>
|
||||||
|
{% for value in condition["HttpRequestMethodConfig"]["Values"] %}
|
||||||
|
<member>{{ value }}</member>
|
||||||
|
{% endfor %}
|
||||||
|
</Values>
|
||||||
|
</HttpRequestMethodConfig>
|
||||||
|
{% endif %}
|
||||||
|
{% if "QueryStringConfig" in condition %}
|
||||||
|
<QueryStringConfig>
|
||||||
|
<Values>
|
||||||
|
{% for value in condition["QueryStringConfig"]["Values"] %}
|
||||||
|
<member>
|
||||||
|
<Key>{{ value["Key"] }}</Key>
|
||||||
|
<Value>{{ value["Value"] }}</Value>
|
||||||
|
</member>
|
||||||
|
{% endfor %}
|
||||||
|
</Values>
|
||||||
|
</QueryStringConfig>
|
||||||
|
{% endif %}
|
||||||
|
{% if "SourceIpConfig" in condition %}
|
||||||
|
<SourceIpConfig>
|
||||||
|
<Values>
|
||||||
|
{% for value in condition["SourceIpConfig"]["Values"] %}
|
||||||
|
<member>{{ value }}</member>
|
||||||
|
{% endfor %}
|
||||||
|
</Values>
|
||||||
|
</SourceIpConfig>
|
||||||
|
{% endif %}
|
||||||
|
{% if "Values" in condition %}
|
||||||
<Values>
|
<Values>
|
||||||
{% for value in condition["values"] %}
|
{% for value in condition["Values"] %}
|
||||||
<member>{{ value }}</member>
|
<member>{{ value }}</member>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</Values>
|
</Values>
|
||||||
|
{% endif %}
|
||||||
</member>
|
</member>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</Conditions>
|
</Conditions>
|
||||||
|
@ -2,6 +2,8 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import sure # noqa
|
import sure # noqa
|
||||||
|
|
||||||
|
from moto.compat import OrderedDict
|
||||||
|
|
||||||
from botocore.awsrequest import AWSPreparedRequest
|
from botocore.awsrequest import AWSPreparedRequest
|
||||||
|
|
||||||
from moto.core.responses import AWSServiceSpec, BaseResponse
|
from moto.core.responses import AWSServiceSpec, BaseResponse
|
||||||
@ -92,3 +94,56 @@ def test_parse_qs_unicode_decode_error():
|
|||||||
body = b'{"key": "%D0"}, "C": "#0 = :0"}'
|
body = b'{"key": "%D0"}, "C": "#0 = :0"}'
|
||||||
request = AWSPreparedRequest("GET", "http://request", {"foo": "bar"}, body, False)
|
request = AWSPreparedRequest("GET", "http://request", {"foo": "bar"}, body, False)
|
||||||
BaseResponse().setup_class(request, request.url, request.headers)
|
BaseResponse().setup_class(request, request.url, request.headers)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_params():
|
||||||
|
subject = BaseResponse()
|
||||||
|
subject.querystring = OrderedDict(
|
||||||
|
[
|
||||||
|
("Action", ["CreateRule"]),
|
||||||
|
("Version", ["2015-12-01"]),
|
||||||
|
(
|
||||||
|
"ListenerArn",
|
||||||
|
[
|
||||||
|
"arn:aws:elasticloadbalancing:us-east-1:1:listener/my-lb/50dc6c495c0c9188/80139731473870416"
|
||||||
|
],
|
||||||
|
),
|
||||||
|
("Priority", ["100"]),
|
||||||
|
("Conditions.member.1.Field", ["http-header"]),
|
||||||
|
("Conditions.member.1.HttpHeaderConfig.HttpHeaderName", ["User-Agent"]),
|
||||||
|
("Conditions.member.1.HttpHeaderConfig.Values.member.2", ["curl"]),
|
||||||
|
("Conditions.member.1.HttpHeaderConfig.Values.member.1", ["Mozilla"]),
|
||||||
|
("Actions.member.1.FixedResponseConfig.StatusCode", ["200"]),
|
||||||
|
("Actions.member.1.FixedResponseConfig.ContentType", ["text/plain"]),
|
||||||
|
("Actions.member.1.Type", ["fixed-response"]),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
result = subject._get_params()
|
||||||
|
|
||||||
|
result.should.equal(
|
||||||
|
{
|
||||||
|
"Action": "CreateRule",
|
||||||
|
"Version": "2015-12-01",
|
||||||
|
"ListenerArn": "arn:aws:elasticloadbalancing:us-east-1:1:listener/my-lb/50dc6c495c0c9188/80139731473870416",
|
||||||
|
"Priority": "100",
|
||||||
|
"Conditions": [
|
||||||
|
{
|
||||||
|
"Field": "http-header",
|
||||||
|
"HttpHeaderConfig": {
|
||||||
|
"HttpHeaderName": "User-Agent",
|
||||||
|
"Values": ["Mozilla", "curl"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Actions": [
|
||||||
|
{
|
||||||
|
"Type": "fixed-response",
|
||||||
|
"FixedResponseConfig": {
|
||||||
|
"StatusCode": "200",
|
||||||
|
"ContentType": "text/plain",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ -1213,7 +1213,7 @@ def test_handle_listener_rules():
|
|||||||
obtained_rule = rules["Rules"][0]
|
obtained_rule = rules["Rules"][0]
|
||||||
obtained_rule["Conditions"][0]["Values"][0].should.equal(new_host)
|
obtained_rule["Conditions"][0]["Values"][0].should.equal(new_host)
|
||||||
obtained_rule["Conditions"][1]["Values"][0].should.equal(new_path_pattern)
|
obtained_rule["Conditions"][1]["Values"][0].should.equal(new_path_pattern)
|
||||||
obtained_rule["Conditions"][2]["Values"][0].should.equal(
|
obtained_rule["Conditions"][2]["PathPatternConfig"]["Values"][0].should.equal(
|
||||||
new_pathpatternconfig_pattern
|
new_pathpatternconfig_pattern
|
||||||
)
|
)
|
||||||
obtained_rule["Actions"][0]["TargetGroupArn"].should.equal(
|
obtained_rule["Actions"][0]["TargetGroupArn"].should.equal(
|
||||||
@ -1253,7 +1253,7 @@ def test_handle_listener_rules():
|
|||||||
obtained_rule = rules["Rules"][2]
|
obtained_rule = rules["Rules"][2]
|
||||||
obtained_rule["Conditions"][0]["Values"][0].should.equal(new_host_2)
|
obtained_rule["Conditions"][0]["Values"][0].should.equal(new_host_2)
|
||||||
obtained_rule["Conditions"][1]["Values"][0].should.equal(new_path_pattern_2)
|
obtained_rule["Conditions"][1]["Values"][0].should.equal(new_path_pattern_2)
|
||||||
obtained_rule["Conditions"][2]["Values"][0].should.equal(
|
obtained_rule["Conditions"][2]["PathPatternConfig"]["Values"][0].should.equal(
|
||||||
new_pathpatternconfig_pattern_2
|
new_pathpatternconfig_pattern_2
|
||||||
)
|
)
|
||||||
obtained_rule["Actions"][0]["TargetGroupArn"].should.equal(
|
obtained_rule["Actions"][0]["TargetGroupArn"].should.equal(
|
||||||
|
297
tests/test_elbv2/test_elbv2_listener_rules.py
Normal file
297
tests/test_elbv2/test_elbv2_listener_rules.py
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import boto3
|
||||||
|
import botocore
|
||||||
|
from botocore.exceptions import ClientError
|
||||||
|
import pytest
|
||||||
|
import sure # noqa
|
||||||
|
|
||||||
|
from moto import mock_elbv2, mock_ec2
|
||||||
|
|
||||||
|
default_action = {
|
||||||
|
"FixedResponseConfig": {"StatusCode": "200", "ContentType": "text/plain"},
|
||||||
|
"Type": "fixed-response",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def setup_listener(conn):
|
||||||
|
ec2 = boto3.resource("ec2", region_name="us-east-1")
|
||||||
|
|
||||||
|
security_group = ec2.create_security_group(
|
||||||
|
GroupName="a-security-group", Description="First One"
|
||||||
|
)
|
||||||
|
vpc = ec2.create_vpc(CidrBlock="172.28.7.0/24", InstanceTenancy="default")
|
||||||
|
subnet1 = ec2.create_subnet(
|
||||||
|
VpcId=vpc.id, CidrBlock="172.28.7.192/26", AvailabilityZone="us-east-1a"
|
||||||
|
)
|
||||||
|
subnet2 = ec2.create_subnet(
|
||||||
|
VpcId=vpc.id, CidrBlock="172.28.7.0/26", AvailabilityZone="us-east-1b"
|
||||||
|
)
|
||||||
|
|
||||||
|
response = conn.create_load_balancer(
|
||||||
|
Name="my-lb",
|
||||||
|
Subnets=[subnet1.id, subnet2.id],
|
||||||
|
SecurityGroups=[security_group.id],
|
||||||
|
Scheme="internal",
|
||||||
|
Tags=[{"Key": "key_name", "Value": "a_value"}],
|
||||||
|
)
|
||||||
|
|
||||||
|
load_balancer_arn = response.get("LoadBalancers")[0].get("LoadBalancerArn")
|
||||||
|
|
||||||
|
# Plain HTTP listener
|
||||||
|
response = conn.create_listener(
|
||||||
|
LoadBalancerArn=load_balancer_arn,
|
||||||
|
Protocol="HTTP",
|
||||||
|
Port=80,
|
||||||
|
DefaultActions=[
|
||||||
|
{
|
||||||
|
"Type": "fixed-response",
|
||||||
|
"FixedResponseConfig": {
|
||||||
|
"StatusCode": "503",
|
||||||
|
"ContentType": "text/plain",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
)
|
||||||
|
listener = response.get("Listeners")[0]
|
||||||
|
http_listener_arn = listener.get("ListenerArn")
|
||||||
|
return http_listener_arn
|
||||||
|
|
||||||
|
|
||||||
|
@mock_elbv2
|
||||||
|
@mock_ec2
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"condition",
|
||||||
|
[
|
||||||
|
{"Field": "host-header", "Values": ["example.com"]},
|
||||||
|
{
|
||||||
|
"Field": "host-header",
|
||||||
|
"HostHeaderConfig": {"Values": ["example.com", "www.example.com"]},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Field": "http-header",
|
||||||
|
"HttpHeaderConfig": {
|
||||||
|
"HttpHeaderName": "User-Agent",
|
||||||
|
"Values": ["Mozilla"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Field": "http-request-method",
|
||||||
|
"HttpRequestMethodConfig": {"Values": ["GET", "POST"]},
|
||||||
|
},
|
||||||
|
{"Field": "path-pattern", "Values": ["/home"]},
|
||||||
|
{
|
||||||
|
"Field": "path-pattern",
|
||||||
|
"PathPatternConfig": {"Values": ["/home", "/about"]},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Field": "query-string",
|
||||||
|
"QueryStringConfig": {"Values": [{"Key": "hello", "Value": "world"}]},
|
||||||
|
},
|
||||||
|
{"Field": "source-ip", "SourceIpConfig": {"Values": ["172.28.7.0/24"]}},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_create_rule_condition(condition):
|
||||||
|
conn = boto3.client("elbv2", region_name="us-east-1")
|
||||||
|
|
||||||
|
http_listener_arn = setup_listener(conn)
|
||||||
|
|
||||||
|
# create_rule
|
||||||
|
response = conn.create_rule(
|
||||||
|
ListenerArn=http_listener_arn,
|
||||||
|
Priority=100,
|
||||||
|
Conditions=[condition],
|
||||||
|
Actions=[default_action],
|
||||||
|
)
|
||||||
|
|
||||||
|
# assert create_rule response
|
||||||
|
response["Rules"].should.have.length_of(1)
|
||||||
|
rule = response.get("Rules")[0]
|
||||||
|
rule["Priority"].should.equal("100")
|
||||||
|
rule["Conditions"].should.equal([condition])
|
||||||
|
|
||||||
|
# assert describe_rules response
|
||||||
|
response = conn.describe_rules(ListenerArn=http_listener_arn)
|
||||||
|
response["Rules"].should.have.length_of(2) # including the default rule
|
||||||
|
|
||||||
|
# assert describe_rules with arn filter response
|
||||||
|
rule = response["Rules"][0]
|
||||||
|
rule["Conditions"].should.equal([condition])
|
||||||
|
response = conn.describe_rules(RuleArns=[rule["RuleArn"]])
|
||||||
|
response["Rules"].should.equal([rule])
|
||||||
|
|
||||||
|
|
||||||
|
@mock_elbv2
|
||||||
|
@mock_ec2
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"create_condition,modify_condition",
|
||||||
|
[
|
||||||
|
(
|
||||||
|
{"Field": "host-header", "Values": ["example.com"]},
|
||||||
|
{
|
||||||
|
"Field": "host-header",
|
||||||
|
"HostHeaderConfig": {"Values": ["example.com", "www.example.com"]},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"Field": "http-header",
|
||||||
|
"HttpHeaderConfig": {
|
||||||
|
"HttpHeaderName": "User-Agent",
|
||||||
|
"Values": ["Mozilla"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Field": "http-header",
|
||||||
|
"HttpHeaderConfig": {
|
||||||
|
"HttpHeaderName": "User-Agent",
|
||||||
|
"Values": ["Mozilla", "curl"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{"Field": "path-pattern", "Values": ["/home"]},
|
||||||
|
{
|
||||||
|
"Field": "path-pattern",
|
||||||
|
"PathPatternConfig": {"Values": ["/home", "/about"]},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_modify_rule_condition(create_condition, modify_condition):
|
||||||
|
conn = boto3.client("elbv2", region_name="us-east-1")
|
||||||
|
|
||||||
|
http_listener_arn = setup_listener(conn)
|
||||||
|
response = conn.create_rule(
|
||||||
|
ListenerArn=http_listener_arn,
|
||||||
|
Priority=100,
|
||||||
|
Conditions=[create_condition],
|
||||||
|
Actions=[default_action],
|
||||||
|
)
|
||||||
|
rule = response.get("Rules")[0]
|
||||||
|
|
||||||
|
# modify_rule
|
||||||
|
response = conn.modify_rule(RuleArn=rule["RuleArn"], Conditions=[modify_condition])
|
||||||
|
response["Rules"].should.have.length_of(1)
|
||||||
|
modified_rule = response.get("Rules")[0]
|
||||||
|
modified_rule["Conditions"].should.equal([modify_condition])
|
||||||
|
|
||||||
|
|
||||||
|
@mock_elbv2
|
||||||
|
@mock_ec2
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"condition,expected_message",
|
||||||
|
[
|
||||||
|
(
|
||||||
|
{"Field": "host-header", "Values": ["x" * 256]},
|
||||||
|
"The 'host-header' value is too long; the limit is '128'",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{"Field": "host-header", "HostHeaderConfig": {"Values": ["x" * 256]}},
|
||||||
|
"The 'host-header' value is too long; the limit is '128'",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{"Field": "host-header", "Values": ["one", "two"]},
|
||||||
|
"The 'host-header' field contains too many values; the limit is '1'",
|
||||||
|
),
|
||||||
|
({"Field": "host-header"}, "A condition value must be specified"),
|
||||||
|
(
|
||||||
|
{"Field": "host-header", "HostHeaderConfig": {"Values": []}},
|
||||||
|
"A condition value must be specified",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{"Field": "path-pattern", "Values": ["x" * 256]},
|
||||||
|
"The 'path-pattern' value is too long; the limit is '128'",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{"Field": "path-pattern", "PathPatternConfig": {"Values": ["x" * 256]}},
|
||||||
|
"The 'path-pattern' value is too long; the limit is '128'",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{"Field": "path-pattern", "Values": ["one", "two"]},
|
||||||
|
"The 'path-pattern' field contains too many values; the limit is '1'",
|
||||||
|
),
|
||||||
|
({"Field": "path-pattern"}, "A condition value must be specified"),
|
||||||
|
(
|
||||||
|
{"Field": "path-pattern", "PathPatternConfig": {"Values": []}},
|
||||||
|
"A condition value must be specified",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"Field": "http-header",
|
||||||
|
"HttpHeaderConfig": {"HttpHeaderName": "x" * 50, "Values": ["y"]},
|
||||||
|
},
|
||||||
|
"The 'HttpHeaderName' value is too long; the limit is '40'",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"Field": "http-header",
|
||||||
|
"HttpHeaderConfig": {"HttpHeaderName": "x", "Values": ["y" * 256]},
|
||||||
|
},
|
||||||
|
"The 'http-header' value is too long; the limit is '128'",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"Field": "http-request-method",
|
||||||
|
"HttpRequestMethodConfig": {"Values": ["get"]},
|
||||||
|
},
|
||||||
|
"The 'http-request-method' value is invalid; the allowed characters are A-Z, hyphen and underscore",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"Field": "http-request-method",
|
||||||
|
"HttpRequestMethodConfig": {"Values": ["X" * 50]},
|
||||||
|
},
|
||||||
|
"The 'http-request-method' value is too long; the limit is '40'",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{"Field": "http-request-method"},
|
||||||
|
"A 'HttpRequestMethodConfig' must be specified with 'http-request-method'",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"Field": "query-string",
|
||||||
|
"QueryStringConfig": {"Values": [{"Key": "x" * 256, "Value": "world"}]},
|
||||||
|
},
|
||||||
|
"The 'Key' value is too long; the limit is '128'",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"Field": "query-string",
|
||||||
|
"QueryStringConfig": {"Values": [{"Key": "hello", "Value": "x" * 256}]},
|
||||||
|
},
|
||||||
|
"The 'Value' value is too long; the limit is '128'",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"Field": "query-string",
|
||||||
|
"QueryStringConfig": {"Values": [{"Key": "hello"}]},
|
||||||
|
},
|
||||||
|
"A 'Value' must be specified in 'QueryStringKeyValuePair'",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{"Field": "source-ip", "SourceIpConfig": {"Values": []}},
|
||||||
|
"A 'source-ip' value must be specified",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{"Field": "source-ip",},
|
||||||
|
"A 'SourceIpConfig' must be specified with 'source-ip'",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_create_rule_validate_condition(condition, expected_message):
|
||||||
|
conn = boto3.client("elbv2", region_name="us-east-1")
|
||||||
|
|
||||||
|
http_listener_arn = setup_listener(conn)
|
||||||
|
|
||||||
|
with pytest.raises(ClientError) as ex:
|
||||||
|
response = conn.create_rule(
|
||||||
|
ListenerArn=http_listener_arn,
|
||||||
|
Priority=100,
|
||||||
|
Conditions=[condition],
|
||||||
|
Actions=[default_action],
|
||||||
|
)
|
||||||
|
|
||||||
|
err = ex.value.response["Error"]
|
||||||
|
err["Code"].should.equal("ValidationError")
|
||||||
|
err["Message"].should.equal(expected_message)
|
Loading…
Reference in New Issue
Block a user