5
3

More than 1 year has passed since last update.

【Terraform】can関数を利用して汎用的なコードを書く

Last updated at Posted at 2023-07-07

1. はじめに

前回の記事でTerraformのcan関数について軽く触れていたので、今回はその紹介のための記事を投稿しようと思います。
使ってみた感想としては、汎用的なコードの書き方ができそうだなと思いました。

2. can関数について

公式ドキュメント:https://developer.hashicorp.com/terraform/language/functions/can

can関数で指定した式に対してboolに変換して値を返してくれるような関数になります。
公式ドキュメントから以下の例を参照します。

> local.foo
{
  "bar" = "baz"
}
> can(local.foo.bar)
true
> can(local.foo.boop)
false

上記の例の場合、local.fooではbarという変数が定義されているため
can(local.foo.bar)では正しく値を取得できるので返り値がtrueとなります。
反対にcan(local.foo.boop)とすると、boop変数というのは定義されておらず、
正しく値を取得できずエラーとなるため、falseが返ってきます。

今回はこちらの関数を活用して、コードの作成を行っていきます。

3. 構成

3-1. 作成するリソース

S3バケットを2つ作成して、それぞれ別のキー値を持つタグの設定をしていこうと思います。

AのS3バケット
・Name:temp-bucket-a
TempA:true

BのS3バケット
・Name:temp-bucket-b
TempB:true

3-2. フォルダ構成

以下のようなフォルダ構成で実装していきます。
リソース名などは変数管理としたいため、Local Valuesを利用します。
参考:Local Values

Terraform/
└─ dev
    ├─ s3.tf
    ├─ locals.tf
    └─ terraform.tf

4. 実装

4-1. terraform.tfとlocals.tf

can関数を利用することでどのくらいコードに差が出るのかを比較したいので、2通りのコードを紹介します。
terraform.tflocals.tfは以下のコードを共通で利用します。

terraform.tf
# providerの定義
provider "aws" {
  region = "ap-northeast-1"
}

terraform {
  required_version = ">=1.0"
  required_providers {
    awscc = {
      source  = "hashicorp/awscc"
      version = "0.45.0"
    }
  }
}

# 実行するAWSアカウント情報取得用
data "aws_caller_identity" "self" {}

# 実行するリージョン情報取得用
data "aws_region" "self" {}
locals.tf
locals {
    s3_bucket = {
        temp-bucket-a = {
            tags = {
                name = "temp-bucket-a"
                temp-a = "true"
            }
        }
        temp-bucket-b = {
            tags = {
                name = "temp-bucket-b"
                temp-b = "true"
            }
        }
    }
}

4-2. can関数を使わない場合

キーの異なるタグを付与する場合は、resourceを2つに分けて作成する必要があるかと思います。

s3.tf
resource "aws_s3_bucket" "temp-a" {
    for_each = local.s3_bucket.temp-bucket-a
    bucket = "temp-bucket-a-${data.aws_caller_identity.self.account_id}"
    tags = {
        Name = each.value.name
        TempA = each.value.temp-a
    }
}

resource "aws_s3_bucket" "temp-b" {
    for_each = local.s3_bucket.temp-bucket-b
    bucket = "temp-bucket-b-${data.aws_caller_identity.self.account_id}"
    tags = {
        Name = each.value.name
        TempB = each.value.temp-b
    }
}

上記の書き方でも問題なく実行はできますが、ほとんど同じコードを複数書く必要があるのであまりスマートなコードとは言えないような気がします。
そこでcan関数の出番です。

4-3. can関数を使う場合

can関数を使うことで、タグのキーが異なっていてもresource "aws_s3_bucket"を一つ書いておけば、複数のバケットが作成できるようになります。
以下のようにコードを書き換えていきます。

s3.tf
resource "aws_s3_bucket" "this" {
    for_each = local.s3_bucket
    bucket = "${each.key}-${data.aws_caller_identity.self.account_id}"
    tags = {
        Name = each.value.tags.name
        TempA = can(each.value.tags.temp-a) ? each.value.tags.temp-a : null
        TempB = can(each.value.tags.temp-b) ? each.value.tags.temp-b : null
    }
}

