/cicd

CI/CD for Cloudflare Workers

In my previous article I showed how to use Cloudflare workers to add custom HTTP headers onto your site to make it compliant with security expectations.

In this instalment I'm going to add onto that a CI/CD pipeline to test and deploy future changes to that worker.

CI/CD?

CI/CD stands for 'continuous integration' & 'continuous deployment'. It is a methodology that says every time a developer commits code we should build it, test it and if the tests pass (the pipeline is 'green') we should deploy it to production (with confidence).

Typically a CI/CD pipeline would consist of the following stages:

  1. Unit Tests
  2. Build & Package
  3. Deploy to Test environment
  4. Functional Tests
  5. Non-functional Tests e.g. performance
  6. Deploy to Production
  7. Smoketest

Not every step is needed every time, but there are some common steps you'll do most times including Unit Tests, Deployments and Functional Tests.

Our pipeline will be simple:

  1. Deploy the worker to a workers.dev subdomain
  2. Test that the headers are coming as expected
  3. Deploy the worker to the production route
  4. Check the site is loading (returning a HTTP 2xx code)

Environments

In the previous article we had a single environment (the production site), but here we are adding a second. This means modifying your wrangler.toml file as follows:

name = "name-of-worker"
type = "javascript"
account_id = "..."
workers_dev = true


[env.production]
route = "..."
zone_id = "..."

Note if you have previously deployed to production without calling it production your first deployment after adding the 'env.production' block will fail with a duplicate route error. You will need to manually delete that route in order for subsequent deployments to be successful.

Of course your account_id, route and zone_id will be populated with meaningful values for your setup. This will give you two environments a 'dev' or 'test' environment and production.

You deploy to dev like this:

wrangler publish

You deploy to production like this:

wrangler publish --env production

Testing

Now we know how to deploy, we need to add some tests to make sure that the deployment to workers.dev has been successful. We can do this test by making a HTTP GET to the worker and checking the headers that are set on the response.

I've published my code for this here https://rjk.xyz/RYRX it uses the axios library so make sure it is installed in your project

npm install --save axios

Looking at the code you'll notice it is a bit hacky, this is because Cloudflare returns an origin error when this request is made (as there is no origin to return a response). However this can still work for us, as the error object still shows us the headers set on the response and that is all we need to make sure the worker is working properly. All you need to do is edit the expectedHeaders array to match your needs:

const expectedHeaders = [
  {
    name: "Strict-Transport-Security",
    value: "max-age=63072000"
  },
  {
    name: "X-Frame-Options",
    value: "DENY"
  },
  {
    name: "X-Content-Type-Options",
    value: "nosniff"
  }, 
  {
    name: "X-XSS-Protection",
    value: "1; mode=block"
  },
  {
    name: "Referrer-Policy",
    value: "strict-origin-when-cross-origin"
  }
]

The script will exit(0) on success or exit(1) on failure, printing the reason why it failed.

There is a feature in the pipeline wrangler dev which will allow you to run a local worker and test against 'localhost'. Once that becomes available I'll update this script. There are more details on this upcoming feature here https://github.com/cloudflare/wrangler/milestone/18

Putting it in a pipeline

I'm using GitHub actions to pull all this together. My workflow file looks like this

name: Build and Deploy Worker

on: [push]

jobs:
  build:

    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [12.x]

    steps:
    - uses: actions/checkout@v1
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v1
      with:
        node-version: ${{ matrix.node-version }}
    - name: install wrangler
      run: npm install -g @cloudflare/wrangler
    - name: deploy to dev
      run: CF_ACCOUNT_ID=${{ secrets.CF_ACCOUNT_ID }} CF_API_TOKEN=${{ secrets.CF_API_TOKEN }} wrangler publish
    - name: install and run tests
      run: |
        cd tests/
        npm ci
        node index.js
    - name: production deployment
      run: CF_ACCOUNT_ID=${{ secrets.CF_ACCOUNT_ID }} CF_API_TOKEN=${{ secrets.CF_API_TOKEN }} wrangler publish --env production

To make it work you will need your tests in a folder called tests/. If those tests fail (exit with anything other than code=0) then the whole workflow will fail and the production deployment will not happen.

Outcome

Now whenever I commit code to my cloudflare worker repository it is deployed to the workers.dev subdomain, tested and if those tests pass it is deployed to production.

-- Richard, Jan 2020