EventBridgeのcron式のバリデーション用terraform providerを書いた

こちらの記事に触発されて terraform planでEventBridgeのcron式を検証するterraform providerを書いた。

github.com

以下のようにデータソースを定義すると

data "cronplan_expr" "every_weekday" {
  expr = "cron(5 0 ? * ? *)"
}

terraform planの時にエラーが表示される。

rate()at()については今のところ無視しているが、そのうち検証するようにしたい。 検証するようにした。

EventBridgeのday-of-weekの奇妙な動作について

EventBridgeのcron式にはいくつかコーナーケースがあって、例えば

  • 0 0 31 * ? *: 31日がない月はどうなるか?
  • 0 0 ? * FRI#5 *: 5週目の金曜日がない月はどうなるか?
  • 0 0 1W 10 ? *: 10/1が土日の場合はどうなるか?

…等々。

一応、EventBridgeのコンソールの出力を正としてそれに合わせるようにしているが、何日、何ヶ月もかけて検証はできないので完全に合っているかはわからない。


そのようなコーナーケースの動作としてday-of-weekのLの動作がある。*1

通常、cron(0 0 ? * 6L *)と書くと*2、毎月の最終の金曜日がスケジュールされる。

# cron(0 0 ? * 6L *)
Fri, 27 Oct 2023 00:00:00
Fri, 24 Nov 2023 00:00:00
Fri, 29 Dec 2023 00:00:00
Fri, 26 Jan 2024 00:00:00
Fri, 23 Feb 2024 00:00:00

しかしLの前に数字や曜日を書かないと、SATと書いたのと同じ動作になる。

# cron(0 0 ? * L *) = cron(0 0 ? * SAT *)
Sat, 07 Oct 2023 00:00:00
Sat, 14 Oct 2023 00:00:00
Sat, 21 Oct 2023 00:00:00
Sat, 28 Oct 2023 00:00:00
Sat, 04 Nov 2023 00:00:00

とても奇妙な仕様なのでL単独で使うのは避けた方がいいと思う。

ちなみに、ウェブを調べていたらJavaQuartz Job Schedulerが同じ仕様だった。

www.quartz-scheduler.org

L (“last”) - ... If used in the day-of-week field by itself, it simply means “7” or “SAT”. ... When using the ‘L’ option, it is important not to specify lists, or ranges of values, as you’ll get confusing/unexpected results.

L ( "last" ) - ... 曜日フィールドで単独で使用される場合は、単に「7」または「SAT」を意味します。... 'L' オプションを使用する場合は、混乱したり予期しない結果が得られるため、リストまたは値の範囲を指定しないことが重要です。

なので、EventBridgeのcron式のパースには秒を無視したQuartzが使われているのではないかと疑っている。

さらに追記

cron式のSUN=0をSUN=1に書き換えた犯人がQuartzではないかと考えているが、ログは見つけられていない…

追記2

※対応しました https://github.com/winebarrel/cronplan/pull/45/files?w=1

*1:https://github.com/winebarrel/cronplan#about-the-behavior-of-l-in-day-of-week

*2:できれば6L→FRILと書いた方がよいと思う

1Passwordのアイテムをdata sourceにするterraform-provider-opを作った

1Password CLI経由で1Passwordのアイテムをdata sourceとして使うterraform-provider-opを作った。

github.com

以下の用のPostgreSQL providerのパスワードを自分のマシンの1Passwordからとってこれる。

provider "op" {
}

data "op_item" "postgres" {
  title = "my-postgres"
}

provider "postgresql" {
  username = "scott"
  password = data.op_item.postgres.password
}

ローカルマシン以外の環境での実行が難しいので使いどころを選ぶが、便利なときは便利…のような気がする。

Terraform Shell plugin

よくよく調べたらすでに1PasswordにはShell pluginという仕組みがあってBetaであるものの1Password CLI経由でクレデンシャルをterraformに渡せるようだった。

