Automating Lambda Deployment With Travis

TCI-Graphics for AdsBlogs (4)

AWS Lambda is a serverless, event-driven compute service that lets you run code for virtually any type of application or backend service without provisioning or managing servers. You can trigger Lambda from over 200 AWS services and software as a service (SaaS) applications, and only pay for what you use. What if you wanted to automate some of these processes in Travis? Well in this short read I’ll let you know how in this short and sweet blog.

Permissions

First, let’s create an AWS Role that has the following permissions/conditionals:

{
    "Version": "2021-12-03",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "lambda:*"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

Remember when dealing with servies, each service should have a basic handler with the following code, at least in my project a (TypeScript) project:

'use strict';
module.exports.hello = async event => {
  return {
    statusCode: 200,
    body: JSON.stringify(
      {
        message: 'Go Serverless v1.0! Your function executed successfully!',
        input: event,
      },
      null,
      2
    ),
  };
// Use this code if you don't use the http event with the LAMBDA-PROXY integration
  // return { message: 'Go Serverless v1.0! Your function executed successfully!', event };
};

Your Travis config

You now then have most of your permissions locked down, you’ll need some of them but in this case it’s a bit more secure. Grab your ACCESS_KEY for this role in AWS. Now let’s focus on the .travis.yml file:

In this example, I have a TypeScript project, you’ll notice I’m using anchoring in the YAML syntax, which isn’t used very often, but it’s the superior way to setup your YAML file, I talk about anchoring in another blog post here.

language: node_js
node_js:
  - "10"
deploy_service_job: &DEPLOY_SERVICE_JOB
  cache:
    directories:
      - node_modules
      - ${SERVICE_PATH}/node_modules
install:
    - npm install -g serverless
    - travis_retry npm install
    - cd ${SERVICE_PATH}
    - travis_retry npm install
    - cd -
   
script:
    - cd ${SERVICE_PATH}
    - serverless deploy -s ${STAGE_NAME}
    - cd -
environments:
  - &PRODUCTION_ENV
    - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID_PRODUCTION}
    - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY_PRODUCTION}
- &UAT_ENV
    - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID_UAT}
    - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY_UAT}
- &DEVELOPMENT_ENV
    - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID_DEVELOPMENT}
    - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY_DEVELOPMENT}
jobs:
  include:
    - <<: *DEPLOY_SERVICE_JOB
      name: "Deploy Users API"
      if: type = push AND branch = develop
      env:
        - SERVICE_PATH="users-api"
        - STAGE_NAME=dev
        - *DEVELOPMENT_ENV
    - <<: *DEPLOY_SERVICE_JOB
      name: "Deploy Test Experiences API"
      if: type = push AND branch = develop
      env:
        - SERVICE_PATH="todo-api"
        - STAGE_NAME=dev
        - *DEVELOPMENT_ENV
# Push @ the UAT branch that deploys to the 'uat' stage
    - <<: *DEPLOY_SERVICE_JOB
      name: "Deploy Test Users API"
      if: type = push AND branch = uat
      env:
        - SERVICE_PATH="montana-api"
        - STAGE_NAME=uat
        - *UAT_ENV
    - <<: *DEPLOY_SERVICE_JOB
      name: "Deploy Test Experiences API"  # I used Regex here
      if: "((branch IN (master, develop) && type = push) OR branch =~ /.*env.*/ OR commit_message
    =~ /\\[recreate env\\]/) AND commit_message !~ /\\[delete env\\]/ AND type !=
    cron AND commit_message !~ /\\[execute .*. test\\]/ AND commit_message !~ /\\[start
    recreate scheduler\\]/"
      env:
        - SERVICE_PATH="todo-api"
        - STAGE_NAME=uat
        - *UAT_ENV
# To the master branch deploys to the 'prod' stage
    - <<: *DEPLOY_SERVICE_JOB
      name: "Deploy Test Users API"
      if: type = push AND branch = master
      env:
        - SERVICE_PATH="users-api"
        - STAGE_NAME=prod
        - *PRODUCTION_ENV
    - <<: *DEPLOY_SERVICE_JOB
      name: "Deploy Test Experiences API"
      if: type = push AND branch = master
      env:
        - SERVICE_PATH="todo-api"
        - STAGE_NAME=prod
        - *PRODUCTION_ENV

Encrypting the AWS Key ID

Let’s encrypt our AWS Key ID to the root file. We’ll also want to add our secret key, but we want to encrypt it. So we’ll use the Travis CLI:

travis encrypt AWS_SECRET_ACCESS_KEY=YOUR_SECRET_ACCESS_KEY --add env.global

So we are all set on the Travis end, now we want to build and upload:

.MONTANAPHONY: build clean upload
build:
	npm install --only=production
	zip -r code.zip . -x *.git*

clean:
	if [ -a code.zip ]; then rm code.zip; fi

upload: build
	./montana.sh

Here is the source of the file entitled montana.sh:

#!/bin/bash
aws lambda update-function-code \
--zip-file=fileb://code.zip \
--region=us-east-1 \
--function-name=NAME_OF_YOUR_LAMBDA
echo "Deployed"

Now everytime you push to master it should update your Lambda to the newest release.

Conclusion

There you have it, it was short and sweet, the .travis.yml file was a bit complex, but it’s a good example for the concept at hand. Remember if you have any questions, any questions at all, please email me at montana@travis-ci.org.

Happy building!