ELBv2 - Streamline parsing of Actions-parameter in create_rule (#4390)

This commit is contained in:
Bert Blommers 2021-10-11 21:12:38 +00:00 committed by GitHub
parent 03083ede42
commit 0d0354438e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 111 additions and 85 deletions

View File

@ -8,8 +8,6 @@ from collections import OrderedDict
from moto.core.exceptions import RESTError
from moto.core import BaseBackend, BaseModel, CloudFormationModel
from moto.core.utils import (
camelcase_to_underscores,
underscores_to_camelcase,
iso_8601_datetime_with_milliseconds,
get_random_hex,
)
@ -367,42 +365,42 @@ class FakeRule(BaseModel):
class FakeAction(BaseModel):
def __init__(self, data):
self.data = data
self.type = data.get("type")
self.type = data.get("Type")
def to_xml(self):
template = Template(
"""<Type>{{ action.type }}</Type>
{% if action.type == "forward" and "forward_config" in action.data %}
{% if action.type == "forward" and "ForwardConfig" in action.data %}
<ForwardConfig>
<TargetGroups>
{% for target_group in action.data["forward_config"]["target_groups"] %}
{% for target_group in action.data["ForwardConfig"]["TargetGroups"] %}
<member>
<TargetGroupArn>{{ target_group["target_group_arn"] }}</TargetGroupArn>
<Weight>{{ target_group["weight"] }}</Weight>
<TargetGroupArn>{{ target_group["TargetGroupArn"] }}</TargetGroupArn>
<Weight>{{ target_group["Weight"] }}</Weight>
</member>
{% endfor %}
</TargetGroups>
</ForwardConfig>
{% endif %}
{% if action.type == "forward" and "forward_config" not in action.data %}
<TargetGroupArn>{{ action.data["target_group_arn"] }}</TargetGroupArn>
{% if action.type == "forward" and "ForwardConfig" not in action.data %}
<TargetGroupArn>{{ action.data["TargetGroupArn"] }}</TargetGroupArn>
{% elif action.type == "redirect" %}
<RedirectConfig>
<Protocol>{{ action.data["redirect_config._protocol"] }}</Protocol>
<Port>{{ action.data["redirect_config._port"] }}</Port>
<StatusCode>{{ action.data["redirect_config._status_code"] }}</StatusCode>
<Protocol>{{ action.data["RedirectConfig"]["Protocol"] }}</Protocol>
<Port>{{ action.data["RedirectConfig"]["Port"] }}</Port>
<StatusCode>{{ action.data["RedirectConfig"]["StatusCode"] }}</StatusCode>
</RedirectConfig>
{% elif action.type == "authenticate-cognito" %}
<AuthenticateCognitoConfig>
<UserPoolArn>{{ action.data["authenticate_cognito_config._user_pool_arn"] }}</UserPoolArn>
<UserPoolClientId>{{ action.data["authenticate_cognito_config._user_pool_client_id"] }}</UserPoolClientId>
<UserPoolDomain>{{ action.data["authenticate_cognito_config._user_pool_domain"] }}</UserPoolDomain>
<UserPoolArn>{{ action.data["AuthenticateCognitoConfig"]["UserPoolArn"] }}</UserPoolArn>
<UserPoolClientId>{{ action.data["AuthenticateCognitoConfig"]["UserPoolClientId"] }}</UserPoolClientId>
<UserPoolDomain>{{ action.data["AuthenticateCognitoConfig"]["UserPoolDomain"] }}</UserPoolDomain>
</AuthenticateCognitoConfig>
{% elif action.type == "fixed-response" %}
<FixedResponseConfig>
<ContentType>{{ action.data["fixed_response_config._content_type"] }}</ContentType>
<MessageBody>{{ action.data["fixed_response_config._message_body"] }}</MessageBody>
<StatusCode>{{ action.data["fixed_response_config._status_code"] }}</StatusCode>
<ContentType>{{ action.data["FixedResponseConfig"]["ContentType"] }}</ContentType>
<MessageBody>{{ action.data["FixedResponseConfig"]["MessageBody"] }}</MessageBody>
<StatusCode>{{ action.data["FixedResponseConfig"]["StatusCode"] }}</StatusCode>
</FixedResponseConfig>
{% endif %}
"""
@ -634,45 +632,13 @@ class ELBv2Backend(BaseBackend):
default_actions = []
for i, action in enumerate(properties["Actions"]):
action_type = action["Type"]
if action_type == "forward" and "ForwardConfig" in action:
action_forward_config = action["ForwardConfig"]
action_target_groups = action_forward_config["TargetGroups"]
target_group_action = []
for action_target_group in action_target_groups:
target_group_action.append(
{
"target_group_arn": action_target_group["TargetGroupArn"],
"weight": action_target_group["Weight"],
}
)
default_actions.append(
{
"type": action_type,
"forward_config": {"target_groups": target_group_action},
}
)
elif action_type == "forward" and "ForwardConfig" not in action:
default_actions.append(
{"type": action_type, "target_group_arn": action["TargetGroupArn"]}
)
elif action_type in [
if action_type in [
"redirect",
"authenticate-cognito",
"fixed-response",
"forward",
]:
redirect_action = {"type": action_type}
key = (
underscores_to_camelcase(action_type.capitalize().replace("-", "_"))
+ "Config"
)
for redirect_config_key, redirect_config_value in action[key].items():
# need to match the output of _get_list_prefix
redirect_action[
camelcase_to_underscores(key)
+ "._"
+ camelcase_to_underscores(redirect_config_key)
] = redirect_config_value
default_actions.append(redirect_action)
default_actions.append(action)
else:
raise InvalidActionTypeError(action_type, i + 1)
return default_actions
@ -853,8 +819,8 @@ class ELBv2Backend(BaseBackend):
for i, action in enumerate(actions):
index = i + 1
action_type = action.type
if action_type == "forward" and "target_group_arn" in action.data:
action_target_group_arn = action.data["target_group_arn"]
if action_type == "forward" and "TargetGroupArn" in action.data:
action_target_group_arn = action.data["TargetGroupArn"]
if action_target_group_arn not in target_group_arns:
raise ActionTargetGroupNotFoundError(action_target_group_arn)
elif action_type == "fixed-response":
@ -862,20 +828,13 @@ class ELBv2Backend(BaseBackend):
elif action_type in ["redirect", "authenticate-cognito"]:
pass
# pass if listener rule has forward_config as an Action property
elif (
action_type == "forward"
and "forward_config._target_groups.member.{}._target_group_arn".format(
index
)
in action.data.keys()
or "forward_config" in action.data.keys()
):
elif action_type == "forward" and "ForwardConfig" in action.data.keys():
pass
else:
raise InvalidActionTypeError(action_type, index)
def _validate_fixed_response_action(self, action, i, index):
status_code = action.data.get("fixed_response_config._status_code")
status_code = action.data.get("FixedResponseConfig", {}).get("StatusCode")
if status_code is None:
raise ParamValidationError(
report='Missing required parameter in Actions[%s].FixedResponseConfig: "StatusCode"'
@ -889,7 +848,7 @@ Member must satisfy regular expression pattern: {}".format(
status_code, index, expression
)
)
content_type = action.data["fixed_response_config._content_type"]
content_type = action.data["FixedResponseConfig"].get("ContentType")
if content_type and content_type not in [
"text/plain",
"text/css",
@ -977,31 +936,20 @@ Member must satisfy regular expression pattern: {}".format(
def convert_and_validate_properties(self, properties):
# transform default actions to confirm with the rest of the code and XML templates
# Caller: CF create/update for type "AWS::ElasticLoadBalancingV2::Listener"
default_actions = []
for i, action in enumerate(properties["DefaultActions"]):
action_type = action["Type"]
if action_type == "forward":
default_actions.append(
{"type": action_type, "target_group_arn": action["TargetGroupArn"]}
{"Type": action_type, "TargetGroupArn": action["TargetGroupArn"]}
)
elif action_type in [
"redirect",
"authenticate-cognito",
"fixed-response",
]:
redirect_action = {"type": action_type}
key = (
underscores_to_camelcase(action_type.capitalize().replace("-", "_"))
+ "Config"
)
for redirect_config_key, redirect_config_value in action[key].items():
# need to match the output of _get_list_prefix
redirect_action[
camelcase_to_underscores(key)
+ "._"
+ camelcase_to_underscores(redirect_config_key)
] = redirect_config_value
default_actions.append(redirect_action)
default_actions.append(action)
else:
raise InvalidActionTypeError(action_type, i + 1)
return default_actions
@ -1040,7 +988,7 @@ Member must satisfy regular expression pattern: {}".format(
balancer.listeners[listener.arn] = listener
for action in default_actions:
if action.type == "forward":
target_group = self.target_groups[action.data["target_group_arn"]]
target_group = self.target_groups[action.data["TargetGroupArn"]]
target_group.load_balancer_arns.append(load_balancer_arn)
return listener
@ -1473,7 +1421,7 @@ Member must satisfy regular expression pattern: {}".format(
for listener in load_balancer.listeners.values():
for rule in listener.rules.values():
for action in rule.actions:
if action.data.get("target_group_arn") == target_group_arn:
if action.data.get("TargetGroupArn") == target_group_arn:
return True
return False

View File

@ -165,12 +165,11 @@ class ELBV2Response(BaseResponse):
@amzn_request_id
def create_rule(self):
params = self._get_params()
actions = self._get_list_prefix("Actions.member")
rules = self.elbv2_backend.create_rule(
listener_arn=params["ListenerArn"],
conditions=params["Conditions"],
priority=params["Priority"],
actions=actions,
actions=params["Actions"],
)
template = self.response_template(CREATE_RULE_TEMPLATE)
return template.render(rules=rules)
@ -214,6 +213,7 @@ class ELBV2Response(BaseResponse):
@amzn_request_id
def create_listener(self):
params = self._get_params()
load_balancer_arn = self._get_param("LoadBalancerArn")
protocol = self._get_param("Protocol")
port = self._get_param("Port")
@ -223,7 +223,7 @@ class ELBV2Response(BaseResponse):
certificate = certificates[0].get("certificate_arn")
else:
certificate = None
default_actions = self._get_list_prefix("DefaultActions.member")
default_actions = params.get("DefaultActions", [])
listener = self.elbv2_backend.create_listener(
load_balancer_arn=load_balancer_arn,
@ -357,7 +357,7 @@ class ELBV2Response(BaseResponse):
rule_arn = self._get_param("RuleArn")
params = self._get_params()
conditions = params["Conditions"]
actions = self._get_list_prefix("Actions.member")
actions = params.get("Actions", [])
rules = self.elbv2_backend.modify_rule(
rule_arn=rule_arn, conditions=conditions, actions=actions
)

View File

@ -434,6 +434,83 @@ def test_create_target_group_without_non_required_parameters():
target_group.should_not.be.none
@mock_ec2
@mock_elbv2
def test_create_rule_forward_config_as_second_arg():
# https://github.com/spulec/moto/issues/4123
# Necessary because there was some convoluted way of parsing arguments
# Actions with type=forward had to be the first action specified
response, vpc, security_group, subnet1, subnet2, elbv2 = create_load_balancer()
load_balancer_arn = response.get("LoadBalancers")[0].get("LoadBalancerArn")
response = elbv2.create_listener(
LoadBalancerArn=load_balancer_arn, Protocol="HTTP", Port=80, DefaultActions=[],
)
http_listener_arn = response.get("Listeners")[0]["ListenerArn"]
priority = 100
response = elbv2.create_target_group(
Name="a-target",
Protocol="HTTP",
Port=8080,
VpcId=vpc.id,
HealthCheckProtocol="HTTP",
HealthCheckPort="8080",
HealthCheckPath="/",
Matcher={"HttpCode": "200"},
)
target_group = response.get("TargetGroups")[0]
# No targets registered yet
target_group_arn = target_group.get("TargetGroupArn")
elbv2.create_rule(
ListenerArn=http_listener_arn,
Conditions=[
{"Field": "path-pattern", "PathPatternConfig": {"Values": [f"/sth*",]},},
],
Priority=priority,
Actions=[
{
"Type": "authenticate-cognito",
"Order": 1,
"AuthenticateCognitoConfig": {
"UserPoolArn": "?1",
"UserPoolClientId": "?2",
"UserPoolDomain": "?2",
"SessionCookieName": "AWSELBAuthSessionCookie",
"Scope": "openid",
"SessionTimeout": 604800,
"OnUnauthenticatedRequest": "authenticate",
},
},
{
"Type": "forward",
"Order": 2,
"ForwardConfig": {
"TargetGroups": [
{"TargetGroupArn": target_group_arn, "Weight": 1},
],
"TargetGroupStickinessConfig": {"Enabled": False,},
},
},
],
)
all_rules = elbv2.describe_rules(ListenerArn=http_listener_arn)["Rules"]
our_rule = all_rules[0]
actions = our_rule["Actions"]
forward_action = [a for a in actions if "ForwardConfig" in a.keys()][0]
forward_action.should.equal(
{
"ForwardConfig": {
"TargetGroups": [{"TargetGroupArn": target_group_arn, "Weight": 1}]
},
"Type": "forward",
}
)
@mock_elbv2
@mock_ec2
def test_create_invalid_target_group():

View File

@ -1,5 +1,6 @@
import boto3
import json
import sure # noqa
from moto import mock_elbv2, mock_ec2, mock_cloudformation
from moto.core import ACCOUNT_ID