Executor

ジョブを実行する際の環境に関するアレコレを設定します。ここではsteps以外のすべての値をここに含めることができます。

executors:
  foo:
    environment:
      TOKEN: foobarbaz
    docker:
      - image: alpine
    working_directory: ~/repo

使う時はジョブ側のexecutorに展開したいエグゼキューター名を渡します。

jobs:
  build:
    executor: foo
    steps:
      - run: echo `pwd`
      # 上記は `/root/repo` と表示

もしジョブ側にエグゼキューターで定義済の値がある場合、ジョブ側の設定が優先されます。例えばジョブ側でもworking_directoryを設定した場合、ワーキングディレクトリは/root/overwriteになります。

jobs:
  build:
    executor: foo
    steps:
      - run: echo `pwd`
    working_directory: ~/overwrite

Job

一連の処理の流れを記載します。これはトップレベルのjobs:以下にジョブの名前となる<job-name>:とネストさせ、その中にジョブの環境や具体的な処理といった内容を置きます。

jobs:
  build:
    executor: alpine
    steps:
      - run: ':'

例えばこの書き方ではbuildという名前のジョブを定義してます。そしてこれは、alpine環境(executor)で処理(steps)を順番に実行するということを中に書いてます。このexecutorstepsは必須なので必ず置かなければなりません。

stepsは配列です。上記では1つの処理しか行ってませんが、以下のように複数書くと順番にそれを実行します。

jobs:
  build:
    executor: alpine
    steps:
      - run: echo foo
      - run: echo bar
      - run: echo baz

また、buildという名前のジョブがある時、workflowsは無くても構いません。buildというのは特別なジョブ名で、もしもworkflowsがない場合以下のように解釈されます。

workflows:
  workflow:
    jobs:
      - build

オプショナルな設定

parameters

stepsと同じ階層にparametersを置くことができます。もし極一部だけ値の違うジョブを使いたいい場合、コピペして極一部だけ値を変えるのではなく、パラメーター値として渡してあげることで行っている事は同じ様なジョブ定義を1つだけにできます。

jobs:
  build:
    parameters
      text:
        type: string
    executor: alpine
    steps:
      - run: echo << parameters.text >>

ワークフローでジョブを使う時にtextパラメーターを渡して使うことができます。以下は最終的にecho fooが実行されます。

workflows:
  foo:
    jobs:
      - build:
          text: foo

working_directory

ジョブを実行する際にいるディレクトリを設定できます。デフォルトでは~/projectという場所にいいます。また、この値はCIRCLE_WORKING_DIRECTORY環境変数に入ります。

shell

ジョブを実行するシェルを設定できます。ジョブを実行するマシンにbashがある場合はそれが、ない場合はshになります。

environment

ジョブ内で使える環境変数を定義できます。

jobs:
  build:
    environment:
      FOO: foo
    executor: alpine
    steps:
      - run: echo $FOO

Parameter

パラメーターはジョブやコマンドなどに動的な値を渡す為に使えます。以下の3-4つの値を設定できます。

  1. description
  2. type
  3. default
  4. enum (typeenumの時に限る)

description

パラメーターの説明を書きます。この説明は Orb ドキュメントに使われます。

type

パラメーターの型を指定します。これには以下の

  1. boolean
  2. integer
  3. string
  4. enum
  5. executor
  6. steps
  7. env_var_name (環境変数)

boolean

真には、y yes true onが使えます。偽には、n no false offがあります。

integer

数値だけの値はintegerとして扱われます。

string

booleanintegerに含まれない値がこの型になります。ちなみにwhenでの判断時に、空文字列はfalse扱いです。

enum

渡せる値を羅列した値のどれかに制限したい時に使います。この型に限りenumキーも必須になります。

parameters:
  a:
    type: enum
    enum:
      - foo
      - bar
      - baz

executor

仕様するexecutorを指定します。これはexecutorsで定義されている名前でなければなりません。もし存在しない名前を渡すと「Cannot find a definition for executor named 渡した名前」のようなエラーになります。

