I have a CloudFormation template that contains an Application Load Balancer ListenerRule. One of the required properties of a ListenerRule is its Priority (a number between 1 and 50000). The priority for each ListenerRule must be unique.
I need to deploy the same template multiple times. The Priority for the ListenerRule should change every time I launch the template.
At the moment, I have turned the Priority into a parameter you can set when launching the stack and this works fine. Is there a way I can automatically set the priority of the ListenerRule to the next available priority?
No it's currently not possible to have it automatically allocated using only the AWS::ElasticLoadBalancingV2::ListenerRule
resource. However, it can be achieved using a custom resource.
First let's create the actual custom resource Lambda code.
allocate_alb_rule_priority.py:
import json
import os
import random
import uuid
import boto3
from botocore.vendored import requests
SUCCESS = "SUCCESS"
FAILED = "FAILED"
# Member must have value less than or equal to 50000
ALB_RULE_PRIORITY_RANGE = 1, 50000
def lambda_handler(event, context):
try:
_lambda_handler(event, context)
except Exception as e:
# Must raise, otherwise the Lambda will be marked as successful, and the exception
# will not be logged to CloudWatch logs.
# Always send a response otherwise custom resource creation/update/deletion will be stuck
send(
event,
context,
response_status=FAILED if event['RequestType'] != 'Delete' else SUCCESS,
# Do not fail on delete to avoid rollback failure
response_data=None,
physical_resource_id=uuid.uuid4(),
reason=e,
)
raise
def _lambda_handler(event, context):
print("Received event: " + json.dumps(event, indent=2))
physical_resource_id = event.get('PhysicalResourceId', str(uuid.uuid4()))
response_data = {}
if event['RequestType'] == 'Create':
elbv2_client = boto3.client('elbv2')
result = elbv2_client.describe_rules(ListenerArn=os.environ['ListenerArn'])
in_use = list(filter(lambda s: s.isdecimal(), [r['Priority'] for r in result['Rules']]))
priority = None
while not priority or priority in in_use:
priority = str(random.randint(*ALB_RULE_PRIORITY_RANGE))
response_data = {
'Priority': priority
}
send(event, context, SUCCESS, response_data, physical_resource_id)
def send(event, context, response_status, response_data, physical_resource_id, reason=None):
response_url = event['ResponseURL']
response_body = {
'Status': response_status,
'Reason': str(reason) if reason else 'See the details in CloudWatch Log Stream: ' + context.log_stream_name,
'PhysicalResourceId': physical_resource_id,
'StackId': event['StackId'],
'RequestId': event['RequestId'],
'LogicalResourceId': event['LogicalResourceId'],
'Data': response_data,
}
json_response_body = json.dumps(response_body)
headers = {
'content-type': '',
'content-length': str(len(json_response_body))
}
try:
requests.put(
response_url,
data=json_response_body,
headers=headers
)
except Exception as e:
print("send(..) failed executing requests.put(..): " + str(e))
According to your question, you need to create multiple stacks with the same template. For that reason I suggest the Custom Resource is placed within a template that is deployed only once. Then have the other template import its ServiceToken
.
allocate_alb_rule_priority_custom_resouce.yml
:
Resources:
AllocateAlbRulePriorityCustomResourceLambdaRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: ''
Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
Path: /
Policies:
- PolicyName: DescribeRulesPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- elasticloadbalancing:DescribeRules
Resource: "*"
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
AllocateAlbRulePriorityCustomResourceLambdaFunction:
Type: AWS::Lambda::Function
Properties:
Handler: allocate_alb_rule_priority.lambda_handler
Role: !GetAtt AllocateAlbRulePriorityCustomResourceLambdaRole.Arn
Code: allocate_alb_rule_priority.py
Runtime: python3.6
Timeout: '30'
Environment:
Variables:
ListenerArn: !Ref LoadBalancerListener
Outputs:
AllocateAlbRulePriorityCustomResourceLambdaArn:
Value: !GetAtt AllocateAlbRulePriorityCustomResourceLambdaFunction.Arn
Export:
Name: AllocateAlbRulePriorityCustomResourceLambdaArn
You can notice that we're passing a ListenerArn to the Lambda function. It's because we want to avoid priority number collision on new allocation.
Lastly, we can now use our new custom resource in the template that is meant to be deployed multiple times.
template_meant_to_be_deployed_multiple_times.yml
:
AllocateAlbRulePriorityCustomResource:
Type: Custom::AllocateAlbRulePriority
Condition: AutoAllocateAlbPriority
Properties:
ServiceToken:
Fn::ImportValue: AllocateAlbRulePriorityCustomResourceLambdaArn
ListenerRule:
Type: AWS::ElasticLoadBalancingV2::ListenerRule
Properties:
Priority: !GetAtt AllocateAlbRulePriorityCustomResource.Priority
[...]
These are snippets and may not work as-is, although they were taken from working code. I hope it gives you a general idea of how it can be achieved. Let me know if you need more help.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With