GItHub Actionsを理解しよう!

Beta参加の秘密保持条項に反しているのでは?という指摘をいただいたので、一部修正を行いました

1人AdventのDay-7です。なるほど、1週間・・・

adventar.org

私の環境でも、GitHub Actionsを触れるようになりました!!
自分の手で実際に触ってみて、動かしてみると「おお!これはすごい!!」という感想です。。。!

実際に動いているものを見ないと「何があってどういうものなのか」のイメージが付きにくかったのですが、手元で動かしてみると「なるほど、、、そういう!」とワクワクしてきます。
今の自分なりの理解を、改めて、ドキュメント等を整理する形でまとめてみたいと思います。

GitHub Actions、ざっくりいうと

  • GitHub上での様々なイベントで発火させる「Workflow」と、Workflowから具体的な起動される「Action」からなる
  • Actionは、利用するDockerイメージを指定して、そのコンテナの内部で実行される
  • 1つのWorkflowに、複数のActionを連鎖・並行させて紐付けることができる
  • Workflowの編集のためのvisual editorが用意されていたり各Actionごとの実行結果(ログ)の閲覧が可能

個人的な感想としては、

  • 今まで1つの汎用CIにまとめていた処理を、細分化・専門化して組み合わせることが用意になりそう
  • 自前のDockerfileをゴニョゴニョして使い放題なので、ローカルでやっていた処理を簡単にクラウドに移せそう
  • 通常のGitHub(git操作やIssue更新など)だけでなく、incoming hook系のAPIも用意されているので、chatops等の実装とも親和性が抜群に高そう

といったところです。
現在、業務でCIの活用見直しやデプロイフローの最適化を進めている最中です。それと絡めて、「Travis CIの処理をいい感じにする」というネタで触ってみました!


やろうとしていること

  • 今の状態
    • Travis CIを利用しているプロジェクトにおいて
    • script/deploy等をベタで書いており
    • 例えば「tagをpushした」際にも丸々とscriptフェーズが実行される
  • やってみたいこと
    • master以外のブランチへのpush/prは、scriptまで。deployはしない
    • masterの修正(merge commit)は、scriptとdeployを行う
    • コードに差分がないときにはdeplyフェーズのみを実行し、scriptを実行させない
      • リリースの作成やタグ付けなど!

これを、「.travis.ymlの修正を行わずにできるかな」という風に遊んでみたいと思います。

サンプルプロジェクトのセットアップ

実際に遊んで見るに当たり、サンプルとなるプロジェクトを用意しました。CakePHPのスケルトンプロジェクトを設置し、ごくごく簡単なtravis.ymlを一緒に置きます。

$ tree -a -L 3 -I .git
.
├── .travis.yml
├── README.md
└── o0h_home
    ├── .gitignore
    └── app
        ├── .editorconfig
        ├── .gitattributes
        ├── .github
        ├── .gitignore
        ├── .htaccess
        ├── .travis.yml
        ├── README.md
        ├── bin
        ├── composer.json
        ├── composer.lock
        ├── config
        ├── index.php
        ├── logs
        ├── phpcs.xml
        ├── phpunit.xml.dist
        ├── plugins
        ├── src
        ├── tests
        ├── tmp
        ├── vendor
        └── webroot

.travis.yml

language: php

php:
  - 7.2

cache:
  composer: true
  directories:
    - o0h_home/app/vendor

install:
  - cd o0h_home/app && composer install && cd $TRAVIS_BUILD_DIR

before_script:
  - echo "I'm in before_script!"

script:
  - cd o0h_home/app && vendor/bin/phpcs

deploy:
  - provider: script
    script: echo "I'm in deploy @master!"
    on:
      branch: master
  - provider: script
    script: echo "I'm in deploy @tag!"
    on:
      tags: true

まずはTravis CIのAPIを、最低限理解する

APIがどんな感じになっていれば行けそうかな?を整理する