これはある言語の複数バージョンでジョブを走らせたい時などに使えます。以下はechoというジョブを NodeJS が12の環境と10の環境で走らせる例です。エグゼキューターそのものにパラメーターがある場合、ワークフローbのようにエグゼキューター名はnameに置き、同じ階層にエグゼキューターのもつパラメーター値を置くことができます。

version: 2.1

executors:
  node12:
    parameters:
      text:
        type: string
        default: a
    docker:
      - image: circleci/node:12
    environment:
      TEXT: << parameters.text >>
  node10:
    parameters:
      text:
        type: string
    docker:
      - image: circleci/node:10
    environment:
      TEXT: << parameters.text >>

jobs:
  echo:
    parameters:
      e:
        type: executor
    executor: << parameters.e >>
    steps:
      - run: echo $TEXT

workflows:
  a:
    jobs:
      - echo:
          e: node12
  b:
    jobs:
      - echo:
          e:
            name: node10
            text: b

steps

個別にstepsを切り替えたい時などに使えます。渡せるのはjob.stepsと同じ構造のものです。パラメータで渡されたステップはstepsjob.stepsの中のsteps)に渡します。

version: 2.1

jobs:
  echo:
    parameters:
      s:
        type: steps
        default:
          - run: echo a
    docker:
      - image: alpine
    steps:
      - steps: << parameters.s >>

workflows:
  a:
    jobs:
      - echo
  b:
    jobs:
      - echo:
          s:
            - run: echo b

なお、stepsを使ったあとに書き直すなどしてsteps: echo ...などとしてしまうとパースエラーという分かりづらいエラーメッセージしか吐かなく、ハマりやすいので注意が要りそうです。

env_var_name

環境変数を渡す為に使います。渡されてた値を使う時は展開前のただの環境変数名が入っているだけなので、展開するには${ }で囲ってあげる必要があります。

この型は特に存在しない環境変数を指定したとしても問題ありません。以下は、

  • CIRCLE_BRANCH (ciがデフォルトで設定する環境変数)
  • PROJECT_ENV_VAR (設定ページで設定した環境変数)
  • JOB_ENV_VAR (ジョブで定義した環境変数)
  • UNDEFINED_ENV_VAR (存在しない環境変数)

echoった例ですが、それぞれ値があればちゃんと展開後の値が表示されました。

version: 2.1

jobs:
  echo:
    parameters:
      value:
        type: env_var_name
        default: CIRCLE_BRANCH
    docker:
      - image: alpine
    steps:
      - run: echo ${<< parameters.value >>}
    environment:
      JOB_ENV_VAR: JOB_ENV_VAR

workflows:
  a:
    jobs:
      - echo
  b:
    jobs:
      - echo:
          value: PROJECT_ENV_VAR
  c:
    jobs:
      - echo:
          value: JOB_ENV_VAR
  d:
    jobs:
      - echo:
          value: UNDEFINED_ENV_VAR

Command

ジョブステップを細かく名前を付けて細分化する為に使います。例えばどこかにバックアップする処理を行うのに

  1. tgzに圧縮
  2. どこかへアップロード

というような複数のフローに対してbackupのようなコマンド名を付けることで、それをジョブステップの中の重要なキーのように置いて使うことができます。

commands:
  backup:
    description: ...
    parameters:
      file_name:
        type: string
        default: ...
    steps:
      - run: tar -cvzf << parameters.file_name >>.tgz foo
      - run: ./backup.sh

jobs:
  build:
    executor: ...
    steps:
      - backup:
          file_name: ...

形から分かる用にcheckoutなどは CircleCI が定義してるコマンドなのです。

Workflow

ジョブでは環境とその中で行う処理フローを定義してきましたが、ワークフローはそのジョブをいつ・どんな時に実行するかの制御を行います。例えばそれは

  • いつ
    • 毎週土曜の0時に
    • 1時間おきに
  • どんな時に
    • masterブランチにコミットがプッシュされた時
    • v0.1.0がプッシュされた時
    • 前のワークフローが終わった時
    • 権限を持つ者が承認した時

