Concourse CI

[Concourse Tutorial by Stark & Wayne](https://concoursetutorial.com/) に従って [Concourse CI](https://concourse-ci.org/) という Jenkins のような物を試す。

Concourse とは、ほぼコマンドラインだけで動作する CI ツールだ。Concourse では、レポジトリやファイルサーバなどの外部の資源を Resource として統一的に扱う。Resource を読み書きする一連の Task をまとめた Job の依存関係を Pipeline と呼び、Pipeline を yaml ファイルを定義する事によって動作を決める。

## インストール

Docker Compose のある環境なら 以下の手順でローカルに Concourse を構築出来る。リモートに構築したい場合は適当に `docker-compose.yml` を編集すれば OK。

    curl -O https://concourse-ci.org/docker-compose.yml
    docker-compose up &

* ブラウザで http://127.0.0.1:8080 を開いて user: test, password: test でログイン出来る。
* ブラウザから fly をダウンロードして適当にパスを通し、[fly login](https://concourse-ci.org/fly.html#fly-login) でログインする。-t (--target)で名前を指定すると、以降このサーバには -t で指定した名前でアクセス出来る。

```
    chmod a+x fly
    PATH=$PATH:$PWD
    fly -t tutorial login -c http://127.0.0.1:8080 -u test -p test
```

* Concourse CI を試すには Docker Compose を使う
* コマンドラインでログインするには Web 画面からダウンロードした `fly` コマンドを使う。

## Task は Concourse CI で行いたい仕事の単位

[Hello World](https://concoursetutorial.com/basics/task-hello-world/) を試す。チュートリアルを clone して [fly execute](https://concourse-ci.org/running-tasks.html#fly-execute) で最初の例を試す。`fly execute` は指定されたタスクをすぐ実行する。

```
    git clone https://github.com/starkandwayne/concourse-tutorial
    cd concourse-tutorial/tutorials/basic/task-hello-world
    fly -t tutorial execute -c task_hello_world.yml
```

`task_hello_world.yml` の内容。[image_resource:](https://concourse-ci.org/tasks.html#image_resource) でビルド用コンテナ指定し、[run:](https://concourse-ci.org/tasks.html#task-run) で指定したコマンドを実行する。この場合、dockerhub にある `busybox` からビルド用コンテナが作られる。

```yaml
platform: linux

image_resource:
  type: docker-image
  source: {repository: busybox}

run:
  path: echo
  args: [hello world]
```

ターミナルに hello world が表示されれば OK

* Task の動作は concourse ファイルの `image_resource` で指定したコンテナ上で `run` で指定したコマンドを実行する事。

## [Task Inputs](https://concoursetutorial.com/basics/task-inputs/) でタスクのコンテナにファイルを配置する

fly コマンドでローカルにあるディレクトリをビルド用コンテナにコピーしたい時は [inputs:](https://concourse-ci.org/tasks.html#task-inputs) を使う

`inputs_required.yaml` の内容。コンテナ内に inputs の name に指定した名前のディレクトリが作られる(この場合は `some-important-input`)。

```yaml
platform: linux

image_resource:
  type: docker-image
  source: {repository: busybox}

inputs:
- name: some-important-input

run:
  path: ls
  args: ['-alR']
```

コピーするディレクトリは `-i` で指定する。この場合カレントディレクトリがコンテナの `some-important-input` にコピーされる。 `execute` の代わりに `e` 一文字で良い。

    cd ../task-inputs
    fly -t tutorial e -c inputs_required.yml -i some-important-input=.

ターミナルにカレントディレクトリの内容が表示されれば OK

カレントディレクトリの名前を inputs の name に指定すると、`-i` でカレントディレクトリを指定した事になる。参照: [input_parent_dir.yml](https://github.com/starkandwayne/concourse-tutorial/blob/master/tutorials/basic/task-inputs/input_parent_dir.yml)

## [Task Scripts](https://concoursetutorial.com/basics/task-scripts/) でタスクで行う内容を細かく記載

普通は concourse ファイルの run で複雑なコマンドを実行せずに、Task Inputs を使ってスクリプトを渡す。

`task_show_uname.sh` の内容

```yaml
platform: linux

image_resource:
  type: docker-image
  source: {repository: busybox}

inputs:
- name: task-scripts

run:
  path: ./task-scripts/task_show_uname.sh
```

実行

    cd ../task-scripts
    fly -t tutorial e -c task_show_uname.yml

Linux のバージョンが表示されたら OK

* この場合、inputs の name で `task-scripts` というディレクトリ名が指定されている。
* 現在のディレクトリも `task-scripts` である。
* ということで、カレントディレクトリがコンテナの `task-scripts` にコピーされる。
* ということで、カレントディレクトリの `task_show_uname.sh` が run: で実行される。

## [Pipeline](https://concoursetutorial.com/basics/basic-pipeline/) と Job で一連の仕事を定義してブラウザから実行したり閲覧したりする。

```
jobs:
- name: job-hello-world # Job の名前
  public: true # true にするとビルドログが認証なしで見れる
  plan: # 実行したい [Steps](https://concourse-ci.org/steps.html) の列を書く
  - task: hello-world # [Task](https://concourse-ci.org/tasks.html) の名前
    config: # Task の内容
      platform: linux
      image_resource: # task_hello_world.yml と同じ。
        type: docker-image
        source: {repository: busybox}
      run:
        path: echo
        args: [hello world]
```

このサンプルでは、job-hello-world という一つの [Job](https://concourse-ci.org/jobs.html) を持つ [Pipeline](https://concourse-ci.org/pipelines.html) を定義している。

    cd ../basic-pipeline
    fly -t tutorial set-pipeline -c pipeline.yml -p hello-world

ここで y を入力すると Pause された pipeline と Pause された Job が出来る。pause の解除はブラウザでも出来るが非常に難しいので CLI の方が良いかも知れない。

    fly -t tutorial unpause-pipeline -p hello-world
    fly -t tutorial unpause-job --job hello-world/job-hello-world

表示された URL http://127.0.0.1:8080/teams/main/pipelines/hello-world から Job を選択し + でトリガすると Job が走る。

    fly -t tutorial trigger-job --job hello-world/job-hello-world

pipeline を削除する。

    fly -t tutorial destroy-pipeline -p hello-world

* Task は仕事の単位
* Job は Task をまとめたもの
* Pipeline は Job をまとめたもの

## [Pipeline Resources](https://concoursetutorial.com/basics/pipeline-resources/) で Git などからファイルを取得する。

Git レポジトリからソースを取ってビルドするような時 resources を使う。

```yaml
resources: # 下の Job の get で参照される。
- name: resource-tutorial # resource の名前、このディテクトリに clone するので後で file で指定出来る。
  type: git # git clone で取ってくるリソース
  source:
    uri: https://github.com/starkandwayne/concourse-tutorial.git
    branch: develop

jobs:
- name: job-hello-world
  public: true
  plan:
  - get: resource-tutorial # 一番目の Step (Get): この名前のディレクトリに resource-tutorial を取得する。
  - task: hello-world # 二番目の Step (Task)
    file: resource-tutorial/tutorials/basic/task-hello-world/task_hello_world.yml # config の代わりに task を指定すると、Concourse 設定ファイルに書かれた Task を実行する。
```

この場合、jobs.plan が2つある。最初の Step でソースを取得して次の Step でソースを実行する。

最初の例では config を使ってコンテナとスクリプトを指定したが、代わりに [file:](https://concourse-ci.org/task-step.html#task-step-file) を使って外部のファイルに記述出来る。file の内容は config 内に書くものと同じ。このように分けると task の内容を resource の中でバージョン管理出来る。

```yaml
platform: linux

image_resource:
  type: docker-image
  source: {repository: busybox}

run:
  path: echo
  args: [hello world]
```

実行

    cd ../pipeline-resources
    fly sp -t tutorial -c pipeline.yml -p hello-world # sp は set-pipeline の略
    fly -t tutorial up -p hello-world # up は unpause-pipeline の略

unpause したらブラウザで起動する。下の色々な方法で Job を実行するも参照。

## [Job を自動起動する。](https://concoursetutorial.com/basics/triggers/)

トリガで実行する例。

    cd ../triggers
    fly sp -t tutorial -c pipeline.yml -p hello-world

`get` に [trigger](https://concourse-ci.org/get-step.html#get-step-trigger): true を指定すると自動的に Pipeline を実行する。ここでは type: time で起動しているが、type: git を使うとコミットを検出して起動するようになる。

```yaml
resources:
- name: resource-tutorial
  type: git
  source:
    uri: https://github.com/starkandwayne/concourse-tutorial.git
    branch: develop

- name: my-timer # 二分間のタイマを定義する。
  type: time
  source:
    interval: 2m

jobs:
- name: job-hello-world
  public: true
  serial: true # [serial](https://concourse-ci.org/jobs.html#job-serial) に true を指定すると一つづつ実行する。
  plan:
  - get: resource-tutorial # ここに trigger: true を追加すると git commit を検知して実行するらしい。
  - get: my-timer # また、タイマによる実行も出来る。
    trigger: true
  - task: hello-world
    file: resource-tutorial/tutorials/basic/task-hello-world/task_hello_world.yml
```

## [ターミナルで Job を操作する。](https://concoursetutorial.com/basics/watch-job-output/)

進行中の Job の動作結果を見る。

    fly -t tutorial watch -j hello-world/job-hello-world

Job のリストを見る。

    fly -t tutorial builds


## [色々な方法で Job を実行する。](https://concoursetutorial.com/basics/trigger-jobs/)

ブラウザ UI で + を押す。

Resource を使う (後述)。

コマンド。-w を付けるとその場で結果を表示する。

    fly -t tutorial trigger-job -j hello-world/job-hello-world -w

## [Resource で Job を自動実行する。](https://concoursetutorial.com/basics/triggers/)

上の Pipeline Resources を自動実行するように変更するには Resouce を Get する際に [trigger](https://concourse-ci.org/get-step.html#get-step-trigger) を使う。

```yaml
resources: # この部分は変更なし
- name: resource-tutorial
  type: git
  source:
    uri: https://github.com/starkandwayne/concourse-tutorial.git
    branch: develop

- name: my-timer # 指定した秒数ごとに実行するリソース
  type: time
  source:
    interval: 2m

jobs:
- name: job-hello-world
  public: true
  serial: true # true だと同時実行しないようにするらしいが、なぜこのサンプルにあるのか謎。
  plan:
  - get: resource-tutorial
  - get: my-timer
    trigger: true # Get に trigger を付けると自動実行するようになる。
  - task: hello-world
    file: resource-tutorial/tutorials/basic/task-hello-world/task_hello_world.yml
```

実行

    cd ../triggers
    fly sp -t tutorial -c pipeline.yml -p hello-world

二分待つと自動実行されている。自分の git レポジトリでやる時は get: resource-tutorial に trigger: true を追加すればコミット時に自動実行される?

## [Job Input](https://concoursetutorial.com/basics/job-inputs/) でテストコードとテスト対象の2つのレポジトリに分ける。

ここでは特に新しい知識は無いが応用としてテストコードを分ける方法。

```
resources:
- name: resource-tutorial # テストコードを resource-tutorial に clone する。
  type: git
  source:
    uri: https://github.com/starkandwayne/concourse-tutorial.git
    branch: develop

- name: resource-app # テスト対象を resource-app に clone する。
  type: git
  source:
    uri: https://github.com/cloudfoundry-community/simple-go-web-app.git

jobs:
- name: job-test-app
  public: true
  plan:
  - get: resource-tutorial
  - get: resource-app
    trigger: true # テスト対象が変更されると自動実行する。
  - task: web-app-tests
    file: resource-tutorial/tutorials/basic/job-inputs/task_run_tests.yml
```

実行方法と確認

```
cd ../job-inputs
fly -t tutorial sp -p simple-app -c pipeline.yml
fly -t tutorial up -p simple-app
fly -t tutorial watch -j simple-app/job-test-app
```

## [Output](https://concoursetutorial.com/basics/task-outputs-to-inputs/) で次の task にファイルを渡す

ある task で作成したディレクトリを [outputs:](https://concourse-ci.org/tasks.html#task-outputs) で指定すると、次の task の input として使える。

```yaml
resources:
- name: resource-tutorial
  type: git
  source:
    uri: https://github.com/starkandwayne/concourse-tutorial.git
    branch: develop

jobs:
- name: job-pass-files
  public: true
  plan:
  - get: resource-tutorial
  - task: create-some-files
    config:
      platform: linux
      image_resource:
        type: docker-image
        source: {repository: busybox}

      inputs:
      - name: resource-tutorial
      outputs: # 次の task にわたすディレクトリ名を指定する
      - name: some-files

      run: # create_some_files.sh で output を作成する。
        path: resource-tutorial/tutorials/basic/task-outputs-to-inputs/create_some_files.sh

  - task: show-some-files
    config:
      platform: linux
      image_resource:
        type: docker-image
        source: {repository: busybox}

      inputs:
      - name: resource-tutorial
      - name: some-files # create_some_files.sh で作成したディレクトリを input にする。

      run:
        path: resource-tutorial/tutorials/basic/task-outputs-to-inputs/show_files.sh
```

create_some_files.sh の内容:

```
#!/bin/sh

mkdir some-files
echo "file1" > some-files/file1
echo "file2" > some-files/file2
echo "file3" > some-files/file3
echo "file4" > some-files/file4

ls some-files/*
```

実行

```
$ cd ../task-outputs-to-inputs
$ fly -t tutorial sp -p pass-files -c pipeline.yml
$ fly -t tutorial up -p pass-files
$ fly -t tutorial trigger-job -j pass-files/job-pass-files -w

started pass-files/job-pass-files #1

initializing
running resource-tutorial/tutorials/basic/task-outputs-to-inputs/create_some_files.sh
mkdir: can't create directory 'some-files': File exists
some-files/file1  some-files/file2  some-files/file3  some-files/file4
initializing
running resource-tutorial/tutorials/basic/task-outputs-to-inputs/show_files.sh
some-files/file1  some-files/file2  some-files/file3  some-files/file4
succeeded
```

実行結果で `mkdir: can't create directory 'some-files': File exists` と出てくるように、output で指定したファイルは自動的に作られるので自分で mkdir しなくても良い。

## [put](https://concoursetutorial.com/basics/publishing-outputs/) でビルド結果をアップロードする

Resource を使って get でダウンロードするだけでなく、[put:](https://concourse-ci.org/put-step.html#put-step-put) でアップロードも出来る。この例では予め作っておいた gist にファイプをアップロードする。

まず https://gist.github.com/ に bumpme という名前のファイルを作る。

Embed ドロップダウンから Clone via HTTP を選んで URL をコピーする。例: https://gist.github.com/bc24f321b58326a97461dfce6c30d2fc.git

https://github.com/settings/tokens/new で Personal access token を作る。

Select scopes で gist を選択 例: 5d4c84b8b868391765c43803982e137028d60fee

テスト

```
$ git clone https://gist.github.com/bc24f321b58326a97461dfce6c30d2fc.git bumpme
$ cd bumpme
$ echo hoge > bumpme
$ git add bumpme
$ git commit -m 'updated'
$ git push origin master
Username for 'https://gist.github.com':
Password for 'https://user@gist.github.com': 
```

上手く行けばこの情報を使う。

    cd ../publishing-outputs
    cp pipeline-missing-credentials.yml pipeline.yml

ここで pipeline.yaml を書き換える。

```yaml
resources:
- name: resource-tutorial
  type: git
  source:
    uri: https://github.com/starkandwayne/concourse-tutorial.git
    branch: develop
- name: resource-gist # アップロードやダウンロードを行うリソースの定義
  type: git
  source:
    branch: master
    uri: https://gist.github.com/bc24f321b58326a97461dfce6c30d2fc.git # ここを gist の clone HTTP URL に変更
    username: your_name # ここを自分のユーザ名に変更
    password: 5d4c84b8b868391765c43803982e137028d60fee # ここを Personal access token に変更

jobs:
- name: job-bump-date
  serial: true
  plan:
  - get: resource-tutorial # タスク内容が置かれたレポジトリを clone
  - get: resource-gist # 書き込むレポジトリを clone
  - task: bump-timestamp-file
    config:
      platform: linux
      image_resource:
        type: docker-image
        source: { repository: starkandwayne/concourse }

      inputs: # コンテナにタスク内容と書き込むディレクトリを入力
        - name: resource-tutorial
        - name: resource-gist
      outputs: # コンテナから更新したディレクトリを出力
        - name: updated-gist
      run:
        path: resource-tutorial/tutorials/basic/publishing-outputs/bump-timestamp-file.sh

  - put: resource-gist # 上のタスクで生成した updated-gist を resource-gist にアップロードする
    params:
      repository: updated-gist
```

タスクの内容 bump-timestamp-file.sh の中身はこのようになっている。resource-gist を updated-gist にコピーして書き換えて commit する。

```
#!/bin/sh

set -e # fail fast
set -x # print commands

git clone resource-gist updated-gist

cd updated-gist
echo $(date) > bumpme

git config --global user.email "nobody@concourse-ci.org"
git config --global user.name "Concourse"

git add .
git commit -m "Bumped date"
```

実行

```
fly -t tutorial sp -p publishing-outputs -c pipeline.yml
fly -t tutorial up -p publishing-outputs
fly -t tutorial check-resource -r publishing-outputs/resource-gist
fly -t tutorial trigger-job -j publishing-outputs/job-bump-date -w
```

[fly check-resource](https://concourse-ci.org/managing-resources.html) を使うと、即時にレポジトリを確認するそうです。

## [パイプライン変数を使って外部から pipeline に値を渡す。](https://concoursetutorial.com/basics/parameters/) 

以下の pipeline.yaml では、[params:](https://concourse-ci.org/tasks.html#task-params) を使って CAT_NAME と DOG_NAME という2つの環境変数を宣言しています。この際に値を [パイプライン変数](https://concourse-ci.org/setting-pipelines.html#pipeline-vars) を ((name)) という形式で指定すると外部から値を指定出来ます。

```
jobs:
- name: show-animal-names
  plan:
  - task: show-animal-names
    config:
      platform: linux
      image_resource:
        type: docker-image
        source: {repository: busybox}
      run:
        path: env
        args: []
      params:
        CAT_NAME: ((cat-name))
        DOG_NAME: ((dog-name))

```

具体的な値を指定するには、fly set-pipeline の -v を使うか、

```shell
$ cd ../parameters
$ fly -t tutorial sp -p parameters -c pipeline.yml -v cat-name=garfield -v dog-name=odie
$ fly -t tutorial up -p parameters
$ fly -t tutorial trigger-job -j parameters/show-animal-names -w
...
initializing
running env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOME=/root
CAT_NAME=garfield
DOG_NAME=odie
USER=root
succeeded
```

変数の値を定義する別の yaml ファイルを作って

```shell
cat > credentials.yml <<YAML
cat-name: tama
dog-name: pochi
YAML
```

-l (--load-vars-flom) でファイル名を渡します。実行:
    
```shell
fly -t tutorial sp -p parameters -c pipeline.yml -l credentials.yml
fly -t tutorial trigger-job -j parameters/show-animal-names -w
```

ただ、[fly get-pipeline](https://concourse-ci.org/managing-pipelines.html#fly-get-pipeline) で丸見えになるので同僚には見える。

todo:
* https://concoursetutorial.com/basics/pipeline-jobs/
* https://concoursetutorial.com/basics/secret-parameters/