Nested loops in Terraform - terraform

I need to have nested loop logic. F.ex.
I have one local:
locals {
build_args = {
api: {
RAILS_ENV: "production"
}
client: {
NODE_ENV: "production"
}
}
}
Now I would like to connect to CircleCI with terraform and set these environments in adequate circleCI projects (api and client). The knowledge about circleci projects (name of a project) I keep here:
apps: {
api: {
desired_count: 1,
load_balancer: {
container_name: "api",
container_port: 5000,
health_check_path: "/",
listener: {
path: "api",
},
},
circleci_project: "some-api",
},
client: {
desired_count: 1,
load_balancer: {
container_name: "client",
container_port: 3000,
health_check_path: "/",
listener: {
path: "web",
},
},
circleci_project: "some-client",
}
}
Now, I need to create resource:
resource "circleci_environment_variable" "this" {
project = projects_from_apps_var
name = names_from_local_build_args
value = value_from_local_build_args
}
So as you can see I need two loops one in another to generate many name/values env pairs for many projects.

Just create a map keyed by project and variable name and apply a bunch of resources for each combination:
locals {
map = merge([
for project, env in local.build_args : {
for name, value in env : "${project}-${name}" => {
name = name,
value = value,
project = project
}
}
]...)
}
resource "circleci_environment_variable" "this" {
for_each = local.map
project = each.value.project
name = each.value.name
value = each.value.value
}

Related

Array of objects in terraform

I would like to have such a array of objects in terraform:
param ArrayOfRules array = [
{
name: '1stRule'
startIpAddress: '0.0.0.0'
endIpAddress: '0.0.0.0'
}
{
name: '2ndRule'
startIpAddress: '0.0.0.1'
endIpAddress: '0.0.0.1'
}
]
On which i would like to simply iterare in order to create firewall rules.
resource sqlServerFirewallRules 'Microsoft.Sql/servers/firewallRules#2022-02-01-preview' = [for rule in ArrayOfRules: {
parent: serverName_resource
name: rule.name
properties: {
startIpAddress: rule.startIpAddress
endIpAddress: rule.endIpAddress
}
}]
I know that i could to something like this in bicep but I don't know how to do it in terraform.
You need to create variable like
variable "ArrayOfRules" {
type = list(map(string))
}
You need to assign variable value like this
var.ArrayOfRules = [
{
name: '1stRule'
startIpAddress: '0.0.0.0'
endIpAddress: '0.0.0.0'
},
{
name: '2ndRule'
startIpAddress: '0.0.0.1'
endIpAddress: '0.0.0.1'
}
]
You need to call dynamic block in your resource..
resource sqlServerFirewallRules 'Microsoft.Sql/servers/firewallRules#2022-02-01-preview' {
parent: serverName_resource
name: rule.name
dynamic "eachElementinArray" {
for_each = each.value.eachElementinArray
properties {
name = name.value.type
startIpAddress = eachElementinArray.value.startIpAddress
endIpAddress = eachElementinArray.value.endIpAddress
}
}
}
You may have to change some syntax... but on a high level, your terraform will look like the above..

Does the Terraform resource kubernetes_ingress_v1 have a "use_annotation" equivalent?