のような場合です。

その前にまずデフォルトの動作の話。以下のような設定の時、ワークフローabは、「いつ」と「どんな時に」どちらも設定していませんが、何かしらコミットがプッシュされた時にどちらも実行されます。

jobs:
  echo:
    docker:
      - image: alpine
    steps:
      - run: echo foo

workflows:
  a:
    jobs:
      - echo
  b:
    jobs:
      - echo

またjobsに複数のジョブを渡した場合、それぞれのjobは個別に順不同に実行されます。つまり以下の場合bの方が先に実行される可能性があります。

workflows:
  a:
    jobs:
      - a
      - b

いつ

「いつ」を設定するにはtriggersが必要です。もしaを「毎週土曜の0時に」だけ実行されるようにしたい場合以下のように設定します。

workflows:
  a:
    triggers:
      - schedule:
          cron: "0 0 * * 6"
    jobs:
      - echo

「一時間おきに」も追加したいなら、schedule.cron: "0 0-23 * * *"も追加してあげます。

これでも良いですが、これはその時存在するすべてのブランチでその時になったら実行されます。もしブランチやタグを指定したい場合はcronと同じ階層にfiltersを置くことができます。

例えばmasterdevelopブランチだけであれば、

cron: ...
filters:
  branches:
    only:
      - master
      - develop

のようにします。またonlyには正規表現も渡すことができ、マッチしたブランチだけというようなこともできます。

cron: ...
filters:
  branches:
    only: /cron.*/

onlyとは逆の意味のignoreというキーを変わりに置くこともできます。

cron: ...
filters:
  branches:
    ignore: /master|develop/

タグの場合はtagsbranchesと同じ階層に置きます。タグの場合デフォルトではすべて対象となっていないため、すべてのタグも実行対象にしたい場合(多分ない)、

cron: ...
filters:
  tags:
    only: /.*/

のようにします。

どんな時に

上記のfiltersでほぼ「どんな時に」の話なのですがこれはトリガー内での話なので、それ単体で設定したい場合はどうすればいいか。それはジョブ毎に設定してあげます。各ジョブにおいてもfiltersを持つことができ、これは上記のものと同じような設定を置けます。

workflows:
  a:
    jobs:
      - echo:
          filters: ...

「前のワークフローが終わった時」に関してはrequiresという設定を持てます。これは配列で中には他のジョブ名が入ります。例えば値が["foo"]の時その意味は「fooジョブが終わったらコレを実行しろ」という感じになります。

以下の例のように1つ前のジョブをrequiresで指定することでジョブを狙った通りの順番で実行することができ、最初のジョブに対してフィルターをかけることで対象の指定もできることになります。

workflows:
  workflow:
    jobs:
      - pre:
          filters:
            branches: /master/
      - main:
          requires:
            - pre
      - suf:
          required:
            - main

他にtype: approvalを各ジョブは持て、これを指定するとワークフローのそのジョブは待機状態で止まります。そのジョブを実行するには権限ある者がワークフローページで「Approve」する必要があります。

Orb

モジュールのように、あることをやるための機能をまとめたものです。使えるものは Explore Orbs にまとまってます。

Orb はトップレベルのorbs以下に持ってきたい Orb を置くことで Orb の中身が使えるようになります。キーは名前空間になり、以下の場合slack/と付けることでcircleci/slack@3.4.2の提供するコマンドなどを扱えます。

orbs:
  slack: circleci/slack@3.4.2

これで slack/notify のようなコマンドが使えるようになりました。

jobs:
  build:
    docker:
      - image: circleci/node:12
    steps:
      - slack/notify:
          color: '#42e2f4'
          message: This is a custom message notification
          webhook: 'Webhook URL'

結果

各 Orb の使い方は Explore Orbs からの各ドキュメントを読めるのでそれを参考に。

