cmdx - task runner

    最近自作した OSS, cmdx の紹介です。 https://github.com/suzuki-shunsuke/cmdx cmdx は task runner です。 task runner の定義はググってもわからなかったので、 cmdx を task runner と呼ぶのが適切かわかりませんが、 ここではプロジェクト固有のタスク 依存するライブラリのインストール ビルド テスト コード整形 lint etc などを管理するものとします。 類似するものとしては以下のようなものがあります。 Make npm scripts Task tj/robo mumoshu/variant 使い方 詳細は README を読んでください。 $ cmdx -i で設定ファイルの雛形を生成します。 そして設定ファイルに task を定義していきます。 設定に関しては README を参照してください。 そうすると cmdx -l でタスクの一覧とその説明が見れます。 例えば次は cmdx のリポジトリでの実行結果です。 $ cmdx -l init, i - setup git hooks coverage, c - test a package (fzf is required) test, t - test fmt - format the go code vet, v - go vet lint, l - lint the go code release, r - release the new version durl - check dead links (durl is required) ci-local - run the Drone pipeline at localhost (drone-cli is required) これにより新しくプロジェクトに参画した人もどのような task があるのか直ぐわかります。 例えば test を実行したければ cmdx t を実行すればいいことがわかります。 cmdx help test とすればここのタスクのより詳細なヘルプが見れます。

    Drone Extension のリスク

    Drone v1 では Extension という仕組みが導入されました。 これは文字通り Drone を拡張する仕組みで、仕様に従って作れば自由に Drone を拡張できます。 https://docs.drone.io/extensions/overview/ 全てを本体でやるのではなく、拡張する仕組みを提供し、あとはコミュニティに委ねるというのが Drone の一つの方針とも言えると思います。 Extension は非常に面白い仕組みだと思いますが、 Drone を運用する立場からすると中々頭が痛い仕組みな気がしてて、 自分は導入に対し慎重な立場です。 単なる杞憂で済めば良いのですが、その懸念について書きたいと思います。 根本は Drone Extension 固有の問題と言うより、一般的な拡張機構全般に言えることだと思います。 ただし、 Drone Extension は全てのビルドに影響を及ぼす、 CI/CDシステムが動かなくなるとサービスのリリースに影響を及ぼしかねないということからよりリスクの高いものになっています。 本体の drone/drone と比べ、開発は活発ではなく、サードパーティの extension はいつ開発が止まってもおかしくない 本体の drone/drone と比べ、ドキュメントやサポート体制が貧弱だと思われる(drone に関しては https://discourse.drone.io でサポートされているが、サードパーティの extension では難しい) ユーザーからの extension に関する要望を受け付けるようになると、管理者の負担になる extension のクォリティはマチマチであり、例外処理が甘かったり、ちゃんとエラーを吐かないものもあるだろう トラブルシューティングが難しいと思われる extension の仕組み上、extension を必要としないビルドにも影響を及ぼしうる 一度追加し、依存しだすと消すのが難しくなる extension が落ちると全 build に影響するので、耐障害性(冗長化)、モニタリングが必要 etc 勿論、上記の懸念点は Extension によって提供される機能とトレードオフであり、 Extension の導入方針は Drone が運用される環境によって大きく依存すると思います。 例えば全員が顔見知りのような小さな組織で特定のサービス専用に Drone を使っていてかつ Drone の運用体制(人員)に十分余裕があるなら 積極的に Extension を導入しても問題ないかもしれません。

    Drone v1 で gRPC が使われなくなった

    v0.8 では server - agent 間の通信に gPRC が使われていましたが、 v1 では使われなくなりました。 理由 https://discourse.drone.io/t/curious-about-decision-to-drop-grpc/3987 gRPC関連のトラブルの問い合わせが多すぎてサポートしきれないので止めた v1 での通信方法 https://discourse.drone.io/t/drone-agents-keep-closing-connections-with-499-code/5197/2 agent がロングポーリングしている 30秒後、なんのビルドもなければコネクションを切って、再接続する(張りっぱなしにしてると、LBやファイアウォールにコネクション切られるため) 自分も v0.8 から Drone を運用していて最近 v1 に upgrade しましたが、 v0.8 では gRPC 関連のトラブルが頻発していました。 server のログでは絶えず gRPC 関連のエラーを吐いていましたし、 server - agent 間の TCP connection が切れっぱなしになって戻らくなって agent 数がどんどん減っていったり ビルドが pending のままになったり、色々ありました。 関連する issue はあり、幾つか対策を打ってみたりしましたが、結局解決しませんでした。 https://github.com/drone/drone/issues/2090 https://github.com/drone/drone/issues/2246 https://github.com/drone/drone/pull/2294 https://www.reddit.com/r/droneci/comments/8opifu/drone_stops_working_after_some_little_time/e06d1gn/ それが v1 にアップグレードして gRPC が使われなくなってから解消し、個人的にはとても助かりました。 管理者的にはアップグレードして一番嬉しい点ですね。

    Golang での時刻の扱い方を整理する

    今更ながら Golang での時刻の扱い方について改めて整理してみました。 まとめ time.Local は明示的に設定する(基本UTC) DB などには 基本UTC で永続化する 出力時に必要になったらタイムゾーンを変更する location は出力時に問題になるので出力時に location を明示的に指定する 逆に言うと出力時以外は問題にならないので無理に location を UTC にしなくても良いかもしれない サードパーティ(ex. ORM) に time.Time を渡す場合は location に注意が必要 文字列として時刻の入力を受け付ける場合は location を明示的にセットする サードパーティが time.Local に依存する場合、 time.Local を明示的に UTC にしたりする必要があるかもしれない アプリケーションで利用する location が分かっている場合、location を取得するヘルパー関数を定義する time.LoadLocation は環境依存なので予め location が分かっているなら使わないほうがよい 文字列を time.Time に変換する場合、time.ParseInLocation で Location を指定して time.Time に変換後、time.Time.UTC() で UTC に変換する time.Time を文字列に変換する場合、time.In で location を変換後、time.Time.Format で文字列に変換する グローバルな location https://golang.org/pkg/time/#Location

    Drone v1 では Jsonnet が extension なしで使える

    Drone では v1 から冗長な YAML を DRY にする一つの手として、 Jsonnet の利用が推奨されています。 これについては過去のブログでも触れています。 https://techblog.szksh.cloud/drone-jsonnet-generator/ しかし、 v1 の rc の時点では Jsonnet の活用には Jsonnet Extension が必要でした。 https://engineering.linecorp.com/ja/blog/go-oss-ci-cd-platform-drone-1-0-0-rc-1/#title7-1 しかし、 v1 の正式版では Jsonnet Extension がなくても Jsonnet が利用できるようになっています。 まず Drone の管理者側で Drone server に環境変数 DRONE_JSONNET_ENABLED=true を設定する必要があります。 そうしたら、ユーザー側は次のようにすることで jsonnet が使えます。 .drone.yml の代わりに .drone.jsonnet をコミットする (.drone.yml は不要) 各リポジトリの settings の Main > Configuration で設定ファイルのパスを変更する こうすることでビルド実行時に自動で Jsonnet が YAML に変換され処理されるようです。 いつから Jsonnet Extension は不要になったのか https://github.com/drone/drone/compare/v1.0.0-rc.6...v1.0.0 https://github.com/drone/drone/commit/5013cfa993fa455fc56f10e45b9f36cf1d6dff57 v1 の rc ではサポートされてませんでしたが、正式版をリリースするタイミングで Jsonnet Extension が不要になっていたようです。

    The Top 10 Most Common Mistakes I’ve Seen in Go Projects を読んでみて

    The Top 10 Most Common Mistakes I’ve Seen in Go Projects という記事を読んで面白かったのでメモります。 翻訳ではないです。メモなので、原文を読んでください。 Unknown Enum Value: Unknown であることを表す enum の値は 0 にしよう。値がセットされていない場合に Unknown として扱えるから Benchmarking: ベンチマークを取るのは難しい。コンパイラの最適化によってベンチマークの結果が不適切になる場合がある Pointers! Pointers Everywhere!: パフォーマンスの観点から基本的にはポインタを使うべきではない。変数を共有する必要がある場合のみ、ポインタを使う Breaking a for/switch or a for/select: for, switch が入れ子になっている場合、switch の中で break しても for から抜けられない。抜けたければ labeled break を使う Errors Management Slice Initialization Context Management Not Using the -race Option: go test コマンドでは -race オプションをつけよう Using a Filename as an Input: 引数としてファイル名を渡すのではなく、 io.

    Flute - Golang HTTP client testing framework

    2019-07-17 追記 プロジェクト名が変わりました https://github.com/suzuki-shunsuke/flute/issues/20 Go の HTTP client のテストフレームワークを作ったので紹介します。 https://github.com/suzuki-shunsuke/flute 執筆時点のバージョンは v0.6.0 です。 リクエストパラメータのテスト HTTP サーバのモッキング を目的としています。 比較的実践的なサンプルとして、ユーザーを作成する簡単な API client とそのテストを書いたので参考にしてください。 https://github.com/suzuki-shunsuke/flute/blob/master/examples/create_user.go https://github.com/suzuki-shunsuke/flute/blob/master/examples/create_user_test.go#L17-L53 元々自分はこの目的のために h2non/gock を使っていました。 ただ、 gock だとリクエストがマッチしなかったときに、なぜマッチしないのかがわからず、調査に困るという問題がありました。 そこで flute では request に対し、matcher と tester という概念を導入し、 matcher でマッチしたリクエストを tester でテストするというふうにしました。 テストでは内部で stretchr/testify の assert を使っており、テストに失敗したときになぜ失敗したのかが分かりやすく出力されるようになっています。 例えば以下の例は、リクエストの Authorization header にトークンがセットされていなかった場合のエラーメッセージです。 === RUN TestClient_CreateUser --- FAIL: TestClient_CreateUser (0.00s) tester.go:168: Error Trace: tester.go:168 tester.go:32 transport.go:25 client.go:250 client.go:174 client.

    Drone で「ビルド実行時にパラメータを渡す」っぽいことをする

    Jenkins では parameterized build という機能で、ビルド実行時に Web UI からパラメータを指定することができます。 Drone では基本的に Git のイベントをフックして動くので「ビルドを実行時に手動でパラメータを設定する」ということは出来ません。 自分は基本的にできなくても構わないと思っていますが、 こういった機能がないから Drone を使わないという人も中にはいるので、 Drone でもちょっとした工夫でそれっぽいことは出来るんじゃないかと思い、簡単なサンプルを書いてみました。 一応言っておくと、 Jenkins の parameterized build を完全に代替するようなものではありません。 https://github.com/suzuki-shunsuke/example-drone-build-parameter 以下のファイルが必要です。 build_params/params.sh.tpl: ビルドパラメータを記述するファイルのテンプレート scripts/deploy.sh: デプロイ時に実行するスクリプト .drone.yml: Drone の設定ファイル スクリプトを実行してデプロイします。 $ bash scripts/deploy.sh するとパラメータを記述するファイルがテンプレートから作成され、エディタで開きます。 https://github.com/suzuki-shunsuke/example-drone-build-parameter/blob/master/scripts/deploy.sh#L12-L17 パラメータを記述し、エディタを閉じます。 するとそのファイルがコミットされ、新しいタグが作成され、コミットとタグがリモートにプッシュされます。 https://github.com/suzuki-shunsuke/example-drone-build-parameter/blob/master/scripts/deploy.sh#L27-L35 Drone でタグをプッシュするイベントをフックしてビルドが実行されます。 https://github.com/suzuki-shunsuke/example-drone-build-parameter/blob/master/.drone.yml#L13-L17 ビルドではコミットされたパラメータの設定ファイルを読み込むことでビルドにパラメータを渡せます。 https://github.com/suzuki-shunsuke/example-drone-build-parameter/blob/master/.drone.yml#L10 こうすることでビルドにパラメータを渡すことができます。 パラメータの設定ファイルはコミットされるので Git で管理できるというのも特徴です。 https://github.com/suzuki-shunsuke/example-drone-build-parameter/blob/master/build_params/2019-07-07T10-04-02JST/params.sh 上記のスクリプトではパラメータの設定ファイルとしてシェルスクリプトで環境変数を定義していますが、 シェルスクリプトである必要性はなく、例えば JSON ファイルを記述してビルドで JSON ファイルを読み込んでもよいし、 パラメータを選択させるようなことがしたければ fzf のようなものを使ってもよいし、 いくらでも改善できます。 以上、簡単な tips でした。

    Drone v0.8 の .drone.yml を v1 の .drone.jsonnet に変換するツールを作った

    Drone v0.8 の .drone.yml を v1 の .drone.jsonnet に変換するツールを作ったので紹介します。 https://github.com/suzuki-shunsuke/drone-jsonnet-generator 背景 https://docs.drone.io/user-guide/pipeline/migrating/ Drone は v0.8 から v1 で .drone.yml のフォーマットが大きく変わっています。 Drone v1 ではビルド実行時に自動で変換しているため、v0.8 の .drone.yml でもそのまま動きます(matrix builds も動きます)。 そのため、Drone v0.8 から v1 に移行する際、すぐに .drone.yml を修正しなくても問題ないのですが、 v1 独自の機能が出てきた場合 v0.8 のフォーマットの場合利用できないかもしれませんし、 いつまでも古いままだと気持ち悪いので出来るならフォーマットを変換したいです。 drone-cli ではフォーマットを変換する drone convert というコマンドが提供されています。 ただし、 drone convert は matrix build を multiple pipeline に変換するのですが、 非常に冗長になります。 そのため、jsonnet を利用することが推奨されています。 https://docs.drone.io/user-guide/pipeline/migrating/ The above syntax can be quite verbose if you are testing a large number of variations.

    .drone.jsonnet と .drone.yml を比較する Drone plugin を作った

    久しぶりに Drone plugin を作ったので紹介します。 https://www.github.com/suzuki-shunsuke/drone-plugin-jsonnet-check .drone.jsonnet から .drone.yml を生成していて、両方を Git で管理している場合に、 .drone.jsonnet と .drone.yml の状態が一致しているかテストするための plugin です。 Drone v1 では matrix builds が廃止され、multiple pipeline が導入されました。 matrix builds を drone convert コマンドで multiple pipeline に変換すると、pipeline の数が多いほど冗長でメンテナンス性が悪くなります。 そこで公式では jsonnet で記述して .drone.yml に変換する方法が推奨されています。 https://docs.drone.io/user-guide/pipeline/migrating/ To simplify your configuration we recommend using jsonnet. $ drone jsonnet --format --stream jsonnet から yaml への変換は Jsonnet extension を使うと Drone がビルド実行時に自動で変換してくれるので .drone.yml を管理する必要はなくなりますが、 使っていない場合、 .drone.jsonnet と .