GitHub Actions Open Beta に申し込んで1ヶ月以上経ち,ようやく使えるようになったみたいなので実際にどう使うのか調査してみたメモ. Beta 版は GitHub のプライベートリポジトリにしか使えないため,公開リポジトリに使うにはもう少し待つ必要がありそう.
GitHub Actions とは
GitHub Universe 2018 で発表されたときにメディアが記事にしているので,そちらを読んでください.
- https://www.publickey1.jp/blog/18/github_actionsdockergithub_universe_2018.html
- https://cloud.watch.impress.co.jp/docs/event/1148428.html
- https://codezine.jp/article/detail/11170
公式ドキュメント
hello world 的なのは下記のリンクから action をつくるチュートリアルと workflow をつくるチュートリアルをやれば良いです.
全体的な流れは,
Dockerfileとentrypoint.shをつくって欲しいアクションを定義する(プリセットのアクションしか使わない場合はこのステップは不要)- GitHub の Actions タブに行き,workflow をビジュアルエディタで定義する.ワークフローの起点と,そこからのアクションの連なりをグラフエディタでつくれる.直接
.github/以下を書いても良いけど,こっちのほうがチェックもしてくれるし良さそう - 実際に起点となるイベントを起こしてみる(例えば
git push) - 再び Actions タブに行くと,各ワークフローがどう走って結果がどうだったか(ログなど)が確認できる
- ワークフローを編集したい時は
.github/以下の.workflowファイルの Edit ボタンをクリックすると再びビジュアルエディタが立ち上がる
参考リンク:
- GitHub Actions
- About GitHub Actions
- Creating a workflow with GitHub Actions
- Creating workflows
- Workflow configuration options
基本
各アクションはアクションを走らせる Docker コンテナのための Dockerfile と処理のエントリポイントになるシェルスクリプト entrypoint.sh の組み合わせで定義できます.
Dockerfile のラベルでメタ情報(description とか action name とか)を記述します.実際の処理は entrypoint.sh(もしくは entrypoint.sh から呼ばれるスクリプトなど)でシェルスクリプトで定義します.依存しているツール(例えば JSON を扱うなら jq とか)は Dockerfile をビルドするときにコンテナに apt install などでインストールしておきます.
コンテナ実行時の情報はスクリプトの引数か,環境変数で与えられます.環境変数はアクションをワークフローエディタで編集する時に指定でき,スクリプト側からそれらが参照できます.またスクリプトの引数もアクションの編集で指定でき,entrypoint.sh の引数としてアクション側に渡ってきます.秘密の情報 (secrets) はリポジトリページの settings から secrets タブを選択してキー・バリューで秘密の情報を入力しておき,アクションの設定でどのキーを使うかを指定しておくと,Docker コンテナから環境変数としてそれらが見える?ようです(env コマンドの出力はフィルタされているのか確認できなかった)
アクションの起点は GitHub の公式ドキュメントに乗っている一覧のイベント から選べます.例えば on: "push" と指定するとコミットをプッシュするたびにワークフローが走ります.
どうやってアクションを書くのか
注:特に明記しない限り,実動作ベースでの調査をしたので,今後動作が変わったり,勘違いしている箇所があるかもしれません
公式のアクション
actions organization に各アクションごとにリポジトリが置かれているので,それを参考にできます.
アクションが実行される環境
pwd で確認すると,アクションのエントリポイントとなる entrypoint.sh は /github/workspace というディレクトリ内で実行されています.
このディレクトリについては下記の公式ドキュメントに詳細がありました:
どうやら対象のリポジトリのルートディレクトリになっているらしいです.試しに ls -la してみると
total 24 drwxr-xr-x 6 root root 4096 Dec 9 13:54 . drwxr-xr-x 5 root root 4096 Dec 9 13:55 .. drwxr-xr-x 8 root root 4096 Dec 9 13:54 .git drwxr-xr-x 2 root root 4096 Dec 9 13:54 .github drwxr-xr-x 2 root root 4096 Dec 9 13:54 action-a drwxr-xr-x 2 root root 4096 Dec 9 13:54 action-b
.git ディレクトリが置かれており,リポジトリのルートにいると分かります.
すでにリポジトリはクローンされた状態で実行されるので,自前で対象のリポジトリをクローンしてくる必要は無さそうです.
アクション内で参照できる環境変数
どうやら $GITHUB_* という環境変数に情報が入っているようです.一覧は公式ドキュメントにあり,
例えば,on: "push" でリポジトリへの push を行った際の環境変数は下記です:
GITHUB_EVENT_PATH=/github/workflow/event.json GITHUB_WORKFLOW=hello GITHUB_ACTION=Hello World GITHUB_REPOSITORY=rhysd/hello-github-actions GITHUB_WORKSPACE=/github/workspace GITHUB_SHA=52875f0b1ed9882770c0cfddbcfe95607e4b2986 GITHUB_ACTOR=rhysd GITHUB_REF=refs/heads/master GITHUB_EVENT_NAME=push
これでどのワークフローやアクションとして自身が実行されているかを知ることができます.
アクション内で参照できるフックイベントの情報
ちなみに $GITHUB_EVENT_PATH の /github/workflow/event.json には起点になったイベントの情報が JSON で入っています.各イベントごとに入っている情報はGitHub の公式ドキュメントで知ることができます.詳細なイベントフックの情報が欲しい場合はこっちを見たほうが良さそうです.
例えば on: "push" での event.json の中身は下記です:
{ "after": "c776e7146a031950fa579791f71448336a07880c", "base_ref": null, "before": "52875f0b1ed9882770c0cfddbcfe95607e4b2986", "commits": [ { "added": [], "author": { "email": "my-email@example.com", "name": "rhysd", "username": "rhysd" }, "committer": { "email": "my-email@example.com", "name": "rhysd", "username": "rhysd" }, "distinct": true, "id": "c776e7146a031950fa579791f71448336a07880c", "message": "check event.json", "modified": [ "action-a/entrypoint.sh" ], "removed": [], "timestamp": "2018-12-09T21:39:47+09:00", "tree_id": "7d4c96c54a304cd3af1bdbac684099bbbeb1dcc9", "url": "https://github.com/rhysd/hello-github-actions/commit/c776e7146a031950fa579791f71448336a07880c" } ], "compare": "https://github.com/rhysd/hello-github-actions/compare/52875f0b1ed9...c776e7146a03", "created": false, "deleted": false, "forced": false, "head_commit": { "added": [], "author": { "email": "my-email@example.com", "name": "rhysd", "username": "rhysd" }, "committer": { "email": "my-email@example.com", "name": "rhysd", "username": "rhysd" }, "distinct": true, "id": "c776e7146a031950fa579791f71448336a07880c", "message": "check event.json", "modified": [ "action-a/entrypoint.sh" ], "removed": [], "timestamp": "2018-12-09T21:39:47+09:00", "tree_id": "7d4c96c54a304cd3af1bdbac684099bbbeb1dcc9", "url": "https://github.com/rhysd/hello-github-actions/commit/c776e7146a031950fa579791f71448336a07880c" }, "pusher": { "email": "rhysd@users.noreply.github.com", "name": "rhysd" }, "ref": "refs/heads/master", "repository": { "archive_url": "https://api.github.com/repos/rhysd/hello-github-actions/{archive_format}{/ref}", "archived": false, "assignees_url": "https://api.github.com/repos/rhysd/hello-github-actions/assignees{/user}", "blobs_url": "https://api.github.com/repos/rhysd/hello-github-actions/git/blobs{/sha}", "branches_url": "https://api.github.com/repos/rhysd/hello-github-actions/branches{/branch}", "clone_url": "https://github.com/rhysd/hello-github-actions.git", "collaborators_url": "https://api.github.com/repos/rhysd/hello-github-actions/collaborators{/collaborator}", "comments_url": "https://api.github.com/repos/rhysd/hello-github-actions/comments{/number}", "commits_url": "https://api.github.com/repos/rhysd/hello-github-actions/commits{/sha}", "compare_url": "https://api.github.com/repos/rhysd/hello-github-actions/compare/{base}...{head}", "contents_url": "https://api.github.com/repos/rhysd/hello-github-actions/contents/{+path}", "contributors_url": "https://api.github.com/repos/rhysd/hello-github-actions/contributors", "created_at": 1544355055, "default_branch": "master", "deployments_url": "https://api.github.com/repos/rhysd/hello-github-actions/deployments", "description": null, "downloads_url": "https://api.github.com/repos/rhysd/hello-github-actions/downloads", "events_url": "https://api.github.com/repos/rhysd/hello-github-actions/events", "fork": false, "forks": 0, "forks_count": 0, "forks_url": "https://api.github.com/repos/rhysd/hello-github-actions/forks", "full_name": "rhysd/hello-github-actions", "git_commits_url": "https://api.github.com/repos/rhysd/hello-github-actions/git/commits{/sha}", "git_refs_url": "https://api.github.com/repos/rhysd/hello-github-actions/git/refs{/sha}", "git_tags_url": "https://api.github.com/repos/rhysd/hello-github-actions/git/tags{/sha}", "git_url": "git://github.com/rhysd/hello-github-actions.git", "has_downloads": true, "has_issues": true, "has_pages": false, "has_projects": true, "has_wiki": true, "homepage": null, "hooks_url": "https://api.github.com/repos/rhysd/hello-github-actions/hooks", "html_url": "https://github.com/rhysd/hello-github-actions", "id": 161032314, "issue_comment_url": "https://api.github.com/repos/rhysd/hello-github-actions/issues/comments{/number}", "issue_events_url": "https://api.github.com/repos/rhysd/hello-github-actions/issues/events{/number}", "issues_url": "https://api.github.com/repos/rhysd/hello-github-actions/issues{/number}", "keys_url": "https://api.github.com/repos/rhysd/hello-github-actions/keys{/key_id}", "labels_url": "https://api.github.com/repos/rhysd/hello-github-actions/labels{/name}", "language": "Dockerfile", "languages_url": "https://api.github.com/repos/rhysd/hello-github-actions/languages", "license": null, "master_branch": "master", "merges_url": "https://api.github.com/repos/rhysd/hello-github-actions/merges", "milestones_url": "https://api.github.com/repos/rhysd/hello-github-actions/milestones{/number}", "mirror_url": null, "name": "hello-github-actions", "node_id": "MDEwOlJlcG9zaXRvcnkxNjEwMzIzMTQ=", "notifications_url": "https://api.github.com/repos/rhysd/hello-github-actions/notifications{?since,all,participating}", "open_issues": 0, "open_issues_count": 0, "owner": { "avatar_url": "https://avatars3.githubusercontent.com/u/823277?v=4", "email": "rhysd@users.noreply.github.com", "events_url": "https://api.github.com/users/rhysd/events{/privacy}", "followers_url": "https://api.github.com/users/rhysd/followers", "following_url": "https://api.github.com/users/rhysd/following{/other_user}", "gists_url": "https://api.github.com/users/rhysd/gists{/gist_id}", "gravatar_id": "", "html_url": "https://github.com/rhysd", "id": 823277, "login": "rhysd", "name": "rhysd", "node_id": "MDQ6VXNlcjgyMzI3Nw==", "organizations_url": "https://api.github.com/users/rhysd/orgs", "received_events_url": "https://api.github.com/users/rhysd/received_events", "repos_url": "https://api.github.com/users/rhysd/repos", "site_admin": false, "starred_url": "https://api.github.com/users/rhysd/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/rhysd/subscriptions", "type": "User", "url": "https://api.github.com/users/rhysd" }, "private": true, "pulls_url": "https://api.github.com/repos/rhysd/hello-github-actions/pulls{/number}", "pushed_at": 1544359187, "releases_url": "https://api.github.com/repos/rhysd/hello-github-actions/releases{/id}", "size": 3, "ssh_url": "git@github.com:rhysd/hello-github-actions.git", "stargazers": 0, "stargazers_count": 0, "stargazers_url": "https://api.github.com/repos/rhysd/hello-github-actions/stargazers", "statuses_url": "https://api.github.com/repos/rhysd/hello-github-actions/statuses/{sha}", "subscribers_url": "https://api.github.com/repos/rhysd/hello-github-actions/subscribers", "subscription_url": "https://api.github.com/repos/rhysd/hello-github-actions/subscription", "svn_url": "https://github.com/rhysd/hello-github-actions", "tags_url": "https://api.github.com/repos/rhysd/hello-github-actions/tags", "teams_url": "https://api.github.com/repos/rhysd/hello-github-actions/teams", "trees_url": "https://api.github.com/repos/rhysd/hello-github-actions/git/trees{/sha}", "updated_at": "2018-12-09T12:34:31Z", "url": "https://github.com/rhysd/hello-github-actions", "watchers": 0, "watchers_count": 0 }, "sender": { "avatar_url": "https://avatars3.githubusercontent.com/u/823277?v=4", "events_url": "https://api.github.com/users/rhysd/events{/privacy}", "followers_url": "https://api.github.com/users/rhysd/followers", "following_url": "https://api.github.com/users/rhysd/following{/other_user}", "gists_url": "https://api.github.com/users/rhysd/gists{/gist_id}", "gravatar_id": "", "html_url": "https://github.com/rhysd", "id": 823277, "login": "rhysd", "node_id": "MDQ6VXNlcjgyMzI3Nw==", "organizations_url": "https://api.github.com/users/rhysd/orgs", "received_events_url": "https://api.github.com/users/rhysd/received_events", "repos_url": "https://api.github.com/users/rhysd/repos", "site_admin": false, "starred_url": "https://api.github.com/users/rhysd/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/rhysd/subscriptions", "type": "User", "url": "https://api.github.com/users/rhysd" } }
アクション間で情報を受け渡しする
各アクションで毎回リポジトリが clone されるわけではなく,共通のワークスペースが使われます.なので前段のアクションで作成したファイルは後段のアクションでもアクセスすることができます.これによって,あるアクションでビルドした生成物を使って,後段で linter やテスト,デプロイを走らせるといったことができそうです.
サードパーティのアクションが後段にいる場合にはアクセストークンなどの秘密情報をファイルに保存しないように気をつける必要があります.試した限りでは環境変数は後段のアクションに受け継がれないので,前述の secrets 機能で環境変数に置いておくのが良さそうです.
Custom GitHub Action をつくる
アクションは別リポジトリや Docker コンテナに切り出して再利用することができます.actions organization に置かれている公式のアクション集が参考になります.
アクション1つのみを公開するとき
つくるリポジトリに対して公開するアクションが1つのときはリポジトリのルートにそのまま Dockerfile と entrypoint.sh を置きます
your-awesome-action/ ├── Dockerfile └── entrypoint.sh
使う側のリポジトリの .github/*.workflow には uses に owner/repo@ref を指定すると使えるようになります.ref はブランチ名か commit SHA1 の初め7桁を指定します(タグ名でも良い?).uses は他にも docker:// で始めて直接 Docker コンテナを指定することもできるようです.
action "Awesome Action" {
uses = "your-name/your-awesome-action@master"
}
workflow "hello" {
on = "push"
resolves = ["Awesome Action"]
}
例: https://github.com/actions/npm
アクションを複数公開する時
1つのリポジトリで複数のアクションを公開したい時は,Dockerfile と entrypoint.sh をサブディレクトリに置きます.
your-awesome-action/
├── action-a
│ ├── Dockerfile
│ └── entrypoint.sh
└── action-b
├── Dockerfile
└── entrypoint.sh
使う側のリポジトリの .github/*.workflow には uses に owner/repo/subdir@ref を指定すると使えるようになります(ref はアクション1つのみの場合と同じ).
action "Awesome Action A" {
uses = "your-name/your-awesome-action/action-a@master"
}
workflow "hello" {
on = "push"
resolves = ["Awesome Action A"]
}
例: https://github.com/actions/bin
感想
ざっと見た感じ,GitHub Action は下記の場合に便利そうです
- 開発のワークフローを自動化したいとき
- 自動でラベルを貼り替える
- 自動で issue を close する
- パッケージングしてデプロイしたりライブラリをリリースしたり
- ちょっとした CI を回したい
ただし下記の点は気をつけておいたほうが良さそうです