Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Automatically set ListenerRule Priority in CloudFormation template

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?

like image 262
demonicdaron Avatar asked Apr 24 '18 13:04

demonicdaron


1 Answers

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.

like image 129
Laurent Jalbert Simard Avatar answered Oct 14 '22 19:10

Laurent Jalbert Simard