Actionsは「どんな条件で」「どんな風に」処理を呼び出すか?という機能に見えます。
今回、私が求めている世界は、「.travis.ymlに記述されている内容を部分的に実行する」という術が必要なのではないでしょうか。つまり、「こういう呼び出し方をしたら、scriptをスキップできるよ」という方法があれば勝利に1歩近づきます。

そのためには、

  • A: 実行対象となるフェーズを任意に組み合わせて、ビルドを実行できる
  • B: 実行内容をAPIキックのタイミングで任意に改変して、ビルドを実行できる
  • C: 予め実行内容(のセット)を定義しておき、ビルドを実行できる

このいずれかの手立てがあれば良さそうです。*1

ということで、「Travis CI側のApiがとうなってるの?」を掴まない限りは、何も始まりません。

実際にAPIを見てみる

Triggering builds with API V3 - Travis CI

こちらを覗くと、

  • ビルド作成のリクエスト時に、.travis.ymlのコンテンツと同じような処理内容を渡すことができる
  • もともとの.travis.ymlの内容と、リクエストに含めた処理内容を、「どう混ぜるか」を指定することができる(merge_mode)
  • merge_modeは以下の3種類
    • replace replaces the full, original build configuration with the configuration in the API request body
    • merge (default) replaces sections in the original configuration with the configuration in the API request body
    • deep_merge merges the configuration in the API request body into the build configuration

これを見ると、「scriptだけ内容を変える」ができそうに思います💡

GitHub ActionsからTravis CIを利用できそう?

次にやるのは、「GitHub ActionsからTravis CIのAPIを叩く」です。
そして、そのリクエスト内容に「script処理をスキップする」といった内容を混ぜたり混ぜなかったりしようと思います。

そう思って探していると、「ActionsからTravis」になんだかお誂え向きのものがありました!

github.com

The Travis CI action wraps the Travis CI API so that new builds can be created based on Push and Pull Request GitHub events.

と書いてあります。これを扱えれば、「ActionsからTravis CIにリクエストを飛ばす」は出来そうな感じがしてきました。
では、今度はtravis-ci/actionsを理解するために、そもそものところである「Actionsから○○を実行する」に関する概念をつかみにいきたいと思います。

GitHub Actionsの正体は、○○を実行させるものだった

travis-ci/actionsを見ると、Dockerfileやentrypointといった単語を発見することができます。ということは、なるほど、なにか「Dockerを使ったもの」であるなと推察できます。
github actions docker でググると、次の記事に行き当たります。

developer.github.com

GitHub Actions are code run in Docker containers.

なるほど、「Dockerコンテナを起動してその中でなんでもやっていいよ!」でしょうか。
そのDockerイメージ/コンテナはどうやって作成されるんだ?

developer.github.com

GitHub Actions execute in Docker containers. An action can use an existing publicly available Docker image, or you can create your own Docker image by including a Dockerfile with your code.

既存のものを使うこともできるし、自作したDockerfileを利用することもできるよ〜と。であれば、恐らく、Actionsの設定のやり方で「どのイメージを使いますか」という設定をする部分があるのでしょう。

ここまできたら、次は「Actionsはどうやって使うんですか」のイメージを掴みに行くのが良さそうです。

Actionsに触ってみる

Actionsへは、GitHubのレポジトリ画面からアクセスできます。

f:id:o0h:20181208161550p:plain
https://help.github.com/articles/creating-a-workflow-with-github-actions/#creating-a-workflow-using-the-visual-editor

ここに、Actionsの作成画面があります。

f:id:o0h:20181208161643p:plain
https://help.github.com/articles/creating-a-workflow-with-github-actions/#creating-a-workflow-using-the-visual-editor

「Workflow」なる概念が飛び出してきました・・・!

Workflowってなんだ・・?

どうやら大本になる概念な予感がするので、Actionsの最も大元となるdocに書いてあるだろうと期待します。

developer.github.com

