====== Lambda based Lambda Layer generator ====== This idea follows on from [[aws:lambda_layers|Lambda Layers]] created from a BASH script. Simply put, it doesn't seem like a cloud native way of working. Spinning up either a Linux VM (if you are stuck using windows)or ec2 instance just to build a Lambda layer is an overkill and probably difficult to build with regard to running and automating. I had the idea to use Lambda to build and import the layer, it could be triggered regularly from CloudWatch events and to allow the layer to be used, it writes the Layer ARN to Parameter Store. import logging import os import shutil import sys import subprocess import boto3 logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) # Example test event:- ''' { "package_name": "boto3", "layer_description": "Lambda Layer for S3 Additional Checksum.", "parameter_store_layer_name": "boto3_lambda_layer", "parameter_store_description":"ARN for boto3 lambda layer" } ''' def get_latest_version(package): # The -m flag makes sure that you are using the pip that's tied to the active Python executable. # sys.executable is the absolute path to the executable that your program was invoked with. # "dummy" forces an error which lists all the available versions. latest_version = str( subprocess.run([sys.executable, '-m', 'pip', 'install', '{}==dummy'.format(package)], capture_output=True, text=True)) # remove leading text and trailing parenthesis latest_version = latest_version[latest_version.find('(from versions:') + 1:] latest_version = latest_version[:latest_version.find(')')] # remove spaces, split on comma and select last item latest_version = latest_version.replace(' ', '').split(',')[-1] return latest_version def lambda_handler(event, context): logger.info("Event Data:- %s", event) package_name = event['package_name'] layer_description = event['layer_description'] parameter_store_layer_name = event['parameter_store_layer_name'] parameter_store_description = event['parameter_store_description'] latest_version = get_latest_version(package_name) package = package_name + '_' + latest_version install_path = os.path.join('/tmp/', package) logger.info("Install Path:- %s", install_path) os.mkdir(install_path) # run pip and tell it to use /tmp as /tmp in lambda function as it is the only writable path installresponse = subprocess.run(["pip", "install", package, "-t", install_path], capture_output=True, text=True) logger.info("Start ZIP process.") shutil.make_archive("/tmp/" + package_name + latest_version, 'zip', install_path) logger.info("End ZIP process.") # This sets the variables to push layer to AWS zipfilename = package_name + latest_version + ".zip" zipfilepath = os.path.join('/tmp/', zipfilename) logger.info("zipfile and full name:- %s", zipfilename, zipfilepath) # Can only use underscores as "." is not allowed by AWS here latest_version = latest_version.replace(".", "_") logger.info("version (with underscore replacement):- %s", latest_version) # Open zip file for 'r'eading as a 'b'yte stream (rb) with open(zipfilepath, 'rb') as zf: zipbytes = zf.read() logger.info("publish layer version - zip package") lambda_client = boto3.client('lambda') response = lambda_client.publish_layer_version( CompatibleRuntimes=[ 'python3.7', 'python3.8', 'python3.9', ], Content={ 'ZipFile': zipbytes, }, Description=layer_description, LayerName=package_name + '_' + latest_version, LicenseInfo='MIT', ) logger.info("LayerArn ", response['LayerArn']) logger.info("LayerVersionArn ", response['LayerVersionArn']) # Write ARN out to Parameter Store. ssm_client = boto3.client("ssm") ssmresponse = ssm_client.put_parameter( Name=parameter_store_layer_name, Description=parameter_store_description, Value=response['LayerVersionArn'], Type='String', Overwrite=True, Tier='Standard', DataType='text') logger.info("ssm response:- %s", ssmresponse) layerpublishpermission.json { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": "lambda:PublishLayerVersion", "Resource": "arn:aws:lambda:*:456712349876:layer:*" } ] } ssm_parameter_permission { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ssm:PutParameter", "ssm:DescribeParameters" ], "Resource": "*" } ] }