hashicorp / terraform-provider-awscc

Terraform AWS Cloud Control provider
https://registry.terraform.io/providers/hashicorp/awscc/latest/docs
Mozilla Public License 2.0
260 stars 118 forks source link

`awscc_ecs_task_definition` and `awscc_ecs_task_definition` forces recreate when no parameters have changed #1448

Open lemiliomoreno opened 8 months ago

lemiliomoreno commented 8 months ago

Community Note

Terraform CLI and Terraform AWS Cloud Control Provider Version

Terraform v1.7.4
on windows_amd64
+ provider registry.terraform.io/hashicorp/aws v5.37.0
+ provider registry.terraform.io/hashicorp/awscc v0.71.0
+ provider registry.terraform.io/hashicorp/random v3.6.0

Affected Resource(s)

Terraform Configuration Files

Please include all Terraform configurations required to reproduce the bug. Bug reports without a functional reproduction may be closed without investigation.

terraform {
  required_version = ">= 1.7.4"

  backend "s3" {
    bucket = "xxx"
    key    = "xxx"
    region = "us-west-2"
  }

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 5.37.0"
    }

    awscc = {
      source  = "hashicorp/awscc"
      version = ">= 0.71.0"
    }
  }
}

Debug Output

Panic Output

Expected Behavior

awscc_ecs_task_definition and awscc_ecs_task_definition should not be replaced/updated when no change has been made to the code.

Actual Behavior

awscc_ecs_task_definition and awscc_ecs_task_definition are being updated/replaced when no change has been made to the code.

Steps to Reproduce

This is my ecs_task and ecs_service definitions:

resource "awscc_ecs_task_definition" "db_task_definition" {
  family             = "${var.environment}-${var.application}-db-task-definition"
  cpu                = 256
  memory             = 512
  network_mode       = "awsvpc"
  execution_role_arn = aws_iam_role.db_execution_role.arn
  task_role_arn      = aws_iam_role.db_task_role.arn

  requires_compatibilities = [
    "FARGATE",
  ]

  volumes = [{
    configured_at_launch = true
    name                 = "${var.environment}-${var.application}-db-volume"
  }]

  container_definitions = [
    {
      name  = "${var.environment}-${var.application}-db"
      image = "postgres:15"
      port_mappings = [{
        name           = "${var.environment}-${var.application}-db-port"
        container_port = 5432
        protocol       = "tcp"
      }]

      log_configuration = {
        log_driver = "awslogs"
        options = {
          awslogs-group         = "${var.environment}-${var.application}-db-log-group"
          awslogs-region        = "${var.aws_region}"
          awslogs-stream-prefix = "ecs"
        }
      }

      environment = [
        {
          name  = "POSTGRES_DB"
          value = "${data.aws_ssm_parameter.db_name.value}"
        },
        {
          name  = "POSTGRES_PASSWORD"
          value = "${data.aws_ssm_parameter.db_password.value}"
        },
        {
          name  = "POSTGRES_USER"
          value = "${data.aws_ssm_parameter.db_username.value}"
        },
      ]

      mount_points = [
        {
          source_volume  = "${var.environment}-${var.application}-db-volume",
          container_path = "/var/lib/postgresql",
          read_only      = false
        }
      ]
    }
  ]
}

