2020/01/09

Go言語のCLIアプリを作りながらCircleCIでCI/CDを実践してみた

概要

以下を目標に、Go言語で簡単なCLIツールを作りながらCI/CDを実践してみました!
  • CIでtestbuildを実行して、READMEにバッジを貼る
  • テストカバレッジ計測し、READMEにバッジを貼る
  • gitでtagがついたら、バイナリを作ってgithubのリリースページにアップする
以下のリポジトリが成果物です。

A repository to play around with using CircleCI.

github.com/kemokemo/try-circle-ci
実践内容をハンズオンっぽくまとめると共に、ポイントやお世話になった記事をあわせて紹介したいと思いますʕ◔ϖ◔ʔ

CIでtestbuildを実行して、READMEにバッジを貼る

今回は、CircleCIを使ってCI/CDを実践することにしました。特にCircleCIに対するこだわりがある訳ではなく、「使用経験ないから学習してみっか!」ぐらいの軽い感覚で選定しました。

dockerイメージを使ってビルド

CIは繰り返し実行するものです。CIの設定が同じなら何度実行しても同じ結果が得られるように(=冪等性があるように)dockerコンテナを使います。幸い、CircleCI公式でGo言語用のコンテナを用意してくれているのでこれを使います。
言語ガイドを見ながら最初のconfig.ymlができました。testbuildのjobを並行に実行します。
version: 2
jobs:
  test:
    docker:
      - image: circleci/golang:1.13.5-node
    working_directory: /go/src/github.com/kemokemo/try-circle-ci

    steps:
      - checkout
      - run: go version
      - run: go get -v -t -d ./...
      - run: go test -v -cover ./...
  build:
    docker:
      - image: circleci/golang:1.13.5-node
    working_directory: /go/src/github.com/kemokemo/try-circle-ci

    steps:
      - checkout
      - run: go get -v -t -d ./...
      - run: go build

workflows:
  version: 2
  test and build:
    jobs:
      - test
      - build

executorを定義して複数のjobで使う

さて、お気づきの方もいらっしゃると思いますが、上記のconfig.ymlでは複数のjobで下記の「stepを実行する環境」の記述が重複して記述されてます。
docker:
    - image: circleci/golang:1.13.5-node
working_directory: /go/src/github.com/kemokemo/try-circle-ci
ちょっとモヤモヤします(´・ω・`) 同じ設定で良いならば、1箇所で定義して使いまわしたいです。そこでexecutorという仕組みの出番です!
まず、executorを使うためにはconfig.ymlのバージョンを2.1以降に変更する必要があります。上述の内容にgo-113-5という名称をつけてexecutorとして定義するとこんな感じになります。
version: 2.1

# add a docker executor for golang
executors:
  go-113-5:
    docker:
      - image: circleci/golang:1.13.5-node
    working_directory: /go/src/github.com/kemokemo/try-circle-ci

(後略)
よしよし。良いですね、名前がつけばそれを呼び出すことができます。実際に使ってみましょうʕ◔ϖ◔ʔ
version: 2.1

executors:
  go-113-5:
    docker:
      - image: circleci/golang:1.13.5-node
    working_directory: /go/src/github.com/kemokemo/try-circle-ci

jobs:
  test:
    executor: go-113-5 # using my executor!
    steps:
      - checkout
      - run: go version
      - run: go get -v -t -d ./...
      - run: go test -v -cover ./...
  build:
    executor: go-113-5 # using my executor!
    steps:
      - checkout
      - run: go get -v -t -d ./...
      - run: go build

workflows:
  version: 2
  test and build:
    jobs:
      - test
      - build

テストカバレッジ計測し、READMEにバッジを貼る

無事にCircleCIでビルドできるようになったら、READMEにバッジを貼りましょう。CIを使っていて且つビルドが成功しているバッジがあれば、「どんなツールかなぁ」と思ってリポジトリを見に来た時の安心感がグンと増します。(もちろん真っ赤なFailバッジになっていると逆効果ですが・・)

テストカバレッジ計測し、READMEにバッジを貼る

せっかくCIでtestジョブも実行しているのですから、テストカバレッジも継続的に計測して可視化したいです。まさにやりたいことそのまんまな以下の記事を参考にして、 Codecov というサービスを使うことにしました。

codecovorbを追加

オーブと聞くと、ドラゴンクエストⅢで6つ集めるアレとか、ドラゴンクエストⅤで砕けたり砕けなかったりするアレとかを思い出すのですが、ここで言うorbとは「CircleCI を手早く使い始めるのに便利なコンフィグパッケージ」(引用元:Orbs を使う - CircleCI)だそうです。codecovorbを追加してみたのが以下です。
version: 2.1

# add codecov's orb
orbs:
  codecov: codecov/codecov@1.0.5

executors:
  go-113-5:
    ...

(後略)
この便利なorbという仕組みは、「CircleCI Orb Registry」で検索して探すことができます。上記で追記した内容は、「CircleCI Orb Registry - codecov/codecov」を参考にしました。その他のorbの例としては、静的サイト生成ツールのhugoが使える「CircleCI Orb Registry - circleci/hugo」なんかもあります。便利ですね(*´ω`*)

