tech.guitarrapc.cóm

Technical updates

Pulumi で Stack解析中の例外でリソースが全て消えてしまうのを防ぐ

Pulumi は Stack の解析を行って、現在のステートとの差分でどのような処理をするか preview / up で表示します。 このため、Stack の解析中 (=コードをビルドして実行してStack生成中) に例外が生じたときにどのようにハンドルされるかは重要です。

今回は ユーザー側の誤った記述で、Stack解析に例外が起こっても処理が止まらず実行されたのをメモしておきます。

目次

tl;dr;

  • sdk/dotnet を利用する場合、Program.Main の返り値の型は int または Task<int> にしましょう。
  • Top-level statement なら return を忘れずに、return await Deployment.RunAsync<MyStack>(); としましょう。

どういう問題なのか

C# でPulumi を記述する場合、多くの場合は Stack を継承した自分の定義を用いるでしょう。 pulumi previewpulumi apply でStack を実行中に例外が発生した場合は、pulumi cli はそれを検知して実行を止めなくてはいけません。 pulumiは 例外を検知すると、「何もStackの内容を実行せず」例外をターミナルに表示して終了します。

しかしProgram.Mainメソッド の返り値の型を int あるいは Task<int> にしていないと、例外が起こっても pulumi cli は実行を継続しようとします。 この状態で例外が起こると、「例外以降のコードで記述されたリソースに削除マーカーを付与」して差分+例外をターミナルに表示して終了します。

// bad
public static async Task Main(string[] args)
{
    await Deployment.RunAsync<MyStack>();
}

// bad (Top-Level statement)
await Deployment.RunAsync<MyStack>();

Issue が作られておりそちらを見ると詳しくわかります。

github.com

対処方法は前述のとおり、Mainメソッドの返り値を intTask<int> にしましょう。

// ok
public static async Task<int> Main(string[] args)
{
    return await Deployment.RunAsync<MyStack>();
}

// ok (Expression-bodied Method)
public static async Task<int> Main(string[] args) => await Deployment.RunAsync<MyStack>();

// ok (Top-Level statement)
await Deployment.RunAsync<MyStack>();

何がおこったのか

Pulumi は言語のビルドは pulumi cli と切り離されているので、何気に .NET 6 でもビルド、実行できたりします。 その際にうかつにも、await Deployment.RunAsync<MyStack>(); と return を忘れて書いてしまったために、作成してあったリソースの多くが消えるという目にあったのでした。

// missing return!!
await Deployment.RunAsync<MyStack>();

Pulumi は GitHub Aapp あるいは GitHub Actions で PR で差分を表示できるのですが、GitHub Actions でコメントを書くのを使っていたために、差分が埋もれてしまい気づけなかったという顛末です。

対処は return をつけるだけです。 誰かの役に立つと幸いです。

Pulumi に期待すること

例外起こったら Environment.ExitCode = -1 など適切な終了コードをセットしてほしいです。 現在はそういったことをしていないのを、終了コードを Main メソッドで示さないと死ぬので基盤で対処してほしい....

github.com