【Terraform】AWS S3リファクタリング

# はじめに
Qiitaの上手な書き方がわからないpiromoです。TerraformもAWSもS3も「？？？」ですが、仕事でS3のリファクタリングをするというタスクがあったので、以下備忘録です。今回のタスクで0.000001mmだけTerraformが分かった気がします。嘘です。


# 概要
Terraform のAWSプロバイダーのバージョンをすでにv4以上に引き上げている環境で、以下のようなエラーが出力されていました。しばらくそのままにしていましたが、将来的にS3のリファクタリングは必須なので、今回泣きながらリファクタリングを進めました。

```
╷
│ Error: Argument is deprecated
│ 
│   with aws_s3_bucket.this,
│   on main.tf line 50, in resource "aws_s3_bucket" "log":
│   19: resource "aws_s3_bucket" "log" {
│ 
│ Use the aws_s3_bucket_lifecycle_configuration resource instead
╵
```


# 背景
・Terraform AWS プロバイダーのバージョン 4.0以降、`aws_s3_bucket`リソースが過負荷となりユーザが単一の構成で管理することが難しくなってきました。その負荷を軽減させるために、新しいS3バケット管理リソースを導入したそうです。

・リファクタリングを行わない場合は、`terraform plan`時に予期しないエラーや警告が出力される可能性があります。

・状態の不一致やデータ損失なしで新しいリソースタイプに移行するには、`terraform import`ステートメントを使用して状態の新しいタイプを追跡する必要があります。

https://www.hashicorp.com/blog/terraform-aws-provider-4-0-refactors-s3-bucket-resource

# importブロックを使用したS3リファクタリング
S3リソースをリファクタリングする際に、リソースが再作成されてしまいます。そのため公式の通りimportブロックを利用することで、リソースの再作成が行わられずにコードの修正のみを実施できました。

## 実施手順
①S3のリファクタリングとimportブロックの記載、反映
②importブロックのみを削除して再度反映

## `import`ブロック使用時の注意
:::note warn
`import`ブロックは、ルートモジュールに記載する必要があります。
:::

※**ルートモジュール**とは、Terraform CLIを実行する作業ディレクトリのことを指します。


## `-refresh-only`コマンド
`import`ブロックと似たような動作をするものとして`-refresh-only`コマンドの利用も試しましたが、`-refresh-only`コマンドでの反映後に再度通常の反映をするとresouceの再作成が行われてしまいました。
`-refresh-only`コマンドはコードの変更のみで、モジュールとの整合性は合わせてくれないんですね（？）
勉強不足なのでこのあたりは勉強次第書き直します。


## リファクタリング前
```ruby:main.tf
resource "aws_s3_bucket" "log" {
  bucket = "Terraform-Test-log"
  policy = data.aws_iam_policy_document.s3.json
  lifecycle_rule {
    enabled = true
    transition {
      days          = 0
      storage_class = "INTELLIGENT_TIERING"
    }
  }
  versioning {
    enabled = true
  }
}
```


## リファクタリング後


```ruby:main.tf
resources "aws_s3_bucket" "log" {
  bucket = "Terraform-Test-log"
}

resource "aws_s3_bucket_policy" "log" {
  bucket = aws_s3_bucket.log.id
  policy = data.aws_iam_policy_document.s3.json
}

resource "aws_s3_bucket_lifecycle_configuration" "log" {
  bucket = aws_s3_bucket.log.id
  rule {
    id = "rule1" #一意のrule_id（ルール名）を入れる必要あり
    status = "Enabled"
    transition {
      days          = 0
      storage_class = "INTELLIGENT_TIERING"
    }
  }
}

resource "aws_s3_bucket_versioning" "log" {
  bucket = aws_s3_bucket.log.id
  versioning_configuration {
    status = "Enabled"
  }
}

import {
    id = "Terraform-Test-log"  #リファクタリング対象のバケット名を記載
    to = aws_s3_bucket.log   #リファクタリング対象のresourceを記載
}

import {
    id = "Terraform-Test-log"  #リファクタリング対象のバケット名を記載
    to = aws_s3_bucket_policy.log   #リファクタリング対象のresourceを記載
}

import {
    id = "Terraform-Test-log"  #リファクタリング対象のバケット名を記載
    to = aws_s3_bucket_lifecycle_configuration.log   #リファクタリング対象のresourceを記載
}

import {
    id = "Terraform-Test-log"  #リファクタリング対象のバケット名を記載
    to = aws_s3_bucket_versioning.log   #リファクタリング対象のresourceを記載
}
```

