以前、spanner.StatementのSQLの文字列をフォーマットするために、go-zetasqlfmtというCLIを作った。

便利に使えていたが、使用しているgo-zetasqlがCGOでzetasqlをBindingしている都合上、少々ビルドに時間がかかってしまう。

そのため、CI上で go run を使って気軽に利用しようとするとCIの時間がかかってしまう問題があった。

使う側でキャシュをうまく使う等で回避はできる問題ではあったが、せっかくなので使う側がそれを気にせず気軽に使えるようにしたいと思い、以下のようにGitHub Actionsでコマンドを提供することにした。

https://github.com/nametake/go-zetasqlfmt-action

ついでにGitHub Actionsを作るにあたってやったことを作業ログとして残しておく。

やったこと Link to heading

色々試行錯誤はしたが、GitHub Actionsとして提供するにあたり、以下のような方針にした。

  • go-zetasqlfmt単体でDockerfileを提供する
  • go-zetasqlfmt-actionはDockerfileを指定したActionにする

まずはgo-zetasqlfmtのDockerfile作成から。

ライブラリ側のDockerfileを参考にして以下のようなDockerfileをgo-zetasqlfmtのリポジトリに作成。

FROM golang:1.22-bookworm as builder

ARG VERSION

RUN apt-get update \
  && apt-get install -y --no-install-recommends clang git \
  && apt-get clean \
  && rm -rf /var/lib/apt/lists/*

ENV CGO_ENABLED 1
ENV CXX clang++

WORKDIR /go-zetasqlfmt

COPY go.mod go.sum ./
RUN go mod download

COPY . .

RUN go install ./cmd/zetasqlfmt

FROM debian:bookworm-slim

RUN apt-get update && apt-get install -y \
    wget \
    ca-certificates \
    && rm -rf /var/lib/apt/lists/*

ENV GO_VERSION 1.22.5

RUN wget https://golang.org/dl/go${GO_VERSION}.linux-amd64.tar.gz \
    && tar -C /usr/local -xzf go${GO_VERSION}.linux-amd64.tar.gz \
    && rm go${GO_VERSION}.linux-amd64.tar.gz

ENV PATH /usr/local/go/bin:$PATH

WORKDIR /app

COPY --from=builder /go/bin/zetasqlfmt /bin/zetasqlfmt

ENTRYPOINT ["/bin/zetasqlfmt"]

ビルドステージ側のImageはgo-zetasqlライブラリのDockerfileを参考にして、debian:bookwormを採用。

コマンド側のImageではslimを使用しつつ、golang.org/x/tools/go/packages.Load でGoのバイナリが要求されるのでそれだけインストール(内部的に go list コマンドを使っているっぽい)。

もうちょっと試行錯誤はできるかもしれないが、まずは動くの優先で最低限の軽量化のみしている。

Actions側は以下のようにGitHub Actions用のYAMLファイルを定義。

---
name: go-zetasqlfmt
description: run go-zetasqlfmt
inputs:
  working-directory:
    description: file paths
    required: false
    default: .
  path:
    description: file paths
    required: true
  nosemicolon:
    description: no semicolon option
    required: false
runs:
  using: docker
  image: Dockerfile
  args:
    - ${{ inputs.working-directory }}
    - ${{ inputs.path }}
    - ${{ inputs.nosemicolon }}

entrypoint.shもシンプルに以下のように定義。

#!/bin/sh

workingDir=$1
path=$2
nosemicolon=$3

cd "$workingDir" || exit

if [ "$nosemicolon" = "true" ]; then
	zetasqlfmt -nosemicolon "$path"
	exit 0
fi

zetasqlfmt "$path"

ハマった点として、GitHub ActionsでDockerfileを指定したとき、起動したDockerfileのWORKDIRにworkflow側で設定したjobs.run.working-directoryが反映されていないことがおきた。

go-zetasqlfmtで利用しているpackages.Loadはgo.modが配置されている場所を実行ディレクトリにする必要がある。

Actionを利用しようとしていたリポジトリはモノレポのプロジェクトで、$PROJECT_ROOT/server/go.mod のようにROOTから一段階下がった場所にgo.modが配置されていたため、フォーマットがうまく効いてくれなかった。

この件の該当っぽいIssue も見つけたが、最適な回避方法もなさそうだったので、Action側で直接working-directoryを指定できるようにして回避した。

GitHub Actionsにしたことで、長いと10分ぐらいかかっていたgo-zetasqlfmtのCIの時間が30秒ぐらいまで削減できた。

もうちょっとImageを軽量化したり等で実行時間を短くしたいが、時間がかかりすぎてしまう問題自体は一旦回避できるようになったので、しばらくこの運用で回してみる。