Concourse CIのflyコマンド&設定ファイルまとめ

# flyコマンド

## ログイン

認証をかけている場合、まずログインしないとfly targets以外の操作ができません。

```
$ fly -t test login
username: foo
password:
target saved
```

## flyコマンドのバージョンアップ

`sync`コマンドで、ターゲットとして指定されているConcourseと同じバージョンの　flyコマンドにアップグレードしてくれます。

```
$ fly -t test sync
targeting http://example-elb-177699448.ap-northeast-1.elb.amazonaws.com

downloading fly from http://example-elb-177699448.ap-northeast-1.elb.amazonaws.com... update successful!
```

なお、`fly login`したときに、ログインしたConcourseとローカルのflyコマンドのバージョンが異なる場合、以下のように警告してくれます。親切。

```
$ fly -t $TARGET login -c $URL
WARNING:

fly version (1.1.0) is out of sync with the target (1.2.0). to sync up, run the following:

    fly -t test sync
```

## ワーカー一覧

ConcourseにはLinux、Mac、Windowsまたその他任意のタグ付をした様々なワーカーを追加することができます。
以下の例だと、`platform: linux`なタスクを実行できるワーカーが２ついることになります。

```
$ fly -t test workers
targeting http://example-elb-177699448.ap-northeast-1.elb.amazonaws.com

name          containers  platform  tags
ip-10-2-3-84  1           linux     none
ip-10-2-3-85  1           linux     none
```

## 一時タスクの実行

Concourse的にはone-offビルドといいます。
ローカルの設定ファイルに定義したタスク（パイプライン→ジョブ→プラン→タスクという親子関係がありますが、その一番最下層にいる「タスク」)をConcourse上で実行します。
ローカルファイルを入力に、リモートのConcourse上でタスクを実行して、出力をローカルにダウンロードする、という一連のタスクを行ってくれます。
なにげにConcourseの特徴的な機能です。

```
$ cat foo.yml
---
platform: linux

image: docker:///busybox

run:
  path: echo
  args: [hello world]

run:
  path: sh
  args:
    - -exc
    - |
      pwd
      ls
      echo foo > some-files/foo
      ls some-files

outputs:
  - name: some-files
$ fly -t test execute -c foo.yml
targeting http://example-elb-177699448.ap-northeast-1.elb.amazonaws.com

executing build 84
initializing with docker:///busybox
running sh -exc pwd
ls
echo foo > some-files/foo
ls some-files

+ pwd
/tmp/build/e55deab7
+ ls
some-files
+ echo foo
+ ls some-files
foo
succeeded
```

## タスクの作成

以下の様なyamlファイルを、プロジェクトの`ci/tasks/タスク名/task.yml`や`ci/tasks/タスク名.yml`や`ci/タスク名.yml`（場所は自由です)あたりにつくって、

```
---
platform: linux

image_resource:
  type: docker-image
  source:
    repository: golang
    tag: '1.6'

params:
  SOME_PARAM: some-default-value

inputs:
- name: some-input
- name: some-input-with-custom-path
  path: some/custom/path

outputs:
- name: some-output

run:
  path: sh
  args:
  - -exc
  - |
    whoami
    env
    go version
    find .
    touch some-output/my-built-artifact
```

`fly execute`を使って実際のCI環境上で実行します。
基本、負荷がめちゃくちゃ高いとかが無い限り、`fly execute`で実際のCI環境でテストすることをおすすめします(それでもCI環境を汚すことはないので)。

```
$ fly -t ターゲット名 execute -c <yamlファイルへの相対パス>
```

inputは前段の`get: リソース名`で指定したリソースと同じ名前のディレクトリ以下にあります。タスクにはinput以外に入力となるファイルは存在しません。なので、基本的にタスクの`run:`中でカレントディレクトリから何かを読むことはないはずです。

## タスクで利用するDockerイメージをつくる

```
$ image=...
$ tag=...
$ docker build -t $image:$tag .
# テスト
$ echo テスト用の入力 | docker run -it --rm -v $(pwd)/input1:/tmp/build/e55deab7/input1 $(pwd)/output1:/tmp/build/e55deab7/output1 $image:tag sh -c "'cd /tmp/build/e55deab7; <run:に指定するコマンド。input1/以下のファイルを入力に、output1/以下にファイルを出力する> || sh'"
# 出力を確認
# テストが通ったらDocker Registryにpush。pushして初めて、Concourseから利用できるようになる
$ docker push $image:tag
```

