Package and deploy a PowerShell Lambda function with custom modules

Recently I had the need to create a Lambda function with PowerShell 7. The function is to synchronize data between two REST APIs. It's fairly simple, but does need to use a custom made module. I spent quite bit time to find out how to deploy PowerShell Lambdas with custom modules. Thought might write a guide to help people want to do the same.   

My script is fairly simple, it get a list of users from one API and then convert it to a XML format object and export into the target API. To reuse some of the code, The script requires AWS.Tools module as well as a custom made module, let's called it CAT. The CAT is module contians some functions that are invoked in by the main script. The inital lines of my main script looks like below:
#Requires -Modules @{ModuleName='AWS.Tools.Common';ModuleVersion='4.0.5.0'}
#Requires -Modules @{ModuleName='AWS.Tools.SecretsManager';ModuleVersion='4.0.5.0'}
#Requires -Modules CAT 
In addition to install the AWS.Tools modules, I manually copied my CAT module folder to the $Env:PSModulePath.

The nex thing is to package the code into a zip file along with the required modules. To do so, install AWSLambdaPSCore module and run this PowerShell command:
New-AWSPowerShellLambdaPackage -ScriptPath "../functions/FunctionName.ps1" -outputPackage "FunctionName.zip"
You should see the script and modules are all packaged as shown below.
Staging deployment at C:\Users\tom\AppData\Local\Temp\myfunction
Configuring PowerShell to version 7.0.0
Generating C# project C:\Users\tom\AppData\Local\Temp\myfunction\myfunction.csproj used to create Lambda function bundle.
Generating C:\Users\tom\AppData\Local\Temp\myfunction\Bootstrap.cs to load PowerShell script and required modules in Lambda environment.
Generating aws-lambda-tools-defaults.json config file with default values used when publishing project.
Copying PowerShell script to staging directory
Copying local module AWS.Tools.Common(4.0.5.0) from C:\Users\tom\OneDrive\Documents\PowerShell\Modules\AWS.Tools.Common\4.0.5.0
Copying local module AWS.Tools.SecretsManager(4.0.5.0) from C:\Users\tom\OneDrive\Documents\PowerShell\Modules\AWS.Tools.SecretsManager\4.0.5.0
Copying local module CAT(0.0.2) from C:\program files\powershell\7\Modules\CAT
Resolved full output package path as C:\Users\tom\Dropbox\github\myproject\deploy\myfunction.zip
Creating deployment package at C:\Users\tom\Dropbox\github\myproject\deploy\myfunction.zip
Restoring .NET Lambda deployment tool
Initiate packaging
When deploying this package to AWS Lambda you will need to specify the function handler. The handler for this package is: myfunction::myfunction.Bootstrap::ExecuteFunction. To request Lambda to invoke a specific PowerShell function in your script specify the name of the PowerShell function in the environment variable AWS_POWERSHELL_FUNCTION_HANDLER when publishing the package.
LambdaHandler                                         PathToPackage                                                         PowerShellFunctionHandlerEnvVar
-------------                                         -------------                                                         -------------------------------
myfunction::myfunction.Bootstrap::ExecuteFunction C:\Users\tom\Dropbox\github\myproject\deploy\myfunction.zip AWS_POWERSHELL_FUNCTION_HANDLER
The steps below are not required if you have a CICD pipeline built up like me. As soon as I push my updated code and package to GitHub, the pipeline will automatically upload the package and deploy it. But if don't, read on! 

The next step is to create a S3 bucket to host your package. You can skip this step if you already have a designated bucket for such purpose.

After created the bucket, we will then create a SAM template. SAM template is basically a transformed CloudFormation template. It allows you to add all the neccessary bits like Events and DeadLetterQueue under AWS::Serverless::Function resource. Here's a simple example. Pay close attention to CodeUri. Notice it's pointing to the local zip file instead of a S3 URL? This is by purpose. I will explain in next step.
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Resources:
  PopCycler:
    Type: 'AWS::Serverless::Function'
    Properties:
      Handler: myfunction::myfunction.Bootstrap::ExecuteFunction
      Runtime: dotnetcore3.1
      CodeUri: './myfunction.zip'
      Role:
        Fn::GetAtt:
        - LambdaRole
        - Arn
      Environment:
        Variables:
          ENV: dev
          CATURL: https://dev.CAT.com
          CATAPPID: Abc12345
          CATSECRETID: CAT-api-key
          VENDORSECRETID: myproject
      Events:
        ScanInterval:
          Type: Schedule
          Properties:
            Schedule: 'rate(1 day)'
      Timeout: 300
      MemorySize: 256
      Tags:
        owner: tom
  LambdaRole:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: myproject-lambdarole
      Tags:
        - 
          Key: 'owner'
          Value: 'myteam'
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      Policies:
        - PolicyName: MyProjectPolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: secretsmanager:GetSecretValue
                Resource:
                  - Fn::Sub: 'arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:CAT-api-key'
              - Effect: Allow
                Action: kms:Decrypt
                Resource:
                  - Fn::Sub: 'arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:CAT-api-key'
After created the template, run the AWS CLI command below. What it does is simply upload the zip file to the specified S3 bucket (myBucket in my example) and update CodeUri in your SAM template with the actual S3 URI path to the zip file. Then output a new template for you to use in the next step. You can open up the SAM template updated.yml to confirm. 
aws cloudformation package --template-file myfunction.yml --s3-bucket mybucket --output-template-file updated.yml
The last step is to deploy the Lambda function from updated.yml
aws cloudformation deploy --stack-name myfunctionstack --template-file updated.yml --capabilities CAPABILITY_AUTO_EXPAND CAPABILITY_NAMED_IAM
That's it folks! I know this isn't as straight forward in comparison to Python and Node.Js runtimes. But as I mentioned before, with a CICD system in place, you should be able to automate the whole deploy process fairly easily. Hope you find it useful!

 



Comments

  1. I unquestionably appreciate every last piece of it. It is an incredible site and has a decent offer. I need to much obliged. Great job! You all do an extraordinary blog, and have some incredible substance about powershell. Keep doing awesome. oracle fusion scm online training

    ReplyDelete

Post a comment

Popular posts from this blog

Install AWS CLI on WSL Ubuntu

On Premise Mailbox user missing in Exchange Online GAL

Migrate Azure AD Connect Between AD Forests