公式サンプル集でCircleCI 2.0を試してみた&所感

## テスト用GitHubレポジトリを作成

[CircleCI 2.0の公式ドキュメント](https://circleci.com/docs/2.0/sample-config/#sample-configuration-with-fan-infan-out-workflow)からもリンクされている

CircleCI Demos: Workflows: https://github.com/CircleCI-Public/circleci-demo-workflows

をforkしてCircleCI 2.0の機能を体験してみる。

今回のfork先は以下
https://github.com/mumoshu/circleci-demo-workflows

```
git clone git@github.com:mumoshu/circleci-demo-workflows.git

cd circleci-demo-workflows
```

masterブランチはREADMEしか置いてなくて、他のブランチにそれぞれ異なるテーマのサンプルが入っている模様。

## ワークフローの仕組みをざっくりおさらい

[2.0 Project Tutorial - CircleCI](https://circleci.com/docs/2.0/project-walkthrough/#service-containers)

- Gitレポジトリ内の`.circleci/config.yml`にワークフローの定義を書く
- ワークフロー=Jobの集合


以下、その前提で読み進めます。

## 「Blogワークフロー」サンプルをビルドしてみる

```
git push origin remotes/origin/blog-workflows-1:master -f
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/5912/e8e32dce-21d0-e34f-83a0-509fe3bf487c.png)

rake_testタスクを覗いてみる。まず、`rake db:create db:schema:load`を実行しているが、この対象のPostgresqlはどうやって起動するんだろう？と疑問に思った。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/5912/c01931a1-aec8-e90d-5122-b6840b189425.png)

答えは、`.circleci/config.yml`の以下の部分

```yaml
  rake_test:
    docker:
      - image: circleci/ruby:2.4-node
      - image: circleci/postgres:9.4.12-alpine
    steps:
      - checkout
      - restore_cache:
          keys:
            - v1-bundle-{{ checksum "Gemfile.lock" }}
            - v1-bundle-
      - run: bundle --path vendor/bundle
      - run: bundle exec rake db:create db:schema:load
      - run:
          name: Run tests
          command: bundle exec rake
```

### 依存先サービスはどうやって定義する・どう実装されている?

CircleCI 2.0の公式ドキュメントを読むと、

> Note that the PostgreSQL database is available at localhost.
> [2.0 Project Tutorial - CircleCI](https://circleci.com/docs/2.0/project-walkthrough/#service-containers)

- rake_test.docker.images[]に依存先サービスのコンテナの定義（使用するコンテナイメージなど）を書く
- PostgreSQLコンテナのPostgreSQLサーバはlocalhostで待ち受けしている

らしいので、おそらくK8SのPodのように、ビルド環境のコンテナ、依存先サービスのコンテナがnetnsを共有している?のかなと想像。詳しい方情報求む！

また、`rake_test.steps[]`にビルド環境のコンテナ内で実行するビルドステップ（任意のコマンド）を書く。

**感想**: Pipelineが第一級市民なCIとしてConcourseCIを長らく使っていましたが、その難点の

### ビルドキャッシュはどうやって使う？どう実装されている？

![image.png](https://qiita-image-store.s3.amazonaws.com/0/5912/b6a74673-c876-89fa-2db2-a528025c976f.png)

`save_cache`というステップを使うと、任意のキャッシュキーで任意のファイルパス（複数指定可）をキャッシュに保存できて、`restore_cache`というステップでキャッシュキーを指定するとキャッシュを保存されたファイルパス以下に復元できる。

キャッシュはワークフロー内に閉じていて、ジョブ間で共有される。結果的に、`save_cache`と`restore_cache`が異なるジョブにあってもOK。

実際、blog-workflowサンプルだとbundle_dependenciesという最初のジョブでsave_cacheしたもの(`key: v1-bundle-{{ checksum "Gemfile.lock" }}`でsave_cache)

```yaml
  bundle_dependencies:
    docker:
      - image: circleci/ruby:2.4-node
      - image: circleci/postgres:9.4.12-alpine
    steps:
      - checkout
      - restore_cache:
          keys:
            - v1-bundle-{{ checksum "Gemfile.lock" }}
            - v1-bundle-
      - run: bundle install --path vendor/bundle
      - save_cache:
          key: v1-bundle-{{ checksum "Gemfile.lock" }}
          paths:
            - ~/circleci-demo-workflows/vendor/bundle
```

を後続のrake_testジョブでrestore_cacheしている(`key: v1-bundle-{{ checksum "Gemfile.lock" }}`でrestore_cache)

```yaml
  rake_test:
    docker:
      - image: circleci/ruby:2.4-node
      - image: circleci/postgres:9.4.12-alpine
    steps:
      - checkout
      - restore_cache:
          keys:
            - v1-bundle-{{ checksum "Gemfile.lock" }}
            - v1-bundle-
```

## Workspace Forwardingサンプルをビルドしてみる

workspace-forwardingブランチのビルドをトリガーするために、コミットを追加してpushする。

```
git checkout -b workspace-forwarding remotes/origin/workspace-forwardin
emacs README.md
git add
git commit -m 'First change'
git push origin workspace-forwarding
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/5912/7e5a449d-6562-8aa8-887c-0dbd86fcee72.png)

### 手動でデプロイメントを実行する

例えば、テストジョブが通った後にデプロイジョブが実行されるようになっているWorkflowで、デプロイジョブの実行を人が承認するまで待機させるという機能はある。

Web UIからボタンぽちでデプロイジョブを実行する、というような機能はない。

[Manually triggered deployment stages - Feature Requests - CircleCI Community Discussion](https://discuss.circleci.com/t/manually-triggered-deployment-stages/5515)

例えば、workspace-forwardingサンプルの.circleci/config.ymlでworkflow定義を以下のように変更する。

BEFORE

```yaml
workflows:
  version: 2
  build-and-deploy:
    jobs:
      - bundle_dependencies
      - rake_test:
          requires:
            - bundle_dependencies
      - precompile_assets:
          requires:
            - bundle_dependencies
      - deploy:
          requires:
            - rake_test
            - precompile_assets
```

AFTER

```yaml
workflows:
  version: 2
  build-and-deploy:
    jobs:
      - bundle_dependencies
      - rake_test:
          requires:
            - bundle_dependencies
      - precompile_assets:
          requires:
            - bundle_dependencies
      - hold:
      	  type:	approval
      	  requires:
            - rake_test
      	    - precompile_assets
      - deploy:
          requires:
            - hold
```

こうすると、deployジョブは、rake_testとprecompile_assetsのビルドが成功したあとで、さらに承認があって初めて実行されるようになる。

実際にこの.circleci/config.ymlの変更をするコミットをpushしてみる。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/5912/01e85d99-1d72-ffc3-2a33-57a38fd8a382.png)

すると、holdの要求するrake_testとprecompile_assetsまで自動的にビルドが走った状態で停止する。

Workflowsをみると、workspace-forwardingブランチのワークフローが表示されているのでそれを見てみる。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/5912/6ea126d4-09b6-4572-cf50-16080d9040c5.png)