サイドメニューを見るに、GitHub Actions = Workflow + Action というイメージですかね?
f:id:o0h:20181207202520p:plain

説明を見てみます。

Workflows are a composite of many different GitHub Actions, or tasks you want to accomplish, and are triggered by webhook events.

Workflowsは、「発動条件」と「発動時の設定」って感じすかね。それで「何をやりますか」というSVOのOに当たる部分が、Actionsである〜という風に理解しました。
それでは、さっきの「どうやったらtravis-ci/actionsを呼び出せるの」は、workflowを作れば何か出てきそうですね。

Workflowを作ってみる!

「Create a new workflow」ボタンを押したら、新規ファイル作成のページに飛びます(ページURLとページタイトルが、new fileのそれ)。
そして、特徴的なGUIが現れます。

f:id:o0h:20181208162034p:plain
https://help.github.com/articles/creating-a-workflow-with-github-actions/#creating-a-workflow-using-the-visual-editor

デフォルトでは、コレは同時に octo-test/.github/main.workflow というファイルの新規作成でもあるということが、見て取れます。
隣にあるタブ、 Edit new file ではテキストベースでの編集に入れます。

f:id:o0h:20181208162357p:plain
https://help.github.com/articles/creating-a-workflow-with-github-actions/#creating-a-workflow-using-the-file-editor

あ!なんか、このDSLは見覚えがあるぞ。travis-ci/actiosnレポのREADMEに、こんな感じのサンプルがありました。

f:id:o0h:20181207180948p:plain

いろいろと、点と点がつながってきました・・・!

f:id:o0h:20181208162627p:plain
上部の黒い枠が「workflow」で、下の点線で囲まれた部分に「action」を定義していく

Workflowの定義

黒いボックス、 workflowの方にある Edit をクリックすると、次のようなメニューが現れます。

f:id:o0h:20181208163239p:plain

キャプチャ上の Issues になっている部分で、発火条件としたいイベントを選択するという寸法!

どこ見ればいいかな?
定義の一覧があるはずなので、公式docを当たりました。

Workflow configuration options | GitHub Developer Guide

f:id:o0h:20181207181815p:plain

resolve が何者かまだわかっていませんが、とりあえず今の我々に必要なのは on に関する知識でしょう。リンクが貼られているので、GitHub eventについて知識を求めに参ります。

今回のスコープでいうと「tagが作成されたとき」です。Event nameとしては、 create でしょうか。 これに、副条件として「タグ(が作成された)」を加えたいのですが。

具体的な処理を行う前に、イベントをフィルタリングするには?

簡単な比較条件を突っ込んで(branch == 'master' みたいな)弾ければいいのに・・・と思うのですが、どうやらそういう記述はできなさそう。
我々が使える武器は「workflow」と「action」なので、じゃあ「何かプログラマブルに処理を注入するには、actionを重ねるのかな?」と思いました。GUI的にも、actionを多層化してチェインすることはできそうなので。

どうでしょう、もし「簡単なシェルスクリプトを実行する」ためだけのDockerイメージがあれば、これは実現できそうな気がします。
GitHub公式のActionを見てみます。

github.com

bin って、めっちゃそれっぽいな・・・w 開いてみます。

github.com

Usage information for individual commands can be found in their respective directories.

はい。そして、filter というディレクトリがあるので、これにすがってみます。

For example, here is a workflow that publishes a package to npm when the master branch receives a push:

どんぴしゃっぽい!
サンプルを見てみます。

workflow "Build, Test, and Publish" {
  on = "push"
  resolves = ["Publish"]
}

action "Build" {
  uses = "actions/npm@master"
  args = "install"
}

action "Test" {
  needs = "Build"
  uses = "actions/npm@master"
  args = "test"
}

# Filter for master branch
action "Master" {
  needs = "Test"
  uses = "actions/bin/filter@master"
  args = "branch master"
}

