diff --git a/moto/elbv2/models.py b/moto/elbv2/models.py
index 585e85013..bdfc11fbd 100644
--- a/moto/elbv2/models.py
+++ b/moto/elbv2/models.py
@@ -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(
"""{{ action.type }}
- {% if action.type == "forward" and "forward_config" in action.data %}
+ {% if action.type == "forward" and "ForwardConfig" in action.data %}
- {% for target_group in action.data["forward_config"]["target_groups"] %}
+ {% for target_group in action.data["ForwardConfig"]["TargetGroups"] %}
- {{ target_group["target_group_arn"] }}
- {{ target_group["weight"] }}
+ {{ target_group["TargetGroupArn"] }}
+ {{ target_group["Weight"] }}
{% endfor %}
{% endif %}
- {% if action.type == "forward" and "forward_config" not in action.data %}
- {{ action.data["target_group_arn"] }}
+ {% if action.type == "forward" and "ForwardConfig" not in action.data %}
+ {{ action.data["TargetGroupArn"] }}
{% elif action.type == "redirect" %}
- {{ action.data["redirect_config._protocol"] }}
- {{ action.data["redirect_config._port"] }}
- {{ action.data["redirect_config._status_code"] }}
+ {{ action.data["RedirectConfig"]["Protocol"] }}
+ {{ action.data["RedirectConfig"]["Port"] }}
+ {{ action.data["RedirectConfig"]["StatusCode"] }}
{% elif action.type == "authenticate-cognito" %}
- {{ action.data["authenticate_cognito_config._user_pool_arn"] }}
- {{ action.data["authenticate_cognito_config._user_pool_client_id"] }}
- {{ action.data["authenticate_cognito_config._user_pool_domain"] }}
+ {{ action.data["AuthenticateCognitoConfig"]["UserPoolArn"] }}
+ {{ action.data["AuthenticateCognitoConfig"]["UserPoolClientId"] }}
+ {{ action.data["AuthenticateCognitoConfig"]["UserPoolDomain"] }}
{% elif action.type == "fixed-response" %}
- {{ action.data["fixed_response_config._content_type"] }}
- {{ action.data["fixed_response_config._message_body"] }}
- {{ action.data["fixed_response_config._status_code"] }}
+ {{ action.data["FixedResponseConfig"]["ContentType"] }}
+ {{ action.data["FixedResponseConfig"]["MessageBody"] }}
+ {{ action.data["FixedResponseConfig"]["StatusCode"] }}
{% 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
diff --git a/moto/elbv2/responses.py b/moto/elbv2/responses.py
index 9b9d400d6..d62115bf6 100644
--- a/moto/elbv2/responses.py
+++ b/moto/elbv2/responses.py
@@ -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
)
diff --git a/tests/test_elbv2/test_elbv2.py b/tests/test_elbv2/test_elbv2.py
index e25247a76..e88517dbd 100644
--- a/tests/test_elbv2/test_elbv2.py
+++ b/tests/test_elbv2/test_elbv2.py
@@ -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():
diff --git a/tests/test_elbv2/test_elbv2_cloudformation.py b/tests/test_elbv2/test_elbv2_cloudformation.py
index cc7ba8246..d2dc21927 100644
--- a/tests/test_elbv2/test_elbv2_cloudformation.py
+++ b/tests/test_elbv2/test_elbv2_cloudformation.py
@@ -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