linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
Intro
Lessons
Footer
linuxlab-TutorialsPricingAboutPrivacy & cookies
Copyright © 2026 LinuxLab. All rights reserved.
linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
  • Introduction
  • Lessons
  • How it works
  • Knowledge base
  • Cheat sheet
  • Capstone
  • Interview prep
home/terraform/lessons/tf-garden-05-rotten-module

lesson ── terraform-garden ── ~20 мин ── 4 шагов

Troubleshooting Garden: the module went stale, the provider got upgraded

The root module was upgraded to AWS provider v5. The child module legacy-bucket/ is written in v3 style: acl, versioning {}, and server_side_encryption_* all sit inside the aws_s3_bucket resource. In v4 those inline blocks were removed, and everything became separate resources.

terraform init will fail on a version-constraint conflict (>= 3.0, < 4.0 and ~> 5.0 are incompatible). Even if you resolve that, plan will fail on the inline blocks.

Decide: either rewrite the module in modern style, or explicitly pin the old provider for the module. The first path is the right one.

▶ интерактивный sandbox

Поднимется пара контейнеров: terraform 1.9 и localstack 3.8 в одной сети. В браузере откроется терминал, можно сразу terraform init. Каждый шаг проверяется автоматически. TTL 45 минут, без регистрации.

запустить sandbox →

stack ── terraform · localstack · 1 GB RAM · самоуничтожается через 45 мин простоя

Шаги

  1. 01

    Run init, catch the version conflict

    bash
    cd /home/student/tf-garden
    terraform init 2>&1 | tee /tmp/init.log

    The log will contain a message like:

    Error: Failed to query available provider packages
    ...
    no available releases match the given constraints
    >= 3.0, < 4.0, ~> 5.0

    Root asks for v5, the child for v3. These ranges do not overlap.

    ✓ The conflict is visible. Now modernize the module.

  2. 02

    Allow the current AWS provider in the module

    Change the version range in the module so it accepts v5+:

    bash
    sed -i 's|version = ">= 3.0, < 4.0"|version = ">= 5.0"|' modules/legacy-bucket/main.tf

    After that, init should pass:

    bash
    terraform init -upgrade 2>&1 | tee /tmp/init2.log

    But plan will still fail: the inline blocks acl, versioning {}, and server_side_encryption_* do not exist in v5.

    ✓ The version constraint is updated. init passes, but the HCL is still legacy.

  3. 03

    Rewrite the module for provider v5

    Replace the contents of modules/legacy-bucket/main.tf with the modern style:

    bash
    cat > modules/legacy-bucket/main.tf <<'EOF'
    terraform {
      required_providers {
        aws = {
          source  = "hashicorp/aws"
          version = ">= 5.0"
        }
      }
    }
    variable "name" {
      type = string
    }
    resource "aws_s3_bucket" "this" {
      bucket = var.name
    }
    resource "aws_s3_bucket_ownership_controls" "this" {
      bucket = aws_s3_bucket.this.id
      rule {
        object_ownership = "BucketOwnerEnforced"
      }
    }
    resource "aws_s3_bucket_versioning" "this" {
      bucket = aws_s3_bucket.this.id
      versioning_configuration {
        status = "Enabled"
      }
    }
    resource "aws_s3_bucket_server_side_encryption_configuration" "this" {
      bucket = aws_s3_bucket.this.id
      rule {
        apply_server_side_encryption_by_default {
          sse_algorithm = "AES256"
        }
      }
    }
    output "arn" {
      value = aws_s3_bucket.this.arn
    }
    EOF

    The main difference: instead of acl = "private", you use aws_s3_bucket_ownership_controls with BucketOwnerEnforced (in v4+, ACLs are officially deprecated). versioning and encryption are separate resources.

    More on splitting things out in tf-refactor-patterns and tf-resource-block.

    подсказка

    If you leave even one inline block (versioning {} inside aws_s3_bucket), there will be no plan. v5 does not accept them at all.

    ✓ The module is rewritten for v5. Now run plan.

  4. 04

    Plan and apply: the module works

    bash
    cd /home/student/tf-garden
    terraform init -upgrade
    terraform plan -out=plan.tfplan
    terraform apply plan.tfplan

    Plan should show "Plan: 4 to add" (bucket + ownership + versioning

    • encryption). Apply will pass with no errors, and the bucket will be in state at the address module.logs.aws_s3_bucket.this.

    ✓ The module is modernized, the resources are in state. The pipeline is green.

    The same thing on OpenTofu

    OpenTofu also supports AWS provider v5. In practice it is the same HashiCorp binary under a different license, not rebuilt: they reuse the official provider releases. So this module migration is valid for both terraform 1.9 and tofu 1.8+. See tf-opentofu-parity for the full list of differences.

    • → Refactoring patterns
    • → Module versioning
    • → OpenTofu parity

Что ты узнал

A major provider version breaks inline resource configs: in v4+, aws_s3_bucket is a bare bucket, and everything else (acl, versioning, encryption, lifecycle, public access block) becomes a separate resource. This is a pattern across the whole provider, not a one-off break.

команды

  • terraform init -upgradere-read modules and providers; needed after you change version in required_providers
  • terraform providerssee which providers are requested and from which files
  • terraform state replace-provider OLD NEWswap the provider on existing resources in state without a destroy

концепции

  • · Inline resource configs are the v3 pattern; v4+ requires splitting them across resources
  • · A version constraint in a child module must account for the root provider version
  • · When a module is legacy, either upgrade it or document the pin and its lifetime

← предыдущий

Update: change an attribute, read the diff

следующий →

templatefile: render configs from HCL

Footer
linuxlab-
Copyright © 2026 LinuxLab. All rights reserved.
Tutorials
Pricing
About
Privacy & cookies