ON HOLDと表示されているワークフロー実行をクリックすると・・・

![image.png](https://qiita-image-store.s3.amazonaws.com/0/5912/dc8980b1-7f33-c1d9-1a05-ff99cbfd3dd5.png)

パイプライン中でholdジョブの色が変わっている。そこをクリックするとダイアログがでる。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/5912/24c5972d-b1f1-ca94-0950-ccb264bb885a.png)

Approveをクリックすると、Workflowが再開する。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/5912/316bf3b3-e633-2a9e-1b9f-d6ad0871db9b.png)

## その他

### Slack通知

Project > Chat Notificationsの設定でSlackのWebhook URLを設定すればOK

![image.png](https://qiita-image-store.s3.amazonaws.com/0/5912/2a924145-b6b5-1ffb-e8a7-cb24228998fd.png)

Test Hookをすると、以下のようにcircleci botがテストメッセージを送る

![image.png](https://qiita-image-store.s3.amazonaws.com/0/5912/02215b93-c54d-39fb-9524-5b0b1346c3cf.png)

## 2.0 WorkflowsはデフォルトではFork PRsのビルドをしない

Travis等だとFork先からFork元にPRを投げた時にビルドを走らせてくれますが、2.0 Workflowsはデフォルトではそれをしません。

**同レポジトリ内のブランチからPRを投げた場合**

CircleCIによるビルドが動いて、結果がPRのステータスに反映されます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/5912/402df3a1-f28a-8d71-ea05-9de5cb51d750.png)

CircleCI側ではBuilds > <GitHub User/Org.>以下に該当ブランチのビルド結果ページができています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/5912/7c8f2b04-00cb-5574-6f95-84aab46dc5ba.png)


