GitHub ActionsのActionを作る

このエントリーをはてなブックマークに追加

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 documentation - GitHub Docs
Go to GitHub Actions documentation - GitHub Docs

GitHub ActionsやActionの作り方などは公式ドキュメントにも書かれてますので1次ソースとしてどうぞ

作り方を決める

GitHub ActionsではAzure Pipelinesと同様にWindows, Linux, macOSで動作します。そしてActionの作り方は表のように2つあります

作り方動作環境
Docker ContainerLinux
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

About custom actions - GitHub Docs
Go to About custom actions - GitHub Docs

Docker Containerに関してはAzure Pipelinesでの対応状況を見る感じそのうちWindowsにも対応しそうですが、現在のところではLinuxでしか動かないみたいです

今回はWindowsやmacOSにも対応させたいのでJavaScript(Node.js)のほうで作ろうと思います。そしてJavaScriptは書きたくないのでTypeScriptを使います

あと、今回はDangerを実行するactionを作ります

リポジトリーセットアップ

Node.jsのv12系統を使うので公式ページなどから最新版をインストールしてください。あとVisual Studio Codeのインストールも忘れずに

ローカルにリポジトリーとなるフォルダーを作成したら、VS Codeで開いてセットアップをしていきます

  1. npm init -y
  2. npm install typescript --save-dev (typescriptがグローバルになければnpm install -g typescriptも)
  3. tsc -init
  4. npm install @types/node --save-dev
  5. npm install @actions/core --save
  6. npm install @actions/io --save
  7. 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/iowhich関数の第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/coregetInput関数があるのでそれを使っていきましょう

今回は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/execexec関数を利用してDangerのインストールやDanger pluginのインストールを行っていきます。第3引数でexec関数のオプションを渡せるので、failOnStdErrtrueにしておきます

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

GitHub - MeilCli/danger-action: Execute danger action for GitHub Actions.
Go to GitHub - MeilCli/danger-action: Execute danger action for GitHub Actions.

Marketplaceにも公開してます:

Danger action - GitHub Marketplace

Run danger

github.com

Danger action - GitHub Marketplace
Go to Danger action - GitHub Marketplace