Skip to main content

· 2 min read
Shunsuke Suzuki

2019-10-01 から 2019-12-31 にかけて仕事でやったことを書きます。 勿論全部は書けないのでいくつかピックアップして書きます。

  • Terraform
    • terraform fmt の導入
    • Terraform の upgrade v0.11 => v0.12
    • State の分割 3 => 40 弱(正確な数は忘れた)
    • Conftest による lint: Remote Backend のパスを間違えるとまずいので test を導入
    • 新しいサービスを追加するときのための generator (シェルスクリプト)を開発
    • CODEOWNERS の設定
    • 突撃隣の Terraform
    • リリースブランチをやめて GitHub Flow に移行
    • 古いリビジョンで apply の実行の禁止(CI がこけるようにした)
  • Ansible
    • CI の高速化
    • Jenkins job を CircleCI の scheduled job にリプレース(脱 Jenkins)
    • ローカルでの開発環境の改善
      • コンテナを使い回せるようにする
    • goss に置き換えて高速化
  • CI の改善
    • 高速化(無駄な処理の削減)

· 3 min read
Shunsuke Suzuki

In this post I introduce how to split a huge .circleci/config.yml.

CircleCI doesn't support to split .circleci/config.yml, so we manage all workflows and jobs configuration into one file .circleci/config.yml. If the repository is Monorepo, the more the number of services increases, the more the size of .circleci/config.yml becomes large and it's hard to maintain .circleci/config.yml. By splitting .circleci/config.yml per service, it makes easy to maintain .circleci/config.yml and we can configure split file's CODEOWNERS.

To split .circleci/config.yml, you have to generate .circleci/config.yml by merging split files and commit both split files and .circleci/config.yml.

circleci config pack

We can merge split files with the command circleci config pack, but I introduce the other tool circleci-config-merge.

CircleCI CLI is an official tool so it's reliable, but I feel the restriction of the file name and the directory structure is a little strict. We have to manage all files on the same directory, and the file path is reflected to generated YAML content.

For the detail of circleci config pack, please see the official document.

https://circleci.com/docs/2.0/local-cli/#packing-a-config

If you can accept the restriction of circleci config pack, I recommend to use it because it is an official tool. But if it is difficult to accept the restriction, maybe circleci-config-merge would help you.

circleci-config-merge

circleci-config-merge is a CLI tool to generate .circleci/config.yml by merging split files.

The usage of circleci-config-merge is like the following.

$ circleci-config-merge merge <file1> [<file2> ...]

There is no restriction of file paths, and the format of split file is same as .circleci/config.yml.

For example, you can manage files on the same directory.

.circleci/
config.yml # generated
src/
service1.yml # split config per service
service2.yml
...

Or you can also manage files on each service directory.

service1/
circleci/
workflow.yml # you can split file freely
jobs.yml
...
service2/
circleci/
config.yml
...
...

circleci-config-merge merges the list of workflow jobs.

For example,

workflows:
build:
jobs:
- foo
workflows:
build:
jobs:
- bar

The workflow build's jobs are merged as the following.

workflows:
build:
jobs: # sort by job name for comparison
- bar
- foo

Test .circleci/config.yml in CI

If you split .circleci/config.yml, you should test in CI whether .circleci/config.yml is generated by merging split files. circleci-config-merge doesn't provide such a feature, but you can implement the test with the other tool like dyff.

I have created an example repository suzuki-shunsuke/example-circleci-config-merge. You can use this example as a reference to split .circleci/config.yml and setup CI.

Use case

Lastly, I introduce a use case of circleci-config-merge. Recently, I split a huge .circleci/config.yml which is over 6,000 lines to about 60 files. It was hard to maintain the original .circleci/config.yml, but by splitting it became easy to maintain .circleci/config.yml. If you are suffer from a huge .circleci/config.yml, let's split it!

Conclusion

In this post I introduced how to split a huge .circleci/config.yml. We can generate .circleci/config.yml by merging split files with circleci-config-merge. Please see the example suzuki-shunsuke/example-circleci-config-merge as a reference to split .circleci/config.yml and setup CI.

· 7 min read
Shunsuke Suzuki

自作の OSS github-ci-monitor の紹介です。

GitHub リポジトリの CI のステータスを定期的に取得し、 DataDog に送ることで、 CI のステータスを監視するツールです。 現状は AWS Lambda で動かすことを想定していますが、他の方法でも動かせるようにするつもりです。

Motivation

