Terraformのバージョン制約の仕様と挙動を調べてみた

2022.06.03

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

しばたです。

TerraformではTerraform本体および各プロバイダーやモジュールの利用バージョンを制限する制約を設定できます。

簡単な例を出すと、たとえば以下の様にterraformブロック内でrequired_versionを設定することでTerraform本体の利用バージョンを、required_providersブロック内のversionで各プロバイダーの利用バージョンを制限することができます。

main.tf

terraform {
  // Terraform本体に対するバージョン制約
  required_version = "~> 1.2.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      // Providerに対するバージョン制約
      version = "~> 4.17.0"
    }
  }
}

これらの設定について、これまでなんとなくでしか覚えていなかったので、改めて具体的な仕様や挙動を調べてみました。

Version Constraints

Terraformのバージョン制約の仕様については以下のドキュメント(Version Constraints)に記載されています。

バージョンの形式

だいたいの仕様は上記ドキュメントにあるものの、そもそも「Terraformで記述可能なバージョン形式」については

Version numbers should be a series of numbers separated by periods (like 1.2.0), optionally with a suffix to indicate a beta release.

と、微妙にあいまいにしか記述されていません。

ということで実際にソースを見て実装を調べてみたのですが、どうやらhashicorp/go-versionで対応している形式であればOKの様です。

hashicorp/go-versionではSemantic Versioningをサポートしつつ、もう少し緩い記述も受け入れる様になっています。
このため、現実にそんなバージョンがリリースされるのかは別として、

  • 1.0.0.0.0 (やたら細かい独自のバージョン指定)
    • この場合 1 = 1.0 = 1.0.0 = 1.0.0.0 = 1.0.0.0.0 と扱われる
  • 1.0.0-beta.1+metadata (Semantic Versioningのプレリリースやビルドメタデータを指定)
  • v1.0.0 (先頭がvで始まる形式。慣用される記法だがこれはSemantic Versioningではない)

といった形式も使用可能でした。

余談 : 実際のTerraformのバージョン履歴

少し前にリリースされたHashicorp Releases APIを使い直近のリリース履歴 *1を取得するとこんな感じでSemantic Versioningになっていることが見て取れます。

