Terraform: create namespaces and role binding using lists - azure

The namespace is created in next way, so "role bindings" are applied depending of the "app_env" code
**variables.tf**
variable app_name {}
variable app_env {}
locals {
custom_role_dev = "Enterprise Development Project"
custom_role_prd = "Enterprise Production Project"
}
**main.tf**
resource "kubernetes_namespace" "kube_ns" {
metadata {
name = var.app_name
}
}
resource "kubernetes_role" "custom_role_dev" {
count var.app_env == "d" ? 1 : 0
metadata {
name = local.custom_role_dev
namespace = var.app_name
}
rule {
api_groups = [""]
resources = ["<options>"]
verbs = ["*"]
}
depends_on = [kubernetes_namespace.kube_ns]
}
resource "kubernetes_role" "custom_role_prd" {
count var.app_env == "p" ? 1 : 0
metadata {
name = local.custom_role_prd
namespace = var.app_name
}
rule {
api_groups = [""]
resources = ["<options>"]
verbs = ["*"]
}
depends_on = [kubernetes_namespace.kube_ns]
}
In order to create several namespace and applying their respective roles, I want to use "lists" to replace "app_name" variable but I don't know how to iterate the "kubernetes_role" block.
I think this 2 links are very close what I want to do
Convert list to map with index in Terraform
Terraform - conditionally creating a resource within a loop
Can this be done with "for_each" or "count"?

Related

Terraform - Output Index along with item

Is there a way to modify this output code to include the index number with the name? The current code below creates one output with Index '0', but each name should ideally be in its own Index (0, 1, 2, etc.)
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "3.1.0"
}
}
}
provider "azurerm" {
features {
}
}
variable "private_link_scopes" {
description = "A list of Azure private link scopes."
type = list(object({
name = string
resource_group_name = string
}))
}
locals {
rgs_map = {
for n in var.private_link_scopes : n.resource_group_name => {
name = n.resource_group_name
}
}
}
data "azurerm_resource_group" "rg" {
for_each = local.rgs_map
name = each.value.name
}
output "list" {
value = { for index, item in [var.private_link_scopes] : index => item[*].name }
}
It seems like there is a lot of structure change going on here. That may be due to other baggage or reasons that depend on it, but I think this could be simplified. I use locals in lieu of the variable, but I hope is help.
I'm not sure the splat operator there is what you want. That is the same as getting a list of all items' name attributes in that item value, but there would only be one per item so that seems strange.
locals {
scopes = [
{
name = "name_a"
rg_name = "rg_a"
},
{
name = "name_b"
rg_name = "rg_b"
}
]
}
data "azurerm_resource_group" "rg" {
# I don't see a reason to generate the intermediary map
# or to build another object in each map value. This is more
# simple. for_each with an object by name: rg_name
value = { for scope in local.scopes : scope.name => scope.rg_name }
name = each.value
}
output "list" {
# I don't see a need to use a splat expression here.
# You can just build a map with the index and the name
# here directly.
value = { for i, v in local.scopes : i => v.name }
}
In fact, if you don't need the resource group resources to be keyed by name, that can be simplified further to:
data "azurerm_resource_group" "rg" {
for_each = toset([for scope in local.scopes : scope.rg_name])
name = each.value
}

How to create Iterative Loop in Terraform