resource "aws_s3_bucket"の記載が一つで済み、行数が格段に少なくなりました。
can関数を利用している箇所について、以下の通り簡単に解説します。

TempA = can(each.value.tags.temp-a) ? each.value.tags.temp-a : null
TempB = can(each.value.tags.temp-b) ? each.value.tags.temp-b : null

Local ValuesにTempAもしくはTempBのキーが存在するかどうかをcan関数の部分で確認しています。
AのS3バケットではtemp-aという変数を定義しているのでその値を利用し、temp-bという変数は定義していないためnullを使用します。
BのS3バケットについてはAと逆の結果となります。

4-4. AWSマネジメントコンソールに展開

4-3のコードで実際にterraform applyしてみます。

PS C:\terraform\dev\s3_qiita> terraform apply
data.aws_region.self: Reading...
data.aws_caller_identity.self: Reading...
data.aws_region.self: Read complete after 0s [id=ap-northeast-1]
data.aws_caller_identity.self: Read complete after 0s [id={accountid}]

Terraform used the selected providers to generate the following execution plan. Resource actions are  
indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_s3_bucket.this["temp-bucket-a"] will be created
  + resource "aws_s3_bucket" "this" {
      + acceleration_status         = (known after apply)
      + acl                         = (known after apply)
      + arn                         = (known after apply)
      + bucket                      = "temp-bucket-a-{accountid}"
      + bucket_domain_name          = (known after apply)
      + bucket_prefix               = (known after apply)
      + bucket_regional_domain_name = (known after apply)
      + force_destroy               = false
      + hosted_zone_id              = (known after apply)
      + id                          = (known after apply)
      + object_lock_enabled         = (known after apply)
      + policy                      = (known after apply)
      + region                      = (known after apply)
      + request_payer               = (known after apply)
      + tags                        = {
          + "Name"  = "temp-bucket-a"
          + "TempA" = "true"
        }
      + tags_all                    = {
          + "Name"  = "temp-bucket-a"
          + "TempA" = "true"
        }
~~~中略~~~

  # aws_s3_bucket.this["temp-bucket-b"] will be created
  + resource "aws_s3_bucket" "this" {
      + acceleration_status         = (known after apply)
      + acl                         = (known after apply)
      + arn                         = (known after apply)
      + bucket                      = "temp-bucket-b-{accountid}"
      + bucket_domain_name          = (known after apply)
      + bucket_prefix               = (known after apply)
      + bucket_regional_domain_name = (known after apply)
      + force_destroy               = false
      + hosted_zone_id              = (known after apply)
      + id                          = (known after apply)
      + object_lock_enabled         = (known after apply)
      + policy                      = (known after apply)
      + region                      = (known after apply)
      + request_payer               = (known after apply)
      + tags                        = {
          + "Name"  = "temp-bucket-b"
          + "TempB" = "true"
        }
      + tags_all                    = {
          + "Name"  = "temp-bucket-b"
          + "TempB" = "true"
        }
~~~中略~~~

Plan: 2 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_s3_bucket.this["temp-bucket-b"]: Creating...
aws_s3_bucket.this["temp-bucket-a"]: Creating...
aws_s3_bucket.this["temp-bucket-b"]: Creation complete after 1s [id=temp-bucket-b-{accountid}]
aws_s3_bucket.this["temp-bucket-a"]: Creation complete after 1s [id=temp-bucket-a-{accountid}]

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

問題なくコマンドが完了しました。
AWSコンソールの方も見てみましょう。
キャプチャ01.JPG
Aのバケット
キャプチャ02.JPG
Bのバケット
キャプチャ03.JPG

バケットが2つ作成され、タグもTerraformで設定した通りにそれぞれのバケットごとにキー値が設定できていることが確認できました。

5. おわりに

今回はcan関数を利用してコーディングする方法について記事にしてみました。
コードの内容によって出力結果を分岐させる方法はcan関数以外にもたくさんあると思うので、もしよりよい方法などあれば教えていただければと思います。
1つの方法としてこちらの記事が参考になれば幸いです。

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3