Terraform Creating Repeat Objects - ACI - CSVs Input

As part of my terraform use case - large numbers of objects are crated for ACI - the details of which would look like an enlarged version of the csv below:

UID,tn_id,AP
1,TNT_1AP_1
2,TNT_1,AP_2
3,TNT_2,AP_1
4,TNT_2,AP_2

Using the following code:

" Import and decode csv data
locals {
csv_data = file(“./variables3.csv”)
instances = csvdecode(local.csv_data)
}
Create tenants for values under the tn_id column
resource “aci_tenant” “example” {
#for_each = { for inst in local.instances : inst.tn_id => inst }
for_each = { for inst in local.instances : inst.UID => inst }
name = each.value.tn_id
}

Terraform will then plan and create the tenant twice.

Terraform will perform the following actions:
aci_tenant.example[“1”] will be created

  • resource “aci_tenant” “example” {
    + annotation = “orchestrator:terraform”
    + description = (known after apply)
    + id = (known after apply)
    + name = “TNT_1”
    + name_alias = (known after apply)
    }
    aci_tenant.example[“2”] will be created
  • resource “aci_tenant” “example” {
    + annotation = “orchestrator:terraform”
    + description = (known after apply)
    + id = (known after apply)
    + name = “TNT_1”
    + name_alias = (known after apply)
    }

The tenant must be specified within the csv due to the hierarchical structure within ACI. And with larger deployments this will cause significant unnecessary load for the ACI controllers. Is there a way to somehow filter this so that it does not attempt to create duplicate tenants multiple times?

This should be doing what you are trying to. Plan tested only, I don’t have access to an ACI endpoint.

terraform {
  required_providers {
    aci = {
      source = "ciscodevnet/aci"
    }
  }
}

provider "aci" {
  url         = "http://aci.test.com"
  username    = "admin"
  cert_name   = "yeah"
  private_key = "right"
}

locals {
  csv_data  = file("./data.csv")
  instances = csvdecode(local.csv_data)
}

resource "aci_tenant" "example" {
  for_each = toset(distinct([for instance in local.instances : lookup(instance, "tn_id")]))

  name = each.value
}

1 Like

Hi Ohmer,

Thanks for that! That solves the issue with single columns!

Unfortunately, the solution does not expand to further down the hierarchichal structure.

UID,tn_id,AP,EPG
1,TNT_1,AP_1,EPG_1
2,TNT_1,AP_1,EPG_2
3,TNT_1,AP_2,EPG_1
4,TNT_1,AP_2,EPG_2
5,TNT_2,AP_1,EPG_1
6,TNT_2,AP_1,EPG_2
7,TNT_2,AP_2,EPG_1
8,TNT_2,AP_2,EPG_2

To create the APs my code looks as such:

#Create AP for each AP listed
resource “aci_application_profile” “test_ap” {
#for_each = { for inst in local.instances : inst.tn_id => inst}
for_each = { for inst in local.instances : inst.UID => inst }
tenant_dn = aci_tenant.example[each.value.tn_id].id
name = each.value.AP
}

The solution does not allow for referencing of other columns defined in the csv - only the column in the for each statement. However, for APs, the tn_id column is necessary as this is the parent object.

Additionally, for APs, the column is no longer what is required to be unique, the tn_id-AP pair is required to be unique (you can have the same AP in multiple tenants).

Your help in this would be greatly appreciated! I appreciate data manipulation via python and excel is an option, however the ability to deploy a seamless terraform solution would be the ultimate goal.

Can you output a JSON instead of a CSV to keep the hierarchical structure to input to TF?

not really beautiful but this plans

terraform {
  required_providers {
    aci = {
      source = "ciscodevnet/aci"
    }
  }
}

provider "aci" {
  url         = "http://aci.test.com"
  username    = "admin"
  cert_name   = "yeah"
  private_key = "right"
}

locals {
  csv_data  = file("./data.csv")
  instances = csvdecode(local.csv_data)

  aps = { for k, v in { for instance in local.instances : instance.tn_id => instance.AP... } : k => distinct(v) }
}

resource "aci_tenant" "this" {
  for_each = toset(distinct([for instance in local.instances : instance.tn_id]))

  name = each.value
}

resource "aci_application_profile" "this" {
  for_each = {
    for i in flatten([
      for tenant, aps in local.aps : [
        for ap in aps : {
          tenant = tenant
          ap     = ap
        }
      ]
    ]) :
    "${i.tenant}.${i.ap}" => {
      tenant = i.tenant
      name   = i.ap
    }
  }

  tenant_dn = aci_tenant.this[each.value.tenant].id
  name      = each.value.name
}

plan:

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aci_application_profile.this["TNT_1.AP_1"] will be created
  + resource "aci_application_profile" "this" {
      + annotation  = "orchestrator:terraform"
      + description = (known after apply)
      + id          = (known after apply)
      + name        = "AP_1"
      + name_alias  = (known after apply)
      + prio        = (known after apply)
      + tenant_dn   = (known after apply)
    }

  # aci_application_profile.this["TNT_1.AP_2"] will be created
  + resource "aci_application_profile" "this" {
      + annotation  = "orchestrator:terraform"
      + description = (known after apply)
      + id          = (known after apply)
      + name        = "AP_2"
      + name_alias  = (known after apply)
      + prio        = (known after apply)
      + tenant_dn   = (known after apply)
    }

  # aci_application_profile.this["TNT_2.AP_1"] will be created
  + resource "aci_application_profile" "this" {
      + annotation  = "orchestrator:terraform"
      + description = (known after apply)
      + id          = (known after apply)
      + name        = "AP_1"
      + name_alias  = (known after apply)
      + prio        = (known after apply)
      + tenant_dn   = (known after apply)
    }

  # aci_application_profile.this["TNT_2.AP_2"] will be created
  + resource "aci_application_profile" "this" {
      + annotation  = "orchestrator:terraform"
      + description = (known after apply)
      + id          = (known after apply)
      + name        = "AP_2"
      + name_alias  = (known after apply)
      + prio        = (known after apply)
      + tenant_dn   = (known after apply)
    }

  # aci_tenant.this["TNT_1"] will be created
  + resource "aci_tenant" "this" {
      + annotation  = "orchestrator:terraform"
      + description = (known after apply)
      + id          = (known after apply)
      + name        = "TNT_1"
      + name_alias  = (known after apply)
    }

  # aci_tenant.this["TNT_2"] will be created
  + resource "aci_tenant" "this" {
      + annotation  = "orchestrator:terraform"
      + description = (known after apply)
      + id          = (known after apply)
      + name        = "TNT_2"
      + name_alias  = (known after apply)
    }

Plan: 6 to add, 0 to change, 0 to destroy.
1 Like

great that does work!

I wonder how this could be extended to go 3 layers deep - what would the code look like for creating EPGs but only if its a unique epg, AP, tenant combination.

Also for referring to cells which arent included in the filtering e.g a description.