I have around 50 resources that I create in a Terraform script.
I need now to add diagnostic logging for each resource.
The following code is what I use:
data "azurerm_monitor_diagnostic_categories" "vnet-spoke01" {
resource_id = module.MOD-VNET-SPOKE01.id
}
resource "azurerm_monitor_diagnostic_setting" "vnet-spoke01" {
name = "diag-${module.MOD-VNET-SPOKE01.vnetName}"
target_resource_id = module.MOD-VNET-SPOKE01.id
log_analytics_workspace_id = module.MOD-LOG-ANALYTICS-WORKSPACE.id
dynamic "log" {
for_each = data.azurerm_monitor_diagnostic_categories.vnet-spoke01.logs
content {
category = log.value
retention_policy {
days = 0
enabled = false
}
}
}
dynamic "metric" {
for_each = data.azurerm_monitor_diagnostic_categories.vnet-spoke01.metrics
content {
category = metric.value
retention_policy {
days = 0
enabled = false
}
}
}
}
As you can see, I'm adding VNET of spoke-1 diagnostic settings.
Can someone kindly guide me as to how I can add a for-loop so it goes through each resource (that I'd put in an array or list) and run through it?
e.g.
variable "myResources" {
type = list(string)
default = ["module.MOD-VNET-SPOKE01", "module.MOD-VNET-SPOKE02" etc...]
}
for a in myResources
{
.... execute diagnostic routine
}
How could I do this?
Many thanks
Sadly you can't do that. You can't dynamically resolve strings (e.g. "module.MOD-VNET-SPOKE01") into resource identifiers (e.g. module.MOD-VNET-SPOKE01.id.
Your variable would already have to contain all ids for your loop to work:
variable "myResources" {
type = list(string)
default = ["<id-SPOKE01>", "<id-SPOKE02>", etc...]
}
or through locals:
locals {
myResources = [module.MOD-VNET-SPOKE01.id, module.MOD-VNET-SPOKE02.id etc...]
}

Terraform: How to set variables in a module based on a conditional?

I would like to pass a variable that will allow me to specify the list of VPC and subnet settings for an AWS instance. There are fixed VPC and subnet settings that make sense so I just want to allow a user to pick one using a single variable, i.e. use A or B.
For instance, let's say I have two available VPCs, and these are specified in a variables.tf file for a module my_instance:
variable "a_vpc_cidr_block" { default = "105.191.44.0/22" }
variable "a_vpc_id" { default = "id_a"}
variable "a_vpc_name" { default = "vpc_a" }
variable "a_subnet_availability_zone" { default = "us-east-1a" }
variable "a_subnet_cidr_block" { default = "105.191.25.0/25" }
variable "a_subnet_name" { default = "instance_A" }
variable "b_vpc_cidr_block" { default = "105.191.45.0/22" }
variable "b_vpc_id" { default = "id_b"}
variable "b_vpc_name" { default = "vpc_b" }
variable "b_subnet_availability_zone" { default = "us-east-1a" }
variable "b_subnet_cidr_block" { default = "105.191.35.0/25" }
variable "b_subnet_name" { default = "instance_B" }
The my_instance module will take a single input variable that an environment will specify, with a value of either 'A' or 'B' (is there a way to limit options for a variable to a list of values such as options=['A', 'B']?), and will be called like so in the terraform.tf for a Terraform configuration with a single instance:
module "my_instance" {
source = "../../modules/my_instance"
option = "A"
}
I want to now implement some logic within the module's main file (modules/my_instance/my_instance.tf) where it decides on which of the two collections of VPC and subnet settings it should use from the ones in modules/my_instance/variables.tf. I want to something like this (pseudocode):
if var.option == 'A'
vpc_cidr_block = var.a_vpc_cidr_block
vpc_id = var.a_vpc_id
vpc_name = var.a_vpc_name
subnet_availability_zone = var.a_subnet_availability_zone
subnet_cidr_block = var.a_subnet_cidr_block
subnet_name = var.a_subnet_name
else if var.option == 'B'
vpc_cidr_block = var.b_vpc_cidr_block
vpc_id = var.b_vpc_id
vpc_name = var.b_vpc_name
subnet_availability_zone = var.b_subnet_availability_zone
subnet_cidr_block = var.b_subnet_cidr_block
subnet_name = var.b_subnet_name
else
raise an error
# get a data resource identified by the VPC variables
data "aws_vpc" "instance_vpc" {
cidr_block = var.vpc_cidr_block
tags = {
Name = var.vpc_name
}
}
# get a data resource identified by the VPC variables
data "aws_subnet" "instance_subnet" {
vpc_id = var.vpc_id
cidr_block = var.subnet_cidr_block
availability_zone = var.subnet_availability_zone
tags = {
Name = var.subnet_name
}
}
# create an AWS key pair resource
resource "aws_key_pair" "instance_aws_key_pair" {
key_name = "component_key_${terraform.workspace}"
public_key = file("~/.ssh/terraform.pub")
}
# create the AWS EC2 instance
resource "aws_instance" "my_aws_instance" {
key_name = aws_key_pair.instance_aws_key_pair.key_name
ami = "ami-b12345"
instance_type = "t2.micro"
subnet_id = data.aws_subnet.instance_subnet.id
connection {
type = "ssh"
user = "terraform"
private_key = file("~/.ssh/terraform")
host = self.public_ip
}
tags = {
"Name" : "my_instance_name"
"Terraform" : "true"
}
}
Is this a matter of somehow using a count, something like this:
count = var.option == 'A'? 1 : 0
Is there a way to do this, or is there a better approach? I am very new to Terraform so I may be missing something obvious.
You have a couple of questions here.
Firstly, you should be able to use the newer, experimental custom validation rules to assert that a value is in a specific list of values.
Secondly, for determining which set of variables to use, I'd recommend going with a good old map in a local value.
For example,
locals {
vpc_info = {
"A" = {
vpc_cidr_block = var.a_vpc_cidr_block
vpc_id = var.a_vpc_id
vpc_name = var.a_vpc_name
subnet_availability_zone = var.a_subnet_availability_zone
subnet_cidr_block = var.a_subnet_cidr_block
subnet_name = var.a_subnet_name
}
"B" = {
vpc_cidr_block = var.b_vpc_cidr_block
vpc_id = var.b_vpc_id
vpc_name = var.b_vpc_name
subnet_availability_zone = var.b_subnet_availability_zone
subnet_cidr_block = var.b_subnet_cidr_block
subnet_name = var.b_subnet_name
}
}
}
Then you should be able to reference a specific field, within the chose option like the following
local.vpc_info[var.option].vpc_name
Let me know if this hits all your questions.

Looping using for or For_each | Terraform 0.12

I need to create multiple subnets in GCP within a network. I am planning to use Terraform 0.12 syntax for the same as follows:
project_name = [
"order-dev",
"ship-dev"
]
variable "project_name" {
type = list(string)
description = "Name of the project"
}
resource "google_compute_subnetwork" "subnetwork" {
name = "${var.project_name}-subnetwork"
ip_cidr_range = var.subnet_ip_cidr_range
region = var.region
network = google_compute_network.network.self_link
}
Is there anyway to use for or for_each expression in this scenario, i am aware of using element and doing this. But want to try a different approach if possible?
variable "project_name" {
type = set(string)
}
resource "google_compute_subnetwork" "subnetwork" {
for_each = var.project_name
name = "${each.key}-subnetwork"
ip_cidr_range = var.subnet_ip_cidr_range
region = var.region
network = google_compute_network.network.self_link
}
Try using the count meta-argument
With your sample, something like this
project_name = [
"order-dev",
"ship-dev"
]
variable "project_name" {
type = list(string)
description = "Name of the project"
}
resource "google_compute_subnetwork" "subnetwork" {
count = length(var.project_name)
name = "${var.project_name[count.index]}-subnetwork"
ip_cidr_range = var.subnet_ip_cidr_range
region = var.region
network = google_compute_network.network.self_link
}
Another option is for_each with key-value pairs, but you only have access to one value and I don't think you can use a list variable like your sample.
resource "google_compute_subnetwork" "subnetwork" {
for_each = {
order = "order-dev"
ship = "ship-dev"
}
name = "${key.value}-subnetwork"
ip_cidr_range = var.subnet_ip_cidr_range
region = var.region
network = google_compute_network.network.self_link
}
Resources:
https://www.terraform.io/docs/configuration/resources.html#count-multiple-resource-instances-by-count
https://blog.gruntwork.io/terraform-tips-tricks-loops-if-statements-and-gotchas-f739bbae55f9
https://www.terraform.io/docs/configuration/resources.html#for_each-multiple-resource-instances-defined-by-a-map-or-set-of-strings

Terraform resource inheritance

Is there a way to declare an abstract resource to inherit from?
Example:
resource "digitalocean_droplet" "worker_abstract" {
abstract = true // ???
name = "swarm-worker-${count.index}"
tags = [
"${digitalocean_tag.swarm_worker.id}"
]
// other config stuff
provisioner "remote-exec" {
//...
}
}
And then use declared resource with overridden variables:
resource "worker_abstract" "worker_foo" {
count = 2
name = "swarm-worker-foo-${count.index}"
tags = [
"${digitalocean_tag.swarm_worker.id}",
"${digitalocean_tag.foo.id}"
]
}
resource "worker_abstract" "worker_bar" {
count = 5
name = "swarm-worker-bar-${count.index}"
tags = [
"${digitalocean_tag.swarm_worker.id}"
"${digitalocean_tag.bar.id}"
]
}
This might be a little more "verbose" of a solution than what you're proposing, but this sounds like a perfect use-case for modules in 0.12.
You could create a module, say in the file worker/main.tf
variable "instances" {
type = number
}
variable "name" {
type = string
}
variable "tags" {
type = list(string)
default = []
}
resource "digitalocean_droplet" "worker" {
count = var.instances
name = "swarm-worker-${var.name}-${count.index}"
tags = var.tags
// other config stuff
provisioner "remote-exec" {
//...
}
}
then you could consume your module almost exactly like your example (let's say from the directory above worker)
module "worker_foo" {
source = "./worker"
instances = 2
name = "foo"
tags = [
"${digitalocean_tag.swarm_worker.id}",
"${digitalocean_tag.foo.id}"
]
}
module "worker_bar" {
source = "./worker"
instances = 5
name = "bar"
tags = [
"${digitalocean_tag.swarm_worker.id}"
"${digitalocean_tag.bar.id}"
]
}

Resources