We're currently migrating our terraform kubernetes_ingress resource to a kubernetes_ingress_v1 resource. Previously, we had these annotations on the ingress:
annotations = {
"kubernetes.io/ingress.class" = "alb"
"alb.ingress.kubernetes.io/scheme" = "internet-facing"
"alb.ingress.kubernetes.io/certificate-arn" = var.create_acm_certificate ? aws_acm_certificate.eks_domain_cert[0].id : var.aws_acm_certificate_arn
"alb.ingress.kubernetes.io/listen-ports" = "[{\"HTTP\": 80}, {\"HTTPS\":443}]"
"alb.ingress.kubernetes.io/actions.ssl-redirect" = "{\"Type\": \"redirect\", \"RedirectConfig\": { \"Protocol\": \"HTTPS\", \"Port\": \"443\", \"StatusCode\": \"HTTP_301\"}}"
"alb.ingress.kubernetes.io/ssl-policy" = "ELBSecurityPolicy-TLS-1-2-Ext-2018-06"
"alb.ingress.kubernetes.io/healthcheck-path" = "/healthz"
}
along with this segment several times in the spec:
path {
backend {
service_name = "ssl-redirect"
service_port = "use-annotation"
}
path = "/*"
}
However, the kubernetes_ingress_v1 requires a format like:
path {
backend {
service {
name = "ssl-redirect"
port {
number = <number_value>
}
}
}
path = "/*"
}
where port is an actual number and not "use-annotation". Is there any way to replicate this "use-annotation" behavior in a kubernetes_ingress_v1 resource? Or, even better, is there a simpler way to handle this ssl-redirect rule in a kubernetes_ingress_v1?
You can achive that using the following sintaxis:
backend {
service {
name = "ssl-redirect"
port {
name = "use-annotation"
}
}
}
As you can see, you need to use the argument name instead port.

Unable to create a new Nutanix VM and assign it to a project

