Skip to main content

56 posts tagged with "oss"

View All Tags

· 3 min read
Shunsuke Suzuki

buildflow というツールを開発しているので buildflow というタグをつけて何回かに分けてブログを書きます。

この記事では buildflow の実行結果の出力フォーマットなどについて説明します。

ちょっと出力はわかりにくいかもしれません。 改善したいと思いつつ、どうあるべきなのかまだ見えてないのでこんな感じになっています。

task の標準出力、標準エラー出力はリアルタイムで出力されます。 また、複数のタスクを並列実行できます。 複数のタスクのログをリアルタイムで出力すると当然混じるので、区別がつくように各行の prefix に timestamp | task name | をつけて出力します。 それでも混じるとわかりにくいので、 phase が完了後に、 phase の全 task のログを混ざらないようにそれぞれ標準エラー出力します。 つまり同じログが 2 回出力されますが 2 回実行されているわけではないです。

==============
= Phase: phase 名 =
==============
10:47:54UTC | task A | + /bin/sh -c echo hello # 実行されるコマンド
10:47:54UTC | task B | + /bin/sh -c echo foo
10:47:54UTC | task A | hello # コマンドの標準(エラー)出力
10:47:54UTC | task A |
... # リアルタイムに出力されるので複数の task のログが混ざる場合がある


================
= Phase Result: phase 名 = # 該当 phase の全 task 完了後に全 task の結果と標準(エラー)出力を出力する
================
status: succeeded
task: task A
status: succeeded
exit code: 0
start time: 2020-10-14T10:47:54Z
end time: 2020-10-14T10:47:54Z
duration: 4.818877ms
+ /bin/sh -c echo hello
hello

...

· 4 min read
Shunsuke Suzuki

buildflow というツールを開発しているので buildflow というタグをつけて何回かに分けてブログを書きます。

この記事では buildflow が自動で Pull Request (以下 PR) の情報を取得してくる機能について説明します。

この機能は GitHub のみサポートしています。 GitLab や BitBucket はサポートしていません。 これは単純に自分が GitHub しか使わないからです。

PR の CI では

  • 変更されたものだけテストする
  • 特定の PR ラベルがついていたら実行する
  • 特定のユーザーの PR だけ処理を変える(bot とか)

のように PR の情報に基づいて挙動を変えたくなったりします。

シェルスクリプトで GitHub API 叩いて情報とってきて jq でパースしてとか、頑張れば別にできるんですが、 毎回そういうコードを書きたくないなと感じていました。

なお、 PR の情報をとってくる機能はデフォルトで無効化されています(GitHub Access Token 必要ですしね)。 設定で pr: true を指定してください。

PR の情報をとってくるには、以下の情報が必要です。

  • repository owner: 設定ファイルで owner を設定するか、自動取得。 owner を設定してある場合はそちらが優先される
  • repository name: 設定ファイルで repo を設定するか、自動取得。 repo を設定してある場合はそちらが優先される
  • pull request number: 自動取得
  • GitHub Access Token: 環境変数 GITHUB_TOKEN または GITHUB_ACCESS_TOKEN を指定してください

取得される情報

以下のパラメータがテンプレートや Tengo script に渡されます。

  • PR: PR の情報: GitHub API のレスポンス body
  • Files: PR で更新されたファイルの一覧: GitHub API のレスポンス body

Files に関してはページネーションされていても全てのファイルが取得できるまで繰り返し API を叩いています。

自動取得の仕組み

各種 CI サービスの組み込みの環境変数からそれらの情報を自動で取得してくれます。

内部的には go-ci-env を使っているので、 PR 情報の自動取得をサポートしている CI サービスは以下のとおりです。

https://github.com/suzuki-shunsuke/go-ci-env#supported-ci-services

PR 番号が環境変数から取得できない場合、 revision から関連する PR のリストを取得し、一番最初の PR とみなします。 これは PR のマージコミットの CI ではマージされた PR の情報を取得することを意図しています。

関連する PR が存在しない場合は取得されるパラメータが nil になるだけで、 buildflow は異常終了したりせずに処理を続行します。

· 7 min read
Shunsuke Suzuki

buildflow というツールを開発しているので buildflow というタグをつけて何回かに分けてブログを書きます。

この記事では buildflow でなぜ Tengo を採用しているのかについて説明します。

https://github.com/d5/tengo

Tengo に関しては https://techblog.szksh.cloud/buildflow-1/ でも多少触れています。

なぜ Tengo を採用しているのかに関しては

  • なぜスクリプト言語を採用しているのか
  • なぜ他の言語ではなく Tengo なのか

の 2 つの観点で話します。

なぜスクリプト言語を採用しているのか