この後、`import`ブロックのみを削除して再度反映します。






# importブロックを使用した複数のS3リファクタリング


## 概要
今回はresourceで管理されている複数のS3をリファクタリングする必要がありました。
さらにS3モジュールはルートモジュールに、reourcesは別ディレクトリのファイルにあり、記載方法を変える必要がありました。

## リファクタリング前
- `main.tf`（ルートモジュール）
```ruby:main.tf
module "paromo"
  source     = "./modules/check"
  name       = "paromo"
  account_id = 123456789876

module "piromo"
  source     = "./modules/check"
  name       = "piromo"
  account_id = 223456789876
```


- `./modules/check/variables.tf`（子モジュール）
```ruby:./modules/check/variables.tf
variable "name" {
}

variable "account_id" {
}
```


- `./modules/check/S3.tf`（子モジュール）
```ruby:./modules/check/S3.tf
locals {
  bucket_name = "Terraform-Test-log"-${var.name}
}

###### 省略 ######

resource "aws_s3_bucket" "log" {
  bucket = local.bucket_name
  policy = data.aws_iam_policy_document.s3.json
  lifecycle_rule {
    enabled = true
    transition {
      days          = 0
      storage_class = "INTELLIGENT_TIERING"
    }
  }
  versioning {
    enabled = true
  }
}
```



## リファクタリング後


- `./modules/check/S3.tf`（子モジュール）
```ruby:./modules/check/S3.tf
module "paromo"
  source     = "./modules/check"
  name       = "paromo"
  account_id = 123456789876

module "piromo"
  source     = "./modules/check"
  name       = "piromo"
  account_id = 223456789876

############################## ここから ##############################

import {
    id = "paromo"  #リファクタリング対象のバケット名を記載
    to = module.paromo.aws_s3_bucket.log   #リファクタリング対象のresourceを記載
}

import {
    id = "paromo"   #リファクタリング対象のバケット名を記載
    to = module.paromo.aws_s3_bucket_policy.log   #リファクタリング対象のresourceを記載
}

import {
    id = "paromo"   #リファクタリング対象のバケット名を記載
    to = module.paromo.aws_s3_bucket_lifecycle_configuration.log   #リファクタリング対象のresourceを記載
}

import {
    id = "paromo"  #リファクタリング対象のバケット名を記載
    to = module.paromo.aws_s3_bucket_versioning.log   #リファクタリング対象のresourceを記載
}

import {
    id = "piromo"  #リファクタリング対象のバケット名を記載
    to = module.piromo.aws_s3_bucket.log   #リファクタリング対象のresourceを記載
}

import {
    id = "piromo"   #リファクタリング対象のバケット名を記載
    to = module.piromo.aws_s3_bucket_policy.log   #リファクタリング対象のresourceを記載
}

import {
    id = "piromo"   #リファクタリング対象のバケット名を記載
    to = module.piromo.aws_s3_bucket_lifecycle_configuration.log   #リファクタリング対象のresourceを記載
}

import {
    id = "piromo"  #リファクタリング対象のバケット名を記載
    to = module.piromo.aws_s3_bucket_versioning.log   #リファクタリング対象のresourceを記載
}

############################## ここまで ##############################
```
:::note
`import`ブロック内の`id`を工夫することで、脳死で作業できるようにしたかったのですが、以下のような形はできなかったので直接S3バケット名を記載しました。
```
import {
    id = module.paromo.name
    to = module.paromo.aws_s3_bucket.log 
}
```
:::


