aws-sdk-go-v2を使ってDownloadCompleteLogFileをよぶ

package main
import (
"context"
"fmt"
"io"
"log"
"net/http"
"os"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/rds"
)
const EmptyStringSHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
func downloadCompleteLogFile(ctx context.Context, dbInstanceIdentifier string, logFileName string, dst io.Writer) error {
cfg, err := config.LoadDefaultConfig(ctx)
if err != nil {
return err
}
endpointResolver := rds.NewDefaultEndpointResolverV2()
endpoint, err := endpointResolver.ResolveEndpoint(ctx, rds.EndpointParameters{
Region: aws.String(cfg.Region),
})
if err != nil {
return err
}
url := endpoint.URI
url.Path = fmt.Sprintf("/v15/downloadCompleteLogFile/%s/%s", dbInstanceIdentifier, logFileName)
req, err := http.NewRequest(http.MethodGet, url.String(), nil)
if err != nil {
return err
}
creds, err := cfg.Credentials.Retrieve(ctx)
if err != nil {
return err
}
signer := v4.NewSigner()
err = signer.SignHTTP(ctx, creds, req, EmptyStringSHA256, "rds", cfg.Region, time.Now())
if err != nil {
return err
}
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
_, err = io.Copy(dst, res.Body)
return err
}
func main() {
dbInstanceIdentifier := "..."
logFileName := "error/postgresql.log.yyyy-mm-dd-hhmi"
ctx := context.Background()
err := downloadCompleteLogFile(ctx, dbInstanceIdentifier, logFileName, os.Stdout)
if err != nil {
log.Fatal(err)
}
}

gist758d768c2efb4b17b45ecb9d4e915c22

CLIも作った。

github.com


実はいにしえのgoamz(のフォーク?)が対応していて、それを使ったツールも作ったけど、SSOやAWS_PROFILEの対応が辛いので作り直した。リトライまわりは未対応

GitHub Actionsのmatrixのconclusionを後続のジョブで取得する

needs.job_id.result がいずれかのmatrixの結果しか返さない、っぽい。

いくつかやり方はありそうだったがAPI使ったチェック用ジョブを設定するのが簡単だった。

  finish:
    needs: test
    if: always()
    runs-on: ubuntu-latest
    steps:
      - name: Check test conclusion
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          gh api /repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/jobs \
          | jq -e '.jobs | map(select(.name | test("^test ")).conclusion) | any(. == "failure") | not'

ssowrapというツールを作ったが特に必要なかった

ssowrapというaws2-wrapGolang製シングルバイナリ版を作った。

github.com

Golang版のaws2-wrapは頑張って探せばありそうだったけれど

ということで、自作した。


使い方はaws2-wrapとほぼ同じでAWS_PROFILEを設定してからssowrapでラップしてコマンドを実行する。

export AWS_PROFILE=my-profile
ssowrap terraform plan

ただ、これを作ったのはどちらかといえば、自分のSSOクレデンシャルを使ってDockerコンテナを動かしたかったのが大きい。

以下のようにDockerコンテナを実行すると、自分のSSOクレデンシャルをコンテナ内のサーバに渡すことができる。

docker run \
  -e AWS_PROFILE=my-profile \
  -v "${HOME}/.ssh":/root/.ssh:ro \
  -v "${HOME}/.aws":/root/.aws:ro \
  ghcr.io/winebarrel/ssowrap:v0.1.2 \
  ssowrap -- my-server-cmd

…と思っていたら、そもそも最近のAWS CLIにはexport-credentialsというコマンドがあるので、わざわさ自作する必要はなかった。

awscli.amazonaws.com

aws configure export-credentials --format env-no-export を実行すると AWS_ACCESS_KEY_ID=... \n AWS_SECRET_ACCESS_KEY=... \n という形式で出力されるので

env $(aws configure export-credentials --format env-no-export) my-server-cmd

と実行するとssowrapと同様のことができる。

まあ、awscliとsession-manager-pluginを含むシンプルな(信頼できる)Dockerイメージはなかったので、わざわざイメージをビルドしたくない場合にはよいかも。 (aws-cliのイメージに対してIssueは投げられている模様)

おまけ

Atlantisで使われているのを見てghcr.ioを使ってみたが、GitHubリポジトリとDockerリポジトリの管理を一体化できるのがなかなかよかった。 x86_64ランナーだとarm64のビルドがめちゃくちゃ遅かったが、ubuntu-24.04-armをつかったら普通に速くなった。

x86_64とarm64で別々にpushしても大丈夫だろうか、と思ったが特に問題なくマルチプラットフォームで使うことができている。

テスト用パッケージをgo.modから除く(非推奨)