action "Publish" {
  needs = "Master"
  uses = "actions/npm@master"
  args = "publish --access public"
  secrets = ["NPM_AUTH_TOKEN"]
}

「masterブランチでフィルタ」をしているのは、 Masterのactionですね。
ついでにというか、さっき疑問だった「どうやって外部のイメージをactionに定義するのか」もこれで解消しました。 use に指定するんですね。そして、依存関係にあるactionは前ステップにあたるあactionをneedsとして宣言する、と。

我々が実行したい createイベントでtagの場合のみ は、filterディレクトリのREADMEに言及があるのでこれをそのまま使います。

.workflowはこのようになります。

workflow "on tag push" {
  on = "create"
  resolves = ["filter-only-tag-created"]
}

action "filter-only-tag-created" {
  uses = "actions/bin/filter@master"
  args = "tag"
}
  • on tag push というworkflowを
    • createイベントに反応して起動させ
    • filter-only-tag-created actionを呼ばせる
  • filter-only-tag-created
    • actions/bin/filter@master" のイメージを利用し
    • tags というパラメータ込みで起動する

actionにおけるuseの指定ですが、

  • 自レポジトリの場合は、./ から初めてPJルートからの相対パス指定
  • 他レポジトリの場合は、 user名/レポジトリ名/ディレクトリへのパス@ブランチ と指定

という形式になるようです。

developer.github.com

filterを設定できたように思うので、Travis CIをキックする処理を入れます。 f:id:o0h:20181207190205p:plain

ここで、request bodyを変えてあげる必要があります。
が、travis-ci/actionsレポでentrypointに指定されているjsの内容を見てみると、どうやらコンテンツが固定されているようです。

自前のDockerfileを用意しよう・・・

どうやら外から注入というのが厳しいような気がしたので、自前で用意してみます。幸い、travis-ci/actionを見ても、複雑な処理は入っていません。なので、丸々っと必要な処理をコピペしてしまいます。

ひとまず、以下のようにしました。

gist.github.com

自レポジトリ内のDockerイメージを利用する方法が、公式ドキュメントに書いてあります。PJルートから、コンテキストとなる = Dockerfileのあるディレクトリへのパスを指定してあげれば良さそうですね。
最終的には、このようになりました。

workflow "on tag push" {
  on = "create"
  resolves = ["deploy"]
}

action "filter-only-tag-created" {
  uses = "actions/bin/filter@master"
  args = "tag"
}

action "deploy" {
  uses = "./.github-aciton/travis-ci"
  needs = ["filter-only-tag-created"]
  secrets = ["TRAVIS_TOKEN"]
}

Travis CIの利用には、TRAVIS_TOKEN が必要です。
https://travis-ci.com/account/preferences で取得し、actionに設定してください。

f:id:o0h:20181208165506p:plain
`Create a new secret` より追加

実行してみる

エディタ右上の、Start commit からコミットします。
その後にタグを打つと、定義したとおりのworkflowが動きます。

その後は、実行結果をグラフィカルに確認が可能です。 GitHub上で「Actionの履歴・実行結果が見れる様子」は、この動画の1:10辺りで垣間見ることができます。

左側に実行されたworkflowの履歴が表示され、その中にあるactionごとの実行結果ステータスが色により表されます。更に、actionのブロック内にあるlogをクリックすると、詳細が確認できるわけです。

まとめ

本来であれば、.travis.ymlもちゃんといじりつつ行うべきだと思っています。
ただ、「新機能のポテンシャル」としては、「ここまで簡単に色々とできる!!」という手応えは間違いなく感じたので、個人的には大満足です。
Dockerでいろいろできる!はやばい。

どんどん活用していくしかねぇな・・・!という温度感です。

参考記事等

*1:もちろん、config等を設けたり環境変数デフラグをもたせて、travis.ymlやそこから呼び出すスクリプトの中でコントロールをする・・という手段はそうていされますが、今回の目的の1つに「あまりtravis.ymlを書き換えたくないな」があるので、こちらは却下