逆にスクリプト言語を採用しない方法としては、 YAML などで独自 DSL のようなものを定義する方法があります。 DSL と言うと大げさかもしれませんが、 AND, OR, NOT といった論理を YAML のようなデータ記述言語で表現しようと思うとそんな感じになると思います。

この方法は扱いたいロジックが単純なものに限られるのであれば問題ないですが、 より柔軟なロジックを表現したいとなった場合に、無理があります。

  • どうやって表現すればいいのか自分で考えないといけない
    • どう頑張っても独自ルールになるため、ユーザーにとって直感的とは言えない
  • 正しく実装しないといけない
  • 仕様をドキュメント化しないといけない

一方、 Go では幾つかのスクリプト言語がサードパーティのライブラリとして実装されており、 buildflow のようなツールに組み込むことが出来ます。

https://github.com/avelino/awesome-go#embeddable-scripting-languages

これらを活用すれば上記の問題は解決できるうえに、非常に柔軟にロジックを実装できます(勿論言語によりますが)。

なぜ他の言語ではなく Tengo なのか

単純に https://github.com/avelino/awesome-go#embeddable-scripting-languages で紹介されているライブラリの中で一番要件にマッチしてそうだったからです。 といっても全てをちゃんとチェックしたわけではありませんが。 Lua とかもあるのでそれでも良かったかもですが、自分は Lua を全然知りません。 あとちゃんとバージョンニングされていたのも理由の一つです。 Tengo より人気のある言語もありましたが、バージョニングされてないという理由で見送ったりしました。

実は Tengo の前に他の言語 antonmedv/expr を採用していたのですが、途中で表現力が足りてないので移行しました。 github-comment でも antonmedv/expr は使ってますし、便利ではあるのですが、 変数が宣言できず、基本ワンライナーで書くしかないので無理だなと判断しました。

https://github.com/suzuki-shunsuke/buildflow/issues/20

buildflow でスクリプト言語に求めているもの

buildflow における Tengo の用途はあくまでロジックの記述、シェルスクリプトでは扱いにくい map 等の操作です。 Tengo で外部コマンドを呼び出したりとかファイルを読み書きしたりとかそういうことは考えていません (てっきりそういうことが出来ない言語なのかと当初思っていましたが、できるようですね)。

Tengo は Python や Ruby, Go といった言語に比べれば言語仕様がコンパクトであり、 よく知らなくてもなんとなく読めるし、簡単にかけると思っています。

また、 Tengo ではテキスト処理などに使える標準ライブラリが提供されています(これがないと辛かったけど、あるので十分)。

なので今の所 Tengo で十分だと考えています。 Tengo よりリッチな言語があったとしても、今の所あまり移行するモチベーションはありません。

Tengo に関する不満

Tengo に関する不満を挙げると以下のようなものがあります。

