I’ve got a scenario in which changing an input requires running plan and apply twice. The first run creates some resources and the second uses those new resources to modify some others. I’m having a heckuva time reducing this to a small example—my configuration is rather complex!
On changing an input, I expect a new IAM Policy to be created and for its ARN to be added to some Roles’ managed_policy_arns. Instead, the first apply creates the policy and the second adds the arn to the role.
$ terraform graph
...
"[root] module.aws.module.prod.aws_iam_role.user (expand)" -> "[root] module.aws.module.prod.local.policy_arns_by_user (expand)"
"[root] module.aws.module.prod.local.policy_arns_by_user (expand)" -> "[root] module.aws.module.prod.local.all_managed_pols (expand)"
"[root] module.aws.module.prod.local.all_managed_pols (expand)" -> "[root] module.aws.module.prod.local.custom_policy_arns (expand)"
"[root] module.aws.module.prod.local.custom_policy_arns (expand)" -> "[root] module.aws.module.prod.aws_iam_policy.create (expand)"
...
These dependencies look correct. But when I make a change that results in a new aws_iam_policy.create
element, and then plan:
$ terraform plan -out=tfplan
...
# module.aws.module.prod.aws_iam_policy.create["deployer"] will be created
... (but I would expect change in the aws_iam_role.user)
Plan: 1 to add, 0 to change, 0 to destroy.
$ terraform graph -plan=tfplan
...
"[root] module.aws.module.prod.local.policy_arns_by_user (expand)" -> "[root] module.aws.module.prod.local.all_managed_pols (expand)"
"[root] module.aws.module.prod.local.all_managed_pols (expand)" -> "[root] module.aws.module.prod.local.custom_policy_arns (expand)"
"[root] module.aws.module.prod.local.custom_policy_arns (expand)" -> "[root] module.aws.module.prod.aws_iam_policy.create[\"deployer\"]"
...
I don’t see module.aws.module.prod.aws_iam_role.user
anywhere in the plan graph, and indeed the plan does not include updating the iam role.
I’ve got all the locals bubbled up as outputs, and the all corectly show up in this first plan:
~ aws_iam_policy_create = [ // <- this is keys(aws_iam_policy.create)
+ "deployer",
...
~ custom_policy_arns = {
+ deployer = (known after apply)
...
~ all_managed_pols = {
~ deployer = [
+ (known after apply),
]
...
}
+ policy_arns_by_user = {
+ "user1" = (known after apply)
...
After applying, if I immediately plan again:
$ terraform plan -out=tfplan
# module.aws.module.prod.aws_iam_role.user["user1"] will be updated in-place
Plan: 0 to add, 1 to change, 0 to destroy.
$ t graph -plan=tfplan
"[root] module.aws.module.prod.aws_iam_role.user[\"user1\"]" -> "[root] module.aws.module.prod.local.policy_arns_by_user (expand)"
"[root] module.aws.module.prod.local.policy_arns_by_user (expand)" -> "[root] module.aws.module.prod.local.all_managed_pols (expand)"
"[root] module.aws.module.prod.local.all_managed_pols (expand)" -> "[root] module.aws.module.prod.local.custom_policy_arns (expand)"
"[root] module.aws.module.prod.local.custom_policy_arns (expand)" -> "[root] module.aws.module.prod.aws_iam_policy.create (expand)"
There are no data sources in this chain, eg. ones that lookup resources that are created in the first apply. The first apply creates a policy and puts its arn into a local, which is referenced in the role resource. But that dependency is getting missed.
I have no other policy attachment resources defined (ala the warnings on the aws_iam_role docs page). I am using inline_policy
and managed_policy_arns
only, so this resource has exclusive management of them.
resource "aws_iam_policy" "create" {
for_each = local.create_custom_policies
...
}
locals {
custom_policy_arns = {
for handle, iam_policy in aws_iam_policy.create :
handle => iam_policy.arn
}
// some unexplained complexity here, but confident it works because output
// above shows "(known after apply)"
all_managed_pols = {
for duty, conf in var.duties :
duty => concat(conf.managed-policies, [
for handle in keys(conf.custom-policies) :
local.custom_policy_arns[handle]
])
}
policy_arns_by_user = {
for username, user in local.all_account_users_who_need_roles :
username => distinct(flatten([
for duty in user.duties :
lookup(local.all_managed_pols, duty, [])
]))
}
}
resource "aws_iam_role" "user" {
for_each = local.all_account_users_who_need_roles
managed_policy_arns = local.policy_arns_by_user[each.key]
...
}
Phew! If anyone made it this far, I greatly appreciate your reading.