GitHub Actionsを使ってLookML開発フローをちょっと自動化する

先日開催された[Year End Looker Meetup 2020](https://discover.looker.com/2020yearendmeetup_ja.html)で登壇の機会をいただき、「ScalebaseにおけるLooker 組込みアナリティクスを活用した顧客向け分析サービスの展開」というタイトルでLTしてきました。 ([Speakerdeck](https://speakerdeck.com/ainoya/scalebase-analytics-powered-by-looker))

LT内では、GitHub Actionsを使ってLookMLの開発フローを一部自動化していることについて紹介しましたが、具体的なGitHub Actionsの記述については言及していませんでした。この記事では補足として、LookML開発フローに組み込んでいるGitHub Actionsのコードサンプルを紹介します。

## リリースフロー自動化

### Looker 7.20以前の構成(本番インスタンス・開発（ステージングインスタンス）を別リポジトリで管理している場合)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12187/1d0d7a8c-909f-4e79-8af5-4a06b6f4eb3e.png)

:warning: [Looker7.20](https://community.looker.com/%E3%83%8B%E3%83%A5%E3%83%BC%E3%82%B9%E3%81%A8%E5%91%8A%E7%9F%A5-100/looker-7-20-%E3%83%AA%E3%83%AA%E3%83%BC%E3%82%B9%E3%83%8E%E3%83%BC%E3%83%88-20673)以降からは任意ブランチをLookerのデプロイ元(production)に指定できるようになりました。今後は、特別な事情がない限り、単一リポジトリで管理したほうが楽です。筆者は新しいデプロイ設定を試してはいませんが、7.20以後はこのようにできるであろうという設定を次の節に書いておきます。

7.20以前では、master以外のブランチをLookerインスタンスのデプロイ元設定にすることができませんでした。この制約だと、gitリポジトリのmasterブランチにマージしたタイミングで開発(ステージング)・本番インスタンス両方に同時にデプロイが走ってしまうことになります。本番インスタンスへの反映は、ステージングインスタンスへのデプロイ反映結果を見てから、ワンクッション置くほうが安全です。これを実現するために、本番・ステージング用に対応する２つのリポジトリを用意しています。２リポジトリ構成の場合、図のような開発フローを実現できます。2リポジトリ間の同期・リリース用Pull Requestの準備は、図中②・③の２つのGitHub Actionsで自動化できます。そのコードは次のような形になります。

```②repoBのmasterが更新されたタイミングでrepoAのstagingブランチに反映するGithubActions.yml
on:
  push:
    branches:
      - master
name:repoBのmasterブランチをrepoAのstagingブランチに同期
jobs:
  PushStagingToProductionRepository:
    #repoBでしかgithub actionsが発火しないようにフィルタ
    if: ${{ github.repository == 'yourorg/repoB' }}
    name: git push
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2.1.1
      with:
        fetch-depth: 0
    - run: |
        #repoAに書き込むための設定
        git remote set-url origin https://x-access-token:${GH_TOKEN_REPOA_PUSH}@github.com/${GITHUB_REPOSITORY}.git
        git remote add prod https://x-access-token:${GH_TOKEN_REPOA_PUSH}@github.com/yourorg/repoA.git
      env:
        # repoAにpushするためのgithub tokenを発行してsecretに設定
        GH_TOKEN_REPOA_PUSH: ${{ secrets.GH_TOKEN_REPOA_PUSH }}
    - run: |
        git fetch origin master
        git checkout master
        git pull --ff-only origin master
        # 他のprivate repositoryにアクセスするための設定
        git config --local --remove-section http."https://github.com/"
        git config --global url."https://x-access-token:${GH_TOKEN_REPOA_PUSH}@github.com/".insteadOf "https://github.com/"
        git fetch prod
        git push -f prod master:staging
      env:
        GH_TOKEN_REPOA_PUSH: ${{ secrets.GH_TOKEN_REPOA_PUSH }}

```

```③repoAのstagingブランチが更新されたタイミングでrepoAのmasterブランチに向けてリリース用PRを作成するGitHubActions.yml
on:
  push:
    branches:
      - staging
name: 本番インスタンスリリース用PR作成
jobs:
  PRReleaseProd:
    #repoAでしかgithub actionsが発火しないようにフィルタ
    if: ${{ github.repository == 'yourorg/repoA' }}
    name: リリースPR作成
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2.1.1
      with:
        fetch-depth: 0
    - run: git remote set-url origin https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        TZ: Asia/Tokyo
    - run: |
        CURRENT_REF=$(git rev-parse HEAD)
        echo ::set-output name=pr_title::"[Production] LookML本番インスタンスへデプロイ $(date +%Y%m%d)"
      id: vars
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        TZ: Asia/Tokyo
    - name: pull-request
      uses: ainoya/pull-request@master
      with:
        source_branch: staging
        destination_branch: master
        pr_title: ${{ steps.vars.outputs.pr_title }}
        pr_body: "このPRをマージすることでLookMLを本番インスタンスにデプロイします。"
        github_token: ${{ secrets.GITHUB_TOKEN }}

```


### Looker 7.20以降の構成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12187/e29525dd-fd46-67a5-f34f-468138159521.png)

Looker 7.20以後は１リポジトリで前述のフローをよりシンプルに実現可能です。図中の②を実現するGitHub Actionsは[x-motemen/git-pr-release](https://github.com/x-motemen/git-pr-release)を使って次のように実現できます。

```②masterブランチが更新されたタイミングでprodブランチに向けてリリース用PRを作成するGitHubActions.yml
on:
  push:
    branches:
      - master
name: Create PR releasing Production
jobs:
  PRReleaseProd:
    name: Create PR releasing Production
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2.1.1
      with:
        fetch-depth: 0
    - run: |
        git remote set-url origin https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git
        git fetch origin master
        git fetch origin prod
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        TZ: Asia/Tokyo
    - run: |
        cat << EOF > pr.template
        [Production] Release <%= Time.now.strftime("%F %T") %>
        <% pull_requests.each do |pr| -%>
        <%=  pr.to_checklist_item %>
        <% end -%>
        EOF
    - name: git-pr-release-prod
      uses: ainoya/git-pr-release-action@master
      env:
        GIT_PR_RELEASE_TEMPLATE: ./pr.template
        GIT_PR_RELEASE_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        GIT_PR_RELEASE_BRANCH_STAGING: master
        GIT_PR_RELEASE_BRANCH_PRODUCTION: prod
        GIT_PR_RELEASE_LABELS: "Release/Production"
        TZ: Asia/Tokyo
```

2020/12/08追記: ③でwebhook経由でデプロイをトリガさせる処理が不足していたので追記しています。[ref](https://docs.looker.com/data-modeling/getting-started/advanced-deploy-mode)

```③prodの更新を検知してLookerインスタンスへのデプロイをトリガするGitHubActions.yml
on:
  push:
    branches:
      - prod
name: Looker APIを叩いてデプロイを起動する
jobs:
  PRReleaseProd:
    name: Create PR releasing Production
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2.1.1
      with:
        fetch-depth: 0
    - run: |
        # webhook経由でprodブランチからの
        # 事前にAdvanced deploy modeをLookerの設定で有効にしておく
　　　　 # https://docs.looker.com/data-modeling/getting-started/advanced-deploy-mode
        curl -X POST -H "X-Looker-Deploy-Secret:${LOOKER_WEBHOOK_SECRET}" 
             https://yourinstance.looker.com/webhooks/projects/yourproject/deploy/prod
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        LOOKER_WEBHOOK_SECRET: ${{ secrets.LOOKER_WEBHOOK_SECRET }}
```

git-pr-releaseを使えばmasterにマージされていたPull Requestを一覧化してチェックリスト化、PRの本文に簡単に書けるので、リリース時の動作確認チェックもしやすくなるかと思います。


## Looker model/dashboard定義の自動生成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12187/6800fe59-68f4-5d2e-6b75-619830693f86.png)

デプロイフローと同じ要領で、LookMLの定義を自動生成することも可能です。次のスクリプトは、上図のように、ベースとなるmodel定義から、各環境ごとのmodel定義を自動生成するために使用しているGitHub Actionsです。

```ベースのLookMLから各環境用の定義を自動生成するGitHubActions.yml
# ベースのLookMLから各環境用の定義を自動生成する
on:
  push:
    branches:
      - master
name: ベースのLookMLから各環境用の定義を自動生成する
jobs:
  generateLookML:
    name: git push
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2.1.1
      with:
        fetch-depth: 0
    - run: |
        git remote set-url origin https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git
    - run: |
        git switch -c generate-lookml
        git config --global user.email "github-actions@example.com"
        git config --global user.name "github-actions"
        make build # ベースの定義を生成するスクリプトを走らせる
        git add generated # generatedディレクトリ配下に自動生成したものが配置されるとして、generatedディレクトリをgit add
        git commit -am 'LookML定義自動生成'
        git push -f origin generate-lookml # 生成結果をgit push
    - name: pull-request
      uses: ainoya/pull-request@master
      with:
        source_branch: generate-lookml
        destination_branch: master
        pr_title: LookML定義 自動生成
        pr_body: "各環境用のLookML定義を生成しました"
        github_token: ${{ secrets.GITHUB_TOKEN }}
```

## まとめ

この記事では、GitHub Actionsを用いてLookMLの開発フローをちょっと自動化する方法について紹介しました。冒頭では最新バージョンのLookerでは不要なフローについてやや冗長に紹介してしまいましたが、どこかで特殊な構成を実現されようとしている方向けの参考になればと思い書いてしまいました。LookMLはgit管理可能であることを生かして、他のプログラミング言語の開発プロジェクトと同様にCI/CDを構築できます。今回紹介したもの以外にも、lintやtestなど、CIにあると便利なものがありそうなので、またの機会に紹介したいと思います。