lesson ── terraform-garden ── ~18 мин ── 3 шагов
In ~/tf-garden there is ready-made HCL. terraform init will pass, plan will not:
Cycle Error. One resource points at another, that one at a third, the third
points back at the first. Without untangling this, the graph cannot be built
and plan will not run.
Your job is to find the cycle, understand why it is there, break it so the meaning stays intact, and prove with plan that the graph is now a DAG.
интерактивный sandbox
Поднимется пара контейнеров: terraform 1.9 и localstack 3.8 в одной сети. В браузере откроется терминал, можно сразу terraform init. Каждый шаг проверяется автоматически. TTL 45 минут, без регистрации.
stack ── terraform · localstack · 1 GB RAM · самоуничтожается через 45 мин простоя
Enter the directory, initialize, and try to run plan.
cd /home/student/tf-garden
terraform init
terraform plan 2>&1 | tee /tmp/cycle.log
In cycle.log you will see a block like this:
Error: Cycle: random_id.suffix, aws_s3_bucket.demo, aws_s3_bucket_policy.demo
Write down the three resource names. That is your cycle.
✓ Cycle confirmed. Now, why it is there.
The most common case: keepers or triggers in a utility resource
points at a resource that itself depends on the utility resource.
Here random_id.keepers.bucket_arn = aws_s3_bucket.demo.arn,
while aws_s3_bucket.demo.bucket is garden-cycle-${random_id.suffix.hex}.
That gives you a dependency in both directions.
The second common case: depends_on added by hand on top of implicit
deps. Terraform builds the graph automatically, and if you also write
depends_on into the same place, you can end up with "A depends on B,
B depends on A through a manual depends_on".
The fix: drop the back-dependency in random_id.keepers (it is useless,
because random_id is recreated through -replace anyway), and move the
condition out of the policy into locals:
locals {suffix_hex = random_id.suffix.hex
}
resource "random_id" "suffix" {byte_length = 4
}
resource "aws_s3_bucket" "demo" { bucket = "garden-cycle-${local.suffix_hex}"}
resource "aws_s3_bucket_policy" "demo" {bucket = aws_s3_bucket.demo.id
policy = jsonencode({Version = "2012-10-17"
Statement = [{Effect = "Allow"
Principal = "*"
Action = "s3:GetObject"
Resource = "${aws_s3_bucket.demo.arn}/*" Condition = { StringEquals = {"aws:RequestTag/SuffixHex" = local.suffix_hex
}
}
}]
})
}
Put this into ~/tf-garden/main.tf over the old file. Afterwards, check
that there is no keepers block left.
The new main.tf must not contain the word `keepers`. Do not try to "fix" the policy by adding depends_on, that is the wrong direction.
✓ HCL rewritten. Now check the graph.
cd /home/student/tf-garden
terraform plan -out=plan.tfplan
Now plan should pass: "Plan: 3 to add, 0 to change, 0 to destroy". Apply is not required in this lesson, what matters is that the graph built.
If plan still complains, look at terraform graph -draw-cycles, there
may be one more cycle you did not notice.
✓ Plan built, the graph is now a DAG. Cycle untangled.
OpenTofu repeats the same Cycle check 1:1, with the same messages and the same graph command. This comes from the shared HCL heritage, it is not a difference between implementations. The full article on the differences and compatibility is tf-opentofu-parity.
A cycle is always a design mistake, not a tf bug. See a cycle? Someone wrote dependencies in both directions. A random_id.keepers that points at a resource which itself depends on the same random_id is the classic case. The fix: remove the back-reference and move the logic into a data block or a local, or accept that the dependency goes one way.
команды
terraform graph -draw-cycles | dot -Tsvg > /tmp/g.svgdraws the cycle visually; if dot is not available, read the ASCII outputterraform graph -type=plan-destroyanother view of the same graph; sometimes the cycle is clearer this wayконцепции