-1

Below is the locals sg_rules output I am getting check the value for cidr_blocks and security_group_id variables. At a time either of the values will be

"security_group_id" = tostring(null)

or

"cidr blocks" = tolist([])

Locals Outputs :

sg_rules = {
    "testsg_1-ingress-1521-tcp-10.80.0.10/32" => {
    "cidr blocks" = tolist(["10.80.0.10/32",]) 
    "description" = "1521 tcp ingress" 
    "from_port" = 1521 
    "protocol" = "tcp" 
    "security_group_id" = tostring(null) 
    "sg_name" = "testsg_1"
    "to_port" = 1521 
    "type" = "ingress" 
  },
    "testsg_2-ingress-1524-tcp-sg-23423439" => {
    "cidr blocks" = tolist([]) 
    "description" = "1524 tcp ingress" 
    "from_port" = 1524 
    "protocol" = "tcp" 
    "security_group_id" = "sg-23423439"
    "sg_name" = "testsg_2"
    "to_port" = 1524 
    "type" = "ingress" 
  }
}

using the above local in the aws_security_group_rule resource like below getting conflicting error, that both cidr_blocks and source_security_group_id should not exist at the same time.

resource "aws_security_group_rule" "tcp_cidr_blocks" {
  for_each  = { for key, sg_rule in local.sg_rules : key => sg_rule }
  type      = each.value.type
  from_port = each.value.from_port
  to_port   = each.value.to_port
  cidr_blocks              = each.value.cidr_blocks
  source_security_group_id = each.value.security_group_id
  protocol                 = each.value.protocol
  security_group_id        = aws_security_group.security_groups.id
}

what I am expecting as either of inputs are null, at a time, so it shouldn't conflict and use either cidr_blocks or source_security_group_id at a time.

Error:

Error: Conflicting configuration arguments\n\n with 
module.sg.aws_security_group_ru1e.tcp_cidr_blocks[\"testsg_2-ingress-1524-tcp-sg-23423439\"],\n on sg/main.tf line 30, in resource 
\"aws_security_group_rule\" \"tcp_cidr_blocks\" : \n 30: 
source_security_group_id 
= each.value.security_group_id\n\n\"security_group_id\": 
conflicts with cidr_blocks\n", 
Marko E
  • 13,362
  • 2
  • 19
  • 28
Abhishek Solanki
  • 374
  • 1
  • 14
  • 1
    You haven't explained what's wrong with the current code. Any errors? – Marcin Dec 19 '22 at 07:21
  • 1
    Why not just omit the arguments you know you are not going to use? Additionally, you could maybe use `try` or ternary operator. – Marko E Dec 19 '22 at 11:11
  • @Marko E. They are in use , I have just omitted them in the code as the error was only in the limited section of the code. – Abhishek Solanki Dec 19 '22 at 11:19
  • @Marko E , I have updated the code with all the arguments as well – Abhishek Solanki Dec 19 '22 at 11:23
  • @MarkoE ***with `try` operator*** `Error: Null condition\n\n on sg/main.tf line 28, in resource \"aws_security_group_rule\" \"tcp_cidr_blocks\":\n 28: source_security_group_id = try(each.value.security_group_id) ? each.value.security_group_id : null\n ├────────────────\n │ each.value.security_group_id is null\n\nThe condition value is null. Conditions must either be true or false.\n",` – Abhishek Solanki Dec 19 '22 at 12:07
  • ***with `can` operator*** `Error: Null condition\n\n on sg/main.tf line 28, in resource \"aws_security_group_rule\" \"tcp_cidr_blocks\":\n 28: source_security_group_id = try(each.value.security_group_id) ? each.value.security_group_id : null\n ├────────────────\n │ each.value.security_group_id is null\n\nThe condition value is null. Conditions must either be true or false.\n",` – Abhishek Solanki Dec 19 '22 at 12:07
  • @AbhishekSolanki I think I found a way to make it work, I posted an answer. – Marko E Dec 20 '22 at 10:43

3 Answers3

2

There are a couple of issues with the current code, one being the => which is not a valid syntax in Terraform:

enter image description here

The second is explicitly casting to types, e.g., tolist(["10.80.0.10/32",]) and tostring(null). If the local variable sg_rules is fixed to look like this:

sg_rules = {
    "testsg_1-ingress-1521-tcp-10.80.0.10/32" = {
      "cidr_blocks"       = ["10.80.0.10/32", ] # <---- list instead of type casting
      "description"       = "1521 tcp ingress"
      "from_port"         = 1521
      "protocol"          = "tcp"
      "security_group_id" = "" # <---- empty string instead of type casting
      "sg_name"           = "testsg_1"
      "to_port"           = 1521
      "type"              = "ingress"
    },
    "testsg_2-ingress-1524-tcp-sg-23423439" = {
      "cidr_blocks"       = [""] # <---- empty list of strings instead of type casting
      "description"       = "1524 tcp ingress"
      "from_port"         = 1524
      "protocol"          = "tcp"
      "security_group_id" = "sg-23423439"
      "sg_name"           = "testsg_2"
      "to_port"           = 1524
      "type"              = "ingress"
    }
  }

Following that change, using the ternary operator on your code will result in terraform plan working:

resource "aws_security_group_rule" "tcp_cidr_blocks" {
  for_each                 = local.sg_rules
  type                     = each.value.type
  from_port                = each.value.from_port
  to_port                  = each.value.to_port
  cidr_blocks              = each.value.cidr_blocks != [""] ? each.value.cidr_blocks : null
  source_security_group_id = each.value.security_group_id != "" ? each.value.security_group_id : null
  protocol                 = each.value.protocol
  security_group_id        = aws_security_group.security_groups.id
}

Setting cidr_blocks or source_security_group_id to null will tell terraform to treat it is an absence of the argument which means it will not complain about conflicting arguments.

Marko E
  • 13,362
  • 2
  • 19
  • 28
  • ```=>``` is a valid syntax you can find it here https://stackoverflow.com/questions/68830880/security-group-using-terraform-with-nested-for-loop , but rest of the issues which you pointed out are correct and working with your solution. Thanks :) – Abhishek Solanki Dec 20 '22 at 10:52
  • 1
    It is not valid for a map, it is valid however when you are creating one using the `for` loop. – Marko E Dec 20 '22 at 10:59
  • I am creating using the for loop only , here I have just shown the output of the local variable – Abhishek Solanki Dec 20 '22 at 14:22
  • Yup, and that output from the screenshot is taken from your question. :) – Marko E Dec 20 '22 at 14:29
