LoginSignup
35
20

More than 3 years have passed since last update.

SlackからGitHub Actionsにパラメータを渡して実行する

Last updated at Posted at 2020-02-06

以下のサービスを組み合わせて、ポチポチCIツールを作成します。

  • Slack
  • GitHub Actions、REST API v3
  • AWS Lambda ( Node.js 12.x (@slack/bolt 1.5.0) )

test.gif

外観図

Slackに対してリクエストすると、SlackとLambdaがやりとりをしてGitHubに命令を出してくれます。

image.png

素材

猫の顔: https://kumamine.blogspot.com/2019/12/blog-post_27.html
Slack: Cacoo内の素材
AWS Lambda: Cacoo内の素材
GitHub: Cacoo内の素材

GitHub ActionsのJobを外部から実行する

GitHub Actionsはプルリク作成やコミットプッシュなどの何らかの「トリガー」で走るCIツールです。
GitHubのAPIを利用し、「トリガー」を発生させることで、外部からActionsのJobを実行することができます。

準備

GitHub ActionsをWebAPIから実行するための準備を行います。

  1. Github Actions用のリポジトリを作成して、ローカルにcloneしておきます。
    https://help.github.com/ja/github/getting-started-with-github/create-a-repo
  2. GitHub REST API v3を利用するためにアクセストークンを作成します。権限にはrepoを付加しておきましょう。
    https://help.github.com/ja/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line
  3. GitHubからcloneしてきたプロジェクト上に、Github Actionsのワークフロー構文ファイルを作成します。作成したファイルはデフォルトブランチ(いわゆるmasterブランチ)に置きましょう。
.github/workflows/main.yml
# Actionの名前
name: Sample

# Jobが実行されるトリガー
# pushされた場合、もしくはイベント種別が「hoge」のリポジトリディスパッチイベントが作成された場合に実行する
on:
  push:
  repository_dispatch:
    types: [hoge]

jobs:
  sample_job:
    runs-on: ubuntu-latest
    name: sample_job
    steps:
      - uses: actions/checkout@v1

      # ブランチが指定されていた場合は、指定されているブランチを利用する
      - if: github.event.client_payload
        uses: actions/checkout@v1
        with:
          ref: ${{ github.event.client_payload.ref }}

      # 「echo_string」要素が指定されていた場合は、その要素の内容を表示する
      - if: github.event.client_payload.echo_string
        name: echo string
        run: echo ${{ toJSON(github.event.client_payload.echo_string) }}

Github ActionsのJobを端末から実行する

準備ができたら、端末からGitHub Actionsに文字列を渡して、Jobのログ上に表示させてみましょう。

# 扱いやすいように変数にアクセストークンを代入する
GITHUB_TOKEN={アクセストークン}

# 実行ブランチ
BRANCH={ブランチ名}

# Actionの実行
curl -X POST https://api.github.com/repos/:owner/:repo/dispatches \
     -H "Authorization: token $GITHUB_TOKEN" \
     -H "Accept: application/vnd.github.everest-preview+json" \
     --data "{\"event_type\": \"hoge\", \"client_payload\": {\"ref\": \"$BRANCH\", \"echo_string\": \"hugahuga\"}}"

curlコマンドの--dataの、
event_typeには、ワークフロー構文ファイルのon.repository_dispatch.typesの文字列を指定します。
client_payload.refには、実行するブランチ名称を指定します。
client_payload.echo_stringには、表示する文字列を指定します。

curlで実行した場合の結果

下図はGithub Actions上の実行結果です。
curlコマンドの--dataで指定したclient_payload.echo_stringの文字列が表示されます。

image.png

pushした場合の結果

ブランチの切り替えも文字列の表示も行いません。
pushされたブランチで処理が行われます。

image.png

ここまでの作業で察した方もいらっしゃると思いますが、デフォルトブランチ上にワークフロー構文ファイルが存在しないと、Jobが実行されません。理由は、APIがデフォルトブランチに対してイベントを発生させているためです。ではどこでブランチが切り替わっているのかというと、Github ActionsのJob中で切り替わっています。
ブランチに対してイベントを発生させるAPIも存在するのですが、本来の使い方ではない上に、意味のないログがプルリクなどに残ってしまうため、今回はdispatchesイベントを採用しました。

※ GitHub Actions用のAPIも用意されているようですが、まだパブリックベータ版のようですし、ジョブが実行できるわけではないみたいです。内容が頻繁に変更される可能性があるため、マスターがリリースされ次第、都合がよさそうであれば記事内容を書き換えようかなと思っています。(2020.02.02)

参考