## ビルド履歴

```
$ fly -t test builds
targeting http://example-elb-177699448.ap-northeast-1.elb.amazonaws.com

id  pipeline/job                 build  status     start                     end                       duration
82  one-off                      n/a    succeeded  2016-05-10@11:12:18+0900  2016-05-10@11:12:24+0900  6s
81  one-off                      n/a    succeeded  2016-04-21@15:16:07+0900  2016-04-21@15:16:10+0900  3s
...
75  one-off                      n/a    failed     2016-04-21@15:07:11+0900  2016-04-21@15:10:47+0900  3m36s
74  one-off                      n/a    aborted    2016-04-21@15:06:14+0900  2016-04-21@15:06:31+0900  17s
...
66  hello-world/hello-aws-cli    15     succeeded  2016-04-20@16:40:20+0900  2016-04-20@16:48:06+0900  7m46s
65  hello-world/hello-aws-cli    14     errored    2016-04-20@16:40:18+0900  2016-04-20@16:40:18+0900  0
```

`pipeline/job`は`パイプライン名/ジョブ名`、`build`はビルド番号、`status`は成功(succeeded)、失敗(failed)、中断(aborted)、エラー終了(errored)のいずれか。

`fly execute`で実行した使い捨てジョブの場合、パイプライン名・ジョブ名どちらもないので、便宜上one-offと表示される。

## ブラウザで特定のパイプラインを見る

flyコマンドじゃないけど・・

```
open', "#{@origin}/pipelines/zap"
```

## パイプラインを作成する

```
$ fly set-pipeline -t $target --load-vars-from $vars_file -c $config_path -n -p $pipeline_name -v $vame=$value
```

## パイプラインを開始する

Concourseの言葉では「unpause」という。
パイプラインはset-pipelineで作った段階では、定義されているけどまだ動き始めていない状態。unpauseして初めてパイプラインが動き始める。

```
fly', 'unpause-pipeline',
    '-t', @target,
    '--pipeline', 'zap'
```

## 全ConcourseクラスタのEndpoint URL一覧を出力


```
$ fly targets
name  url                                                                      expiry
lite  http://192.168.59.4:8080                                                 n/a
test  http://example-elb-177699448.ap-northeast-1.elb.amazonaws.com  Wed, 11 May 2016 02:11:50 UTC
tf    http://example-elb-177699448.ap-northeast-1.elb.amazonaws.com  Sat, 16 Apr 2016 10:39:42 UTC
```

## fly interceptでビルドのデバッグ

デバッグ対象のタスクを実行したコンテナでshを起動するためには、以下のようにします。

※元になったDockerイメージにshが存在することが前提です。bashがあればbash等でも使える、ということでもあります。

```
$ fly -t lite intercept sh
```

interceptには、デバッグ対象のビルドを絞り込むためのフィルタを指定できるのですが、このように省略してもOKです。省略すると、複数候補があった場合に以下のように選択肢を表示してくれます。

```
targeting http://192.168.100.4:8080

1: build id: 4, step: one-off, type: check
2: build id: 4, step: one-off, type: get
3: build id: 4, step: one-off, type: task
4: build id: 4, step: some-input, type: get
5: build id: 4, step: some-input-with-custom-path, type: get
6: build id: 4, step: some-output, type: put
```

```
choose a container: 3
```

試しに３を選んでみました。
すると、rootユーザでshが起動して、カレントディレクトリがタスクを実行したときのそれになっています。inputやoutputもあります。この状態でデバッグしたい処理を実際に実行したり、結果を確認することができます。

```
# export
export GOLANG_DOWNLOAD_SHA256='e40c36ae71756198478624ed1bb4ce17597b3c19d243f3f0899bb5740d56212a'
export GOLANG_DOWNLOAD_URL='https://golang.org/dl/go1.6.2.linux-amd64.tar.gz'
export GOLANG_VERSION='1.6.2'
export GOPATH='/go'
export HOME='/root'
export PATH='/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
export PWD='/tmp/build/e55deab7'
export SOME_PARAM='some-default-value'
export TERM='xterm-256color'
export USER='root'
# pwd
/tmp/build/e55deab7
# ls
some  some-input  some-output
```