cronplanにそれなりにスターがついたので、go.modからテスト用パッケージを除いてみた。

以下、cronplanをv1.10.1→v1.10.4に変更したときのgo.sumの差分。

$ go get github.com/winebarrel/cronplan@v1.10.1
$ go mod tidy
$ cp go.sum go.sum.bak

$ go get github.com/winebarrel/cronplan@v1.10.4
$ go mod tidy
$ diff -u go.sum.bak go.sum
--- go.sum.bak   2024-11-22 23:06:12
+++ go.sum    2024-11-22 23:11:45
@@ -4,15 +4,7 @@
 github.com/alecthomas/participle/v2 v2.1.1/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c=
 github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
 github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
 github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
-github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
-github.com/winebarrel/cronplan v1.10.1 h1:sTnmKWpGjXr3tDpgSSTCohltaimpBNXW/LgFf0SEMwo=
-github.com/winebarrel/cronplan v1.10.1/go.mod h1:FXpmoZVzj9eZoyHe1lpUezcFL3Tk6p5OBSovWeHq4qY=
-gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
-gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+github.com/winebarrel/cronplan v1.10.4 h1:lpDvd+gihqGEi7aYcvmJ1nyFVCXDULMk8XPvUQtcPyQ=
+github.com/winebarrel/cronplan v1.10.4/go.mod h1:ZGEHrRZU3YNI22nDLTr9obcRMX3uBYPektCQoCmFmaY=

testify関係のパッケージがなくなっていることがわかる。

経緯

cmd/以下のCLIの依存がgo.modに入るのがいやだったので、ライブラリと関係ないものをgo.modから除外してみた(…のだが、その後調べたらcmd/以下で使われているパッケージは go get github.com/winebarrel/cronplan をしても依存に追加されなかった)

そのついでで、テスト用のパッケージもgo.modに含まれないようになった。

方法

サブディレクトリにgo.modがあると、ルートディレクトリのgo.modにはサブディレクトリのパッケージが含まれなくなる。 なのでテスト用のソースコードはtestディレクトリに置くようにして、そこにgo.modをおいた。

ただし、そのままだとライブラリのパッケージを直接参照できないので、replaceを使って上位ディレクトリのパッケージを参照するようにした。

具体的にはtest/go.modがこんな感じ:

module github.com/winebarrel/cronplan/test

go 1.21

toolchain go1.21.1

// replaceで上位ディレクトリのパッケージを参照する
replace github.com/winebarrel/cronplan => ../

require (
    github.com/stretchr/testify v1.9.0
    // バージョン番号はダミー値
    github.com/winebarrel/cronplan v0.0.0-00010101000000-000000000000
)

require (
    github.com/alecthomas/participle/v2 v2.1.1 // indirect
    github.com/davecgh/go-spew v1.1.1 // indirect
    github.com/pmezard/go-difflib v1.0.0 // indirect
    gopkg.in/yaml.v3 v3.0.1 // indirect
)

これで go get github.com/winebarrel/cronplan してもtestifyが依存に追加されなくなる。

非推奨

以下の点であまり推奨できるやり方ではないと思っている。

  • ディレクトリ構成が非標準的になる
  • replaceがhacky
  • テストでしか使われているパッケージがダウンロードされても実害はないような気がする
    • _test.goであればビルドから除外されるし、使ってないものはリンクされないのではないだろうか?
    • dependabotが少しうっとうしくなるかもしれない

先行事例

k1low.hatenablog.com

go mod tidyを使いたかったので、このやり方にはしなかった。

その他

増えたgo.modを再帰的に探索してほしかったので、cronplanの依存パッケージのバージョンアップをdependabotからrenovateに変えることになった。

EventBridgeの複数のcron式のスケジュールを表示するCLIを作った

https://github.com/winebarrel/cronplan?tab=readme-ov-file#cronskd-cli

cronの時間の隙間を探すことがよくあるので…

$ cat exprs.txt
0 10 * * ? *
15 12 * * ? *
0 18 ? * MON-FRI *
0 8 1 * ? *
5 8-10 ? * MON-FRI *

$ cronskd -s '2024-11-11' exprs.txt
Mon, 11 Nov 2024 08:05:00    5 8-10 ? * MON-FRI *
Mon, 11 Nov 2024 09:05:00    5 8-10 ? * MON-FRI *
Mon, 11 Nov 2024 10:00:00    0 10 * * ? *
Mon, 11 Nov 2024 10:05:00    5 8-10 ? * MON-FRI *
Mon, 11 Nov 2024 12:15:00    15 12 * * ? *
Mon, 11 Nov 2024 18:00:00    0 18 ? * MON-FRI *