developer.1password.com

あまりきちんと仕組みを把握していないが、以下のブログを読んだ感じ環境変数でクレデンシャルを渡せるように見えた。

blog.mmmcorp.co.jp

data sourceほどの柔軟性はなさそうだけれど、公式が提供しているしtfstateに残らないのはよいかもしれない。

op run

あと全然知らなかったけど、1Password CLIrunというコマンドがあって、op run --- hoge command と実行すると環境変数経由でhogeにクレデンシャルをわたす便利機能があった

export DB_PASSWORD="op://app-prod/db/password"
op run -- printenv DB_PASSWORD

developer.1password.com

www.taccoform.com

しらない便利機能が多い。

1Password Connect serverも調べよう

esa.ioにファイル・ディレクトリをインポートする

esa.ioCLI、kasaにインポート機能を追加した。

github.com

ファイルをインポートすることもできるし

$ echo test > test.txt

$ kasa import test.txt hello/world
https://winebarrel.esa.io/posts/1

$ kasa cat hello/world
test

$ kasa import test.txt hello/
https://winebarrel.esa.io/posts/2

$ kasa cat hello/test.txt
test

標準入力からインポートすることもできる。

$ echo test | kasa import - hello/world2
https://winebarrel.esa.io/posts/3

$ kasa cat hello/world2
test

また、ディレクトリをインポートすることもできる。

$ tree foo
foo
├── bar
│   └── zoo.txt
└── baz.txt

$ kasa import foo import/
https://winebarrel.esa.io/posts/4
https://winebarrel.esa.io/posts/5

$ kasa ls import/
2022-01-02 03:45:00  -  https://winebarrel.esa.io/posts/4    import/bar/zoo.txt
2022-01-02 03:45:01  -  https://winebarrel.esa.io/posts/5    import/baz.txt

AWS SSOで直接AWSアカウントのManagement Consoleを開く

AWS SSOで各AWSアカウントのManage Consoleを開く場合、AWSアクセスポータルURL(d-xxxxxxxxxx.awsapps.com/start)から遷移するが、赤枠のリンクは固定値のようなので、そのリンクから個別のアカウントのManage Console開くことができる。

リンクのURLは下のようになっていて

https://d-12345abcde.awsapps.com/start/#/saml/custom/123456789012%20%28my-account%29/MTIzNDU2Nzg5MDEyX2lucy0xMjM0NTY3ODkwYWJjZWRmX3AtMTIzNDU2Nzg5MGFi%3D%3D

/custom/の下は123456789012 (my-account)アカウントID (アカウント名)をURLエンコードしたもの。

その下のMTIzNDU2Nzg5MDEyX2lucy0xMjM0NTY3ODkwYWJjZWRmX3AtMTIzNDU2Nzg5MGFi%3D%3Dから末尾の%3D%3D*1をとってBase64デコードすると

~% echo 'MTIzNDU2Nzg5MDEyX2lucy0xMjM0NTY3ODkwYWJjZWRmX3AtMTIzNDU2Nzg5MGFi' | base64 -D
123456789012_ins-1234567890abcedf_p-1234567890ab

という値が出てくる。

先頭の値がAWS Organizationsの親アカウント。ins-xxxAWSアカウントごとに振られたID(Instance ID?)。p-xxxはアカウントのロールごとに振られたID(Permission ID?)。

ins-xxxp-xxxを公開されているAWSAPIから引っ張ってくることはできなかったが*2、アクセスポータルのリンクから取得できるので、それらを組み合わせて動的に直接AWSアカウントのManagement Consoleを開くリンクを作成できる。


例)

  • アカウントID: 999456789012
  • アカウント名: my-alt-account-xxx
  • 親アカウントID: 123456789012
  • ins: ins-abcedf7890abcedf
  • p: p-abcdef7890ab (ロール DBOperator に紐付く値とする)
