Terraform v1.2の新機能preconditionとpostconditionを調べた

2022.05.24

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Terraformのversion 1.2.0が 2022/05/18にGAになりました。新機能preconditionとpostconditionについて調べたのでレポートします。

preconditionとpostconditionとは

一言でいうとバリデーションのための新機能です。

これまでTerraformでのバリデーションというと、variablesに対して行なうことができました。typeで簡単な型チェックができ、validationブロック内で正規表現を使うなどして柔軟なチェックもできます。

variable "image_id" {
  type        = string
  description = "The id of the machine image (AMI) to use for the server."

  validation {
    condition     = length(var.image_id) > 4 && substr(var.image_id, 0, 4) == "ami-"
    error_message = "The image_id value must be a valid AMI id, starting with \"ami-\"."
  }
}

対して、今回追加されたpreconditionとpostconditionは、resource、data source、outputに対してのバリデーションです。それぞれ(resource、data source、output)のブロックの中に書くと、

  • preconditonは、そのresourceが作成される前、もしくはその data sourceが取得される前にprecondition内に書かれたチェックを行ない、結果がfalseならエラーメッセージを返し、以降の処理を停止します。(outputではpreconditionは書けません。postconditionのみ)
  • postconditionは、そのresourceが作成された/その data sourceが取得された/そのoutputが出力された後に、postcondition内に書かれたチェックを行ない、結果がfalseならエラーメッセージを返し、以降の処理を停止します。(すでに実施済の処理をundoすることはありません)
  • precondition/postcondition両方とも、結果がfalseの場合のエラーメッセージを定義しておく必要があります。つまりわかりやすいエラーメッセージを出すことができます。

例えば、EC2インスタンスを作成するとします。variablesを用意して、インスタンスタイプをモジュール外で指定できるようにします。

variable "aws_instance_type" {
  description = "EC2 instance type."
  type        = string
}

resource "aws_instance" "app" {
  instance_type = var.aws_instance_type
  
  (他のattirbutesは割愛)
}

この時、作成されるEC2インスタンスはEBS最適化がサポートされている必要がある、とします。サポートされている/いないはインスタンスタイプによって決まります。これをvariableのvalidationだけでチェックするのは難しいですよね。そこでpreconditionの出番です。

  variable "aws_instance_type" {
    description = "EC2 instance type."
    type        = string
  }

+ data "aws_ec2_instance_type" "app" {
+   instance_type = var.aws_instance_type
+ }

  resource "aws_instance" "app" {
    instance_type = var.aws_instance_type
   
    (他のattirbutesは割愛)

+   lifecycle {
+     precondition {
+       condition     = data.aws_ec2_instance_type.app.ebs_optimized_support != "unsupported"
+       error_message = "The EC2 instance type (${var.aws_instance_type}) must support EBS + optimization."      
+     }
+   }
  }

上記のように、まずvariablesからdata sourceでインスタンスタイプを取得して、そのインスタンスタイプのEBS最適化がサポートされているかをaws_instanceのpreconditionでチェックする、という事ができます。サポートされていない場合、error_messageで定義されたエラーメッセージが表示され処理がストップします。このように、従来のvariablesのバリデーションでは実現できなかった、動的に導出される値に対してバリデーションがかけられる、というのがこのprecondition、postconditionのポイントです。

preconditionとpostcondition、使い分けの指針

前述のEBS最適化がサポートされているか云々の例でいうと、以下のように インスタンスタイプのdata sourceのpostconditionとしてバリデーションをかけることもできますよね。

  variable "aws_instance_type" {
    description = "EC2 instance type."
    type        = string
  }

  data "aws_ec2_instance_type" "app" {
    instance_type = var.aws_instance_type

+   lifecycle {
+     postcondition {
+       condition     = self.ebs_optimized_support != "unsupported"
+       error_message = "The EC2 instance type (${var.aws_instance_type}) must support EBS optimization."      
+     }
+   }
  }

  resource "aws_instance" "app" {
    instance_type = var.aws_instance_type
   
    (他のattirbutesは割愛)

-   lifecycle {
-     precondition {
-       condition     = data.aws_ec2_instance_type.app.ebs_optimized_support != "unsupported"
-       error_message = "The EC2 instance type (${var.aws_instance_type}) must support EBS + optimization."      
-     }
-   }
  }

また、インスタンスタイプのdata sourceのpostconditionと、インスタンスresourceのprecondition両方書くこともできます。どの方法が良いでしょうか?

公式ドキュメントではチェックの目的がassumption(想定)なのかgurantee(保証)なのかで使い分けてくださいと述べられています。つまり、そのresourceを作成する、もしくはdata sourceを取得するための文字通り前提条件(=precondition)はpreconditionとして書く。そのresource・data sourceが持つべき特性(他のresourceやdata sourceがこの特性を信頼できる(=前提とできる))はpostconditionとして書く、ということです。ちょっと抽象的ですが…

他にも以下の点を考慮してくださいと述べられています。

  • どこでエラーメッセージがレポートされるのが一番わかりやすいか? : precondition/postconditionのエラーはそれが定義されたオブジェクトのブロックにてレポートされます。開発者にとって一番わかりやすい場所がどこか考えましょう。
  • 利便性: リソースAのあるattribute値がリソースB,C,D,Eの前提条件となっている場合、リソースB,C,D,Eでそれぞれpreconditionを書くより、リソースAのpostconditionひとつを書くほうが実用的です。
  • 両方書く: これは依存関係を持つオブジェクト同士が別々のモジュールにいる場合に有効的です。それぞれのモジュールが独自に改良され続けていったとしても、お互いを検証し続けることができます。

apply時にしかチェックしない、事もある

あるresource(便宜上resource Aと名付けます)のprecondition/postcondition内で、別のresource(resource Bと名付けます)のattributesを参照している場合、このprecondition/postconditionはresource Bを作成してからじゃないとチェックできないです。ですのでplan時にはこのチェックは行われず、apply時にのみチェックされる場合があります。「場合がある」というのは、resource Bがすでに作成されている状況においてはplan時にもチェックされるからです。要は「可能な限りできるだけ早くチェックする」と考えればOKです。

まとめ

新機能preconditionとpostconditionについてレポートしました。正直すぐに多用するイメージはあまりありませんが、ある程度複雑な構成のモジュールを作成する場合には出番が出てきそうな気がします。また、モジュールを公開して自分以外の誰かに利用してもらうような場合では、この機能を使えばモジュール内の各resource・data source・outputの意図がわかりやすくなって利用者に喜ばれるのではないかと思います。

参考情報