Automate GitHub Releases With CircleCI

Releases is a GitHub feature that allows you to present significant snapshots of your code, marked with a git tag, in GitHub’s nice UI. If you’re not currently using releases, I want to show you why you might want to, and how to implement them automatically.

With releases, you get what tags provide–a version number and description–but you also get a longer section for release notes and a place to store and display release artifacts. This means your software’s binary, .deb, .rpm, and AppImage files will be hosted by GitHub for each release, providing a convenient place for users to install your software.

In this post, I will show you how to create releases within CircleCI. For a more general overview, see GitHub’s doc on creating releases.

At its core, GitHub Releases is simply a GitHub feature layered on top of git tags. Let’s break it down:

Git Tags

Git tags give you a version number to mark a specific git commit as well as a short description. Believe it or not, just pushing a git tag to GitHub will create a new release in the “Releases” tab of your project on GitHub. This is a barebones release that includes only the tag information.

Releases

Releases add to git tags by providing a longer, “rich” description, the ability to mark the tag as a normal release or a pre-release, and most importantly, the ability to upload artifacts such as binaries for that release. Uploading these artifacts from CircleCI is what we’re going to walk through here.

Tools

There are a few ways to publish artifacts from a CircleCI build to a GitHub Release:

Using ghr

The ghr command full details can be found on GitHub, however, we really only need to learn one subcommand:

ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -delete ${VERSION} ./artifacts/


This command uploads specific artifacts, typically binaries, to our GitHub release on GitHub. Let’s break this down:

Specifically created via your personal GitHub account, you’ll want to safeguard this token and only load it into the CircleCI environment via a private environment variable.

Plain CircleCI 2.0 Example

We’ve walked through how to use ghr to create a GitHub Release but let’s see how it can look inside of a CircleCI 2.0 configuration file:

  publish-github-release:
    docker:
      - image: circleci/golang:1.8
    steps:
      - attach_workspace:
          at: ./artifacts
      - run:
          name: "Publish Release on GitHub"
          command: |
            go get github.com/tcnksm/ghr
            VERSION=$(my-binary --version)
            ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -delete ${VERSION} ./artifacts/


CI Builds CircleCI 2.0 Example

This is a CircleCI job called publish-github-release that uses a CircleCI Go convenience image. Here’s the breakdown:

We can shave a few seconds off of the job above by using the ghr Docker image from CI Builds. It’s a lightweight Docker image that already has ghr installed. Here’s how the new example config would look:

  publish-github-release:
    docker:
      - image: cibuilds/github:0.10
    steps:
      - attach_workspace:
          at: ./artifacts
      - run:
          name: "Publish Release on GitHub"
          command: |
            VERSION=$(my-binary --version)
            ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -delete ${VERSION} ./artifacts/


Workflow Example

Here’s an example of how one of the variations of the above job can be implemented within a Workflow to publish tagged commits to GitHub releases:

workflows:
  version: 2
  main:
    jobs:
      - build:
          filters:
            tags:
              only: /^\d+\.\d+\.\d+$/
      - publish-github-release:
          requires:
            - build
          filters:
            branches:
              ignore: /.*/
            tags:
              only: /^\d+\.\d+\.\d+$/


In this example, we have CircleCI kick off the publish-github-release job when a git tag is pushed. How exactly you choose when to publish a GitHub release is up to you, but here we’ll do it with a SemVer-like git tag. We say SemVer-like because the tag regex we’re using /^\d+\.\d+\.\d+$/ matches tags such as 1.2.3 but doesn’t take into account all of the rules of SemVer (Semantic Versioning). For the sake of completeness, here’s what a complete regex for SemVer would look like according to rgxdb.com:

/(?<=^[Vv]|^)(?:(?<major>(?:0|[1-9](?:(?:0|[1-9])+)*))[.](?<minor>(?:0|[1-9](?:(?:0|[1-9])+)*))[.](?<patch>(?:0|[1-9](?:(?:0|[1-9])+)*))(?:-(?<prerelease>(?:(?:(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?|(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?)|(?:0|[1-9](?:(?:0|[1-9])+)*))(?:[.](?:(?:(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?|(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?)|(?:0|[1-9](?:(?:0|[1-9])+)*)))*))?(?:[+](?<build>(?:(?:(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?|(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?)|(?:(?:0|[1-9])+))(?:[.](?:(?:(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?|(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?)|(?:(?:0|[1-9])+)))*))?)$/


 

 

 

 

Top