To fail, or not to fail, that is the question

Have you ever pushed a commit to your CI server, waiting to see if all your tests pass only to discover an earlier step like linting failed before you ever got your answer?

This is super frustrating and has probably led to the death of more than a couple CI initiatives.

You can do something to make this a bit less painful.

Most build servers have some kind of flag to allow a pipeline to continue running even if an individual step fails.

This is how I allowed some steps to fail without immediately breaking the build using GitHub Actions.

Each step has a continue-on-error property.

- name: Lint
  id: lint
  continue-on-error: true
  run: yarn lint

Now even if the linting step fails, the following steps will run.

Unfortunately, GitHub does not show this well on the UI, and if nothing else fails, the job will succeed.

So we now need to make sure that the job correctly shows the failure, and we need to give feedback to the user about our step that failed.

First, let’s fail the job.

- name: Check linting
  if: steps.lint.outcome != 'success'
  uses: actions/github-script@v3
  with:
    script: |
      core.setFailed('The Linting step failed!')

Next, let’s write a message to the PR notifying the user of the failure. I use a service account for this, aka a GitHub account only used for programmatically doing stuff in GitHub Actions. If you want to just use the built-in GitHub token, you’ll need to adjust the permissions.

- name: Update Pull Request
  uses: actions/github-script@0.9.0
  if: github.event_name == 'pull_request'
  env:
    LINT: "linting\n${{ steps.lint.outputs.stdout }}"
  with:
    github-token: ${{ secrets.SERVICE_ACCOUNT_TOKEN }}
    script: |
      const output = `#### Linting\`${{ steps.lint.outcome }}\`

      <details><summary>Show linting output</summary>

      \`\`\`\n
      ${process.env.LINT}
      \`\`\`

      </details>`;

      github.issues.createComment({
        issue_number: context.issue.number,
        owner: context.repo.owner,
        repo: context.repo.repo,
        body: output
      })

These code snippets are licensed under the Unlicense. Go forth and have fun!

GitHub Action Configuration Variables

Last week GitHub released a feature I’ve wanted for a long time, configuration variables in GitHub Actions.

Today, while I was pairing with a coworker, I got an excuse to use configuration variables, which made what we were working on much more straightforward.

GitHub Actions has the concept of environments, which is super handy since we set up a new app to deploy to different environments.

GitHub environments offer various options, including requiring approvals from users before deploying to that environment, associating branches with that environment, and associating secrets and configuration variables with that environment.

In the past, I used secrets to store information like what S3 bucket I wanted to use for a particular environment. This worked great since it abstracted the knowledge of where to push files from the pipelines and let me change settings without touching the pipeline files.

What was challenging about this approach was that GitHub treated every secret as if it was a secret that needed to be removed from logs and never visible after setting it.

Today we used a configuration variable to store the S3 bucket, and we could see the value while looking at the environment. We could also see the value in the logs where it showed us the full path as we did an s3 copy.

This might not seem too important, but it was critical because I typed the wrong bucket name when setting the new variable. We wouldn’t have noticed if it had been a secret until we ran the pipeline and it failed. Instead we noticed before hand, quickly corrected the value, and watched out deploy succeed.

If you’d like to start using configuration variables in your pipelines check out the docs Creating configuration variables for an environment.