Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Produce a Cartesian product with two lists in Terraform

Tags:

terraform

I have two lists in a terraform module...

cidr_blocks = ["1.2.3.4/32","5.6.7.8/32"]

and I have another list of settings for a network ACL

ingress_ports = [
    {
      from_port   = 80
      to_port     = 80
      protocol    = "tcp"
    },
    {
      from_port   = 443
      to_port     = 443
      protocol    = "tcp"
    },
    {
      from_port   = 22
      to_port     = 22
      protocol    = "tcp"
    }
]

and I want to join these lists together so I can then use the resulting list to create a network ACL.

result = [
  {
      cidr_block = "1.2.3.4/32"
      from_port   = 80
      to_port     = 80
      protocol    = "tcp"
   },
   {
      cidr_block = "1.2.3.4/32"
      from_port   = 443
      to_port     = 443
      protocol    = "tcp"
    },
    {
      cidr_block = "1.2.3.4/32"
      from_port   = 22
      to_port     = 22
      protocol    = "tcp"
    },
  {
      cidr_block = "5.6.7.8/32"
      from_port   = 80
      to_port     = 80
      protocol    = "tcp"
   },
   {
      cidr_block = "5.6.7.8/32"
      from_port   = 443
      to_port     = 443
      protocol    = "tcp"
    },
    {
      cidr_block = "5.6.7.8/32"
      from_port   = 22
      to_port     = 22
      protocol    = "tcp"
    }
]

Is this sort of thing possible in Terraform ?

like image 901
grbonk Avatar asked Feb 15 '18 19:02

grbonk


3 Answers

In terraform 0.12, we can finally use setproduct which makes this endeavor a lot easier:

cronjobs = [
  {
    schedule_expression   = "cron(5 0 * * ? *)"
    command_and_arguments = ["app/aws-console.sh", "task1"]
    description           = "Hello World"
  },
  {
    schedule_expression   = "cron(0 5 1 * ? *)"
    command_and_arguments = ["app/aws-console.sh", "task2"]
    description           = "Send Bills"
  }
]

environments = ["dev", "stage"]

locals {
  cronjobs_for_all_environments = setproduct(var.cronjobs, var.environments)
}

resource "aws_cloudwatch_event_rule" "cronjob" {
  count               = length(local.cronjobs_for_all_environments)
  name                = "cronjob-${local.cronjobs_for_all_environments[count.index][1]}"
  description         = "${local.cronjobs_for_all_environments[count.index][0].description} (${local.cronjobs_for_all_environments[count.index][1]})"
  schedule_expression = local.cronjobs_for_all_environments[count.index][0].schedule_expression
}
like image 60
KiNgMaR Avatar answered Oct 11 '22 20:10

KiNgMaR


Here is a solution I used Below is your test terraform script

provider "aws" {
    region = "us-east-1"
}

variable "lista" {
    default = ["1", "2", "3"]
}

variable "listb" {
    default = ["A", "B", "C", "D"]
}

resource "aws_eip" "eip" {
    count = "${length(var.lista) * length(var.listb)}"
    tags {
        Name = "test-eip ${count.index}. ${element(var.lista, ceil(count.index/length(var.listb)))}:${element(var.listb, count.index)}"
    }
}

to test run terraform plan | grep tags.Name

below is the output

tags.Name: "0. 1:A"
tags.Name: "1. 1:B"
tags.Name: "2. 1:C"
tags.Name: "3. 1:D"
tags.Name: "4. 2:A"
tags.Name: "5. 2:B"
tags.Name: "6. 2:C"
tags.Name: "7. 2:D"
tags.Name: "8. 3:A"
tags.Name: "9. 3:B"
tags.Name: "10. 3:C"
tags.Name: "11. 3:D"

My usecase was to map multiple peerings to multiple route-tables (one per AZ/NAT).

like image 31
Gowrishankar Holalkere Avatar answered Oct 11 '22 21:10

Gowrishankar Holalkere


Here's what I came up with. I remembered that there is a way to use modulo and integer division to perform what I wanted to do. I did this for both ingress and egress but I will only show ingress. To aid in the flexibility of using this in a module, I create the Network ACL outside of the module and pass it in.

Example module on Github

Variables:

variable "acl-id" {}
variable "offset" {}

variable "ingress-rules" {
  type = "list"
  description = "The List of Ingress Rules. Each item in the list is a map.  The Maps will be joined with the 'ingress-cidr'"
}

variable "ingress-cidr" {
  type = "list"
  description = "List of IPv4 CIDR ranges to apply to all ingress rules"
}

Locals: I created these for some clarity.

locals {
  ingress-cidr-size = "${length( var.ingress-cidr )}"
  ingress-rules-size = "${length( var.ingress-rules )}"

  ingress-join-size = "${local.ingress-cidr-size * local.ingress-rules-size}"

  ingress-joined-rules-cidrs = "${data.null_data_source.ingress-join.*.outputs}"
}

Data:

#Perform a cartesian like join of all of the CIDRs to apply to all of the rules
data "null_data_source" "ingress-join" {

  count = "${local.ingress-join-size}"

  inputs = {
    rule-number = "${count.index + var.offset}"
    cidr-block = "${ var.ingress-cidr[count.index / local.ingress-rules-size] }"
    from-port = "${ lookup ( var.ingress-rules[count.index % local.ingress-rules-size],  "from-port" ) }"
    to-port = "${ lookup ( var.ingress-rules[count.index % local.ingress-rules-size],  "to-port" ) }"
    action = "${ lookup ( var.ingress-rules[count.index % local.ingress-rules-size],  "action" ) }"
    protocol = "${ lookup ( var.ingress-rules[count.index % local.ingress-rules-size],  "protocol" ) }"
  }
}

The Network ACL Rule configuration:

##########################
# Ingress - Maps of rules
##########################
# Takes a list of 'ingress-rules' where each list item is a Map with the following keys
#  action: ether "allow" or "deny"
#  from-port: a port number
#  to-port:   a port number
#  protocol: A string like "tpc" or "-1"
#  rule-number:  A unique value to prevent collisions with other rules
#  cidr-block:  The CIDR that is applied to this rule
resource "aws_network_acl_rule" "ingress-rules-and-cidr-list" {

  count = "${var.create ? local.ingress-join-size : 0}"

  network_acl_id = "${var.acl-id}"
  egress              = false

  rule_number     = "${lookup( local.ingress-joined-rules-cidrs[count.index], "rule-number", "")}"
  rule_action     = "${lookup( local.ingress-joined-rules-cidrs[count.index], "action", "")}"

  cidr_block     = "${lookup(local.ingress-joined-rules-cidrs[count.index], "cidr-block", "")}"

  from_port    = "${lookup(local.ingress-joined-rules-cidrs[count.index], "from-port", "")}"
  to_port     = "${lookup(local.ingress-joined-rules-cidrs[count.index], "to-port", "")}"
  protocol    = "${lookup(local.ingress-joined-rules-cidrs[count.index], "protocol", "")}"

}
like image 2
grbonk Avatar answered Oct 11 '22 21:10

grbonk