- `./modules/check/variables.tf`（子モジュール）

```ruby:./modules/check/variables.tf
variable "name" {
}

variable "account_id" {
}
```

- `./modules/S3.tf`（別ディレクトリ）

```ruby:./modules/S3.tf
locals {
  bucket_name = "Terraform-Test-log"-${var.name}
}

###### 省略 ######

############################## ここから ##############################

resources "aws_s3_bucket" "log" {
  bucket = local.id
}

resource "aws_s3_bucket_policy" "log" {
  bucket = aws_s3_bucket.log.id
  policy = data.aws_iam_policy_document.s3.json
}

resource "aws_s3_bucket_lifecycle_configuration" "log" {
  bucket = aws_s3_bucket.log.id
  rule {
    id = "rule1" #一意のrule_id（ルール名）を入れる必要あり
    status = "Enabled"
    transition {
      days          = 0
      storage_class = "INTELLIGENT_TIERING"
    }
  }
}

resource "aws_s3_bucket_versioning" "log" {
  bucket = aws_s3_bucket.log.id
  versioning_configuration {
    status = "Enabled"
  }
}

############################## ここまで ##############################
```

# S3リファクタリングその他
## `aws_s3_bucket_acl`
`acl = "private"`を挿入する場合、`aws_s3_bucket_ownership_controls`を設定する必要があります。
なおリファクタリングの際に`aws_s3_bucket_acl`を削除すると、`aws_s3_bucket_ownership_controls`は変更されずデフォルトの設定のままになります。以下の通り、2023年にデフォルトのACL有効/無効が変更されたので、一つのモジュールを複数のアカウントで使用している場合は、ACLの設定を見直しましょう。

:::note
2023年4月からAmazon S3 で、すべての新しいバケットに対して自動的に S3 パブリックアクセスブロックが有効化、アクセスコントロールリストが無効化されました。
参考リンク：[AWS-NEWS](https://aws.amazon.com/jp/about-aws/whats-new/2022/12/amazon-s3-automatically-enable-block-public-access-disable-access-control-lists-buckets-april-2023/)
:::

```ruby:例
resource "aws_s3_bucket" "example" {
  bucket = "my-tf-example-bucket"
}

resource "aws_s3_bucket_ownership_controls" "example" {
  bucket = aws_s3_bucket.example.id
  rule {
    object_ownership = "BucketOwnerPreferred"
  }
}

resource "aws_s3_bucket_acl" "example" {
  depends_on = [aws_s3_bucket_ownership_controls.example]

  bucket = aws_s3_bucket.example.id
  acl    = "private"
}
```

`acl = "private"`以外のパターンについては、以下のドキュメントを参考にしてください。
参考リンク：[Resource: aws_s3_bucket_acl](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_acl)

## `aws_s3_bucket_replication_configuration`

`# Must have bucket versioning enabled first`となっているので、リファクタリングの際は`depends_on = [aws_s3_bucket_versioning.source]`を挿入する必要があります。

参考リンク：[aws_s3_bucket_replication_configuration](https://registry.terraform.io/providers/hashicorp/aws/5.73.0/docs/resources/s3_bucket_replication_configuration#:~:text=%3D%20aws.central-,%23%20Must%20have%20bucket%20versioning%20enabled%20first,-depends_on%20%3D%20%5B)

```ruby:例
resource "aws_s3_bucket_replication_configuration" "replication" {
  provider = aws.central
  # Must have bucket versioning enabled first
  depends_on = [aws_s3_bucket_versioning.source]

  role   = aws_iam_role.replication.arn
  bucket = aws_s3_bucket.source.id

  ###### 省略 ######
}
```


# 参考
https://www.hashicorp.com/blog/terraform-aws-provider-4-0-refactors-s3-bucket-resource

https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket

https://zenn.dev/tournesol014s/articles/207672f6f3d957