カバレッジの生成と収集

testのジョブでカバレッジ情報をファイルに書き込む処理を追加してcodecovにアップロードしましょう。その際、codecovサービスに対象リポジトリを追加した時に生成されるtokenを、CircleCIのCODECOV_TOKEN環境変数に設定するのを忘れずに。以下のようにtestジョブのステップを書き換えます。
jobs:
  test:
    executor: go-113-5
    steps:
      - checkout
      - run: go version
      - run: go test -v -cover
      - run: go get -v -t -d ./...
      # adding coverprofile
      - run: go test -v -cover -coverprofile=coverage.out ./...
      # adding upload process to the codecov
      - codecov/upload:
          file: ./coverage.out
  build:
    ...
(後略)
これで、testジョブを実行する度に最新のカバレッジ情報がcodecovにアップロードされるようになります。
カバレッジの可視化と言えばバッジです。上図のようにcodecovSettings - BadgeからREADMEにコピペして使いましょう。

gitでtagがついたら、バイナリを作ってgithubのリリースページにアップする

このパートの内容は、以下の記事を参照してイメージを掴みつつ現在のCircleCI公式のドキュメントで学びながら実践しました。素晴らしい記事です、ありがとうございます。

deployジョブを追加

deployジョブでのおおまかな処理内容は以下です。
  • mitchellh/gox を使って、マルチプラットフォーム向けのバイナリを生成
  • tcnksm/ghr を使って、GitHubのタグページにバイナリをアップロード
まずは、goxを使ってマルチプラットフォーム向けのバイナリを生成する処理を追加します。これが実行されると、releaseフォルダにドババっとバイナリが生成されます。
(前略)
jobs:
(中略)
  deploy:
  executor: go-113-5
  steps:
    - run: echo 'export PATH=${GOPATH}/bin/:${PATH}' >> $BASH_ENV
    - checkout
    - run: go get -v -t -d ./...
    # install gox and add the build process using the gox
    - run: go get github.com/mitchellh/gox
    - run: mkdir release
    - run: gox -output "./release/{{.Dir}}_{{.OS}}_{{.Arch}}"  ./ ./...

workflows:
  ....
(後略)
次に、ghrを使ってGitHubのタグページにファイルをアップする処理を追加します。実際に動かす前に、CircleCIに対してGITHUB_API環境変数の設定が必要です。 「GitHub API Token - tcnksm/ghr」 の内容を参考に、repoスコープ付きのトークンを生成して設定しておきましょう。
(前略)
jobs:
(中略)
  deploy:
  executor: go-113-5
  steps:
    - run: echo 'export PATH=${GOPATH}/bin/:${PATH}' >> $BASH_ENV
    - checkout
    - run: go get -v -t -d ./...
    - run: go get github.com/mitchellh/gox
    - run: mkdir release
    - run: gox -output "./release/{{.Dir}}_{{.OS}}_{{.Arch}}"  ./ ./...
    # install ghr and add the upload process using ghr
    - run: go get github.com/tcnksm/ghr
    - run: ghr -u $CIRCLE_PROJECT_USERNAME $CIRCLE_TAG release/
    ...
(後略)

deployワークフローを追加

「v」から始まるタグに対してのみ、上述のdeployジョブを実行するようにワークフローを設定してみましょう。「ジョブの実行を Workflow で制御する - CircleCI」の内容を参考に以下ようにしてみました。
(前略)
workflows:
(中略)
  - deploy:
    jobs:
      - build:
          filters:
            tags:
              only: /.*/
      - deploy:
          requires:
            - build
          filters:
            tags:
              only: /^v.*/ # 「v」から始まるタグに対してのみ実行します。
            branches:
              ignore: /.*/
実際に「v0.0.1」というタグをつけてdeployワークフローを動作させてみた結果が以下です。うむ、素晴らしいです。およそ考えられるほぼ全てのプラットフォーム向けのバイナリが、CI/CDの仕組みによって生成されました。goxghrも実に良いですね。

おまけ

Go言語のリポジトリなら「Go Report Card | Go project code quality report cards」という仕組みを使って、多角的にコードの品質を可視化してくれるサービスも利用可能です。自分のGitHubリポジトリのパスを入力すればレポートを作れますし、バッジも貼れます。良きʕ◔ϖ◔ʔ

まとめ

CircleCI良いですね。特にorbの仕組みに感動しました。Ver. 2.1から使用可能になったexecutorの仕組みも大変興味深く、自在にCI/CDを構築できる感触を得ました。他にもいくつか同様の仕組みを導入したいツールがあるので、引き続き学習しながら使って行きたいと思います。