# pipeline.yml

## まとめ

* Job
  * `serial: (true|false)` trueなら必ず順番にジョブを実行する。falseなら前のJobが実行完了する前に次がトリガーされたら、前のJobと平行で実行することを許可する。 [参照](https://concourse.ci/configuring-jobs.html#serial)
  * `public: (true|fales)` trueなら、認証していないユーザがビルドログを閲覧できる [参照](https://concourse.ci/configuring-jobs.html#public) 
  * `plan: [...]`
    * =ビルドプラン=ステップの配列
      * ステップ
         * get
             * リソースを取得する
                 * get: taskに渡すinput名
                 * resource: リソース名
                 * trigger: true
         * task
             * タスクを実行する
                 * task: タスク名
                 * file: 実行したいタスクの定義=相対パス.yml
                 * attempts: リトライ回数(デフォルト1)

## リソースタイプとリソース

### リソースタイプとリソースの例

#### 組み込みリソースタイプgit

gitリソースタイプはConcourse CIに組み込みなので、resource_typeの定義は不要

```
resources:
- name: myresource
  type: git
  source:
    # gitレポジトリのURLと
    uri: https://github.com/18F/concourse-compliance-testing.git
    # そのレポジトリ内のブランチ名を指定して
    branch: {{script-branch}}

jobs:
- name: myjob
  plan:
  - get: myresource
  - task: mytask1
    config:
      platform: linux
      image_resource:
        type: docker-image
        source:
          repository: busybox
      outputs:
      - name: project-data
      run:
        path: echo
        args:
  - task: mytask2
    # gitレポジトリの特定ブランチの内容がリソース名と同じ名前のディレクトリに入っているので
    # リソース名/パスでファイルを読み書きできる。
    file: myresource/tasks/filter-project-data/task.yml
```

`.jobs[].plan[].get`で指定しているリソース名と、`.jobs[].plan[].file`で指定しているファイルパスの先頭（＝リソース名）が同じであることに注目。プラン中で予めgetしたものが後続のタスクから利用できる。

#### 非組み込みリソースタイプi18fgsa/http-resource

```
# 組み込みリソースタイプgitとは違って、リソースだけではなくリソースタイプを定義しておく必要がある
# Concourseに「こんなリソースタイプがあるから、リソースとして使わせてね」と伝えるイメージ
resource_types:
# このpipeline.yml内の`resources`内で、`type:リソースタイプ名`として参照するときに使う名前。自由に決めていいが、あとでリソースを自分で定義するとき、このリソース名を指定する必要がある。
- name: http-resource
  # Docker Hubからとってきた18fgsa/http-resourceイメージで作ったコンテナを使ってリソースのin/out/checkを行うリソースタイプを定義する、という意味
  type: docker-image
  source:
    repository: 18fgsa/http-resource

resources:
- name: scripts
  type: git
  source:
    uri: https://github.com/18F/concourse-compliance-testing.git
    branch: {{script-branch}}
- name: after-midnight
  type: time
  source:
    interval: 1h
    start: 12:00 AM -0500
    stop: 12:30 AM -0500
- name: slack
  type: slack-notification
  source:
    url: {{slack-hook}}
<% projects.each do |project| -%>
- name: s3-result-<%= project['name'] %>
  type: s3
  source:
    bucket: {{aws-bucket}}
    versioned_file: results/<%= project['name'] %>.json
    access_key_id: {{aws-access-key}}
    secret_access_key: {{aws-secret-key}}
- name: s3-summary-<%= project['name'] %>
  type: s3
  source:
    bucket: {{aws-bucket}}
    versioned_file: summaries/<%= project['name'] %>.json
    access_key_id: {{aws-access-key}}
    secret_access_key: {{aws-secret-key}}
<% unless project['skip_team_api'] -%>
- name: fetch-project-data-<%= project['name'] %>
  type: http-resource
  source:
    uri: https://team-api.18f.gov/public/api/projects/<%= project['name'] %>/
    filename: project.json
    index: https://team-api.18f.gov/public/api/projects/<%= project['name'] %>/
    etag: true
```

# 参考

[18F/concourse-compliance-testingのpipeline.yml](https://github.com/18F/concourse-compliance-testing/blob/master/pipelines/zap/pipeline.yml)