Azure Pipelines職人(自称)ですが、そのAzure PipelinesのクローンであるGitHub Actionsでも職人(自称)をやらないとなぁ~~となったのでActionを作って公開してみましたので、Actionの作り方など
前置き
GitHub Actions documentation - GitHub Docs
Automate, customize, and execute your software development workflows right in your repository with GitHub Actions. You can discover, create, and share actions to perform any job you'd like, including CI/CD, and combine actions in a completely customized workflow.
help.github.com
GitHub ActionsやActionの作り方などは公式ドキュメントにも書かれてますので1次ソースとしてどうぞ
作り方を決める
GitHub ActionsではAzure Pipelinesと同様にWindows, Linux, macOSで動作します。そしてActionの作り方は表のように2つあります
作り方 | 動作環境 |
---|---|
Docker Container | Linux |
JavaScript(Node.js) | Linux, Windows, macOS |
About custom actions - GitHub Docs
Actions are individual tasks that you can combine to create jobs and customize your workflow. You can create your own actions, or use and customize actions shared by the GitHub community.
help.github.com
Docker Containerに関してはAzure Pipelinesでの対応状況を見る感じそのうちWindowsにも対応しそうですが、現在のところではLinuxでしか動かないみたいです
今回はWindowsやmacOSにも対応させたいのでJavaScript(Node.js)のほうで作ろうと思います。そしてJavaScriptは書きたくないのでTypeScriptを使います
あと、今回はDangerを実行するactionを作ります
リポジトリーセットアップ
Node.jsのv12系統を使うので公式ページなどから最新版をインストールしてください。あとVisual Studio Codeのインストールも忘れずに
ローカルにリポジトリーとなるフォルダーを作成したら、VS Codeで開いてセットアップをしていきます
npm init -y
npm install typescript --save-dev
(typescriptがグローバルになければnpm install -g typescript
も)tsc -init
npm install @types/node --save-dev
npm install @actions/core --save
npm install @actions/io --save
npm install @actions/exec --save
@actions/core
とかはGitHub Actions用に用意されているパッケージでactions/toolkitに見に行けば使い方などが書かれてます。今回はRubyとBundlerが入ってるかを確認するために@actions/io
、コマンドの実行のために@actions/exec
も入れます
また、tsconfig.json
ではoutDir
を./lib
に、rootDir
を./src
にしておいてください
実行コードを書く
src/main.ts
ファイルを作り、そこにactionの実行コードを書いていきます
import * as core from "@actions/core";
async function run() {
try {
} catch (error) {
core.setFailed(error.message);
}
}
run();
基本の形としてrun関数を書いておき、その中のtry-catchのtry部分に処理を書いていくことにします
import * as io from "@actions/io";
async function checkEnvironment() {
await io.which("ruby", true);
await io.which("bundle", true);
}
まず、RubyとBundlerの存在確認ですが、@actions/io
のwhich
関数の第2引数をtrue
にしておくとコマンドが存在しなかった場合にエラーを排出するようなのでこれを利用します
interface Option {
readonly dangerVersion: string;
readonly pluginsFile: string | null;
readonly dangerFile: string;
readonly dangerId: string;
}
async function getOption(): Promise<Option> {
return {
dangerVersion: core.getInput("danger_version", { required: true }),
pluginsFile: core.getInput("plugins_file"),
dangerFile: core.getInput("danger_file", { required: true }),
dangerId: core.getInput("danger_id", { required: true })
};
}
次にactionに渡される引数を取得する部分を書きます。@actions/core
にgetInput
関数があるのでそれを使っていきましょう
今回はDangerを実行するだけのactionなので、Dangerのバージョン情報とDangerのpluginをインストールするためのgemfileのパスとDangerを実行するための情報を引数から取得します
import * as exec from "@actions/exec";
async function installDanger(option: Option) {
await exec.exec(
`gem install danger --version "${option.dangerVersion}"`,
undefined,
{ failOnStdErr: true }
);
if (option.pluginsFile != null) {
await exec.exec(
`bundle install --gemfile=${option.pluginsFile}`,
undefined,
{ failOnStdErr: true }
);
}
}
そして@actions/exec
のexec
関数を利用してDangerのインストールやDanger pluginのインストールを行っていきます。第3引数でexec
関数のオプションを渡せるので、failOnStdErr
をtrue
にしておきます
async function runDanger(option: Option) {
await exec.exec(
`danger --dangerfile=${option.dangerFile} --danger_id=${option.dangerId}`,
undefined,
{ failOnStdErr: true }
);
}
ここまで来るとあとはただDangerを実行するだけのコードを書けばいいだけです
async function ignoreRubyWarning() {
await core.exportVariable("RUBYOPT", "-W0");
}
ただ、これを実行してみるとRubyがおせっかいにも warning: Insecure world writable dir {RubyBinPath} in PATH, mode 040777
を排出してきて、actionが失敗した状態になってしまうので、Rubyの警告を出さないようにしておきます
あとはこれらの関数を先ほどのrun
関数で繋げていけばいいだけです
完成コード: src/main.ts
action.ymlを書く
あとはリポジトリートップにaction.yml
を書くだけです
name: 'Danger action'
description: 'Run danger, unofficial action'
author: 'MeilCli'
branding:
icon: zap
color: red
inputs:
danger_version:
description: 'danger version'
default: '>= 6.0.0'
plugins_file:
description: 'gemfile for danger plugin'
danger_file:
description: 'dangerfile for danger'
required: true
danger_id:
description: 'danger id, set identifier string'
required: true
runs:
using: 'node12'
main: 'lib/main.js'
今回作成したactionのaction.yml
はこのようになっています
inputs
に引数の情報を書いていき、runs
でNode.jsを使うことと実行ファイルを指定してください
作ったactionはMarketplaceに公開するためにbranding
とか書いてますがリポジトリーを公開するだけなら必要ないはずです
公開する
公開するにはGitHubのリポジトリーに置いておく必要があります
tsc
コマンドなどでTypeScriptをトランスコンパイルしておきlib/main.js
があることを確認します。確認出来たらpushしましょう
注意点として、実行ファイルが依存しているnode_modules
もリポジトリー内に配置しておく必要があります
node_modules/*
!node_modules/@actions
今回は必要最低限にとどめるために.gitignore
にこのように書きました。またRelease Tagを作っておくと便利です(v1とかv2のような感じのもの)
利用する
jobs:
danger:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/setup-ruby@v1
with:
ruby-version: '2.6'
- uses: MeilCli/danger-action@v1
with:
plugins_file: 'Gemfile'
danger_file: 'Dangerfile'
danger_id: 'danger-pr'
env:
DANGER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
利用するときは通常のactionを利用するように{アカウント名}/{リポジトリー名}@{バージョン}
を書きます
実はactions/checkout
などはactionsのOrganizationで公開されていて、usesの部分で特殊対応が入ってるわけではありません(action.ymlのところで特殊対応はあったりしますが)
おわりに
というわけで、GitHub ActionsのActionの作り方の流れはわかってもらえたかなと思います。TypeScriptを使えば比較的簡単にActionを作ることができるので、皆さんもどんどん作っていって、どんどん便利なものができていけばなと思います
今回作ったもの:
GitHub - MeilCli/danger-action: Execute danger action for GitHub Actions.
Execute danger action for GitHub Actions. Contribute to MeilCli/danger-action development by creating an account on GitHub.
github.com
Marketplaceにも公開してます:
Danger action - GitHub Marketplace
Run danger
github.com