CloudFormation - Access Parameter from Lambda Code

2019-02-25 01:29发布

问题:

I have a CloudFormation template that looks like this:

{
    "AWSTemplateFormatVersion": "2010-09-09",
    "Description": "This template will deploy stuff",
    "Parameters":{
    "myParamToLambdaFunction" : {
        "Description" : "Please enter the the value",
        "Type" : "String",
        "ConstraintDescription" : "must have some value."
    }
},
"Resources": {
    "SecGrpValidatorFromCFTemplate": {
        "Type": "AWS::Lambda::Function",
        "Properties": {
            "FunctionName": "mylambdafunctionname",
            "Handler": "myfile.lambda_handler",
            "Role": {
                "Fn::GetAtt": ["somerole", "Arn"]
            },
            "Timeout": "30",
            "Runtime": "python2.7",
            "Code": {
                "S3Bucket":"mybucket",
                "S3Key":"mylambdafunction.zip"
            }
        }
    }
}

I need to pass the value of myParamToLambdaFunction to the Lambda function.

Is there a way to do so?

回答1:

As of 18 Nov 2016, AWS Lambda now supports environment variables, which CloudFormation supports through the Environment property on the AWS::Lambda::Function resource.

Add an Environment property to your resource like this:

{
    "AWSTemplateFormatVersion": "2010-09-09",
    "Description": "This template will deploy stuff",
    "Parameters":{
    "myParamToLambdaFunction" : {
        "Description" : "Please enter the the value",
        "Type" : "String",
        "ConstraintDescription" : "must have some value."
    }
},
"Resources": {
    "SecGrpValidatorFromCFTemplate": {
        "Type": "AWS::Lambda::Function",
        "Properties": {
            "FunctionName": "mylambdafunctionname",
            "Handler": "myfile.lambda_handler",
            "Role": {
                "Fn::GetAtt": ["somerole", "Arn"]
            },
            "Timeout": "30",
            "Runtime": "python2.7",
            "Code": {
                "S3Bucket":"mybucket",
                "S3Key":"mylambdafunction.zip"
            },
            "Environment": {
                "Variables": {
                    "myParam": {
                        "Ref": "myParamToLambdaFunction"
                    }
                }
            }
        }
    }
}

Then reference the environment variable from your deployed Lambda function according to your runtime platform (e.g., os.environ['myParam'] in Python).



回答2:

Once you create the lambda function as you did in your template, you can define a Lambda-backed custom resource to invoke the function with custom parameters and also you can process the response coming from lambda. See https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources-lambda.html for more information and examples.



回答3:

If your lambda is not too complex, you can inline it in your template instead of relying on an uploaded zip file.

Here's one that I did that uses an input parameter:

"CopyLogsStreamToFirehose": {
  "Type": "AWS::Lambda::Function",
  "Properties": {
    "Code": {
      "ZipFile": {
        "Fn::Join": [
          "",
          [
            "import base64\n",
            "import boto3\n",
            "def on_new_kinesis_data(event, context):\n",
            "   client = boto3.client('firehose')\n",
            "   records = [ {'Data': base64.b64decode(r['kinesis']['data']) + '\\n' } for r in event['Records']]\n",
            "   client.put_record_batch(DeliveryStreamName='",
            {
              "Ref": "LogsDeliveryStream"
            },
            "', Records=records)\n",
            "   print 'Successfully processed {} records.'.format(len(event['Records']))\n"
          ]
        ]
      }
    },
    "Description": "Kinesis Stream to Firehose Stream",
    "Handler": "index.on_new_kinesis_data",
    "Role": {
      "Fn::GetAtt": [
        "CopyLogsStreamToFirehoseRole",
        "Arn"
      ]
    },
    "Runtime": "python2.7",
    "Timeout": 5
  }


回答4:

This is where Lambda needs environment variables. It's been a huge issue for my work. I've tried two approaches.

1 - Your lambda function name will contain your stack name. Use some splitting, extract that value e.g var stackName = context.functionName.split('-')[0];, then call describeStacks to retrieve your stack. Pull out your required data from outputs/parameters from there. This works fine, but note that querying CloudFormation stacks has a fairly modest limit. If your lambda is firing rapidily, option 2 will work better for you.

2 - Manually output your endpoints into a DynamoDB table with the stack name as the key (a file in S3 could work too, depending on your latency requirements) and query that for your environment data. If you really wanted to you could automate this stack data output using a custom cloudformation resource.



回答5:

Whatever calls your lambda function can pass metadata to a lambda function when it is invoked.

For example, with an auto-scaling group, perhaps you want to call a lambda function when an instance launches or is terminated. In this case, the AWS::AutoScaling::LifecycleHook resource includes NotificationMetadata which contains a dictionary with one item, Route53ZoneId:

"MyLifecycleHook": {
  "Type": "AWS::AutoScaling::LifecycleHook",
  "Properties": {
    "AutoScalingGroupName": { "Ref": "MyASG" },
    "LifecycleTransition": "autoscaling:EC2_INSTANCE_LAUNCHING",
    "NotificationMetadata": { "Fn::Join": [ "", [
      "{",
      "  \"Route53ZoneId\": \"YOUR_ROUTE53_ZONE_IDENTIFIER_HERE\"",
      "}"
    ] ] },
    "NotificationTargetARN": { "Ref": "MyTopic" },
    "RoleARN": { "Fn::GetAtt": [ "MyLifecycleHookRole", "Arn" ] }
  }
}

Then in your lambda handler, assuming you're writing it in Python, you can access variables in that dictionary, for example:

def handler(event, context):
    message = json.loads(event[u'Records'][0][u'Sns'][u'Message'])
    metadata = json.loads(message['NotificationMetadata'])

    logger.info("Route53 Zone Identifier {0}".format(metadata['Route53ZoneId']))


回答6:

If your Lambda function is behind API Gateway, you can use RequestTemplates to modify the request before it reaches the function. The configuration below would send the original request body along with the parameter defined as YourCloudformationParameter:

"RequestTemplates": {
  "application/json": {
    "Fn::Join": [
      "",
      [
        "{",
          "\"body\": $input.json('$'),",
          "\"env\": {",
            "\"yourCloudformationParameter\": \"", { "Ref": "YourCloudformationParameter" }, "\"",
          "}",
        "}"
      ]
    ]
  }
},