applyとは
pulumiを使っていると必ずapplyを使う場面が出てくるのですが、私はこのapplyが最初なんなのかよくわかりませんでした。ドキュメント読んだり使ったりしてなんとなくわかったのでまとめてみます。
pulumiの実行順序
pulumiは以下のようなステップを経て最終的なインフラの変更反映が行われます。
- ソースコード(typescript/golang/python)の評価
- ソースコードで定義されたリソース(awsのec2とか)の依存関係を解決
- リソースごとの依存関係を表すツリー(state)を作成
- app.pulumi.comにアップロードされたstateとの差分から最終的なインフラの変更操作を導き出す
- リソースのプロバイダー(awsとかgcpとか)に対して変更を反映する
たぶんterraformと同じような感じだと思います。
問題
プログラミング言語でインフラを定義した時に問題になるのが「リソースが生成された後の値をコードで使うにはどうしたらいいのか?」ということです。例としてawsでs3とiam roleを以下のように定義します。
// s3
const bucket = new aws.s3.Bucket("my-bucket");
// s3のバケット名(ハッシュが末尾に付きます my-bucket-xyz123 のような感じ)
const bucketName = bucket.bucket;
// s3への権限を持つiam role
const role = new aws.iam.Role("my-role", {
assumeRolePolicy: JSON.stringify({
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": [`arn:aws:s3:::${bucket.bucket}`]
},
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject"
],
"Resource": [`arn:aws:s3:::${bucket.bucket}/*`]
}
]
})
});
上記を実行すると以下のエラーが発生します
Plan apply failed: Error creating IAM Role my-role-8a4d5da: MalformedPolicyDocument: Has prohibited field Resource
なんだかよくわかりませんね
console.log
で該当のjsonを出力してみます。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::[object Object]"
]
},
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::[object Object]/*"
]
}
]
}
バケット名がobjectになっています
これはリソース生成後のバケット名をtypescriptが評価される時に文字列として扱おうとしているためです。
前述のpulumiの実行順序では、まずtypescriptが評価され、その後にリソースの依存関係を構築します。このためリソース反映の結果を加工して扱おうとすると、リソース変更が反映される前にtypescript上で評価されてしまうため、うまく動きません。
pulumi.Output
pulumiではリソースの反映結果をOutput<T>型
として扱います。これは平たく言うとPromise
であり、リソース作成の実行結果ではありません。pulumiはOutputをリソース同士で参照して依存関係を構築します。
以下はec2インスタンスにkeypairを割り当てる例です
// keypair
const keyPair = new aws.ec2.KeyPair("my-ec2-keypair", {
publicKey: "YOUR_PUBLIC_KEY"
});
// ec2
const instance = new aws.ec2.Instance("my-ec2-instance", {
instanceType: "t3.nano",
keyName: keyPair.keyName,
// 他にも必須パラメータがありますが、省略
});
これはkeypairをOutput型のまま参照しているので、エラーにならずに依存関係が構築されます。エラーになるのはtypescript上でリソースの反映結果を加工する場合です。
apply
本題のapplyです。リソースの反映結果をtypescript上で加工して扱う場合にapplyを使います。
applyはリソース生成の実行結果を表すOutputを実行後に加工するメソッドで、戻り値はまたOutputになります。
const bucket = new aws.s3.Bucket("my-bucket");
const bucketName = bucket.bucket;
const role = new aws.iam.Role("my-role", {
assumeRolePolicy: bucketName.apply(realBucketName=>{
// stringを返却するとOutput<string>型になる
return JSON.stringify({
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": [`arn:aws:s3:::${realBucketName}`]
},
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject"
],
"Resource": [`arn:aws:s3:::${realBucketName}/*`]
}
]
})
})
});
これでポリシーの文字列が遅延評価されるようになり、s3バケット作成とiam role作成の間に実行されます
pulumi.all
複数のapplyを扱うにはallを使います。配列でOutputを渡します。
const bucket1 = new aws.s3.Bucket("my-bucket-1");
const bucketName1 = bucket1.bucket;
const bucket2 = new aws.s3.Bucket("my-bucket-2");
const bucketName2 = bucket2.bucket;
const role = new aws.iam.Role("my-role", {
assumeRolePolicy: pulumi.all([bucketName1, bucketName2]).apply(([realBucketName1, realBucketName2]) => {
return JSON.stringify({
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": [
`arn:aws:s3:::${realBucketName1}`,
`arn:aws:s3:::${realBucketName2}`
]
},
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject"
],
"Resource": [
`arn:aws:s3:::${realBucketName1}/*`,
`arn:aws:s3:::${realBucketName2}/*`
]
}
]
})
})
});
pulumi.interpolate
前述のコードははっきり言って読みにくい のですが、簡単な文字列加工であればpulumi.interpolate
を使います。
// s3
const bucket = new aws.s3.Bucket("my-bucket");
const bucketName = bucket.bucket;
const role = new aws.iam.Role("my-role", {
assumeRolePolicy: JSON.stringify({
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": [pulumi.interpolate `arn:aws:s3:::${bucketNanme}`]
},
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject"
],
"Resource": [pulumi.interpolate `arn:aws:s3:::${bucketName}/*`]
}
]
})
});
簡潔に表現できました 他に配列用の pulumi.concat
があります。