GitHub Actionsアクション自作からMarketplace公開まで

この記事は、[klis Advent Calendar 2020](https://adventar.org/calendars/5135)の~~21~~20日目です。
今年も[去年](https://qiita.com/eggplants/items/c956b87250ea29a26b85)に引き続き、klis要素の薄い記事になります…

# 目次

- <a href="#%E4%BD%95%E3%82%92%E3%81%99%E3%82%8B%E3%81%AE%E3%81%8B">何をするのか</a>
- <a href="#github-actions%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6">GitHub Actionsについて</a>
  - <a href="#%E9%81%B8%E6%8A%9E%E8%82%A2">選択肢</a>
  - <a href="#docker-container%E3%82%A2%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E6%A7%8B%E6%88%90">Docker Containerアクションの構成</a>
- <a href="#%E4%BD%9C%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%8B">作ってみる</a>
  - <a href="#actionyml">action.yml</a>
      - <a href="#name-author-description">name, author, description</a>
      - <a href="#inputs">inputs</a>
      - <a href="#runs">runs</a>
      - <a href="#branding">branding</a>
  - <a href="#dockerfile">Dockerfile</a>
  - <a href="#%E5%AE%9F%E9%9A%9B%E3%81%AE%E5%87%A6%E7%90%86%E3%82%92%E8%A1%8C%E3%81%86%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0">実際の処理を行うプログラム</a>
- <a href="#%E4%BD%9C%E6%88%90%E3%81%97%E3%81%9F%E3%82%A2%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E3%83%86%E3%82%B9%E3%83%88%E3%81%99%E3%82%8B">作成したアクションをテストする</a>
- <a href="#marketplace%E3%81%AB%E5%85%AC%E9%96%8B%E3%81%99%E3%82%8B">Marketplaceに公開する</a>
- <a href="#%E5%85%AC%E9%96%8B%E3%81%97%E3%81%9F%E3%82%A2%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E5%AE%9A%E6%9C%9F%E5%AE%9F%E8%A1%8C%E3%81%97%E3%81%A6%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%8B">公開したアクションを定期実行して使ってみる</a>
- <a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a>

# 何をするのか

リポジトリ: [**eggplants/twitter-weathername-action**](https://github.com/eggplants/twitter-weathername-action)

GitHub Actions上で動くアクションをPython+Dockerベースで作成し、GitHubの[Marketplace](https://github.com/marketplace)に公開するまでの手順を解説します。
例として作成するのは、Twitterのスクリーンネームを下の写真のように、**明日の3時間天気予報**にするものです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278919/d78bd1e9-45b5-e8ae-8c05-0451a1d18fbf.png)

また、普段お手持ちのサーバやVPS上で走らせているような**毎日JST00:00になると自動で更新**する定期実行のcronをGitHub Actions上で完全無料・サーバなしでやってみます。
~~但し、走らせるためのリポジトリを作成する必要がありますが…~~

今回作成したアクションを用いたWorkflowは[こちら](https://github.com/eggplants/twitter-weathername-action/blob/main/.github/workflows/cron_renamer.yml)、またMarketplaceにアクションを公開したものは[こちら](https://github.com/marketplace/actions/twitter-weathername)です。

<blockquote class="twitter-tweet"><p lang="und" dir="ltr"><a href="https://t.co/ufco0Vy7VO">https://t.co/ufco0Vy7VO</a></p>&mdash; 明日: ☀/☀/☀/☀/☀/☀/☁/☁ (@egpl0) <a href="https://twitter.com/egpl0/status/1341039332245741569?ref_src=twsrc%5Etfw">December 21, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# GitHub Actionsについて

GitHub Actionsとは、GitHubが提供しているCI/CDサービスです。リポジトリの`.github/workflows`ディレクトリにyamlを書き、pushやpull request、また定期実行するscheduleなどのトリガで走らせることが出来ます。また、公開リポジトリであれば**完全無料**で回し放題なのも魅力的です。なお、非公開リポジトリやorganizationリポジトリでは一定量使用すると[課金されます](https://docs.github.com/en/free-pro-team@latest/github/setting-up-and-managing-billing-and-payments-on-github/about-billing-for-github-actions#about-billing-for-github-actions)。

## 選択肢

Actions作成には3つの作り方があります。(`action.yml`に記述)

- [Docker Containerアクション](https://docs.github.com/en/free-pro-team@latest/actions/creating-actions/creating-a-docker-container-action)

```yaml:action.yml
runs:
  using: 'docker'
  image: 'Dockerfile'
```

- [Javascriptアクション](https://docs.github.com/en/free-pro-team@latest/actions/creating-actions/creating-a-javascript-action)

```yaml:action.yml
runs:
  using: 'node12'
  main: 'hogehoge.js'
```

- [複合実行アクション](https://docs.github.com/en/free-pro-team@latest/actions/creating-actions/creating-a-composite-run-steps-action)

```yaml:action.yml
runs:
  using: 'composite'
```

今回はこのうちのDocker Containerアクションを作ります。

## Docker Containerアクションの構成

大きく分けて3つの部分を書けばよく、

- [**action.yml**](https://github.com/eggplants/twitter-weathername-action/blob/main/action.yml)
    - アクションの説明
    - 渡す引数の名前、説明、属性、必須かどうか、返り値を定義
    - Marketplaceでの公開時のアイコンとその背景色
- [**Dockerfile**](https://github.com/eggplants/twitter-weathername-action/blob/main/Dockerfile)
    - 環境の構築
    - 実際の処理を動かすプログラムを`ENTRYPOINT`として実行
- [**実際の処理を行うプログラム**](https://github.com/eggplants/twitter-weathername-action/blob/main/src/main.py)
    - 今回はPythonで記述 

を記述します。

# 作ってみる

## action.yml

それぞれ記述するべき値を列挙します。

### `name`, `author`, `description`

アクション公開時の「アクション名」、「作者」「アクションの説明」です。

### `inputs`

アクションの実行時に渡せる、「引数の定義」です。

アクションは、workflowでの使用時に`jobs.<job_id>.steps.with`で渡された引数を受け取ることが出来ます。
後述しますが、渡された引数`XXX`は環境変数`INPUT_XXX`として定義され、ENTRYPOINT内で参照できるようになります。
`required: true`としたものに関しては、使用時に指定しないと実行できません。
また`required: false`の場合(オプション引数)に、`default`で引数のデフォルト値を定義できます。

<details><summary>今回定義した引数</summary><div>

- 必須引数
  - `open_weather_api_token`
      - [Openweathermap API](https://openweathermap.org/appid)のトークン
  - `twitter_consumer_key`, `twitter_consumer_secret`, `twitter_access_key`, `twitter_access_secret`
      - [Twitter API](https://developer.twitter.com/en/docs/twitter-api/getting-started/guide)のトークン
  - `location_query`
      - 天気予報の場所
- オプション引数記述
  - `time_zone`
      - 用いる予報のタイムゾーン
      - デフォルトだと`location_query`の場所のタイムゾーンを用いる
  - `name_format`
      - 天気予報をはめ込むフォーマット
  - `icon_sep`
      - 天気を区切るパラメータ
  - `forecast_day`
      - 予報する日数をn日先で設定
      - 天気APIの都合上`0`~`2`で指定
      - "明日"だと`1`
</div></details>

### `runs`

今回はDocker Containerアクションなので、

```yaml:action.yml
runs:
  using: "docker"
  image: "Dockerfile"
```

とします。今回は`image`に、自分で作成したDockerfileを指定していますが、[DockerHub](https://hub.docker.com/)の公開Dockerイメージや[GitHub Packagesの公開コンテナ](https://docs.github.com/en/free-pro-team@latest/actions/guides/publishing-docker-images)も指定できます。

### `branding`

[Marketplace](https://github.com/marketplace)での公開時のアイコンとその背景色を指定できます。

- `icon`
  - [Feather](https://feathericons.com/)と対応
- `color`
    - `white`, `yellow`, `blue`, `green`, `orange`, `red`, `purple`, `gray-dark`の中から選べます。

```yaml:action:yml
icon: "link"
color: "green"
```

だと、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278919/a802318c-98cf-6937-c6c5-7aee7e8763a4.png)

のようになります。

以下[作成した](https://github.com/eggplants/twitter-weathername-action/blob/main/action.yml)`action.yml`です。

<details><summary>action.yml</summary><div>


```yaml:action.yml
name: "Twitter Weathername"
author: "haruna"
description: "Update Twit記述ter Username to a daily forecast of given location"
inputs:
  open_weather_api_token:
    description: "Openweathermap API Token"
    required: true
  twitter_consumer_key:
    description: "Twitter API consumer key"
    required: true
  twitter_consumer_secret:
    description: "Twitter API consumer secret key"
    required: true
  twitter_access_key:
    description: "Twitter API access key"
    required: true
  twitter_access_secret:
    description: "Twitter API access secret key"
    required: true
  location_query:
    description: "City name, state code and country code divided by comma, use ISO 3166 country codes"
    required: true
  time_zone:
    description: "Time zone to be used as a time reference (ex. Asia/Tokyo) (def: LOCATION_QUERY's local time zone)"
    required: false
  name_format:
    description: "Username format ({} is substituted by joined weather icons with icon_sep)"
    required: false
    default: "＜{}"
  icon_sep:
    description: "String for joining icons"
    required: false
    default: ":"
  forecast_day:
    description: "Show the weather in the next n days (n=0 is today)"
    required: false
    default: "0"
runs:
  using: "docker"
  image: "Dockerfile"
branding:
  icon: "link"
  color: "green"
```
</div></details>

## Dockerfile

今回はPythonを実行するので、Pythonを動かすための実行環境を作成するDockerfileを記述する。
また引数を受け取って、実際の処理を行う(ここではTwitterのユーザ名を、取得した天気予報に更新する)プログラムを`ENTRYPOINT`に指定します。`CMD ["python", "/src/main.py"]`のように指定しても良いです。

以下[作成した](https://github.com/eggplants/twitter-weathername-action/blob/main/Dockerfile)Dockerfileです。

```dockerfile:Dockerfile
FROM python:latest

ADD src /src
ADD requirements.txt /requirements.txt

RUN pip install -r requirements.txt
RUN chmod +x src/main.py

ENTRYPOINT ["/src/main.py"]
```

## 実際の処理を行うプログラム

workflowで渡された引数(例えば`XXX`)は、

```python:/src/main.py
from os import environ

xxx = environ.get('INPUT_XXX', 'default')
```
のように受け取れます。

今回は、

①3日間天気.jsonをOpenweathermap APIから取得
②3日間天気.jsonを名前に整形
③Twitter APIから名前を更新

を行うプログラムを書きます。作成したプログラムは[こちら](https://github.com/eggplants/twitter-weathername-action/tree/main/src)です。

# 作成したアクションをテストする

ここまでで作成したアクションがちゃんと動くかどうかはGitHub Actionsでテスト出来てしまいます。Marketplaceに公開する前に動くかどうかテストしましょう。

また、APIトークンなどの一般に公開したくない文字列は、リポジトリの`[Settings]->[Secrets]->[New repository secret]`から追加でき、`${{ secrets.XXX }}`で呼び出せます。

リポジトリでのpush毎に実行して、うまく更新できるか試すworkflowは[このように](https://github.com/eggplants/twitter-weathername-action/settings/secrets/actions)なります。

```yml:.github/workflows/test.yml
name: Test
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Change Name
        uses: eggplants/twitter-weathername-action@main # アクションのリポジトリ
        with:
          open_weather_api_token: ${{ secrets.OPEN_WEATHER_API_TOKEN }}
          twitter_consumer_key: ${{ secrets.TWITTER_CONSUMER_KEY }}
          twitter_consumer_secret: ${{ secrets.TWITTER_CONSUMER_SECRET }}
          twitter_access_key: ${{ secrets.TWITTER_ACCESS_KEY }}
          twitter_access_secret: ${{ secrets.TWITTER_ACCESS_SECRET }}
          location_query: tsukuba
          name_format: "明日: {}"
          icon_sep: "/"
          forecast_day: 1 # 明日の天気
```

うまく動いたかどうかは、[Actionsタブ](https://github.com/eggplants/twitter-weathername-action/actions?query=workflow%3ATest)から確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278919/aefbcb33-7736-bcfe-e943-092052605307.png)

# Marketplaceに公開する

作成したアクションをMarketplaceに公開することで、誰にでも簡単に、すぐ作成したアクションを使ってもらえるように出来ます。

詳細な手順は[公式ドキュメント](https://docs.github.com/en/free-pro-team@latest/actions/creating-actions/publishing-actions-in-github-marketplace#about-publishing-actions)がわかりやすいです。

`action.yml`がリポジトリに存在していれば、リポジトリに`[Draft a release]`バナーが出現します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278919/1de1de4e-4568-94ef-e15c-bf6c78111a62.png)

クリックすると

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278919/28f0345b-7e6c-cca0-ad0f-962fdd64f6cd.png)

のように表示されるので、`accept the GitHub Marketplace Developer Agreement`リンクをクリックすると、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278919/f7029bab-89ea-7619-cea8-d1dd0c5fc5d2.png)


[GitHub Marketplace Developer Agreement](https://docs.github.com/en/free-pro-team@latest/github/site-policy/github-marketplace-developer-agreement)に同意するように言われます。

同意すると、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278919/ff0c42e5-b2c1-58c4-b229-ee80493d842d.png)

のように、`action.yml`のメタデータが足りているか、また`README`があるかどうかがチェックされ、不十分だと修正するように言われます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278919/af0d841d-591d-e7e1-83a9-f909b42ef5c8.png)


カテゴリを設定し、`[Publish release]`ボタン(画像では`Update release`ですが…)を押下して、公開できます。お疲れ様でした！

# 公開したアクションを定期実行して使ってみる

最初に書いたように、Actionsのworkflowの実行トリガには、cronのように定期実行できる`schedule`トリガが存在しており、

```yaml:cron_renamer.yml
on:
  schedule:
    - cron: "0 15 * * *" # JSTでは"0 0 * * *"
```

のように、まさにcronの構文で定期実行できます。ここで注意ですが、Actionsのworkflowは**UTC**(協定世界時)で実行されるため、日本時間に即した実行を行いたい場合は**JST**(日本標準時)**-9**の時間を指定しましょう。

以下が、現在[実際に使っている](https://github.com/eggplants/twitter-weathername-action/blob/main/.github/workflows/cron_renamer.yml)cron workflowです。

```yaml:cron_renamer.yml
name: Cron renamer

on:
  schedule:
    - cron: "0 15 * * *"

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Change Name
        uses: eggplants/twitter-weathername-action@v2
        with:
          open_weather_api_token: ${{ secrets.OPEN_WEATHER_API_TOKEN }}
          twitter_consumer_key: ${{ secrets.TWITTER_CONSUMER_KEY }}
          twitter_consumer_secret: ${{ secrets.TWITTER_CONSUMER_SECRET }}
          twitter_access_key: ${{ secrets.TWITTER_ACCESS_KEY }}
          twitter_access_secret: ${{ secrets.TWITTER_ACCESS_SECRET }}
          location_query: tsukuba
          name_format: "明日: {}"
          icon_sep: "/"
          forecast_day: 1
```

実行ログはテスト同様[Actionsタブ](https://github.com/eggplants/twitter-weathername-action/actions?query=workflow%3A%22Cron+renamer%22)から確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278919/d52a9169-7fc8-71a3-aa62-b0897234bb92.png)

# おわりに

GitHub Actionsの自作アクションの作成とテスト、公開や定期実行の手順がひとところにあるわかりやすい記事がなかなかなかったので、備忘録も兼ねて書こうとずっと思ってたんですけど、今回Adventカレンダーが良い契機となりました。

良いお年を〜