🔥

GitHub ActionsでオープンデータをS3にアップロードしてLambdaで処理する

2022/03/18に公開

先日、GitHubで公開しているオープンデータを AWS内で処理する設定を行いました。
その過程を若干一般化して投稿したいと思います。

AWS側の処理はオープンデータをS3にアップロードして、Lambdaで実行する流れになります。
S3へのアップロードをトリガーにLambdaを動かすこともできますが、既にファイルパスを渡すとS3のファイルを取得して処理するものを作成していますので、S3へのアップロード後にGitHub側からLambdaを呼び出すことにしました。

概要

GitHub Actions で AWSのリソースを使えるようにするには、AWS側とGitHub側双方に設定が必要です。
具体的には以下の設定が必要になります。

  1. Amazon Web Service 設定
    1-1. IDプロバイダ追加
    1-2. IAMロール作成
    1-3. IAMポリシー作成
  2. GitHub 設定
    2-1. シークレット変数作成
    2-2. ワークフローファイル作成
    2-3. 実行テスト

それぞれはそんなに難しいものではないので、順に説明していきます。

1. Amazon Web Service設定

AWS側の設定は、ここではWebコンソール上で行っていきます。

1-1. IDプロバイダ追加

IAMのページから以下を開きます。

アクセス管理 > IDプロバイダ

次に プロバイダの追加 ボタンをクリックします。

IDプロバイダの作成 ページで以下を入力します。

項目
プロバイダのタイプ OpenID Connect
プロバイダの URL https://token.actions.githubusercontent.com
対象者 sts.amazonaws.com

IDプロバイダ

次に、サムプリントを取得 ボタンをクリックして、サムプリントを取得します。

正しく取得されたら、プロバイダを追加 ボタンをクリックします。
これで IDプロバイダが作成されます。

1-2. IAMロール作成

次にIAMロールを作成します。

アクセス管理 > ロール

を開いて、ロールを作成ボタンをクリックします。

ステップ 1 信頼されたエンティティ を選択のページでは以下を入力して、次へをクリックします。

項目
信頼されたエンティティタイプ ウェブアイデンティティ
アイデンティティプロバイダー token.actions.githubusercontent.com
Audience sts.amazonaws.com

ステップ 2 許可を追加 では IAMポリシーを設定しますが、後で説明するのでここでは何も選択せず次へをクリックします。

ステップ 3 名前、確認、および作成 では ロールの名前を付けて、ロールを作成ボタンをクリックします。

これでIAMロールは作成されますがこのままでは使えないようなので、もう一度、アクセス管理 > ロール を開いて、作成したロールのページを開きます。

信頼関係タブを開くと、以下のようなJSONが表示されると思います。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::[AWSアカウントID]:oidc-provider/token.actions.githubusercontent.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
        }
      }
    }
  ]
}

ここの Condition 以下の部分を書き換える必要があります。信頼ポリシーを編集ボタンをクリックして以下のように書き換えます。
これで、特定のリポジトリの特定のブランチのみで、AWSリソースを使えるようにできます。
[GitHubアカウント名][リポジトリ名][対象とするブランチ名] は使用したいアカウント、リポジトリ、ブランチの名前に置き換えてください。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::[AWSアカウントID]:oidc-provider/token.actions.githubusercontent.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "token.actions.githubusercontent.com:sub": "repo:[GitHubアカウント名]/[リポジトリ名]:ref:refs/heads/[対象とするブランチ名]"
        }
      }
    }
  ]
}

後述の設定ではmainブランチを使っていますが、それであれば以下のようになります。

      "Condition": {
        "StringEquals": {
          "token.actions.githubusercontent.com:sub": "repo:[GitHubアカウント名]/[リポジトリ名]:ref:refs/heads/main"
        }
      }

また、StringEqualsStringLike に変更することでワイルドカード*が使用でき、ref:refs/heads/main* に置き換えれば、mainブランチ以外でもAWSのリソースを使えるようにできます。

      "Condition": {
        "StringLike": {
          "token.actions.githubusercontent.com:sub": "repo:[GitHubアカウント名]/[リポジトリ名]:*"
        }
      }

このIAMロールのARNは後ほどGitHub側で使います。

1-3. IAMポリシー作成

最後にIAMポリシーを作成します。IAMポリシーは今回使用する S3へのアップロードs3:putObject と Lambdaの呼び出し lambda:InvokeFunction のみ許可するようにしました。
もし S3のputイベントをトリガーにLambdaを動かすのであれば、lambda:InvokeFunction は必要ないです。
作成したIAMポリシーをIAMロールにアタッチしておきます。
これで IAMロールで設定した GitHubのリポジトリの指定したブランチから、許可したリソースを使えるようになるはずです。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
          "s3:PutObject"
      ],
      "Resource": "arn:aws:s3:::[S3バケット名]/*"
    },
    {
      "Effect": "Allow",
      "Action": [
          "lambda:InvokeFunction"
      ],
      "Resource": "[LambdaファンクションのARN]"
    }
  ]
}

2. GitHub設定

ここからは GitHub側の設定です。

2-1. シークレット変数作成

GitHub リポジトリページ内にシークレット変数を作成します。
Settingsタブの

General > Security > Secret > Actions

を開きます。
New repository secret ボタンを押すとシークレット変数を作成できます。

ここに設定した変数は、GitHub Actions のログには表示されません(マスクされる)。
これでパブリックリポジトリでもAWS側の設定が漏洩することはないはずです。

