テスト用パッケージを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 *

メニューバーでPagerDutyのステータスがわかるmacOSアプリを作った(10ヶ月ぶり2回目)

以前作ったPagerDuty通知用のmacOSアプリを書き直してPagerCallというアプリを作った。

github.com

基本的な機能は前作のPDStatusとあんまり変わってなくて、メニューバーに常駐してインシデントの一覧を表示して通知を飛ばすだけのもの。

ただPDStatusは初めてのmacOSアプリでコードや設計が変なところが多かったので、その辺を諸々書き直した。 あとレースコンディションまわりを修正してSwift6に対応したのと、Swift Concurrencyにきちんと対応した(つもり)。 見栄えも少しよくなったと思う。

以下の動画がとても参考になった。


www.youtube.com

Atlantisを使ったオペレーションの検証(あるいはPoor man's Bytebase)

BytebaseのようにSQLだけでなく任意のワンショットのコマンドの実行をAtlantisのワークフローに乗せられるのではないか…ということを思いついたので検証してみた。


まずワンショットのコマンドのterraform providerを作成。

github.com

resource "oneshot_run" "hello" {
  command      = "echo 'hello, oneshot'"
  plan_command = "echo \"hello, oneshot (plan=$ONESHOT_PLAN)\""
}

terraform planplan_commandで実行され、terraform applycommandが実行される(plan_commandはapply時には実行されない)。

plan_commandのログがplan-stdout.log plan-stderr.log に、commandのログが stdout.log stderr.log に出力される。

terraformなので、一度apply(実行)されたら二度目は実行されない。


Atlantisのワークフローの設定は以下の通り。

workflows:
  operation:
    plan:
      steps:
        - init
        - run: echo "[WARN] Plan command was not executed" > plan-stdout.log
        - run: touch plan-stderr.log
        - run:
            command: terraform${ATLANTIS_TERRAFORM_VERSION} plan -input=false -refresh -out $PLANFILE
            output: hide
        - run: cat plan-stdout.log
        - run: cat plan-stderr.log
    apply:
      steps:
        - run: echo "[WARN] Apply command was not executed" > stdout.log
        - run: touch stderr.log
        - run:
            command: terraform${ATLANTIS_TERRAFORM_VERSION} apply $PLANFILE
            output: hide
        - run: cat stdout.log
        - run: cat stderr.log

GitHubのコメントが冗長なのでテンプレートを上書き。

  • プロジェクトごとのapply/delete/planコマンドのコメントを削除
  • <details>...</details>の折りたたみを削除

  • plan_success_unwrapped.tmpl
{{ define "planSuccessUnwrapped" -}}

{{ if .EnableDiffMarkdownFormat }}{{ .DiffMarkdownFormattedTerraformOutput }}{{ else }}{{ .TerraformOutput }}{{ end }}

{{ if .PlanWasDeleted -}}
This plan was not saved because one or more projects failed and automerge requires all plans pass.
{{ end -}}
{{ template "mergedAgain" . -}}
{{ end -}}
  • plan_success_wrapped.tmpl
{{ define "planSuccessWrapped" -}}

{{ if .EnableDiffMarkdownFormat }}{{ .DiffMarkdownFormattedTerraformOutput }}{{ else }}{{ .TerraformOutput }}{{ end }}

{{ if .PlanWasDeleted -}}
This plan was not saved because one or more projects failed and automerge requires all plans pass.
{{ end -}}
{{ .PlanSummary -}}
{{ template "mergedAgain" . -}}
{{ end -}}


任意のコマンドを実行するPRを作成してみる。

resource "oneshot_run" "any_command" {
  command      = <<-EOT
    echo 'これはapplyに実行されるコマンドの出力だよ'
  EOT
  plan_command = <<-EOT
    echo -e "これはplanで実行されるコマンドの出力だよ\n(plan=$ONESHOT_PLAN)"
  EOT
}

Atlantisでplan_commandが実行されて結果がコメントとして追記される。

plan_commandを修正してpush。

diff --git a/project/hello/terraform.tf b/project/hello/terraform.tf
index f1f2851..b88f327 100644
--- a/project/hello/terraform.tf
+++ b/project/hello/terraform.tf
@@ -27,6 +27,6 @@ resource "oneshot_run" "any_command" {
     echo 'これはapplyに実行されるコマンドの出力だよ'
   EOT
   plan_command = <<-EOT
-    echo -e "これはplanで実行されるコマンドの出力だよ\n(plan=$ONESHOT_PLAN)"
+    echo -e "これはplanで実行されるコマンドの出力だよ\n※修正したよ※\n(plan=$ONESHOT_PLAN)"
   EOT
 }

修正したコマンドが実行される。


atlantis applyとコメントするとcommandが実行される。

メモ

resource "example_thing" "example" {
  for_each = fileset("scripts", "**/main.sh")

  command      = each.value
  plan_command = each.value
}
# main.sh
if [ "$ONESHOT_PLAN" = "1" ] ; then
  # (planの処理)
else
  # (applyの処理)
fi
  • atlantis planで検証用のplan_commandが実行されるのは悪くない、ような気がする
  • 任意のコマンドを実行できるのでセキュリティまわりが大変かも
    • 入出力に秘匿情報がある場合はどうしたらよいか…
  • plan/apply時にコマンドではなくterraform自体の出力を出す必要があるかも知れない

AWS Secrets Managerから秘匿値を取得して環境変数を設定しコマンドを実行するツールを作った

envchainのバックエンドにAWS Secrets Managerを使ったようなツールを作った。

github.com

使い方

Secrets Managerに秘匿値を設定した上で

$ aws secretsmanager get-secret-value --secret-id foo/bar
{
  ...
  "SecretString": "BAZ",
  ...

$ aws secretsmanager get-secret-value --secret-id foo/zoo # JSON secret
{
  ...
  "SecretString": "{\"TOKEN\":\"AAA\",\"SECRET\":\"BBB\"}",
  ...

sevの設定ファル(~/.sev.toml)を作成し

[hello]
BAR = "secretsmanager://foo/bar"
ZOO = "secretsmanager://foo/zoo:TOKEN"
BAZ = "BAZBAZBAZ"

[world]
HOGE = "secretsmanager://foo/zoo:SECRET"
FOGA = "secretsmanager://foo/bar"
PIYO = "PIYOPIYOPIYO"

sevでラップしてプロファイル名を指定してコマンドを実行すると、秘匿値が環境変数経由でコマンドに渡される。

$ sev hello -- env
FOO=BAZ
ZOO=AAA
BAZ=BAZBAZBAZ

$ sev world -- env
HOGE=BBB
FUGA=BAZ
PIYO=PIYOPIYOPIYO

設定ファイルにAWS_PROFILEを指定しておくと、デフォルトではそのAWS_PROFILEを使ってSecrets Manager APIを呼び出す。

[hello]
AWS_PROFILE = "my-profile"
BAR = "secretsmanager://foo/bar"
# foo/barの値の取得にmy-profileが使われる