Data block containing variables resulting in 'known after apply' behaviour

We have an issue with a module we have written for ECS. Its designed to take in a few variables such as subnet names and cluster name for ECS to use. Within the module, we are using data blocks to get the resources for vpc, subnets, ecs cluster etc.

We have noticed a huge amount of delta changes in a terraform plan with lots of resources requiring replacement due to changes such as vpc_id.

When switching the data blocks to not use passed in variables, it works fine and can determine the values at plan.
Given these variables are just strings or lists, these should be known at plan so I’m not sure what is going on here.

We are slightly confused why this results in the data blocks returning ‘known after apply’ when the variable values are known at plan time.

Terraform version 1.0.1
AWS Provider 3.75.2

Terraform Version

Terraform v1.0.1
on linux_amd64

Your version of Terraform is out of date! The latest version
is 1.2.5. You can update by downloading from Downloads | Terraform by HashiCorp

Terraform Configuration Files

Module call

module "myecstask" {
  source           = "../../modules/ecs_fargate_service"
  name             = "myecstask"
  ecs_cluster_name = "myecsclustername"
  account_name     = "myaccount"
  environment_name = "myenvironment"
  subnets = [
    "MySubnet-A",
    "MySubnet-B"
  ]
}

Module Resources

data aws_vpc vpc {
  filter {
    name   = "tag:Type"
    values = [ "main" ]
  }
}

data aws_subnet_ids service {
  vpc_id = data.aws_vpc.vpc.id
  filter {
    name = "tag:Name"
    values = var.subnets
  }
}

resource "aws_ecs_service" "app" {
  name                  = var.name
  cluster               = data.aws_ecs_cluster.main.id
  task_definition       = "${aws_ecs_task_definition.app.family}:${aws_ecs_task_definition.app.revision}"
  desired_count         = var.instance_count

  network_configuration {
     subnets = data.aws_subnet_ids.service.ids
     assign_public_ip = false
     security_groups = [
         aws_security_group.app.id
     ]
  }
  .
  .
  .
}

Expected Behavior

No Changes

Actual Behavior

# module.myecs.data.aws_ecs_cluster.main will be read during apply
  # (config refers to values not yet known)
 <= data "aws_ecs_cluster" "main"  {
      ~ arn                                  = "arn:aws:ecs:eu-west-2:acc_id:cluster/myecsclustername" -> (known after apply)
      ~ id                                   = "arn:aws:ecs:eu-west-2:acc_id:cluster/myecsclustername" -> (known after apply)
      ~ pending_tasks_count                  = 0 -> (known after apply)
      ~ registered_container_instances_count = 0 -> (known after apply)
      ~ running_tasks_count                  = 3 -> (known after apply)
      ~ setting                              = [
          - {
              - name  = "containerInsights"
              - value = "disabled"
            },
        ] -> (known after apply)
      ~ status                               = "ACTIVE" -> (known after apply)
        # (1 unchanged attribute hidden)
    }

  # module.myecs.data.aws_subnet_ids.service will be read during apply
  # (config refers to values not yet known)
 <= data "aws_subnet_ids" "service"  {
      ~ id     = "vpc-zzzzzzzzzzzzzzzz" -> (known after apply)
      ~ ids    = [
          - "subnet-aaaaaaaaaaaaaaaaa",
          - "subnet-bbbbbbbbbbbbbbbbb",
        ] -> (known after apply)
      + tags   = (known after apply)
      ~ vpc_id = "vpc-zzzzzzzzzzzzzzzz" -> (known after apply)

      - filter {
          - name   = "tag:Name" -> null
          - values = [
              - "MySubnet-a",
              - "MySubnet-b",
            ] -> null
        }
      + filter {
          + name   = "tag:Name"
          + values = [
              + "subnet-aaaaaaaaaaaaaaaaa",
              + "subnet-bbbbbbbbbbbbbbbbb",
            ]
        }
    }

  # module.myecs.data.aws_vpc.vpc will be read during apply
  # (config refers to values not yet known)
 <= data "aws_vpc" "vpc"  {
      ~ arn                     = "arn:aws:ec2:eu-west-2:acc_id:vpc/vpc-zzzzzzzzzzzzzzzz" -> (known after apply)
      ~ cidr_block              = "10.10.0.0/16" -> (known after apply)
      ~ cidr_block_associations = [
          - {
              - association_id = "vpc-cidr-assoc-ccccccccccccccccccc"
              - cidr_block     = "10.0.0.0/16"
              - state          = "associated"
            },
        ] -> (known after apply)
      ~ default                 = false -> (known after apply)
      ~ dhcp_options_id         = "dopt-ddddddddddddddddd" -> (known after apply)
      ~ enable_dns_hostnames    = true -> (known after apply)
      ~ enable_dns_support      = true -> (known after apply)
      ~ id                      = "vpc-zzzzzzzzzzzzzzzz" -> (known after apply)
      ~ instance_tenancy        = "default" -> (known after apply)
      + ipv6_association_id     = (known after apply)
      + ipv6_cidr_block         = (known after apply)
      ~ main_route_table_id     = "rtb-eeeeeeeeeeeeeeeee" -> (known after apply)
      ~ owner_id                = "acc_id" -> (known after apply)
      + state                   = (known after apply)
      ~ tags                    = {
          - "Name" = "shared"
          - "Type" = "main"
        } -> (known after apply)

        # (1 unchanged block hidden)
    }

  # module.myecs.aws_ecs_service.app must be replaced