resource "awscc_ecs_service" "db_service" {
  service_name    = "${var.environment}-${var.application}-db-service"
  cluster         = aws_ecs_cluster.app_cluster.id
  task_definition = awscc_ecs_task_definition.db_task_definition.id
  desired_count   = 1
  launch_type     = "FARGATE"

  network_configuration = {
    awsvpc_configuration = {
      assign_public_ip = "DISABLED"
      security_groups = [
        aws_security_group.db_security_group.id
      ]
      subnets = [
        var.private_subnet_1_id,
        var.private_subnet_2_id,
      ]
    }
  }

  service_connect_configuration = {
    enabled   = true
    namespace = aws_service_discovery_http_namespace.app_namespace.arn

    services = [
      {
        port_name      = "${var.environment}-${var.application}-db-port",
        discovery_name = "${var.environment}-${var.application}-db-service"
        client_aliases = [
          {
            port = 5432
          }
        ]
      }
    ]

    log_configuration = {
      log_driver = "awslogs"
      options = {
        "awslogs-group" : "${var.environment}-${var.application}-db-log-group",
        "awslogs-region" : "${var.aws_region}",
        "awslogs-stream-prefix" : "ecs",
      }
    }
  }

  volume_configurations = [
    {
      name = "${var.environment}-${var.application}-db-volume"
      managed_ebs_volume = {
        role_arn        = aws_iam_role.db_task_role.arn
        filesystem_type = "ext4"
        size_in_gi_b    = 30
        volume_type     = "gp3"
      }
    }
  ]
}

When I run terraform plan I get the following output even no chage has been made:

...
# module.ecs.awscc_ecs_service.db_service will be updated in-place
  ~ resource "awscc_ecs_service" "db_service" {
      + capacity_provider_strategy        = (known after apply)
      + health_check_grace_period_seconds = (known after apply)
        id                                = "arn:aws:ecs:us-west-2:123456789123:service/cluster/db|arn:aws:ecs:us-west-2:123456789123:cluster/cluster"
      + load_balancers                    = (known after apply)
        name                              = "db-service"
      ~ network_configuration             = {
          ~ awsvpc_configuration = {
              ~ subnets          = [
                  - "subnet-0348fa76fe2bd34e2",
                    "subnet-0f9406f55ca370a8f",
                  + "subnet-0348fa76fe2bd34e2",
                ]
                # (2 unchanged attributes hidden)
            }
        }
      + placement_constraints             = (known after apply)
      + placement_strategies              = (known after apply)
      + propagate_tags                    = (known after apply)
      ~ service_connect_configuration     = {
          ~ log_configuration = {
              + secret_options = (known after apply)
                # (2 unchanged attributes hidden)
            }
          ~ services          = [
              ~ {
                  ~ client_aliases        = [
                      ~ {
                          + dns_name = (known after apply)
                            # (1 unchanged attribute hidden)
                        },
                    ]
                  + ingress_port_override = (known after apply)
                  + timeout               = (known after apply)
                  + tls                   = (known after apply)
                    # (2 unchanged attributes hidden)
                },
            ]
            # (2 unchanged attributes hidden)
        }
      + service_registries                = (known after apply)
      + tags                              = (known after apply)
      ~ task_definition                   = "arn:aws:ecs:us-west-2:123456789123:task-definition/db-task-definition:28" -> (known after apply)   
      ~ volume_configurations             = [
          ~ {
              ~ managed_ebs_volume = {
                  + encrypted          = (known after apply)
                  + iops               = (known after apply)
                  + kms_key_id         = (known after apply)
                  + snapshot_id        = (known after apply)
                  + tag_specifications = (known after apply)
                  + throughput         = (known after apply)
                    # (4 unchanged attributes hidden)
                }
                name               = "db-volume"
            },
        ]
        # (12 unchanged attributes hidden)
    }

  # module.ecs.awscc_ecs_task_definition.db_task_definition must be replaced
-/+ resource "awscc_ecs_task_definition" "db_task_definition" {
      ~ container_definitions    = (sensitive value) # forces replacement
      + ephemeral_storage        = (known after apply) # forces replacement
      ~ id                       = "arn:aws:ecs:us-west-2:123456789123:task-definition/db-task-definition:28" -> (known after apply)
      + inference_accelerators   = (known after apply) # forces replacement
      + ipc_mode                 = (known after apply) # forces replacement
      + pid_mode                 = (known after apply) # forces replacement
      + placement_constraints    = (known after apply) # forces replacement
      + proxy_configuration      = (known after apply) # forces replacement
      + runtime_platform         = (known after apply) # forces replacement
      + tags                     = (known after apply)
      ~ task_definition_arn      = "arn:aws:ecs:us-west-2:123456789123:task-definition/db-task-definition:28" -> (known after apply)
      ~ volumes                  = [
          - { # forces replacement
              - configured_at_launch = true -> null
              - name                 = "db-volume" -> null
            },
          + { # forces replacement
              + configured_at_launch        = true
              + docker_volume_configuration = (known after apply)
              + efs_volume_configuration    = (known after apply)
              + host                        = (known after apply)
              + name                        = "db-volume"
            },
        ]
        # (7 unchanged attributes hidden)
    }