# Hashicorp Release APIから直近20リリースのバージョンを取得
C:\ > (Invoke-RestMethod -Uri https://api.releases.hashicorp.com/v1/releases/terraform/?limit=20) | Select-Object version, timestamp_created

version              timestamp_created
-------              -----------------
1.2.2                6/1/2022 4:58:55 PM
1.2.1                5/23/2022 10:49:08 PM
1.2.0                5/18/2022 9:47:46 PM
1.2.0-rc2            5/11/2022 6:57:37 PM
1.2.0-rc1            5/4/2022 5:00:45 PM
1.2.0-beta1          4/27/2022 7:29:14 PM
1.1.9                4/20/2022 1:46:15 PM
1.2.0-alpha20220413  4/13/2022 6:30:29 PM
1.1.8                4/7/2022 5:05:13 PM
1.2.0-alpha-20220328 3/28/2022 10:27:08 AM
1.1.7                3/2/2022 7:32:54 PM
1.1.6                2/16/2022 6:44:06 PM
1.1.5                2/2/2022 8:46:14 PM
1.1.4                1/19/2022 6:40:18 PM
1.1.3                1/6/2022 9:40:53 PM
1.1.2                12/17/2021 9:53:10 PM
1.1.1                12/15/2021 9:33:14 PM
1.1.0                12/8/2021 9:25:17 PM
1.1.0-rc1            12/1/2021 10:32:17 PM
1.1.0-beta2          11/17/2021 11:29:33 PM

ちなみにベータ版などのプレリリース版についてはホームページおよびGitHub上で公開されていないので実際の運用においてはあまり気にする必要はないと思います。
私自身このAPIを調べて初めてベータ版のダウンロードができることを知りました...

利用可能な演算子

バージョン制約で利用可能な演算子はドキュメントにある通りです。

  • = (もしくは演算子を省略) : 指定バージョンと等しい
  • != : 指定バージョンと等しくない
  • >, >=, <, <= : 指定のバージョンより大きい、以上、より小さい、以下
  • ~> : 一番右のバージョン要素が、指定の値以上

特徴的なのは最後のアロー演算子(~>)であり、これは一番右の要素が指定の値以上である制約となります。

要はメジャーバージョンおよびマイナーバージョンの互換性を保ちつつより新しいバージョンを許容したい場合に使うものですが、例えば~> 1.0~> 1.0.0では意味が変わるので注意が必要です。

~> 1.0であれば1.11.2が許容されますが~> 1.0.0の場合は1.0.11.0.2が許容され、書き方を変えると

  • ~> 1.0 = >= 1.0.0, < 2.0.0
  • ~> 1.0.0 = >= 1.0.0, < 1.1.0

と等価になります。

動作確認

ここからは実際にバージョン制約の挙動を確認しました。
本記事ではTerraform本体とAWS Providerを対象にしています。

Terraform本体の挙動

Terraform本体のバージョン制約では違反するバージョンが使われた場合にエラーとなります。

例えば利用しているTerraformのバージョンがVer.1.2.2(本日時点の最新バージョン)の場合で、以下の様なVer.1.2.0を要求する制約を設定した場合、

main.tf

terraform {
  required_version = "= 1.2.0"
}

この状態でterraform initを実行すると以下の様なエラーメッセージとともに実行が停止されます。

C:\ > terraform init
╷
│ Error: Unsupported Terraform Core version
│
│   on main.tf line 2, in terraform:
│    2:   required_version = "= 1.2.0"
│
│ This configuration does not support Terraform version 1.2.2. To proceed, either choose another supported Terraform
│ version or update this version constraint. Version constraints are normally set for good reason, so updating the
│ constraint may lead to other errors or unexpected behavior.
╵

AWS Providerの挙動

AWS Providerの場合も制約に違反するバージョンを使用するとエラーになりますが、現在であれば基本的にロックファイル(.terraform.lock.hcl)を使うでしょうし、一度terraform initした後はバージョンを固定する方が多いでしょう。

どちらかといえばProviderのバージョン制約はterraform initおよびterraform init -upgradeで取得・更新するバージョンを制御するために使う方が多いんじゃないかと思います。

terraform initおよびterraform init -upgradeではバージョン制約を満たすなかで最新のバージョンを取得します。
たとえば以下の設定(version = "~> 3.0")でterraform initした場合はVer.3系の最新バージョンを取得します。

main.tf

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
  }
}
# ~> 3.0 では Ver.3系の最新を取得
C:\ > terraform init

Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 3.0"...
- Installing hashicorp/aws v3.75.2...
- Installed hashicorp/aws v3.75.2 (signed by HashiCorp)

# --- (後略) ---

代わりにversion = ">= 3.0"にした場合はVer.4系の現在最新のバージョンを取得します。
(これだとあまり制約の意味がないですね...)

# >= 3.0 だとVer.4系の最新が対象になる
C:\ > terraform init

Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/aws versions matching ">= 3.0.0"...
- Installing hashicorp/aws v4.17.0...
- Installed hashicorp/aws v4.17.0 (signed by HashiCorp)

# --- (後略) ---

そして、特定バージョンの最新パッチにしたい場合はversion = "~> 4.15.0"といった記述にすると良いです。
(バージョンを固定しつつパッチは最新のものを許容する割と現実的な指定になります)

# ~> 4.15.0 だと Ver.4.15.x の最新バージョンを取得
C:\ > terraform init

Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 4.15.0"...
- Installing hashicorp/aws v4.15.1...
- Installed hashicorp/aws v4.15.1 (signed by HashiCorp)

# --- (後略) ---

最後に、あまり無いケースだとは思いますが、ある特定バージョン以上にしたくない場合にversion = "< 4.16.0"といった記述をすることもできます。

# < 4.16.0 だとVer.4.16.0の直前のバージョンを取得
C:\ > terraform init

Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/aws versions matching "< 4.16.0"...
- Installing hashicorp/aws v4.15.1...
- Installed hashicorp/aws v4.15.1 (signed by HashiCorp)

# --- (後略) ---

終わりに

以上となります。

いろいろ調べて書きましたが、バージョン制約を使う動機は「利用バージョンを固定したい」ことがほとんどでしょう。
なので=および~>演算子の挙動だけ押さえておけば基本的には問題ないと思います。

脚注

  1. APIの都合直近20リリース(limit=20)