モチベーションは、 PR をマージしたあとに CI がこけた場合に通知が欲しいというものです。 マージしたあとに CI が一瞬で終わるなら無事終わるのを見届けてもいいんですが、 数分かかると待ってるのも時間がもったいないです。 しばらくしたあとに結果を確認すればいいんですが、それも面倒くさいですし、普通に忘れます。 そうするとデプロイしたつもりが実は CI がこけてたなんてことが普通にあります。

そういうことにすぐ気づけるよう、 Slack に通知がほしいと思っていました。

仕組み

仕組みは単純です。

GitHub API で各リポジトリのステータスを取得し、 DataDog API でステータスを送信しています。 DataDog API は Service Check API を使っています。 status は以下のようになります。

  • 0: 正常
  • 1: 異常
  • 3: ステータスの取得に失敗

また以下の tag が付きます。

  • owner: リポジトリのオーナー
  • repo: リポジトリ名
  • ref: ブランチ名

各リポジトリのステータスは現状 3 つをサポートしています。

それぞれ on/off を設定でき、複数指定した場合は、どれか 1 つでも失敗していたら status が 1 になります。

Lambda で動かす場合のアーキテクチャ

CloudWatch Events で定期的(5分毎とか)に Lambda Function を実行します。 リポジトリのリストなどの設定は環境変数で渡し、 GitHub Access Token などのクレデンシャルは AWS Secrets Manager 経由で渡します。

実装方針

CI がこけたら通知してほしいという要件を満たす方法は色々あると思います。

まず CI の中でこけた場合に通知を飛ばすようにすることが考えられます。 以下のようなメリットがあります(書いてみたら結構ありますね)。

  • ツールをどっかで動かしたりツール自体を監視したりする必要がない
  • より詳細なメッセージを送れる
    • コマンドの標準エラー出力を含めたり
    • CI のリンク貼ったり、 PR の author をメンションしたりもしやすい
  • CI がこけたらリアルタイムで通知できる
  • ポーリングと違い、無駄に API を叩く必要がない

一方で、これを漏れなく実装するのはけっこう大変だと思います。 例えば CircleCI だと全ての Job でちゃんとハンドリングしないといけなかったりすると思います。 リポジトリが 1 個だけならそれでもいいですが、何十個もあるとなるとだいぶ大変だと思います。

今回のツールのような方式だと対象のリポジトリの CI に一切手を加えずに実装できるのが大きいです。

また、 CI の結果を取得する API として CI サービスが提供する API を使って取得することも考えられます。 しかし、 GitHub API を使えば CI サービス毎に実装したりする必要がなくて楽です。

Slack API を使ってメッセージを投稿するようなことも一瞬考えましたが、 DataDog を使うことで以下のメリットがあります。

  • 送信先やメッセージのテンプレートとかをツールで管理しなくて良い
  • 何度もメッセージを送らないように状態を DB で持たなくて良い
  • アラートを一時的に止めたりするのも簡単

また、時間軸でどれだけ CI が壊れた状態だったか、復旧するのにどのくらい時間がかかったか分かるのもなにかに使えるかもしれません。

今回のツールに限らず、 Slack に直接通知するより DataDog や Sentry を経由したほうが上手くいくことも結構あると思っています。

また、定期実行する方法としては Lambda 以外にも

  • Jenkins
  • 適当なサーバで cron
  • CI サービス
  • k8s の CronJob
  • k8s の Deployment

など色々あると思います。そういう風にも実行できるようにバイナリを今後提供したいと思っています。 Lambda を使うとインフラを管理しなくて良いのがメリットだと思います。

また、 DataDog API で結果を送る push 型のアーキテクチャとは別に、 DataDog Agent + Prometheus Exporter の pull 型もあるんじゃないかなと思います。 そうするとツール側で DataDog API Key が不要になるというメリットがあります。 こちらのパターンも今後実装してみたいと思います。

· 6 min read
Shunsuke Suzuki

自作の CLI ツール matchfile について紹介します。

https://github.com/suzuki-shunsuke/matchfile

この記事の執筆時点で最新バージョンは v0.1.1 です。

変更されたファイルの一覧から実行する必要のあるタスクを導出するための CLI ツールです。 Go で書かれていて、バイナリをダウンロードしてくれば使えます。

Pull Request (以下 PR) の CI では PR で変更されたファイルに応じて 必要なタスク(build, test, lint, etc) だけを実行したかったりします。

そこで、 PR で変更されたファイルパスのリストタスクが依存するファイルパスの条件 を元に、そのタスクを実行する必要があるか判定するためのコマンドとして matchfile を開発しました。

ただし、 matchfile の機能としては PR や CI とは独立しているので、もっと別の目的でも使えるとは思います。