変数名
AWS_ROLE_ARN IAMロールのARN
AWS_REGION リージョン(例: ap-northeast-1)
BUCKET_NAME S3バケット名
FUNCTION_NAME Lambdaファンクション名

Secret

2-2. ワークフローファイル作成

GitHub Actions のワークフローファイルをリポジトリに作成して、pushします。

.github/workflows/ci.yml
name: CI

on:
  push:
    branches:
      - main

jobs:
  check-secret:
    runs-on: ubuntu-latest
    outputs:
      my-key: ${{ steps.my-key.outputs.defined }}
    steps:
      - id: my-key
        if: "${{ env.MY_KEY != '' }}"
        run: echo "::set-output name=defined::true"
        env:
          MY_KEY: ${{ secrets.AWS_ROLE_ARN }}

  deploy:
    runs-on: ubuntu-latest
    needs: 
      - check-secret
    if: needs.check-secret.outputs.my-key == 'true'
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@v2
        with:
          fetch-depth: 0
      - uses: tj-actions/changed-files@v18
        id: changed-files
        with:
          files: |
            **/*.ttl
      - uses: aws-actions/configure-aws-credentials@v1
        if: steps.changed-files.outputs.any_changed == 'true'
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
          aws-region: ${{ secrets.AWS_REGION }}
      - name: Upload all changed files
        if: steps.changed-files.outputs.any_changed == 'true'
        run: |
          for file in ${{ steps.changed-files.outputs.all_changed_files }}; do
            echo "$file was changed"
            aws s3 cp $file s3://${{ secrets.BUCKET_NAME }}/$file
            echo '{"file": "/'$file'" }' > payload.json
            aws lambda invoke --function-name ${{ secrets.FUNCTION_NAME }} --payload "$(cat payload.json | base64)" response.json
            cat response.json
          done

このワークフローファイルについては、以下のように記述しています。

mainブランチに push したときにワークフローを実行します。

on:
  push:
    branches:
      - main

シークレット変数 AWS_ROLE_ARN が定義されているかどうかチェックします。
定義がなければ、deploy の処理をスキップします。これで、誰かがこのリポジトリをフォークしても シークレット変数が定義されていないことで GitHub Actions がエラーになることが避けられると思います。

  check-secret:
    runs-on: ubuntu-latest
    outputs:
      my-key: ${{ steps.my-key.outputs.defined }}
    steps:
      - id: my-key
        if: "${{ env.MY_KEY != '' }}"
        run: echo "::set-output name=defined::true"
        env:
          MY_KEY: ${{ secrets.AWS_ROLE_ARN }}
  deploy:
    runs-on: ubuntu-latest
    needs: 
      - check-secret
    if: needs.check-secret.outputs.my-key == 'true'

追加・更新のあったファイルを検出します。
ここでは ttl拡張子を持つファイルのみを抽出する設定にしています。
これにより、README.md など対象でないファイルの処理を除外することができます。

      - uses: tj-actions/changed-files@v18
        id: changed-files
        with:
          files: |
            **/*.ttl

AWSのリソースを使えるようにIAMロールを指定します。
if: steps.changed-files.outputs.any_changed == 'true' で 更新ファイルにttlファイルがあるかどうかをチェックしています。
なければ、スキップします。

      - uses: aws-actions/configure-aws-credentials@v1
        if: steps.changed-files.outputs.any_changed == 'true'
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
          aws-region: ${{ secrets.AWS_REGION }}

更新のあった ttlファイルを S3バケットにアップロードして、Lambdaファンクションを呼び出します。
lambdaファンクションの payload の指定は Base64エンコードが必要みたいなので、--payload "$(cat payload.json | base64)" としています。

      - name: Upload all changed files
        if: steps.changed-files.outputs.any_changed == 'true'
        run: |
          for file in ${{ steps.changed-files.outputs.all_changed_files }}; do
            echo "$file was changed"
            aws s3 cp $file s3://${{ secrets.BUCKET_NAME }}/$file
            echo '{"path": "/'$file'" }' > payload.json
            aws lambda invoke --function-name ${{ secrets.FUNCTION_NAME }} --payload "$(cat payload.json | base64)" response.json
            cat response.json
          done

2-3. 実行テスト

作成したワークフローファイルと、アップロードしたい ttlファイル、正しくアップロード除外されるかのテストのためttlファイル以外を push してみます。
例えば、以下の3つのファイルをpushします。

  • .github/workflows/ci.yml
  • go2museum/museum.ttl
  • README.md

Actions タブで、GitHub Actionsが正しく動いているか確認します。
以下のように表示されると、アップロードとLambdaの呼び出しが行えていることになります(Lambdaの戻り値は実装により異なります)。

実行テスト

以後、ttlファイルが追加・更新されると、S3アップロードとLambdaが呼び出されます。

まとめ

今回 GitHub と AWS を連携させて処理を行ってみました。思ったよりも簡単に設定できることがわかりました。
オープンデータ等、広く外部に公開したいファイルについては、AWSでホスティングするよりもGitHubで公開したほうが扱いやすいと思いますし、将来的に他の方が作られたオープンデータを受け入れるようなことをしたい場合もこの仕組みであれば、そのまま流用して自動化ができると思います。
割とそういった使い方には相性の良い組み合わせかもしれません。

この設定は、以下のWebサイトのために行ったものです。

https://uedayou.net/ldapinavi/

特定の形式で公開されるオープンデータWeb API(SPARQLエンドポイント)を登録して、検索できるようにしたサイトです。
以下に特徴と使い方をまとめていますので、興味がある方いましたら使ってみてください。

https://qiita.com/uedayou/items/93f849e8c57733dddeeb

Discussion