Plan: 1 to add, 5 to change, 1 to destroy.

I've found that if I change some values same as the one's returned by the provider, will not detect a change, i.e.:

~ subnets          = [
    - "subnet-0348fa76fe2bd34e2",
      "subnet-0f9406f55ca370a8f",
    + "subnet-0348fa76fe2bd34e2",
  ]

If I just replace the subnets in the correct order, it won't show as change.

This still happens in https://github.com/hashicorp/terraform-provider-aws/issues/23726, https://github.com/hashicorp/terraform-provider-aws/issues/11526.

As this happens with ECS Task Definition, it's the same for ECS Service as it detects a change in Task Definition.

This might be a limitation with Cloud Control API, as it seems that there is not UpdateTask, only RegisterTask actions for the API.

Important Factoids

References

lemiliomoreno commented 8 months ago

I also tried to set up the default values stored in the state file but still forces replacement

resource "awscc_ecs_task_definition" "db_task_definition" {
  container_definitions = [
    {
      name  = "${var.environment}-${var.application}-db"
      image = "postgres:15"
      port_mappings = [{
        name           = "${var.environment}-${var.application}-db-port"
        container_port = 5432
        protocol       = "tcp"
      }]

      log_configuration = {
        log_driver = "awslogs"
        options = {
          awslogs-group         = "${var.environment}-${var.application}-db-log-group"
          awslogs-region        = "${var.aws_region}"
          awslogs-stream-prefix = "ecs"
        }
      }

      environment = [
        {
          name  = "POSTGRES_DB"
          value = "${data.aws_ssm_parameter.db_name.value}"
        },
        {
          name  = "POSTGRES_PASSWORD"
          value = "${data.aws_ssm_parameter.db_password.value}"
        },
        {
          name  = "POSTGRES_USER"
          value = "${data.aws_ssm_parameter.db_username.value}"
        },
      ]

      mount_points = [
        {
          source_volume  = "${var.environment}-${var.application}-db-volume",
          container_path = "/var/lib/postgresql",
          read_only      = false
        }
      ]
    }
  ]

  cpu = 256

  ephemeral_storage = {
    size_in_gi_b = 30
  }

  execution_role_arn     = aws_iam_role.db_execution_role.arn
  family                 = "${var.environment}-${var.application}-db-task-definition"
  inference_accelerators = null
  ipc_mode               = null
  memory                 = 512
  network_mode           = "awsvpc"
  pid_mode               = null
  placement_constraints  = null
  proxy_configuration    = null

  requires_compatibilities = [
    "FARGATE",
  ]

  runtime_platform = {
    cpu_architecture        = "X86_64"
    operating_system_family = "LINUX"
  }

  tags          = null
  task_role_arn = aws_iam_role.db_task_role.arn

  volumes = [
    {
      configured_at_launch        = true
      docker_volume_configuration = null
      efs_volume_configuration    = null
      host                        = null
      name                        = "${var.environment}-${var.application}-db-volume"
    }
  ]
}
wellsiau-aws commented 5 months ago

I was able to replicate the behavior on resource awscc_ecs_task_definition.

It seemed that these attributes that forces replacement are declared in the Terraform state file as null. On contrary, CCAPI GetResource is returning an empty value or empty list.

I suspect this is related to #1139

wellsiau-aws commented 5 months ago

Problem with resource awscc_ecs_service also seemed to stem from the same behavior as described in #1139