0

The second object in your example has:

  • "cidr blocks" = tolist([])
  • "security_group_id" = "sg-23423439"

An empty list is not null, so for this object both of the arguments are set at once.

If you set the CIDR blocks attribute to tolist(null) instead then you will have a null value for that argument, which may allow this validation rule to pass.

This validation rule is implemented by the hashicorp/aws provider in its aws_security_group_rule implementation and so exactly how it will interpret these cases is decided by how the logic in the provider is written -- the provider could potentially choose to treat an empty list the same as unset -- but a null value for a resource argument is always exactly equivalent to leaving it unset and so making sure at least one of them is really null is the most robust way to configure this, which should work regardless of how the provider plugin is implemented.

Martin Atkins
  • 62,420
  • 8
  • 120
  • 138
0

I will leave my answer here just as another example of how to work with JSON files (I imported the security group rules first as I was working with existing resources):

[
 {
    "description": "Remove me",
    "from_port": 500,
    "to_port": 500,
    "protocol": "udp",
    "source_security_group_id": "",
    "cidr_blocks": ["xxx.xx.xx.xxx/xx"]
 },
 {
    "description": "Remove this rule",
    "from_port": 5000,
    "to_port": 7000,
    "protocol": "tcp",
    "source_security_group_id": "sg-xxxx8f180c16xxxx",
    "cidr_blocks": [""]
 }
]

locals {
  ingress_security_groups = jsondecode(file("${path.module}/components/ingress_security_groups.json"))
}

resource "aws_security_group_rule" "ingress_rule" {
  count                    = length(local.ingress_security_groups)
  type                     = "ingress"
  description              = local.ingress_security_groups[count.index].description
  from_port                = local.ingress_security_groups[count.index].from_port
  to_port                  = local.ingress_security_groups[count.index].to_port
  protocol                 = local.ingress_security_groups[count.index].protocol
  source_security_group_id = local.ingress_security_groups[count.index].source_security_group_id != "" ? local.ingress_security_groups[count.index].source_security_group_id : null
  cidr_blocks              = local.ingress_security_groups[count.index].cidr_blocks != [""] ? local.ingress_security_groups[count.index].cidr_blocks : null
  security_group_id        = aws_security_group.this.id
}