I'm trying to create a new machine on Nutanix with terraform v0.15.0 and assign the machine to an existing project I already have (created on the UI without terraform). Using the Nutanix documentation
(https://registry.terraform.io/providers/nutanix/nutanix/latest/docs/resources/virtual_machine#project_reference) , I was able to create a VM, but not assign it to the existing project.
Using this main.tf works, however, uncommenting the project_reference attribute results in the following error:
nutanix_virtual_machine.vm1[0]: Creating...
Error: error: {
"api_version": "3.1",
"code": 422,
"message_list": [
{
"details": {
"metadata": [
"Additional properties are not allowed (u'project_reference' was unexpected)"
]
},
"message": "Request could not be processed.",
"reason": "INVALID_REQUEST"
}
],
"state": "ERROR"
}
on main.tf line 67, in resource "nutanix_virtual_machine" "vm1":
67: resource "nutanix_virtual_machine" "vm1" {
Here's my code:
provider "nutanix" {
username = "user"
password = "pass"
port = 1234
endpoint = "ip"
insecure = true
wait_timeout = 10
}
data "nutanix_cluster" "cluster" {
name = "NTNXCluster"
}
data "nutanix_image" "ubuntu-clone" {
image_name = "Ubuntu-20.04-Server"
}
variable "counter" {
type = number
default = 1
}
resource "nutanix_virtual_machine" "vm1" {
name = "test-${count.index+1}"
count = var.counter
description = "desc"
num_vcpus_per_socket = 2
num_sockets = 2
memory_size_mib = 4096
guest_customization_sysprep = {}
cluster_uuid = "my_uuid"
nic_list {
subnet_uuid = "my_uuid"
}
#project_reference = {
# kind = "project"
# uuid = "my_uuid"
# name = "my_project"
#}
disk_list {
data_source_reference = {
kind = "image"
uuid = data.nutanix_image.ubuntu-clone.id
}
device_properties {
disk_address = {
device_index = 0
adapter_type = "SATA"
}
device_type = "DISK"
}
}
}
After Nutanix support asked me to use debug mode in terraform I found the issue.
In debug mode, I saw that terraform is using API calls that can't be used on Nutanix Elements.
Creating a VM with a project can be done ONLY from Nutanix Prism, and I used the Nutanix Elements provider instead.
Switching the provider from Nutanix Elements to Nutanix Prism, using the same terraform main.yml worked as expected.
Use:
project_reference = {
kind = "project"
uuid = your_id
}
Leave project name out of the block, with ID is enough.

How to rename a dynamic block in terraform

I'm trying to use a dynamic block in a kubernetes_ingress resource. The dynamic block is for the spec.rule.http.path block. Unfortunately, I am trying to dynamically create a path block which causes issues as path appears to be a reserved word.
Is it possible to rename the loop variable within the dynamic block, or to otherwise circumvent this issue?
This is my current code:
resource "kubernetes_ingress" "ingress" {
metadata { ... }
spec {
tls { ... }
rule {
http {
dynamic "path" {
for_each = var.services
content {
path = path.value.path
backend {
service_name = path.value.name
service_port = path.value.port
}
}
}
}
}
}
}
The services variable has the following structure:
[
{
name: "foo",
port: 3000,
path: "/foo",
}
]
Dynamic blocks take an argument called iterator that lets you rename the symbol it assigns values to.
dynamic "path" {
for_each = var.services
iterator = "service"
content {
path = service.value.path
backend {
service_name = service.value.name
service_port = service.value.port
}
}
}

Iterate over list of list of maps in terraform

Consider I have a variable that is a list of list of maps.
Example:
processes = [
[
{start_cmd: "a-server-start", attribute2:"type_a"},
{start_cmd: "a-worker-start", attribute2:"type_b"}
{start_cmd: "a--different-worker-start", attribute2:"type_c"}
],
[
{start_cmd: "b-server-start", attribute2:"type_a"},
{start_cmd: "b-worker-start", attribute2:"type_b"}
]
]
In each iteration, I need to take out the array of maps, then iterate over that array and take out the values of the map. How do I achieve this in terraform?
I have considered having two counts and doing some arithmetic to trick terraform into performing a lookalike nested iteration Check reference here. But in our case the number of maps in the inner array can vary.
Also we are currently using the 0.11 terraform version but dont mind using the alpha 0.12 version of terraform if it is possible to achieve this in that version.
Edit:
Added how I would use this variable:
resource “create_application” “applications” {
// Create a resource for every array in the variable processes. 2 in this case
name = ""
migration_command = ""
proc {
// For every map create this attribute for the resource.
name = ““
init_command = “a-server-start”
type = “server”
}
}
Not sure if this clears up the requirement. Please do ask if it is still not clear.
Using terraform 0.12.x
locals {
processes = [
[
{ start_cmd: "a-server-start", type: "type_a", name: "inglorious bastards" },
{ start_cmd: "a-worker-start", type: "type_b", name: "kill bill" },
{ start_cmd: "a--different-worker-start", type: "type_c", name: "pulp fiction" },
],
[
{ start_cmd: "b-server-start", type: "type_a", name: "inglorious bastards" },
{ start_cmd: "b-worker-start", type: "type_b", name: "kill bill" },
]
]
}
# just an example
data "archive_file" "applications" {
count = length(local.processes)
type = "zip"
output_path = "applications.zip"
dynamic "source" {
for_each = local.processes[count.index]
content {
content = source.value.type
filename = source.value.name
}
}
}
$ terraform apply
data.archive_file.applications[0]: Refreshing state...
data.archive_file.applications[1]: Refreshing state...
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
If a create_application resource existed, it can be modeled like so
resource "create_application" "applications" {
count = length(local.processes)
name = ""
migration_command = ""
dynamic "proc" {
for_each = local.processes[count.index]
content {
name = proc.value.name
init_command = proc.value.start_cmd
type = proc.value.type
}
}
}
Here is my solution that work like charm. Just note the tricks google_service_account.purpose[each.value["name"]].name where I can retrieve the named array element by using its name.
variable "my_envs" {
type = map(object({
name = string
bucket = string
}))
default = {
"dev" = {
name = "dev"
bucket = "my-bucket-fezfezfez"
}
"prod" = {
name = "prod"
bucket = "my-bucket-ezaeazeaz"
}
}
}
resource "google_service_account" "purpose" {
for_each = var.my_envs
display_name = "blablabla (terraform)"
project = each.value["name"]
account_id = "purpose-${each.value["name"]}"
}
resource "google_service_account_iam_binding" "purpose_workload_identity_binding" {
for_each = var.my_envs
service_account_id = google_service_account.purpose[each.value["name"]].name
role = "roles/iam.whatever"
members = [
"serviceAccount:${each.value["name"]}.svc.id.goog[purpose/purpose]",
]
}
resource "google_storage_bucket_iam_member" "purpose_artifacts" {
for_each = var.my_envs
bucket = each.value["bucket"]
role = "roles/storage.whatever"
member = "serviceAccount:${google_service_account.purpose[each.value["name"]].email}"
}

Resources