**別レポジトリからPRを投げた場合**

CircleCIによるビルドは動かず、PRのステータスにも登場しません。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/5912/ed18f987-1503-24d2-44ad-b19a6f448a2f.png)

## Forked PRもビルドしたい場合

ドキュメントには未対応と書いてあった気がするのですが、Advanced Settings > Build forked pull requestsにチェックを入れるとできました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/5912/0a2a1bd8-cae8-e156-6296-0a375fa53a94.png)

この状態からPRを投げると、以下のようにCircleCIのビルドがトリガーされます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/5912/fd0e0ffc-4ccf-8ffc-023d-91a3f7e82bc9.png)

### Forked PRの注意点(?)

Forked PR元で.circleci/config.ymlを変更すると、変更後のワークフローとジョブがPR先のCircleCIプロジェクトで走ってしまいます。

以下はmumoshuがPR先、mumoshubotがPR元で、mumoshubot側のブランチで`.circleci/config.yamlにfooというjobしかないような状態に書き換えたPRを投げたときの様子です。

CircleCIが、mumoshubotが仕込んだfooというジョブをmumoshuプロジェクトで実行してしまっています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/5912/b84a686d-87c7-21b6-8a76-c1dc125082f5.png)

これでクレデンシャルなどを抜けたりするとまずいので、もしそうならパブリックなCircleCIプロジェクトではForked PRのビルドは有効にしないほうがいいんでしょうか・・。

## APIで任意のジョブを実行する

まずUser > User SErttings > Peronal API Tokensで適当なAPIトークンを作成する。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/5912/9f70da02-b684-50bb-cdf1-90e2e624c372.png)

[Running Jobs With the API - CircleCI](https://circleci.com/docs/2.0/api-job-trigger/)を参考に以下のようなコマンドでジョブを実行する。

```
curl -u ${CIRCLE_API_TOKEN}: \
     -d build_parameters[CIRCLE_JOB]=deploy_production \
     https://circleci.com/api/v1.1/project/<vcs-type>/<org>/<repo>/tree/master
```

- `<vcs-type>`はGitHubを使っている場合`github`
- `<org>`はGitHubユーザまたはOrganizationアカウント名
- `<repo>`はレポジトリ名
- `build_parameters[CIRCLE_JOB]=`の右辺、上記の例でいうと`deploy_production`の部分が、.circleci/config.ymlのjobs直下に書いたjob名

例えば、blog-workflows-1サンプルのrake_testジョブを実行する場合は、以下のようなコマンドになる。

```
curl -u ${CIRCLE_API_TOKEN}: \
     -d build_parameters[CIRCLE_JOB]=rake_test \
     https://circleci.com/api/v1.1/project/github/mumoshu/circleci-demo-workflows/tree/master
```

実行すると、以下のようにWorkflowとは独立して、該当jobだけが実行される。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/5912/02d61de1-aeb5-fd64-8d68-5569d5666bc0.png)

## 条件付きで後続ジョブを実行する

ワークフロー中などでdeployがビルドされるとして、deployのビルド後に特定の条件に合致したときだけ後続のdeploy_dockerをビルドする例。

前述のAPIを使って任意のジョブを実行する方法を応用して、同じプロジェクトのワークフロー内の他のジョブをAPIを使って呼ぶという荒技（？）。

```
- deploy:
          name: conditionally run a deploy job
          command: |
            # replace this with your build/deploy check (i.e. current branch is "release")
            if [[ true ]]; then
              curl --user ${CIRCLE_API_TOKEN}: \
                --data build_parameters[CIRCLE_JOB]=deploy_docker \
                --data revision=$CIRCLE_SHA1 \
                https://circleci.com/api/v1.1/project/github/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/tree/$CIRCLE_BRANCH
            fi

  deploy_docker:
    docker:
      - image: ruby:2.4.0
    working_directory: /
    steps:
      - ...
```

参考: [Conditionally Running Jobs With the API](https://circleci.com/docs/2.0/api-job-trigger/#conditionally-running-jobs-with-the-api)


## 手動で特定のジョブを実行する

できないみたい?