We are facing a dependency issue when we are using data block in source modules of terraform. Example: Consider we have a VPC, Subnet and Cloudwatch - If we are making minor changes (changing retention_days) in Amazon Cloudwatch causes VPC and Subnet to be recreated. In our case VPC is depends on Cloudwatch(since cloud_watch_log_group block in VPC(vpc_flow_log) requires cloudwatch name) and Subnet is depends on VPC (due to vpc id used in Subnet).
So whenever there is a minor change in Cloudwatch, it forces replacement for vpc-flowlog due to which vpc is recreated, which makes subnet also to be recreated.
We are facing this issue in specifically when we are using data block to handle the dependency irrespective of any provider.
Please let me know for any further details on this issue.
If we remove the data block in source modules then it produces expected result. We tried upgrading terraform and provider versions too but nothing solves our problem.
main.tf
module "cloudwatch" {
for_each = var.Amazon_CloudWatch
source = "./source_modules/cloudwatch"
cloud_watch_group_name = each.value["cloud_watch_group_name"]
retention_days = each.value["retention_days"]
}
module "vpc" {
for_each = var.AWS_VPC_Virtual_Private_Cloud
source = "./source_modules/vpc"
vpc_name = each.value["vpc_name"]
data_cloud_watch_name = each.value["data_cloud_watch_name"]
vpc_cidr = each.value["vpc_cidr"]
depends_on = [module.cloudwatch]
}
module "subnet" {
for_each = var.AWS_Subnet
source = "./source_modules/subnet"
subnet_cidr = each.value["subnet_cidr"]
subnet_name = each.value["subnet_name"]
data_vpc_name = each.value["data_vpc_name"]
depends_on=[module.vpc]
}
source_modules/vpc/main.tf
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
tags = {
Name = var.vpc_name
}
}
resource "aws_iam_role" "example" {
name = "example111"
assume_role_policy = data.aws_iam_policy_document.assume_role.json
}
resource "aws_iam_role_policy" "example" {
name = "example111"
role = aws_iam_role.example.id
policy = data.aws_iam_policy_document.example.json
}
resource "aws_flow_log" "example" {
iam_role_arn = aws_iam_role.example.arn
log_destination = data.aws_cloudwatch_log_group.test_clg.arn
traffic_type = "ALL"
vpc_id = aws_vpc.main.id
}
source_modules/vpc/variables.tf
variable "data_cloud_watch_name" {
type = string
}
variable "vpc_name" {
type = string
}
variable "vpc_cidr" {
type = string
}
source_modules/vpc/data.tf
data "aws_cloudwatch_log_group" "test_clg" {
name = var.data_cloud_watch_name
}
data "aws_iam_policy_document" "assume_role" {
statement {
effect = "Allow"
principals {
type = "Service"
identifiers = ["vpc-flow-logs.amazonaws.com"]
}
actions = ["sts:AssumeRole"]
}
}
data "aws_iam_policy_document" "example" {
statement {
effect = "Allow"
actions = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams",
]
resources = ["*"]
}
}
source_modules/subnet/main.tf
resource "aws_subnet" "main" {
vpc_id = data.aws_vpc.vpcid.id
cidr_block = var.subnet_cidr
tags = {
Name = var.subnet_name
}
}
source_modules/subnet/data.tf
data "aws_vpc" "vpcid" {
tags = {
Name=var.data_vpc_name
}
}
source_modules/subnet/variables.tf
variable "subnet_cidr" {
type = string
}
variable "subnet_name" {
type = string
}
variable "data_vpc_name" {
type = string
}
source_modules/cloudwatch/main.tf
resource "aws_cloudwatch_log_group" "test" {
name = var.cloud_watch_group_name
retention_in_days = var.retention_days
}
source_modules/cloudwatch/variables.tf
variable "cloud_watch_group_name" {
type = string
}
variable "retention_days" {
type = number
}
**Terraform Plan Results**
terraform plan
module.cloudwatch["logging_01"].aws_cloudwatch_log_group.test: Refreshing state... [id=testdataissuecwg1211q]
module.vpc["vpc_01"].aws_vpc.main: Refreshing state... [id=vpc-0dba4643e6f19061a]
module.vpc["vpc_01"].aws_iam_role.example: Refreshing state... [id=stackissue1qw]
module.vpc["vpc_01"].aws_iam_role_policy.example: Refreshing state... [id=stackissue1qw:stackissue1qw]
module.vpc["vpc_01"].aws_flow_log.example: Refreshing state... [id=fl-0bf918ffe07281dd4]
module.subnet["subnet_01"].aws_subnet.main: Refreshing state... [id=subnet-0ec569f1c4cb9ef15]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
~ update in-place
-/+ destroy and then create replacement
<= read (data resources)
Terraform will perform the following actions:
# module.cloudwatch["logging_01"].aws_cloudwatch_log_group.test will be updated in-place
~ resource "aws_cloudwatch_log_group" "test" {
id = "testdataissuecwg1211q"
name = "testdataissuecwg1211q"
~ retention_in_days = 30 -> 60
tags = {}
# (4 unchanged attributes hidden)
}
# module.subnet["subnet_01"].data.aws_vpc.vpcid will be read during apply
# (depends on a resource or a module with changes pending)
<= data "aws_vpc" "vpcid" {
+ arn = (known after apply)
+ cidr_block = (known after apply)
+ cidr_block_associations = (known after apply)
+ default = (known after apply)
+ dhcp_options_id = (known after apply)
+ enable_dns_hostnames = (known after apply)
+ enable_dns_support = (known after apply)
+ enable_network_address_usage_metrics = (known after apply)
+ id = (known after apply)
+ instance_tenancy = (known after apply)
+ ipv6_association_id = (known after apply)
+ ipv6_cidr_block = (known after apply)
+ main_route_table_id = (known after apply)
+ owner_id = (known after apply)
+ state = (known after apply)
+ tags = {
+ "Name" = "testvpc0111"
}
}
# module.subnet["subnet_01"].aws_subnet.main must be replaced
-/+ resource "aws_subnet" "main" {
~ arn = "arn:aws:ec2:us-east-1:************:subnet/subnet-0ec569f1c4cb9ef15" -> (known after apply)
~ availability_zone = "us-east-1c" -> (known after apply)
~ availability_zone_id = "use1-az4" -> (known after apply)
- enable_lni_at_device_index = 0 -> null
~ id = "subnet-0ec569f1c4cb9ef15" -> (known after apply)
+ ipv6_cidr_block_association_id = (known after apply)
- map_customer_owned_ip_on_launch = false -> null
~ owner_id = "************" -> (known after apply)
~ private_dns_hostname_type_on_launch = "ip-name" -> (known after apply)
tags = {
"Name" = "testdatasnet1"
}
~ vpc_id = "vpc-0dba4643e6f19061a" # forces replacement -> (known after apply) #
forces replacement
# (8 unchanged attributes hidden)
}
# module.vpc["vpc_01"].data.aws_cloudwatch_log_group.test_clg will be read during apply
# (depends on a resource or a module with changes pending)
<= data "aws_cloudwatch_log_group" "test_clg" {
+ arn = (known after apply)
+ creation_time = (known after apply)
+ id = (known after apply)
+ kms_key_id = (known after apply)
+ log_group_class = (known after apply)
+ name = "testdataissuecwg1211q"
+ retention_in_days = (known after apply)
+ tags = (known after apply)
}
# module.vpc["vpc_01"].data.aws_iam_policy_document.assume_role will be read during apply
# (depends on a resource or a module with changes pending)
<= data "aws_iam_policy_document" "assume_role" {
+ id = (known after apply)
+ json = (known after apply)
+ statement {
+ actions = [
+ "sts:AssumeRole",
]
+ effect = "Allow"
+ principals {
+ identifiers = [
+ "vpc-flow-logs.amazonaws.com",
]
+ type = "Service"
}
}
}
# module.vpc["vpc_01"].data.aws_iam_policy_document.example will be read during apply
# (depends on a resource or a module with changes pending)
<= data "aws_iam_policy_document" "example" {
+ id = (known after apply)
+ json = (known after apply)
+ statement {
+ actions = [
+ "logs:CreateLogGroup",
+ "logs:CreateLogStream",
+ "logs:DescribeLogGroups",
+ "logs:DescribeLogStreams",
+ "logs:PutLogEvents",
]
+ effect = "Allow"
+ resources = [
+ "*",
]
}
}
# module.vpc["vpc_01"].aws_flow_log.example must be replaced
-/+ resource "aws_flow_log" "example" {
~ arn = "arn:aws:ec2:us-east-1:************:vpc-flow-log/fl-0bf918ffe07281dd4" -> (known after apply)
~ id = "fl-0bf918ffe07281dd4" -> (known after apply)
~ log_destination = "arn:aws:logs:us-east-1:************:log-group:testdataissuecwg1211q" # forces replacement -> (known after apply) # forces replacement
~ log_format = "${version} ${account-id} ${interface-id} ${srcaddr} ${dstaddr} ${srcport} ${dstport} ${protocol} ${packets} ${bytes} ${start} ${end} ${action} ${log-status}" -> (known after apply)
~ log_group_name = "testdataissuecwg1211q" -> (known after apply)
- tags = {} -> null
~ tags_all = {} -> (known after apply)
# (5 unchanged attributes hidden)
}
# module.vpc["vpc_01"].aws_iam_role.example will be updated in-place
~ resource "aws_iam_role" "example" {
~ assume_role_policy = jsonencode(
{
- Statement = [
- {
- Action = "sts:AssumeRole"
- Effect = "Allow"
- Principal = {
- Service = "vpc-flow-logs.amazonaws.com"
}
},
]
- Version = "2012-10-17"
}
) -> (known after apply)
id = "stackissue1qw"
name = "stackissue1qw"
tags = {}
# (8 unchanged attributes hidden)
# (1 unchanged block hidden)
}
# module.vpc["vpc_01"].aws_iam_role_policy.example will be updated in-place
~ resource "aws_iam_role_policy" "example" {
id = "stackissue1qw:stackissue1qw"
name = "stackissue1qw"
~ policy = jsonencode(
{
- Statement = [
- {
- Action = [
- "logs:PutLogEvents",
- "logs:DescribeLogStreams",
- "logs:DescribeLogGroups",
- "logs:CreateLogStream",
- "logs:CreateLogGroup",
]
- Effect = "Allow"
- Resource = "*"
},
]
- Version = "2012-10-17"
}
) -> (known after apply)
# (1 unchanged attribute hidden)
}
Plan: 2 to add, 3 to change, 2 to destroy.
It's probably because of the depends_on, you better use expression reference, just use for example instead of the data data_vpc_name, do
module.vpc.vpc_name
same for the cloudwatch.
from Hashicorp docs:
You should use depends_on as a last resort because it can cause Terraform to create more conservative plans that replace more resources than necessary. For example, Terraform may treat more values as unknown “(known after apply)” because it is uncertain what changes will occur on the upstream object. This is especially likely when you use depends_on for modules.
Instead of depends_on, we recommend using expression references to imply dependencies when possible. Expression references let Terraform understand which value the reference derives from and avoid planning changes if that particular value hasn’t changed, even if other parts of the upstream object have planned changes.
https://developer.hashicorp.com/terraform/language/meta-arguments/depends_on