https://developer.github.com/v3/repos/#create-a-repository-dispatch-event
https://help.github.com/ja/actions/automating-your-workflow-with-github-actions/events-that-trigger-workflows
https://swfz.hatenablog.com/entry/2020/01/23/080000
https://qiita.com/proudust/items/51599abd2b107b708e1e

Slackにモーダルウィンドウを表示して、入力値を取得する

Slackから提供されているWebAPIを組み合わせることで、モーダルウィンドウなどのインターフェースを実装することができます。
今回はそのラッパーであるBoltフレームワークを利用して実装していきます。

準備

  1. Slackのワークスペースを作成します。
    https://slack.com/intl/ja-jp/help/articles/206845317
  2. SlackのAppsを作成します。https://api.slack.com/apps からCreate New Appを押して、App NameDevelopment Slack Workspaceの項目を入力してCreate Appを押せばOKです。(2020.02.02現在)
    https://api.slack.com/start/overview
  3. Features > OAuth & Permissions > Scopes > Bot Token Scopes にapp_mentioned:readchat:writecommandsを追加します。
    https://api.slack.com/apps/{APP_ID}/oauth
  4. Settings > Install App からアプリをワークスペースにインストールします。
    https://api.slack.com/apps/{APP_ID}/install-on-team

アプリをハンドリングするサーバの作成

今回はServerless FrameworkとAWS Lambdaを利用してサーバを作成します。
まずは、端末で以下のコマンドを実行し、プロジェクトを作成します。

# プロジェクトの作成
npm init

# ライブラリのインストール
npm install serverless \
            aws-serverless-express \
            dotenv \
            @slack/bolt \
            axios

# サーバーレスフレームワークプロジェクトの作成
npx serverless create --template aws-nodejs

サーバーレスの設定を追加します。

serverless.yml
service: sample

provider:
  name: aws
  runtime: nodejs12.x
  region: ap-northeast-1

functions:
  events:
    handler: handler.app
    timeout: 30
    events:
      # Bolt App
      - http:
          method: post
          path: /slack/events
      # OAuth Flow
      - http:
          method: get
          path: /slack/installation
      - http:
          method: get
          path: /slack/oauth

内部のロジックを作成します。
/repos/:owner/:repo/dispatchesの部分は「Github ActionsのJobを外部から実行する」で利用したリポジトリを指定してください。

handler.js
'use strict';

// ------------------------------
// AWS Lambda handler
// ------------------------------
require('dotenv').config();
const { App, LogLevel, ExpressReceiver } = require('@slack/bolt');

const expressReceiver = new ExpressReceiver({
  signingSecret: process.env.SLACK_SIGNING_SECRET
});

const app = new App({
  token: process.env.SLACK_BOT_TOKEN,
  receiver: expressReceiver,
  logLevel: LogLevel.DEBUG
});

const awsServerlessExpress = require('aws-serverless-express');
const server = awsServerlessExpress.createServer(expressReceiver.app);

module.exports.app = (event, context) => {
  console.log('⚡️ Bolt app is running');
  console.log(event);
  awsServerlessExpress.proxy(server, event, context)
}

app.error(printCompleteJSON);

// ------------------------------
// Application Logic
// ------------------------------

// slash commandでモーダルウィンドウを表示するためのロジック
app.command('/build', async ({ ack, body, context }) => {
  ack();
  const result = app.client.views.open({
    "token": context.botToken,
    "trigger_id": body.trigger_id,
    "view": {
      "type": "modal",
      "callback_id": "task-modal",
      "private_metadata": JSON.stringify(body),
      "title": {
        "type": "plain_text",
        "text": "Github Actions",
        "emoji": true
      },
      "submit": {
        "type": "plain_text",
        "text": "Submit",
        "emoji": true
      },
      "close": {
        "type": "plain_text",
        "text": "Cancel",
        "emoji": true
      },
      "blocks": [
        {
          "type": "input",
          "block_id": "input-title",
          "element": {
            "type": "plain_text_input",
            "action_id": "input",
            "initial_value": body.text
          },
          "label": {
            "type": "plain_text",
            "text": "branch",
            "emoji": true
          },
          "optional": false
        },
        {
          "type": "input",
          "block_id": "input-description",
          "element": {
            "type": "plain_text_input",
            "action_id": "input",
            "multiline": true
          },
          "label": {
            "type": "plain_text",
            "text": "echo string",
            "emoji": true
          },
          "optional": true
        }
      ]
    }
  });

  console.log( await result);
});

