osozombiee
osozombiee

Reputation: 51

How can I do so that policies are not attached when I want

I have a problem I need groups to be created and policies attached to those groups but I also need that if the variable policy_name is equal to "" he does not believe me anything, I know that it is repetitive but I have been asked to do it in this way I appreciate your support

This is my main.tf

resource "aws_iam_group" "this" {
 count = length(var.name) != 0 ? length(var.name) : 0
 
 name = element(var.name, count.index)
 path = var.path
}
 
resource "aws_iam_policy" "prueba" {
 count = var.policy_name != "" ? 1 : 0
 
 name = var.policy_name
 policy = jsonencode(var.policy)
}
 
resource "aws_iam_group_policy_attachment" "test-attach" {
 for_each = toset(var.name)
 group = each.key
 policy_arn = aws_iam_policy.prueba[0].arn
}

This is variables

variable "name" {
 description = "Name of IAM group"
 type = list
 default = []
}
  
variable "policy" {
 description = "The policy in IAM (tpl file)"
 type = any
 default = null
}
 
variable "policy_name" {
 type = string
 default = ""
}

This is de var.tfvars

policy = {
 "Version": "2012-10-17",
 "Statement": [
 {
 "Action": [
 "autoscaling:Describe*",
 "cloudwatch:*",
 "logs:*",
 "sns:*",
 "iam:GetPolicy",
 "iam:GetPolicyVersion",
 "iam:GetRole"
 ],
 "Effect": "Allow",
 "Resource": "*"
 },
 {
 "Effect": "Allow",
 "Action": "iam:CreateServiceLinkedRole",
 "Resource": "arn:aws:iam:::role/aws-service-role/events.amazonaws.com/AWSServiceRoleForCloudWatchEvents",
 "Condition": {
 "StringLike": {
 "iam:AWSServiceName": "events.amazonaws.com"
 }
 }
 },
 {
 "Action": [
 "cloudwatch:DeleteAlarms",
 "cloudwatch:DeleteAnomalyDetector",
 "cloudwatch:DeleteDashboards",
 "cloudwatch:DeleteInsightRules",
 "cloudwatch:DeleteMetricStream"
 ],
 "Effect": "Deny",
 "Resource": "*"
 },
 {
 "Action": [
 "logs:DeleteDestination",
 "logs:DeleteLogDelivery",
 "logs:DeleteLogGroup",
 "logs:DeleteLogStream",
 "logs:DeleteMetricFilter",
 "logs:DeleteQueryDefinition",
 "logs:DeleteResourcePolicy",
 "logs:DeleteRetentionPolicy",
 "logs:DeleteSubscriptionFilter"
 ],
 "Effect": "Deny",
 "Resource": "*"
 },
 {
 "Action": [
 "sns:DeleteEndpoint",
 "sns:DeletePlatformApplication",
 "sns:DeleteSMSSandboxPhoneNumber",
 "sns:DeleteTopic",
 "sns:RemovePermission"
 ],
 "Effect": "Deny",
 "Resource": "*"
 },
 {
 "Action": "events:*",
 "Effect": "Allow",
 "Resource": "*"
 },
 {
 "Action": [
 "events:RemovePermission",
 "events:DeleteApiDestination",
 "events:DeleteRule",
 "events:DeleteArchive",
 "events:DeleteConnection",
 "events:DeletePartnerEventSource",
 "events:DeleteEventBus"
 ],
 "Effect": "Deny",
 "Resource": "*"
 }
 ]
}

name = ["test_group","test_group_1","test_group_2"]
policy_name = ""

This is the error

 Error: Invalid index
    
           on ../groups/main.tf line 18, in resource "aws_iam_group_policy_attachment" 
"test-attach":
           18:   policy_arn = aws_iam_policy.prueba[0].arn
             ├────────────────
             │ aws_iam_policy.prueba is empty tuple
        
         The given key does not identify an element in this collection value.

Upvotes: 1

Views: 88

Answers (1)

Martin Atkins
Martin Atkins

Reputation: 74064

If you have an object that is only defined in certain situations then you must explain to Terraform how other parts of the module should react to that object not being present.

In your case you have an IAM policy that may or may not exist depending on the condition var.policy_name != "" ? 1 : 0. However, you have another resource which can only exist if the policy does, and so Terraform correctly returned an error explaining that you cannot refer to the ARN of a policy that doesn't exist.

Since the meaning of aws_iam_group_policy_attachment is to create a relationship between an IAM group and an IAM policy, you presumably need an instance of that resource type for every pair of group and policy. A typical way to represent that relationship is with the setproduct function, which computes a set of all of the combinations of elements in two other sets.

For example:

resource "aws_iam_group_policy_attachment" "test-attach" {
 for_each = {
   for pair in setproduct(toset(var.name), toset(aws_iam_policy.prueba)) :
   "${pair[0]}:${pair[1].name}" => {
     group_name = pair[0]
     policy_arn = pair[1].arn
   }
 }

 group      = each.value.group_name
 policy_arn = each.value.policy_arn
}

This for_each expression has considerably more components than the one you started with, so I will try to summarize what each part is doing:

  • The { for ... in ... : ... => ...} part is a for expression, which allows deriving one collection value from another collection value. In this case, the source collection is the setproduct result.
  • setproduct(...) takes one or more set values and returns a new set of tuples representing all of the combinations of elements in the given sets. In this case, it returns a set of [ group_name, policy_object ] pairs, where group_name is one of the elements from var.name and policy_object is the object representing one of the instances of aws_iam_policy.prueba.
  • for_each requires a unique key for each instance specified as a map key, and so the for expression is transforming that set of tuples into a map where the keys are built from combining a group name with a policy name, and the values are each an object that describes the one relevant group name and the one relevant policy ARN.
  • Since each.value inside that resource block always refers to one of the values from that resulting map, we can refer to each.value.group_name and each.value.policy_arn to populate the specific pairing of those for a single instance.

Note that setproduct always returns a set whose number of elements is the multiplication of the number of elements in the given sets, and so if either of them has zero elements the result will have zero elements -- zero multiplied by anything is zero. That then ultimately avoids the problem you encountered here, because if you have either zero groups or zero policies then you will have zero attachments.

Upvotes: 1

Related Questions