sorenandersen.com

Automating test and deployment of serverless projects with GitHub Actions

September 17, 2020

Last week’s “Off-by-none” serverless newsletter featured a link to automating application deployment using GitHub Actions. It’s a great run-through of what needs to be set up in order to do AWS deployments from a GitHub workflow.

Inspired by Rajan’s post I decided to build a demo project of my own, in Node.js and with the Serverless Framework as the IaC- and deployment tool. I also wanted to expand the scope a little by introducing automated tests so that the resulting workflow resembles a basic but complete CI/CD pipeline, running solely on GitHub.

So I put together an example project with a single Lambda backed by an integration test. This implies invoking the handler function from code and letting it interact with real services, e.g. via the AWS SDK, to validate that our use of downstream services is working as expected.

The testing framework is Jest and a basic test looks like this:

describe(`Invoking endpoint GET /`, () => {
  it(`Should return a message`, async () => {
    const response = await invokeFunction('index', {})
    expect(response.statusCode).toEqual(200)
    expect(response.body).toHaveProperty('message')
  })
})

GitHub workflow file

The workflow file, after installing Node.js in the runner environment, simply exercises a few npm and serverless commands.

Remember that by installing the Serverless Framework CLI as a dev dependency we get to use this great framework without risking incompatible versions between environments? Another reason the npm run sls -- deploy script comes in handy is that it reduces the number of steps in our workflow jobs. Node.js, with npm, is all the runner needs to build, test and deploy our serverless project.

Now, as jobs in a worflow by default run in parallel, it’s important to make the deploy-to-staging job depend on integration-test to succeed by using the needs keyword. This will make the jobs run sequentially as well as skip deployment if the test job does not complete successfully.

Tbe gist of the workflow file is as follows:

... on push and pull request on master

env:
  AWS_REGION: us-east-1
  AWS_STAGE_CI_TEST: ci-test
  AWS_STAGE_STAGING: staging
  NODE_VERSION: "12"

jobs:
  integration-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - uses: actions/setup-node@v1
        with:
          node-version: ${{ env.NODE_VERSION }}

      - name: Install dependencies
        run: npm ci

      - name: Deploy to stage "${{ env.AWS_STAGE_CI_TEST }}"
        run: npm run sls -- deploy --region $AWS_REGION --stage $AWS_STAGE_CI_TEST
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

      - name: Run integration tests
        run: npm run integration-test

      ... remove from AWS_STAGE_CI_TEST

  deploy-to-staging:
    needs: integration-test
    runs-on: ubuntu-latest
    steps:
      ... checkout, setup-node, install deps like above

      - name: Deploy to stage "${{ env.AWS_STAGE_STAGING }}"
        run: npm run sls -- deploy --region $AWS_REGION --stage $AWS_STAGE_STAGING
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

The referenced secrets are encrypted environment variables created in the repository or organization.

You can find the complete example here.

References

Testing techniques learned from attending Yan Cui’s “Production-Ready Serverless” workshop. I really recommend this workshop for anyone wanting to up their AWS serverless skills.


Søren Andersen

Digital garden of Søren Andersen.
Posts on tech that I use and learn.