// モーダルウィンドウから送信された値を受け取って、処理を行うロジック
app.view('task-modal', async ({ body, ack, context }) => {
  // モーダルウィンドウでのデータ送信イベントを確認
  ack();

  // ユーザーにメッセージを送信
  try {
    app.client.chat.postMessage({
      token: context.botToken,
      channel: body['user']['id'],
      text: 'Build request received!'
    });
  }
  catch (error) {
    console.error(error);
  }


  // ------------------------------
  // axios flow
  // ------------------------------
  const axiosBase = require('axios');
  const axios = axiosBase.create({
    baseURL: 'https://api.github.com',
    headers: {
      "Authorization": `token ${process.env.GITHUB_TOKEN}`,
      "Accept": "application/vnd.github.everest-preview+json"
    }
  });

  const requestBody = JSON.stringify({
    event_type: "hoge",
    client_payload: {
      ref: body.view.state.values['input-title'].input.value,
      echo_string: body.view.state.values['input-description'].input.value
    }
  });

  // GitHubへのリクエスト処理
  axios.post('/repos/:owner/:repo/dispatches', requestBody)
    .then(response => (this.info = response))
    .catch(function(e) {
      console.log(e);
  });
});

function printCompleteJSON(error) {
  console.log(JSON.stringify(error));
}

環境変数ファイルを作成して、センシティブな情報を入力します。

SLACK_SIGNING_SECRET : Features > OAuth & Permissions > OAuth Tokens & Redirect URLs > Tokens for Your Workspace > Bot User OAuth Access Token ( https://api.slack.com/apps/{APP_ID}/oauth
SLACK_BOT_TOKEN : Basic Information > App Credentials > Signing Secret
GITHUB_TOKEN : GitHub REST API v3を利用するためにアクセストークン

# .envファイルの作成
touch .env
.env
SLACK_SIGNING_SECRET=xxxxxxxxxx
SLACK_BOT_TOKEN=xoxb-xxxxxxxxxx
GITHUB_TOKEN=xxxxxxxxxx

作成し終わったらデプロイします。

$ serverless deploy
 :
endpoints:
  POST - https://xxx.execute-api.ap-northeast-1.amazonaws.com/dev/slack/events
  GET - https://xxx.execute-api.ap-northeast-1.amazonaws.com/dev/slack/installation
  GET - https://xxx.execute-api.ap-northeast-1.amazonaws.com/dev/slack/oauth
# => デプロイ後のURLをメモしておく

中身はこれで完成です。

Slackのコマンドの設定を行う

  1. Features > Interactive Components > Interactivity を有効化します。また、Request URL にデプロイ後のPOST URL(https://xxx.execute-api.ap-northeast-1.amazonaws.com/dev/slack/events)を貼り付けて保存します。
    https://api.slack.com/apps/{APP_ID}/interactive-messages
  2. Features > Slash Commands で/buildというスラッシュコマンドを作成します。URLにはhttps://xxx.execute-api.ap-northeast-1.amazonaws.com/dev/slack/eventsを設定します。
    https://api.slack.com/apps/{APP_ID}/slash-commands
  3. Features > Event Subscriptions へアクセスし、有効化します。Enable Events > Request URL にはhttps://xxx.execute-api.ap-northeast-1.amazonaws.com/dev/slack/eventsを設定します。Subscribe to bot events にはapp_mentionを追加します。
    https://api.slack.com/apps/{APP_ID}/event-subscriptions
  4. アプリを再インストールします。
    https://api.slack.com/apps/{APP_ID}/install-on-team

ここまでの設定は「参考」の1つ目のseratchさんの手順書と、2つ目のチュートリアルがとても参考になります。
また、開発のノウハウも書かれていますので一読をオススメします。

SlackからGitHub Actionsにパラメータを渡して実行する

  1. ワークスペースの適当なチャンネルにアプリを追加します。
  2. テキストボックスに/buildと打ち込み、送信します。
  3. モーダルウィンドウが現れるので、項目を入力し、Submitボタンを押します。
  4. うまく設定できていれば、GitHub ActionsのJobが実行されます。

以上です。おつかれさまでした。

参考

https://github.com/seratch/bolt-starter
https://slack.dev/bolt/ja-jp/tutorial/getting-started
https://github.com/okimurak/slack-bolt-sample-app-aws
https://api.slack.com/dialogs
https://api.slack.com/surfaces/modals/using
https://dev.classmethod.jp/tool/slack-multistep-modal-example/

おわりに

うまく組み込めば、スマホから手軽に緊急対応できるようになっていいかもしれませんね。
それと、GitHub ActionsのJob終了時に、Slackに通知を送るようにすると色々と捗るかも。

また、時間がある時に少しいじってみようと思います。

35
20
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
35
20