~% echo -n '123456789012_abcedf7890abcedf_p-abcdef7890ab' | base64
MTIzNDU2Nzg5MDEyX2FiY2VkZjc4OTBhYmNlZGZfcC1hYmNkZWY3ODkwYWI=

Base64部分は別に変更しなくてもフォールバックするみたいで、/999456789012%20%28my-alt-account-xxx%29/の部分だけ修正しても一応、対象のAWSアカウントに遷移できた


Goで実装したリダイレクター

https://example.com/my-alt-account-xxx/DBOperator のようなURLでManagement Consoleを開くWebアプリの例。

gistfc640578525ac9f4619082be61b9b880

別にins-xxx p-xxx から動的にBase64の文字列を生成せず、Base64の文字列ままで保持しておいてもいいのだけれど、実はins-xxx p-xxxを引っ張ってこれるAPIが見つかるのではないか、、、と考えてこうしている。

*1:Base64のパディングをURLエンコードしたもの

*2:AWS SSOまわりだと思うが見つけられなかった

Datadogの月ごとの料金を表示するツールを作った

Datadogでマルチオーガニゼーションを利用しているが、Webコンソールでは使用量は表示されても料金が表示されず、また月ごとの料金を比べることもできないので、APIを呼び出して複数月の料金を月ごと・組織ごとに表示するツールを作った。

github.com

使い方

環境変数DD_API_KEY DD_APP_KEYを設定して、ddcostコマンドを実行すると以下のようにテーブル形式で料金を表示する。(tsv、jsoncsvにも対応)

$ export DD_API_KEY=...
$ export DD_APP_KEY=...
$ ddcost -v sub-org -s 2022-12
       ORG       |       PRODUCT       | CHARGE TYPE | 2022-12 | 2023-01 | 2023-02 | 2023-03 | 2023-04
-----------------+---------------------+-------------+---------+---------+---------+---------+----------
  organization1  | fargate_container   | committed   |    1.00 |    1.00 |    1.00 |    1.00 |    1.00
                 |                     | on_demand   |    2.00 |    2.00 |    2.00 |    2.00 |    2.00
                 |                     | total       |    3.00 |    3.00 |    3.00 |    3.00 |    3.00
                 | logs_indexed_15day  | committed   |    0.00 |    0.00 |    0.00 |    0.00 |    0.00
                 |                     | on_demand   |    0.50 |    0.50 |    0.50 |    0.50 |    0.50
                 |                     | total       |    0.50 |    0.50 |    0.50 |    0.50 |    0.50
                 | total               |             |    3.50 |    3.50 |    3.50 |    3.50 |    3.50
                 |                     |             |         |         |         |         |
  organization2  | infra_host          | committed   |   10.00 |   10.00 |   10.00 |   10.00 |   10.00
                 |                     | on_demand   |   20.00 |   20.00 |   20.00 |   20.00 |   20.00
                 |                     | total       |   30.00 |   30.00 |   30.00 |   30.00 |   30.00
                 | logs_indexed_15day  | committed   |    1.00 |    1.00 |    1.00 |    1.00 |    1.00
                 |                     | on_demand   |    1.50 |    1.50 |    1.50 |    1.50 |    1.50
                 |                     | total       |    2.50 |    2.50 |    2.50 |    2.50 |    2.50
                 | total               |             |   32.50 |   32.50 |   32.50 |   32.50 |   32.50

-v summaryで親オーガニゼーションのサマリ、-v sub-orgで親・子オーガニゼーションごとの内訳を表示する。

また、APIで前月の料金を取得できるのが当月の9日以降ぐらいになるので、--estimateで見積もり料金を表示できるようにした。こちらは過去二ヶ月分まで。

Webアプリ化

経理の方からDatadogの明細を見たいという話があったので、社内ではWebアプリ化して運用している。

その他

関係ないけどAWSのコスト集計にはawscostを使っている。便利