.circleci/config.yml が正しいか確認する

CircleCI CLI (circleciコマンド)を使います。インストールがまだの場合以下で。

curl -fLSs https://circle.ci/cli | bash

このコマンドのconfig validateを実行します。Config file at .circleci/config.yml is valid.と出れば大丈夫です。

circleci config validate
# Config file at .circleci/config.yml is valid.

もしエラーがある場合は以下のようなエラーメッセージが吐き出されるので、エラー文の内容を直して確認…を繰り返します。

Error: ERROR IN CONFIG FILE:
[#] required key [jobs] not found
Error: ERROR IN CONFIG FILE:
[#/workflows/workflow] only 1 subschema matches out of 2
1. [#/workflows/workflow/jobs/0] 0 subschemas matched instead of one
|   1. [#/workflows/workflow/jobs/0] expected type: String, found: Mapping
|   |   SCHEMA:
|   |     type: string
|   |   INPUT:
|   |     build:
|   |       filters:
|   |         branch:
|   |           only: master
|   2. [#/workflows/workflow/jobs/0/build/filters] extraneous key [branch] is not permitted
|   |   This is similar to to other `filters` in config, but has an additional key, `tags`
|   |   Permitted keys:
|   |     - branches
|   |     - tags
|   |   Passed keys:
|   |     - branch

設定ファイルの分割・結合

設定ファイルの結合は、

circleci config pack <directory-name>

で行えます。今はこの<directory-name>configということにします。

例えば、config/以下はこのような構造になります。

config
├── executors
│   └── alpine
│       ├── @docker.yml
│       └── environment.yml
├── jobs
│   └── build.yml
└── meta.yml

.ymlファイルがそれぞれ分割によって生まれたファイルです。慣れてる人にはディレクトリー構造に見覚えがあるかもしれませんがその通りで、分割と言ってもだいたいの場合は、ディレクトリー構造やファイルのベース名(environment.ymlでいうenvironment)がそのまま YAML の構造になるだけです。例えばenvironment.ymlであれば、

executors:
  alpine:
    environment:
      # `environment.yml` の中身

のようになります。

例外として@から始まるファイル(@docker.yml)は少し変わり、ベース名を構造化しません。つまり以下ではなく、

executors:
  alpine:
    '@docker':
      # `@docker.yml`の中身

こうなります。

executors:
  alpine:
    # `@docker.yml`の中身

どう解釈されるかが分かったので結合してみます。circleci config pack config/の結果はこうなりました。

executors:
  alpine:
    '@docker':
    - image: alpine
    environment:
      TOKEN: foobarbaz
jobs:
  build:
    executor: alpine
    steps:
    - run: ':'
version: 2.1

ちなみにjobs/build.ymlversion.ymlはそれぞれ以下でした。

executor: alpine
steps:
  - run: ":"
version: 2.1

CircleCI2.1 の設定から CircleCI2.0 の設定に変換する

CircleCI CLI (circleciコマンド)を使います。インストールがまだの場合以下で。

curl -fLSs https://circle.ci/cli | bash

このコマンドのconfig process <config.ymlへの相対パス>を実行します。ここで対象となる設定には注意しなければならない事が2つほどあります。

  1. jobs.buildが存在する
  2. またはworkflowsがある

以下のような.circleci/config.ymlを変換してみます。

version: 2.1

executors:
  node:
    docker:
      - image: circleci/node:10

jobs:
  build:
    executor: node
    steps:
      - run: node -v

結果は標準出力なので書き込み先も指定します。

circleci config process .circleci/config.yml > .circleci/config2.0.yml

結果はこうなりました。

version: 2
jobs:
  build:
    docker:
    - image: circleci/node:10
    steps:
    - run:
        command: node -v
workflows:
  version: 2
  workflow:
    jobs:
    - build

# Original config.yml file:
# version: 2.1
#
# executors:
#   node:
#     docker:
#       - image: circleci/node:10
#
# jobs:
#   build:
#     executor: node
#     steps:
#       - run: node -v

ちなみに注意することで言ったように以下は変換できません。

version: 2.1

executors:
  node:
    docker:
      - image: circleci/node:10

jobs:
  sandbox:
    executor: node
    steps:
      - run: node -v

jobs.buildがない場合はこのようにします。

version: 2.1

executors:
  node:
    docker:
      - image: circleci/node:10

jobs:
  sandbox:
    executor: node
    steps:
      - run: node -v

workflows:
  workflow:
    jobs:
      - sandbox

CircleCIをローカルマシンで実行する

CircleCI CLI (circleciコマンド)と Docker を使います。CircleCI CLI のインストールがまだの場合以下で入れます。

curl -fLSs https://circle.ci/cli | bash

コマンドはlocal executor --config <設定へのパス>ですが、注意する点として、2020-01現在では CircleCI2.1 の書き方には対応していません。よって使う前に CircleCI2.0 の書き方に変換する必要があります。

後はコマンドを実行するだけです。

circleci local executor \
  --config .circleci/config.yml

デフォルトではこれはjobs.buildを実行します。もし別のジョブを実行したい場合は--jobフラグがあります。以下のようにするとsandboxジョブを走らせることができます。

circleci local executor \
  --config .circleci/config.yml \
  --job sandbox

他に環境変数を渡せる--env VAR=VALやブランチをデフォルトのplaygroundから変更できる--branchなどのオプションがあります。

ただしローカル実行ではワークフローは見ていないようです。つまり以下のような設定の時に、

workflows:
  workflow:
    jobs:
      - sandbox:
          filters:
            branches:
              only:
                - playground

--branch developオプションを付けてもワークフローのフィルターなどがちゃんと動作してるかなどは確認できません。

GitHub にプッシュする

CircleCI 上のリポジトリでは、最初から以下のようなリモート設定がされているようです。

origin  git@github.com:user/repo.git (fetch)
origin  git@github.com:user/repo.git (push)

さてこの方法には以下のような方法があります。

  1. GitHub Personal Access Token を使う (2020-01 追記)
  2. ssh を使う

GitHub Personal Access Token

こっちのほうが楽かもしれない。

準備

まず、GitHub で Personal Access Token を取得します。もし対象のリポジトリがプライベートならrepoにチェック、パブリックならpublic_repoにだけチェックを入れトークンを生成します。

step で push する

git pushを実行するには、 git ユーザーのメールと名前は最低限設定する必要があるのでそれらを設定します。またoriginは上記の通りgit@...となっているのでhttps:スキーマの方にユーザー情報を付け加えたやつに変更します。変更するコマンドは以下です。

git remote set-url origin <新URL>

その後はこのoriginに向かってgit pushするだけです。

steps:
  - ...
  - run:
      name: git push
      command: |
        git config --global user.email "$GIT_AUTHOR_EMAIL"
        git config --global user.name "$GIT_AUTHOR_NAME"
        git remote set-url origin https://$GIT_AUTHOR_NAME:$GITHUB_PERSONAL_ACCESS_TOKEN@github.com/<org>/<repo>.git
        git push origin master:****

ssh

github.com に ssh できるようにする

対象のプロジェクトの設定の

  1. SSH Permissions
  2. Add SSH Key

から以下のように「Hostname」にはgithub.comを設定し、「Private Key」には自分がいつも使っている秘密鍵の内容をコピペして追加します。

一覧画面のそのホストの行にフィンガープリントが一緒に置いてあるはずなのでそれをコピーしておきます。

.circleci/config.yaml の編集

ssh時に鍵認証できる必要があるので.ssh/configを編集する必要があります。これにはstepsadd_ssh_keysを設定しておきます。以下のような感じです。

jobs:
  - checkout
  - add_ssh_keys:
      fingerprints:
        - '**:**:**:**:**:**:**:**:**:**:**:**:**:**:**:**'

この'**:**:...の部分に先ほどコピーしたフィンガープリントを置きます。これでここを実行した時に~/.ssh/configに以下のような設定が追加されます。

Host github.com
  IdentitiesOnly yes
  IdentityFile /home/circleci/.ssh/id_rsa_********************************

step で push する

git pushを実行するには、 git ユーザーのメールと名前は最低限設定する必要があるのでそれらを設定した上でgit pushします。

steps:
  - ...
  - run:
      name: git push
      command: |
        git config --global user.email "$GIT_AUTHOR_EMAIL"
        git config --global user.name "$GIT_AUTHOR_NAME"
        # add とかするならここで
        export GIT_SSH_COMMAND="ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
        git push origin master:****

APIを使ってトリガーする

この記事 GitHub が対象です。

以下のようにリクエストを飛ばすだけです。

curl -u ${CIRCLE_API_USER_TOKEN}: \
     -d build_parameters[AN_ENV_VAR]=foo \
     https://circleci.com/api/v1.1/project/github/<org>/<repo>/tree/<branch>

CIRCLE_API_USER_TOKENUser > Personal API Tokens から生成した文字列です。

build_parametersではジョブへ好きな環境変数を渡すことができます。
build_parameters[KEY]=VALUEKEYはユニーク)のように好きなキーバリューを指定できます。
上記のbuild_parameters[AN_ENV_VAR]=fooであれば、ジョブ内でecho $AN_ENV_VARと実行した時fooと表示されます。

URLには3つの< >で囲まれた値があります。これらはそれぞれ、

  1. <org>には対象リポジトリのユーザーか組織
  2. <repo>には対象リポジトリの名前
  3. 対象となるブランチ

を置きます。

リクエストが成功すると以下のようなレスポンスが返ります。

{
  "compare" : null,
  "previous_successful_build" : {
    "build_num" : 6,
    "status" : "success",
    "build_time_millis" : 22268
  },
  "build_parameters" : {
    "AN_ENV_VAR" : "foo"
  },
  "oss" : false,
  "all_commit_details_truncated" : false,
  "committer_date" : null,
  "body" : null,
  "usage_queued_at" : "2020-01-14T03:10:45.029Z",
  "context_ids" : [ ],
  "fail_reason" : null,
  "retry_of" : null,
  "reponame" : "<repo>",
  "ssh_users" : [ ],
  "build_url" : "https://circleci.com/gh/<org>/<repo>/7",
  "parallel" : 1,
  "failed" : null,
  "branch" : "master",
  "username" : "nju33",
  "author_date" : null,
  "why" : "github",
  "user" : {
    "is_user" : true,
    "login" : "nju33",
    "avatar_url" : "https://avatars2.githubusercontent.com/u/15901038?v=4",
    "name" : "純",
    "vcs_type" : "github",
    "id" : 15901038
  },
  "vcs_revision" : "...",
  "workflows" : {
    "job_name" : "build",
    "job_id" : "2285aaf7-3786-49f5-a087-0d2a62941c4a",
    "workflow_id" : "f205bea7-8f37-42a0-a7a7-abbcd3139c14",
    "workspace_id" : "f205bea7-8f37-42a0-a7a7-abbcd3139c14",
    "upstream_job_ids" : [ ],
    "upstream_concurrency_map" : { },
    "workflow_name" : "build"
  },
  "vcs_tag" : null,
  "build_num" : 7,
  "infrastructure_fail" : false,
  "committer_email" : null,
  "previous" : {
    "build_num" : 6,
    "status" : "success",
    "build_time_millis" : 22268
  },
  "status" : "queued",
  "committer_name" : null,
  "retries" : null,
  "subject" : null,
  "vcs_type" : "github",
  "timedout" : false,
  "dont_build" : null,
  "lifecycle" : "queued",
  "no_dependency_cache" : false,
  "stop_time" : null,
  "ssh_disabled" : true,
  "build_time_millis" : null,
  "picard" : {
    "build_agent" : {
      "image" : "circleci/picard@sha256:24c624cb110a720e3c554b54f2b2d41e1d8090de45f95d47b2d2afe948358824"
    },
    "resource_class" : {
      "cpu" : 2.0,
      "ram" : 4096,
      "class" : "medium"
    },
    "executor" : "docker"
  },
  "circle_yml" : {
    "string" : "`config.yml`の中身..."
  },
  "messages" : [ ],
  "is_first_green_build" : false,
  "job_name" : null,
  "start_time" : null,
  "canceler" : null,
  "all_commit_details" : [ ],
  "platform" : "2.0",
  "outcome" : null,
  "vcs_url" : "https://github.com/<org>/<repo>",
  "author_name" : null,
  "node" : null,
  "queued_at" : "2020-01-14T03:10:45.068Z",
  "canceled" : false,
  "author_email" : null
}

now を使ってデプロイするワークフロー

version: 2

references:
  config: &config
    working_directory: ~/repo
    docker:
      - image: circleci/node:8
  commands:
    print_pkg: &print_pkg
      run:
        name: Print package.json
        command: cat package.json
    restore_cache: &restore_cache
      restore_cache:
        keys:
          - v1-dependencies-{{ checksum "package.json" }}
          - v1-dependencies-
    save_cache: &save_cache
      save_cache:
        paths:
          - node_modules
        key: v1-dependencies-{{ checksum "package.json" }}
  scripts:
    yarn_install: &yarn_install
      run:
        name: Yarn install
        command: yarn --ignore-scripts
    yarn_install_now: &yarn_install_now
      run:
        name: Yarn install now
        command: yarn add -D now
    yarn_test: &yarn_test
      run:
        name: Yarn test
        command: yarn test
    yarn_now_deploy: &yarn_deploy
      run:
        name: Yarn deploy to now
        command: yarn deploy

jobs:
  setup:
    <<: *config
    steps:
      - checkout
      - *print_pkg
      - *restore_cache
      - *yarn_install
      - *save_cache
  test:
    <<: *config
    steps:
      - checkout
      - *print_pkg
      - *restore_cache
      - *yarn_install
      - *save_cache
      - *yarn_test
  deploy:
    <<: *config
    steps:
      - checkout
      - *print_pkg
      - *restore_cache
      - *yarn_install
      - *save_cache
      - *yarn_install_now
      - *yarn_deploy

workflows:
  version: 2
  deploy:
    jobs:
      - setup:
          filters:
            branches:
              only:
                - master
      - test:
          requires:
            - setup
      - deploy:
          requires:
            - test

基本複雑になりそうな部分はリファレンスブロックで細分化して管理しています。ワークフローの流れは以下のような感じです。

setup

masterブランチがgit pushで更新されると、まずこれが走ります。これは単に後の共通フローの高速化のためです。ここではyarnした時の結果をキャッシュして、後のフローでのyarnが早く終わるようにしています。

test

setupが無事に終わったのを確認してから走ります。主にテストを行い、それが正常に終了したらdeployにバトンタッチします。

deploy

testが無事に終わったのを確認してから走ります。ここでは now でのデプロイを行います。

- *yarn_install_now
- *yarn_deploy

がここ固有のステップでこれらは展開すると以下のようになります。

- run:
    name: Yarn install now
    command: yarn add -D now
- run:
    name: Yarn deploy to now
    command: yarn deploy
  1. nowをインストール
  2. now でデプロイ

now をインストール

nowを依存に含めないかと思うかもしれないですが、含めています。ただ依存のインストールを(環境によっては)もっと時間短縮になるyarn --ignore-scriptsを使ってインストールしていて、これだと now のインストールが完全に行われないため再度インストールしています。--ignore-scriptsというのはフックスクリプトを実行しないというオプションフラグです。

now でデプロイ

この run-scripts の中はこのように定義しています。(npm-run-allの中のrun-sという並行実行の記述をちょっと短縮できるコマンドを使ってます)

  1. デプロイ
  2. エイリアス設定
  3. エイリアス付けた環境以外削除

といういつものプロセスを行っています。

{
  "scripts": {
    "now.deploy": "now --team $NOW_TEAM --token $NOW_TOKEN",
    "now.alias": "now alias --team $NOW_TEAM --token $NOW_TOKEN",
    "now.rm": "now rm smilesumai-sindan.now.sh --team $NOW_TEAM --token $NOW_TOKEN",
    "deploy": "run-s now.deploy now.alias now.rm"
  }
}

$NOW_TEAMにはデプロイしたいチーム名、$NOW_TOKENには個人トークンを発行して設定します。これはら CircleCI の「 Environment Variables 」へ設定します。

結果

git pushして、うまくいけば以下のように完了するはずです。

タグをプッシュした時にワークフローが走るようにする

CircleCI ではデフォルトで、すべてのブランチが対象で、すべてのタグ名は除外のような設定になっています。
そのため、以下のようなタグプッシュではワークフローが走ってくれません。

git tag -a v0.0.1 -m '...'
git push origin v0.0.1

CircleCI ではタグの場合「このタグ名(タグ名のパターン)で来たとき走ってほしい」というような名前指定をする必要があります。これはワークフローのフィルターのtagsブロックで設定できます。

以下は、/v[0-9]+\.[0-9]+\.[0-9]+/というタグ名(v0.0.1にマッチ)の時にワークフローが走る設定です。

workflows:
  version: 2
  workflow_name:
    jobs:
      - job_name:
          filters:
            tags:
              only: /v[0-9]+\.[0-9]+\.[0-9]+/

タグ名プッシュの時だけに走るワークフロー

上の設定はまだ以下のような設定のものと同じです。

filters:
  tags:
    only: /v[0-9]+\.[0-9]+\.[0-9]+
  branches:
    only: /.*/

なのですべてのブランチを除外するという設定も必要になります。

filters:
  tags:
    only: /v[0-9]+\.[0-9]+\.[0-9]+
  branches:
    ignore: /.*/

失敗した時だけ実行されるステップ

runオブジェクトにwhen: on_failセクションを置くと、前回のステップが失敗した時にだけ実行させることができます。

以下の例は1つ目のステップでfoo != barじゃない時は失敗(exit: 0以外が返される)とするようなスクリプトになっています。もちろんこれは失敗するので、次のステップのecho "bar"が実行されるはずです。

version: 2.1

jobs:
  build:
    docker:
    - image: alpine
    steps:
    - run: |
        if [ "foo" != "bar" ]; then
          exit 1
        fi
    - run:
        name: on fail
        command:
          echo "bar"
        when: on_fail
    - run: ': 1'
    - run: ': 2'
    - run: ': 3'

望んだ通りになりました。

処理を続ける

when: on_failなステップを複数置いている時、1度でもwhen: on_failなステップが実行されると、それ以降のwhen: on_fail(とalways)なステップはすべて実行されます。

以下のような場合最後のon_success以外のすべてのステップが実行されます。

version: 2.1

jobs:
  build:
    docker:
    - image: alpine
    steps:
    - run: |
        if [ "foo" != "bar" ]; then
          exit 1
        fi
    - run:
        name: on fail 1
        command:
          echo on fail 1
        when: on_fail
    - run:
        name: on fail 2
        command:
          echo on fail 2
        when: on_fail
    - run:
        name: on fail 3
        command:
          echo on fail 3
        when: always
    - run:
        name: on fail 4
        command:
          echo on fail 4
        when: on_success

NodeJS 関連の CI が落ちる原因

都度更新。

NodeJS のインストールができない

NodeJS のサーバーが落ちていました。[このサイト]の「Node.js Website Status History」の最新のログがバスマークになっていないか確認しましょう。

落ちた時のスクリーンショット

これは、 2019-06-24 19:45:00 辺りから 2019-06-24 21:30:00 まで落ちていた時のものです。当時はこんなIssue(Website is down #2289)も建っていました。

バツがチェックマークになったときにジョブを「rebuild」した所正常に処理されました。