-/+ resource "aws_ecs_service" "app" {
      ~ cluster                            = "arn:aws:ecs:eu-west-2:acc_id:cluster/myecsclustername" -> (known after apply) # forces replacement
      - health_check_grace_period_seconds  = 0 -> null
      ~ iam_role                           = "aws-service-role" -> (known after apply)
      ~ id                                 = "arn:aws:ecs:eu-west-2:acc_id:service/myecsclustername/myecstask" -> (known after apply)
      + launch_type                        = (known after apply)
        name                               = "myecstask"
      ~ platform_version                   = "LATEST" -> (known after apply)
      - propagate_tags                     = "NONE" -> null
      - tags                               = {} -> null
      ~ tags_all                           = {} -> (known after apply)
      ~ task_definition                    = "myecstask:12" -> (known after apply)
      + wait_for_steady_state              = false
        # (6 unchanged attributes hidden)


      - deployment_circuit_breaker {
          - enable   = false -> null
          - rollback = false -> null
        }


      - load_balancer { # forces replacement
          - container_name   = "myecstask" -> null
          - container_port   = 45679 -> null
          - target_group_arn = "arn:aws:elasticloadbalancing:eu-west-2:acc_id:targetgroup/myecstask/7dfa44c1e48c93c6" -> null
        }
      + load_balancer { # forces replacement
          + container_name   = "myecstask"
          + container_port   = 45679
          + target_group_arn = (known after apply)
        }

      ~ network_configuration {
          ~ security_groups  = [
              - "sg-ggggggggggggggggggg",
            ] -> (known after apply)
          ~ subnets          = [
              - "subnet-aaaaaaaaaaaaaaaaa",
              - "subnet-bbbbbbbbbbbbbbbbb",
            ] -> (known after apply)
            # (1 unchanged attribute hidden)
        }

      - timeouts {}
        # (2 unchanged blocks hidden)
    }

  # module.myecs.aws_ecs_task_definition.app must be replaced
-/+ resource "aws_ecs_task_definition" "app" {
      ~ arn                      = "arn:aws:ecs:eu-west-2:acc_id:task-definition/myecstask:12" -> (known after apply)
      ~ container_definitions    = jsonencode(
          ~ [
              ~ {
                  - mountPoints      = [] -> null
                  ~ portMappings     = [
                      ~ {
                          - protocol      = "tcp" -> null
                            # (2 unchanged elements hidden)
                        },
                    ]
                  - volumesFrom      = [] -> null
                    # (8 unchanged elements hidden)
                },
            ]
        )
      ~ id                       = "myecstask" -> (known after apply)
      ~ revision                 = 12 -> (known after apply)
      + skip_destroy             = false
      - tags                     = {} -> null
      ~ tags_all                 = {} -> (known after apply)
      ~ task_role_arn            = "arn:aws:iam::acc_id:role/ecsTaskExecutionRole" -> (known after apply) # forces replacement
        # (6 unchanged attributes hidden)
    }

  # module.myecs.aws_security_group.app must be replaced
-/+ resource "aws_security_group" "app" {
      ~ arn                    = "arn:aws:ec2:eu-west-2:acc_id:security-group/sg-ggggggggggggggggggg" -> (known after apply)
      ~ id                     = "sg-ggggggggggggggggggg" -> (known after apply)
        name                   = "myecstask"
      + name_prefix            = (known after apply)
      ~ owner_id               = "acc_id" -> (known after apply)
        tags                   = {
            "Name" = "myecstask"
        }
      ~ vpc_id                 = "vpc-zzzzzzzzzzzzzzzz" -> (known after apply) # forces replacement
        # (5 unchanged attributes hidden)
    }

Hi @spxglenn,

The configuration shown contains the data sources for aws_vpc and aws_subnet_ids, however the plan output is for an entirely different data source; aws_ecs_cluster. Can you show how the aws_ecs_cluster data source configuration is derived?

Also, if the configuration for data sources like the aws_vpc are entirely static like shown, they will not be deferred until apply, so there is something else going on in the configuration which is not present in the example.

Issue was found to be a delta change from a different module. This ecs module was set to depend on another changed resource hence the plan wanting to rebuild the resources.

Glad you sorted it out. I would also suggest updating your version of Terraform to a recent release (or at least a final patch release) to avoid other issues. Recent releases improve the error message in this particular situation, indicating that the data sources "depends on a resource or a module with changes pending" rather than "refers to values not yet known".