情報が少ないのに関しては、言語仕様がシンプルなので個人的には今の所困ってません(公式ドキュメント読めば分かる

Tengo script の実行、 Test

よく知らない言語であれば、試しに実行してみたり、ちゃんとテストを書いたりしたいですよね。

実行に関しては公式の方でツールがあったりします。

テストに関しては簡単なツールを別に作りました https://github.com/suzuki-shunsuke/tengo-tester 従来シェルスクリプトでこういうロジックを実装しても「動けばいい」程度に考えていてテストは書かないことが多かったですが、 ロジックだけを Tengo のスクリプトとして切り出し、テストツールも用意することでちゃんとテストを書くようになることを期待しています。

· 3 min read
Shunsuke Suzuki

buildflow というツールを開発しているので buildflow というタグをつけて何回かに分けてブログを書きます。

この記事では buildflow の dynamic task という機能について説明します。 dynamic task では task.items の値でループを回し、複数の task を動的に生成できます。 勿論 task.items はオプションなので、指定しなければ普通の task として扱われます。 task.items を指定する場合、 map か list か、それらを返す Tengo script でないといけません。

---
phases:
- name: main
tasks:
- name: "list {{.Item.Key}} {{.Item.Value.name}}"
command:
command: "echo {{.Item.Key}} {{.Item.Value.name}} {{.Item.Value.age}}"
items:
- name: foo
age: 10
- name: bar
age: 20

上記の設定は dynamic task を使わないとこうなります。

---
phases:
- name: main
tasks:
- name: "list 0 foo"
command:
command: "echo 0 foo 10"
- name: "list 1 bar"
command:
command: "echo 1 bar 20"

パラメータ Item は Key, Value を持ち、 Items が map の場合、それぞれ map の key, value が渡され、 list の場合、 index と value が渡されます。

上記の例は単純すぎてイマイチかもしれませんが、 例えばファイルなどの一覧を返すコマンドの実行結果を元に dynamic task でファイルごとに別の task で並列に処理するとかが考えられそうです。

制約

task.items は phase の最初に評価されます。 つまり同じ phase の task の結果を参照したり出来ません。

これは task.dependency の評価時に、 task のリストが定まっていないと評価できないためです。

ただし、前の phase 及び phase の task の実行結果は参照できるため、 items で特定の task の実行結果を参照したい場合は、phase を分けることになります。

実は dynamic task を実現する上で上記の問題をクリアするために phase という概念を導入したという経緯があったります。

· 2 min read
Shunsuke Suzuki

buildflow というツールを開発しているので buildflow というタグをつけて何回かに分けてブログを書きます。

この記事では buildflow の task の input, output という機能について説明します。 task の input, output は Tengo script で task のパラメータを整形する機能です。

task の command.command や write_file.template など、幾つかの設定では Go の text/template が使えますが、 text/template は複雑なロジックを記述したりするのには向いていません。 そこで task の input で Tengo script を使って必要なデータの整形を行うことで、 template は比較的きれいな状態に保つことが出来ます。

これは MVC モデルで View とロジックを分離するみたいな考え方と似ているかもしれません。

output ではコマンドの実行結果を整形することが出来ます。 例えばコマンドの標準出力をユニークな文字列のリストにしたり出来ます。

task.input は task.when が評価されたあと、 task の command などが実行される前に評価されます。 つまり、 task.when や task.dependency で同じ task の input の結果を参照は出来ません。

· 2 min read
Shunsuke Suzuki

buildflow というツールを開発しているので buildflow というタグをつけて何回かに分けてブログを書こうかなと思います。

この記事では buildflow の設定ファイルを分割する方法について説明します。

buildflow では一部の設定項目について他のファイルのパスを指定して読み込むということが出来ます。 1 つのファイルに全部の設定を書いていると、ファイルが大きくなってメンテナンス性が悪くなったり、 コードオーナーが曖昧になったりするので、そういう場合は分割すると良いでしょう。 コードオーナーが異なる複数のサービスで共通の設定ファイルを用いる場合、ファイルを分割して GitHub の CODEOWNERS を設定するのもよいでしょう。 あまりないかもしれませんが、ファイルを分割すると同じファイルを読み込んで再利用も出来ます。

また、 Tengo script を独立したファイルに分割すると、 test が可能になります。 Tengo script をテストするためのツールとして tengo-tester というツールも開発しているので、そちらをお使いください。

以下のようなファイル読み込みの設定があります。

  • phase.import
  • task.import:
  • task.input_file
  • task.output_file
  • task.when_file
  • command.command_file
  • command.env[].value_file
  • write_file.template_file

ファイルのパスは、絶対パスか、実行中の build の設定ファイルが存在するディレクトリからの相対パスになります。

· 3 min read
Shunsuke Suzuki

buildflow というツールを開発しているので buildflow というタグをつけて何回かに分けてブログを書こうと思います。

この記事では buildflow の Tengo script やテンプレートにパラメータとして渡される変数について紹介します。

buildflow では Tengo script はテンプレートが使える設定項目が多くあります。それらの設定には共通のフォーマットのパラメータが渡されます。

  • PR: Pull Request の情報: GitHub API のレスポンス body
  • Files: Pull Request で更新されたファイルの一覧: GitHub API のレスポンス body
  • Phases: 対象の Phase よりも前の Phase の結果
  • Phase: 対象の Phase
  • Tasks: 対象の Phase の Task の結果
  • Task: 対象の Task
  • Item: dynamic task のパラメータとして渡される
  • Meta: 設定 meta

Phase

  • Status: Phase の実行結果
    • succeeded
    • failed
    • skipped
  • Tasks: Phase の task の実行結果
  • Meta: phase の 設定 meta

Task

type によらず共通

  • Type: task の type
  • Name: task 名
  • Status: Task の実行結果
    • queue
    • running
    • succeeded
    • failed
    • skipped
  • Meta: task の meta の設定
  • Input:
  • Output:

command

  • ExitCode
  • StdOut
  • StdErr
  • CombinedOutput

read_file

  • File
    • Text: ファイルの内容
    • Data: read_file の format を指定した場合、パースされた結果

write_file

  • File
    • Text: ファイルの内容

Template でパラメータを参照する

例えば command の場合

---
phases:
- name: main
tasks:
- name: hello
command:
command: 'echo "{{.Task.Name}}"'

Tengo script でパラメータを参照する

例えば task foo が成功した場合のみ、 task bar を実行したい場合

---
phases:
- name: main
tasks:
- name: foo
command:
command: echo hello
- name: bar
command:
command: echo hello
dependency:
- foo
when: |
task := {}
for t in Tasks {
if t.Name == "foo" {
task = t
break
}
}
result := task.Status == "succeeded"

このように他の task の実行結果や PR の情報などを使ってビルドの挙動を変えることが出来ます。

· 3 min read
Shunsuke Suzuki

buildflow というツールを開発しているので buildflow というタグをつけて何回かに分けてブログを書こうと思います。

この記事では buildflow の task の基本的な設定項目などについて説明します。 数が多いので、個々の設定の詳細はまた別の記事に書きます。

task には幾つか type がありますが、全ての type に共通するパラメータが以下になります。

  • name: task 名。 unique である必要はない。 Go の text/template が使える
  • when: task を実行するか否か。 真偽値か Tengo script
    • when_file で外部ファイルを読み込める
  • dependency: task の依存関係の定義。 task 名のリストか、 Tengo script
  • items: dynamic task の設定。 loop を使って複数の task を動的に生成できる
    • 任意の list か map か、 Tengo script
  • input: Tengo script で task のコマンドのパラメータを生成できる
    • input_file で外部ファイルを読み込める
  • output: Tengo script で task の実行結果を整形できる。他の task が参照して挙動を変えたりできる
    • output_file で外部ファイルを読み込める
  • meta: ユーザーが自由にパラメータを定義できる map

上記の設定は name 以外はオプションです。

task の type としては以下のものがあります。

  • command: 外部コマンドを実行
  • read_file: ファイルを読み込む。ファイルの内容を他の task で参照できる
  • write_file: ファイルを書き込む

command の設定

  • shell, shell_opts: コマンドの実行シェル。デフォルトは /bin/sh -c
  • command: コマンド。 Go の text/template で処理される
    • command_file で外部ファイルを読み込める
  • stdin: コマンドの標準入力。 Go の text/template で処理される
    • stdin_file で外部ファイルを読み込める
  • env: 環境変数。環境変数名と値は Go の text/template で処理される

read_file の設定

  • path: ファイルのパス。Go の text/template で処理される
  • format: ファイルのフォーマット。オプション。 json と yaml をサポート。指定するとパースした結果を他の task が参照できる

write_file の設定

  • path: ファイルのパス。Go の text/template で処理される
  • template: ファイルの内容。Go の text/template で処理される
    • template_file で外部ファイルを読み込める

· 3 min read
Shunsuke Suzuki

buildflow というツールを開発しているので buildflow というタグをつけて何回かに分けてブログを書こうと思います。

この記事では buildflow の概念である build, phase, task について書きたいと思います。

buildflow には Build, Phase, Task という概念があります。 CircleCI の Pipeline, Workflow, Job みたいなものと思ってもらえるとよいと思います。

$ buildflow run

で 1 つの build が実行されます。 build は複数の phase からなり、 phase が 1 つずつ順に実行されます。 phase は複数の task からなり、 task が全て終了すると、その phase も終了となります。 task は並列に実行したり、依存関係を定義したりできます。 task では外部コマンドを実行したりできます。

設定ファイルでは phases, tasks をそれぞれ配列で指定します。

---
phases:
- name: setup
tasks:
- name: hello
command:
command: echo hello
- name: foo
command:
command: echo foo
- name: build
tasks:
- name: hello
command:
command: echo hello
- name: foo
command:
command: echo foo
dependency:
- hello
- name: post build
tasks:
- name: hello
command:
command: echo hello

上の例では 3 つの phase setup, build, post build が順に実行されます。 デフォルトではどれかの phase が失敗するとそれ以降の phase は実行されません(この挙動は変えられます)。

task も phase 同様配列で指定しますが、配列の順序に意味はありません。 依存関係がない限り、並列で実行されますし、実行順序は不定です。

· 2 min read
Shunsuke Suzuki

buildflow というツールを開発しているので buildflow というタグをつけて何回かに分けてブログを書こうかなと思います。

この記事では buildflow で Tengo というスクリプト言語をどのように使っているか書きたいと思います。

https://github.com/d5/tengo

buildflow の設定では task.when や task.dependency, task.input などで Tengo script が使えますが、 1 つの共通のルールがあります。 result という変数を宣言し、 script の実行結果をその変数に持たせるというルールです。 これは Tengo の仕様とかではなく、 buildflow 特有のルールです。 もっとも単純な例だと次のような感じです。

result := true

task.input, output などだと result の値が Task.Input, Task.Output として参照できるようになります。

---
phases:
- name: main
tasks:
- name: hello
input: |
result := {
foo: "bar"
}
command:
command: 'echo "{{.Task.Input.foo}}"'
when: "result := true"

Tengo の標準ライブラリ

Tengo には標準ライブラリがあります。 buildflow では全ての標準ライブラリが使えます。