matchfile は PR で変更されたファイルパスのリストタスクが依存するファイルパスの条件 を取得したりする機能はありません。

PR で変更されたファイルパスのリストci-info という自分が作った別のツールを使うと取得できます。

タスクが依存するファイルパスの条件 はタスクに大きく依存するので matchfile はカバーしていません。

matchfile の使い方としては

$ matchfile run <PR で変更されたファイルパスのリストが書かれたファイルへのパス> <タスクが依存するファイルパスの条件が書かれたファイルへのパス>

で、 PR で変更されたファイルパスのリスト のうち一つでも タスクが依存するファイルパスの条件 にマッチすれば true を、マッチしなければ false を標準出力します。 コマンドの exit code で結果を表現することも考えられましたが、そうすると set -e しているときに若干面倒くさいので、標準出力で表現しました。

ごく簡単な例を示します。

$ echo template/foo.tpl > changed_files.txt
$ echo template > template_dependencies.txt
$ matchfile run changed_files.txt template_dependencies.txt
true

タスクが依存するファイルパスの条件 は独自のフォーマットで指定します。 gitignore のフォーマットにインスパイアされていますが、正規表現が使えるなど、独自のフォーマットになっています。 CI の中でシェルスクリプトで動的に生成することを想定し、行指向のフォーマットになっています。 Go 実装のパーサーが提供されたよく知られた行指向の(コマンドで生成しやすい)フォーマットがあれば良かったんですが、見つからなかったので簡単にフォーマットを定義してみました。

