GitHub Actions (Action) on CircleCI

# 概要

アイハブアぎっとはぶあくしょんず :raised_hand: 
アイハブアさーくるしーあい　:raised_hand: 
ンー！ :boom: 
**GitHub Actions on CircleCI** :smirk:

# 前置き

GitHub Actions の Action を GitHub Actions ではなく CircleCI で動かそう、というものです。
この取り組みには2つの目的があります。

- GitHub Actions が使えない旧プラン勢でも Action を使えるようにする
- なんだかイケナイコトをしている気分を楽しむ

ちなみに、GitHub Actions 用の YAML を CircleCI で動かすとか、GitHub Actions のように `push` 以外のイベントを CircleCI でも動かそう、といった取り組みではありません。

# 手順

GitHub Actions (Action) on CircleCI を実現するには次の手順が必要になります。

- GitHub Actions の Action をローカル環境で動かす
- 動いたスクリプトを CircleCI の config.yml に書く

# GitHub Actions の Action をローカル環境で動かす

今回は [stale](https://github.com/actions/stale) という Action で試します。
これは放置された Issue や PullRequest を見つけて「放置されてるよ！」や「クローズしとくね！」といった整理をしてくれる便利な Action です。

まずは `clone` します。

```console
$ git clone https://github.com/actions/stale
$ cd stale
```

リポジトリには `docs/contributors.md` というファイルがあります。
とてもシンプルなドキュメントになっており、どうすれば手元でも使えるようになるのか予想する手助けをしてくれます。

```console
$ cat docs/contributors.md
# Contributors

### Checkin

- Do check in source (src)
- Do not check in build output (lib)
- Do not check in node_modules
```

~~全くわかりませんね。~~ 

## `action.yml` を読む

Actions の設定が記述された `action.yml` [^1] を読むと nodejs v12 な環境で `lib/main.js` というファイルを実行すれば良さそうだと分かります。

```console
$ cat action.yml | yq -y '.runs'
using: node12
main: lib/main.js
```

ちなみに [yq](https://github.com/mikefarah/yq) は YAML を [jq](https://github.com/stedolan/jq) で扱えるようになる便利ツールです。 

[^1]: 本記事では action.yml の説明はしません. 気になる方はドキュメントをお読みください -> https://help.github.com/ja/actions/automating-your-workflow-with-github-actions/metadata-syntax-for-github-actions

## ビルドする

`package.json` を読むとなんとなく `npm run build` すれば良さそうです。
その前に `npm install` して依存モジュールをインストールしたいところですが、何やらエラーが出ます。

```console
$ npm install
npm ERR! code ETARGET
npm ERR! notarget No matching version found for @actions/exec@^0.0.0.
npm ERR! notarget In most cases you or one of your dependencies are requesting
...略
```

細かい話はおいといて `toolkit` ディレクトリ以下に `tgz` ファイルがあるのでこれらを指定して install する必要があります。

```console
$ npm install ./toolkit/*
# ...略
+ @actions/core@0.0.0
+ @actions/tool-cache@0.0.0
+ @actions/io@0.0.0
+ @actions/exec@0.0.0
+ @actions/github@0.0.0
added 646 packages from 453 contributors in 17.345s
```

これで `npm run build ` すれば OK.


```console
$ npm run build

> stale-action@1.0.0 build /Users/yasuhiroki/ghq/github.com/actions/stale
> tsc

 $ ls lib
main.js
```

## ローカルで動かす

無事にビルドできたので実行してみましょう。

```console
$ node lib/main.js
##[error]Error: Input required and not supplied: repo-token
##[error]Input required and not supplied: repo-token
```

何やら必須な値があるようです。
ここも `action.yml` を読むとヒントが書いてあります。

```console
$ cat action.yml | yq -y .inputs
repo-token:
  description: Token for the repository. Can be passed in using {{ secrets.GITHUB_TOKEN }}
  required: true
stale-issue-message:
  description: The message to post on the issue when tagging it. If none provided, will not mark issues stale.
stale-pr-message:
  description: The message to post on the pr when tagging it. If none provided, will not mark pull requests stale
...略
```

`repo-token` や `stale-issue-message` などといった値が必要のようです。これらの値は環境変数で設定するのですが `stale` ではさらに、 `INPUT_XXX` などと INPUT を prefix につけなければなりません。[^2]

[^2]: staleが依存している `@actions/core` のこの[実装](https://github.com/actions/toolkit/blob/d4975510fed64dc22ede73dd0dc0cc91059da797/packages/core/src/core.ts#L67-L72) によるものです.

というわけでこんな感じです。

```console
$ env INPUT_REPO-TOKEN=xxx \
      INPUT_DAYS-BEFORE-STALE=1 \
      INPUT_DAYS-BEFORE-CLOSE=1 \
      INPUT_STALE-ISSUE-LABEL=stale \
      INPUT_STALE-PR-LABEL=stale \
      INPUT_STALE-ISSUE-MESSAGE='stale issue' \
      INPUT_STALE-PR-MESSAGE='stale pull request' \
      INPUT_OPERATIONS-PER-RUN=30 \
      GITHUB_REPOSITORY=yasuhiroki/enjoy-circleci \
      node lib/main.js
##[debug]found issue: test issue last updated 2019-12-12T08:26:30Z
##[debug]marking issuetest issue as stale
```

お。無事に動きました。

# 動いたスクリプトを CircleCI の config.yml に書く

ローカルで動かせたので CircleCI でも動かしてみましょう。
stale を `clone` して `install` して `build` して実行すればOKなので↓のようなスクリプトになりますね。

```yaml
command: |
  git clone https://github.com/actions/stale.git
  cd stale
  npm install ./toolkit/*
  npm run build
  node lib/main.js
```

## 環境変数を定義

実行には環境変数が必要でした。ということで `environment` で定義しましょう。

ただし `INPUT_REPO-TOKEN` は GitHub の Token なので YAML にベタ書きしたくありません。
CircleCI のリポジトリ設定や context などで定義しても良いですが、 `INPUT_REPO-TOKEN` という他では使わなさそうな名前でわざわざ設定するのはイマイチな気もします。

ここは `GITHUB_TOKEN` という汎用性のありそうな名前で設定をしておき、 `env INPUT_REPO-TOKEN=${GITHUB_TOKEN} node lib/main.js` という形にしてみましょう。[^3]

```yaml
command: |
  git clone https://github.com/actions/stale.git
  cd stale
  npm install ./toolkit/*
  npm run build
  env INPUT_REPO-TOKEN=${GITHUB_TOKEN} node lib/main.js
environment:
  # INPUT_REPO-TOKEN: xxx
  INPUT_DAYS-BEFORE-STALE: 1
  INPUT_DAYS-BEFORE-CLOSE: 1
  INPUT_STALE-ISSUE-LABEL: stale
  INPUT_STALE-PR-LABEL: stale
  INPUT_OPERATIONS-PER-RUN: 30
  GITHUB_REPOSITORY: yasuhiroki/enjoy-circleci
```

[^3]: GitHub Actionsのように `INPUT_REPO-TOKEN: ${{secrets.GITHUB_TOKEN}}` などと書けると嬉しいのですが...

## node が使える executor を指定

node が必要なので executor は `circleci/node` を使いましょう。

```yaml
docker:
  - image: circleci/node:latest
```

## config.yml の完成

そんなこんなで GitHub Actions の stale を実行する CircleCI の config.yml が完成しました。
ついでに `cache` の設定もして↓のようなYAMLになります。

```yaml
version: 2.1

jobs:
  build:
    docker:
      - image: circleci/node:latest
    steps:
      - run:
          command: |
            git clone https://github.com/actions/stale.git
      - restore_cache:
          keys:
            - node_modules-{{checksum "stale/package.json"}}
            - node_modules
      - run:
          command: |
            cd stale
            npm install ./toolkit/*
      - save_cache:
          key: node_modules-{{checksum "stale/package.json"}}
          paths:
            - stale/node_modules
      - run:
          command: |
            cd stale
            npm run build
            env INPUT_REPO-TOKEN=${GITHUB_TOKEN} node lib/main.js
          environment:
            # INPUT_REPO-TOKEN: xxx
            INPUT_DAYS-BEFORE-STALE: 1
            INPUT_DAYS-BEFORE-CLOSE: 1
            INPUT_STALE-ISSUE-LABEL: stale
            INPUT_STALE-PR-LABEL: stale
            INPUT_STALE-ISSUE-MESSAGE: 'stale issue'
            INPUT_STALE-PR-MESSAGE: 'stale pull request'
            INPUT_OPERATIONS-PER-RUN: 30
            GITHUB_REPOSITORY: yasuhiroki/enjoy-circleci

workflows:
  version: 2
  build:
    jobs:
      - build:
          # 環境変数 GITHUB_TOKEN を定義した context
          context: my_context
```

# 実行結果

無事に CircleCI で stale が動きました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27201/a9783a1f-62e6-6a1b-860f-c8529af5e7b5.png)

# まとめ

これで GitHub Actions が使えない環境の人でも「GitHub Actions (の Action を CircleCI上で) 使ってるよ :+1:」 と言えますね。

今回紹介した stale 以外の Action も似たような流れで CircleCI 上で動かせる...と言いたいところですが、実際には `github` Context に依存していたり、そもそも Node.js じゃなくて Docker で動くものだったりすると、なかなか難しいところがあります。
GitHub Actions の Action を実行するための orb があれば少しは楽になるでしょうが、そこまでしてがんばって GitHub Actions の Action が使いたいなら、Action のロジック部分を抜き出してモジュール化して、orb化する方が適切のような気もします。