[#][!][<kind>,...] <path>
...

1行に1つ条件を書きます。 上から全部評価されます(どれかマッチしても終わりません)。 # はコメントです。行の途中にコメントを書くことはできません。

! は gitignore と似ていますが、その行を評価する時点で評価結果が true であり、! を除いたその行の評価が true の場合、評価結果が false になります。 日本語が下手くそですね。

簡単な例を示すと、 foo/foo.txt 以外の foo ディレクトリ直下のファイル としたい場合、次のようになります。

glob foo/*
!glob foo/foo.txt

kindpath をどう扱うかを示していて、幾つか種類があります。

  • equal: ファイルパスが文字列として完全に一致すればマッチ
  • dir: ファイルパスが <path>/ で始まればマッチ
  • regexp: 正規表現
  • glob: グロブ。 ** はサポートされてません

kind はカンマつなぎで複数指定でき、複数指定した場合は、先に指定したものからマッチするかテストされ、一つでもマッチしたらその条件がマッチするものとして扱われます。

kind の指定は任意で、指定しない場合、 kind は equal,dir,glob として扱われます。

つまり

foo.txt

equal,dir,glob foo.txt

と同じです。

· 11 min read
Shunsuke Suzuki

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

この記事では なぜ buildflow を作ったのかについて説明します。 開発者である自分の好みや置かれた環境などが所々に反映された内容になっています。

解決したい課題

自分は CI/CD の DX の改善に業務として取り組んでいます。 リポジトリはたくさんあり、横断的にメンテナンスしています。 幾つかのリポジトリはモノレポになっており、 CI の複雑さが増していたり、 CI の実行時間が長かったりします。

現在の CI/CD には以下のような問題があると感じています(他にもあるんですが、 buildflow と関係ないので割愛)。

  • 実行時間が長い
    • PR とは関係ない処理(test, build, etc) が実行されている
  • 金銭的に高い
    • 実行時間が長いので無駄にお金がかかっている
    • CI サービスによっては並列度を上げることで実行時間が縮む場合があるが、それでもその分お金がかかる
  • PR とは直接関係ないところで失敗する
    • PR とは関係ない処理(test, build, etc) が実行されていて、それらが flaky で失敗する
  • メンテナンス性が悪い
    • 属人化気味
    • 何をやっているのか分かりにくい
  • 同じような機能を複数のリポジトリで実装・メンテしたくない

これらの問題を解決するために buildflow を開発しました。

buildflow で必要な処理だけを実行する

buildflow では PR の情報を自動で取得し、それらに応じて実行する処理を変更できます。 変更されたファイルに応じてだけでなく、 label や PR の author などでも変更できます。 Tengo script を用いて柔軟なロジックを実装できます。 JSON や YAML の読み込みもサポートしているので、依存関係などの設定を別ファイルで管理することも出来ます。

一部の CI サービスはこれを解決するための機能を提供しています。 CodeBuild は Webhook の Filter で特定のファイルが変更された場合のみ build を実行できますし、 GitHub Actions でも似たようなことが出来ます。

それらで事足りるならそれでも良いでしょう。 それらだけだと難しい場合、 buildflow を使うとより柔軟に対応できるかもしれません。

並列処理による高速化

シェルスクリプトで for loop などで処理していて時間がかかっている場合、 buildflow で並列処理すると高速化するかもしれません。

メンテナンス性

buildflow を使わなくても「必要な処理だけを実行」したり「並列処理で高速化」したりはできるでしょう。 それでも buildflow を開発したのは、楽をするため、メンテナンス性を高めるためです。

PR の情報はよく必要になるので自動で取得するようにしています。

シェルスクリプトで複雑な CI を実装していると、メンテナンス性が悪くなります。 チームメンバーのシェルスクリプトへの習熟度に依存しますが、 シェルスクリプトはエンジニアなら誰でも書ける分全員が習熟しているとは限りませんし、 容易にバグが生まれます。 チームによりますが、Python や Ruby, Go といった他の言語と比べ、 lint や test がされてないことが多いせいもあるとは思います。 アプリケーションのコードは当然 CI で test, lint するのに、 CI とかのシェルスクリプトはしないというのも珍しくないと思います。

あとはサポートされているデータ構造が貧弱だったり、関数の I/F がわかりにくかったり、ググりにくい機能が多かったり、 コマンドのオプションを逐一調べないとわからなかったりします。

余談ですが、 shellcheck や shfmt を使うことをオススメします。 shellcheck を始めて使うと、シェルスクリプトにはこんなに色々罠があるのかと気付かされると思います。

Google の Shell Style Guide では次のように書かれています。

If you are writing a script that is more than 100 lines long, or that uses non-straightforward control flow logic, you should rewrite it in a more structured language now

では Ruby や Python といったスクリプト言語で書いたらどうでしょうか? シェルスクリプトで挙げた問題は解決すると思いますし、非常に自然で合理的な選択だと思います。

それでも buildflow を実装したのには、幾つか課題感があったからです。 まずは処理系、サードパーティのライブラリ、 OS パッケージに依存することです。 サードパーティのライブラリは使わなければいい話ですが、 Ruby や Python を使っていれば使いたいという声も出てくることはあるでしょう。 アプリケーションで同じ言語を使っていればそれとの共存も気にしないといけないかもしれません。 buildflow に限らず、自分は Go の「ワンバイナリで動く」という世界観が非常に好きです。

自分はこれまでシェルスクリプトを Go で書き直すということをやってきました。 その場合以下の2つがありますが、どちらにせよ課題感があります。

  • ビルド済みのバイナリを使う
    • 配布方法を考えないといけない
  • スクリプト言語のように go run で実行する
    • 他のスクリプト言語と同様の問題がある

ロジックとコマンドの分離

シェルスクリプトを何かしらの言語で書き直す場合、 全てをそれらで書きたいわけではありません。 だからこそ、規模が小さいうちはシェルスクリプトで書くのでしょう。 シェルスクリプトで書いたほうが楽な部分もあるのです。

buildflow ではコードを以下の3つに分離します。

  • 設定ファイル(YAML)
  • Tengo script
  • シェルスクリプト(細かいこと言うと、シェルスクリプト以外も実は使えるけど)

こうしてシェルスクリプトで書きにくい部分を分離し、適切な粒度で管理することでメンテナンス性を高めるというのが一つの狙いです。 Tengo script は基本的にデータの整形などに役割を限定し、外部ファイルに切り出せるようにすることで テストしやすいようになっています。

buildflow にはフレームワークとしての側面があり、 buildflow に乗っかることで共通の機能の実装を省いたり、コードを適切に分割してメンテナンス性を維持することができると期待しています。

尤もここはトレードオフがあるでしょう。 上記の 3 つを行き来しないといけなくて辛いというフィードバックをもらったこともあります。 コード分割は必須ではないので YAML にインラインで書くことも出来ますが、あまりおすすめしないのと、 そもそも上記のフィードバックはファイルの行き来だけでなく

  • buildflow の設定
  • Tengo Script
  • シェルスクリプト

という 3 つの異なる言語を行き来するという意味もあるのでしょう。

それはそういう側面もあるでしょう。 これは buildflow の根本的な部分なので変更されることはないと思います。 もし変更するようなら多分別のツールとして作っているでしょう。

まとめ

buildflow は自分が直面している CI/CD の課題

  • 必要な処理だけ実行したい
  • 共通の処理を逐一実装したくない
  • メンテナンス性を高めたい

を解決するために作りました。

buildflow はワンバイナリで動きます。 コードを適切に分離し、シェルスクリプトから複雑なロジックを除去することで、メンテナンス性を高めることを目指しています。

· 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 の結果を参照は出来ません。