It's this possible? - terraform - terraform

I will like to create a policy resource depending on how many projects are described. Also, the policy must be different between these projects, for example, I have this far:
projects = ["project1", "project2"]
projects_resources = ["project1/X", "project1/Y", "project2/X", "project2/Y"]
data source:
data "template_file" "project_policy" {
template = file("${path.module}/project_policy.tpl")
vars = {
projects_resources = join(",", var.projects_resources)
}
}
project_policy.tpl:
...
"Resource": [
%{ for projects_resources in slice(split(",", projects_resources), 0, length(split(",", projects_resources))-1) }
"arn:aws:...${projects_resources}}",
%{ endfor }
%{ for projects_resources in slice(split(",", projects_resources), length(split(",", projects_resources))-1, length(split(",", projects_resources))) }
"arn:aws:...${projects_resources}}"
%{ endfor }
policy resource:
resource "aws_iam_policy" "iam_policies_projects" {
count = length(var.projects)
name = "policy_${var.projects[count.index]}"
policy = data.template_file.policy_projects.rendered
}
Current Outcome:
policy_proyect1 and policy_proyect2 created with:
"Resource":
"arn:aws:...proyect1/X",
"arn:aws:...proyect1/Y",
"arn:aws:...proyect2/X",
"arn:aws:...proyect2/Y"
what I would like to achieve:
policy_proyect1 with:
"Resource":
"arn:aws:...proyect1/X",
"arn:aws:...proyect1/Y",
policy_proyect2 with:
"Resource":
"arn:aws:...proyect2/X",
"arn:aws:...proyect2/Y",
I don't know if there is a way to create 2 rendered files for each policy in which only the values that correspond to the projects are passed as arguments.
Thanks!

You can create new local map and then use that for the template:
variable "projects" {
default = ["project1", "project2"]
}
variable "projects_resources" {
default = ["project1/X", "project1/Y", "project2/X", "project2/Y", "project2/Z"]
}
locals {
proj_res_map = {for p in var.projects:
p => [for v in var.projects_resources : v if length(regexall("${p}.*", v)) > 0]
}
}
which gives:
proj_res_map = {
"project1" = [
"project1/X",
"project1/Y",
]
"project2" = [
"project2/X",
"project2/Y",
"project2/Z",
]
}
Then use it:
data "template_file" "project_policy" {
for_each = local.proj_res_map
template = file("${path.module}/project_policy.tpl")
vars = {
projects_resources = join(",", each.value)
}
}
Then iam_policies_projects probably will also need to be adjusted to account for for_each in template_file:
resource "aws_iam_policy" "iam_policies_projects" {
for_each = local.proj_res_map
name = "policy_${each.key}"
policy = data.template_file.policy_projects[each.key].rendered
}

Related

Terraform nested loop conditionnal

I have written code that works well in order to create Azure Firewall Network Rules via Terraform. Weeks later i'm asked to add an attribute "expiration" in order to enable users to create temporary network rules.
If the expiration field is 0, means the rule is permanent, if not, i compare the date with the sysdate.
I have tried nearly every possible "for_each" with "for" and "if" combinations, but nothing seems to work. That "expiration" attribute seems inaccessible from outside the loop.
Here are the files of my code:
main.tf
module.tf (edits must be done here, in the dynamic "rule" bloçk of the network_rule_collection block, all other files have been simplified and are here only for better understanding of the code logic)
variables.tf
net_rules.yaml
fw_collections.yaml
Main.tf
locals {
collections = yamldecode(file("../environments/fw_collections.yaml"))
netrules = yamldecode(file("../environments/net_rules.yaml"))
apprules = yamldecode(file("../environments/app_rules.yaml"))
}
module "tdf_az_firewall_policy_rule_collection_group" {
source = "../../modules/tdf_az_firewall_policy_rule_collection_group"
fw_policy_id = module.tdf_az_firewall_policy.firewall_policy_id
fw_c = local.collections.FW_Collections
fw_nr = local.netrules.FW_NET_Rules
fw_ar = local.apprules.FW_APP_Rules
rgname = data.azurerm_resource_group.ipgrg.name
sub = element(split("/", module.tdf_az_virtual_hub_module.virtual_hub_id), 2)
depends_on = [module.tdf_az_firewall_policy, module.tdf_az_firewall]
}
Module.tf
resource "azurerm_firewall_policy_rule_collection_group" "fw-prcg" {
for_each = var.fw_c
name = each.key
firewall_policy_id = var.fw_policy_id
priority = each.value[0]
dynamic "network_rule_collection" {
for_each = { for nr, name in var.fw_nr : nr => name if nr == each.key }
content {
name = each.key
priority = each.value[0]
action = each.value[1]
dynamic "rule" {
for_each = { for x, y in var.fw_nr[each.key] : x => y.expiration if timecmp(y.expiration, timestamp()) == 1 }
content {
name = rule.value.name
protocols = rule.value.protocols
source_ip_groups = [for s in rule.value.source_ip_groups : join("/", ["/subscriptions", var.sub, "resourceGroups", var.rgname, "providers/Microsoft.Network/ipGroups", s])]
destination_ip_groups = [for d in rule.value.destination_ip_groups : join("/", ["/subscriptions", var.sub, "resourceGroups", var.rgname, "providers/Microsoft.Network/ipGroups", d])]
destination_ports = rule.value.destination_ports
}
}
}
}
dynamic "application_rule_collection" {
for_each = { for ar, name in var.fw_ar : ar => name if ar == each.key }
content {
name = each.key
priority = each.value[0]
action = each.value[1]
dynamic "rule" {
for_each = var.fw_ar[each.key]
content {
name = rule.value.name
dynamic "protocols" {
for_each = rule.value.protocols
content {
type = protocols.value.type
port = protocols.value.port
}
}
source_ip_groups = [for s in rule.value.source_ip_groups : join("/", ["/subscriptions", var.sub, "resourceGroups", var.rgname, "providers/Microsoft.Network/ipGroups", s])]
destination_fqdns = rule.value.destination_fqdns
destination_urls = rule.value.destination_urls
terminate_tls = rule.value.terminate_tls
web_categories = rule.value.web_categories
destination_fqdn_tags = rule.value.destination_fqdn_tags
}
}
}
}}
variables.tf
variable "fw_policy_id" {}
variable "fw_c" {}
variable "fw_nr" {}
variable "fw_ar" {}
variable "sub" {}
variable "rgname" {}
netrules.yaml
FW_NET_Rules:
Blacklist-Net:
- name: blacklisted_inbound_ips_rule_inbound
source_ip_groups: ["blacklisted_inbound_ips"]
destination_ip_groups: ["Any-ip"]
estination_ports: ["*"]
protocols: ["Any"]
expiration: 0
- name: k8saas_backlisted_idps_rule_inbound
source_ip_groups: ["k8saas_blacklist_ip_inbound"]
destination_ip_groups: ["Any-ip"]
destination_ports: ["*"]
protocols: ["Any"]
expiration: 0
Mobility:
- name: Mobility-LAN-VNET
source_ip_groups: ["Mobility-LAN"]
destination_ip_groups: ["Mobility-VNET"]
destination_ports: ["443"]
protocols: ["TCP"]
expiration: "2022-12-12T00:00:00Z"
- name: Mobility-LAN-To-Data
source_ip_groups: ["Mobility-LAN"]
destination_ip_groups: ["Data-VNET"]
destination_ports: ["443"]
protocols: ["TCP"]
expiration: "2022-12-12T00:00:00Z"
fw_collections.yaml
FW_Collections:
Blacklist-Net: [100, "Deny"]
Mobility: [105, "Allow"]

How to refer conditional resources on another resource in terraform

I created 2 buckets. One will create always and one will create when env is QA.
resource "aws_s3_bucket" "bucket_always" {
bucket_prefix = format("bucket.always")
acl = "private"
versioning {
enabled = true
}
server_side_encryption_configuration {
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
}
}
}
}
resource "aws_s3_bucket" "bucket_conditional" {
count = var.name == "qa" ? 1 : 0
bucket_prefix = format("bucket.conditional")
acl = "private"
versioning {
enabled = true
}
server_side_encryption_configuration {
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
}
}
}
}
Now how can I create an IAM and add both buckets arn? The below policy won't work because of conditional bucket
data "aws_iam_policy_document" "test_policy_document" {
statement {
actions = [
"s3:ListBucket",
]
resources = [
aws_s3_bucket.bucket_always.arn,
aws_s3_bucket.bucket_conditional.arn,
]
}
statement {
actions = [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
]
resources = [
"${aws_s3_bucket.bucket_always.arn}/*",
"${aws_s3_bucket.bucket_conditional.arn}/*",
]
}
}
Split the IAM statements up, and use dynamic blocks. You don't really need to iterate over a specific list of values here, just pass it a list with one value if you want to create the block, or an empty list if you don't want to create the block. For example:
data "aws_iam_policy_document" "test_policy_document" {
statement {
actions = [
"s3:ListBucket",
]
resources = [
aws_s3_bucket.bucket_always.arn
]
}
statement {
actions = [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
]
resources = [
"${aws_s3_bucket.bucket_always.arn}/*"
]
}
dynamic "statement" {
for_each = var.name == "qa" ? ["qa"] : []
content {
actions = [
"s3:ListBucket",
]
resources = [
aws_s3_bucket.bucket_conditional[0].arn
]
}
}
dynamic "statement" {
for_each = var.name == "qa" ? ["qa"] : []
content {
actions = [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
]
resources = [
"${aws_s3_bucket.bucket_conditional[0].arn}/*"
]
}
}
}

Terraform access map

I am trying to access all groups and create groups in the below terraform code. But I am facing error This object does not have an attribute named "groups". Is there any logic I am missing here in the resource "og" "example"
for_each=toset(flatten(local.instances[*].groups)). Thanks
locals {
instances = {
test1 = {
baseUrl = "url1"
subDomain = "sd1"
groups = [
"app1",
"app2",
],
}
test2 = {
baseUrl = "url2"
subDomain = "sd2"
groups = [
"t1",
"t2",
],
}
}
}
resource "og" "example" {
for_each = toset(flatten(local.instances[*].groups))
name = each.value
description = "${each.value}-access"
}
Your local variable is a map, not a list. So it should be:
for_each = toset(flatten(values(local.instances)[*].groups))

Terraform AWS IAM Iterate Over Rendered JSON Policies

How can I iterate over the JSON rendered data.aws_iam_policy_document documents within an aws_iam_policy?
data "aws_iam_policy_document" "role_1" {
statement {
sid = "CloudFront1"
actions = [
"cloudfront:ListDistributions",
"cloudfront:ListStreamingDistributions"
]
resources = ["*"]
}
}
data "aws_iam_policy_document" "role_2" {
statement {
sid = "CloudFront2"
actions = [
"cloudfront:CreateInvalidation",
"cloudfront:GetDistribution",
"cloudfront:GetInvalidation",
"cloudfront:ListInvalidations"
]
resources = ["*"]
}
}
variable "role_policy_docs" {
type = list(string)
description = "Policies associated with Role"
default = [
"data.aws_iam_policy_document.role_1.json",
"data.aws_iam_policy_document.role_2.json",
]
}
locals {
role_policy_docs = { for s in var.role_policy_docs: index(var.role_policy_docs, s) => s}
}
resource "aws_iam_policy" "role" {
for_each = local.role_policy_docs
name = format("RolePolicy-%02d", each.key)
description = "Custom Policies for Role"
policy = each.value
}
resource "aws_iam_role_policy_attachment" "role" {
for_each = { for p in aws_iam_policy.role : p.name => p.arn }
role = aws_iam_role.role.name
policy_arn = each.value
}
This example has been reduced down to the very basics. The policy documents are dynamically generated with the source_json and override_json conventions. I cannot simply combine the statements into a single policy document.
Terraform Error:
Error: "policy" contains an invalid JSON policy
on role.tf line 35, in resource "aws_iam_policy" "role":
35: policy = each.value
This:
variable "role_policy_docs" {
type = list(string)
description = "Policies associated with Role"
default = [
"data.aws_iam_policy_document.role_1.json",
"data.aws_iam_policy_document.role_2.json",
]
}
Is literally defining those default values as strings, so what you're getting is this:
+ role_policy_docs = {
+ 0 = "data.aws_iam_policy_document.role_1.json"
+ 1 = "data.aws_iam_policy_document.role_2.json"
}
If you tried removing the quotations around the data blocks, it will not be valid because you cannot use variables in default definitions. Instead, assign your policy documents to a new local, and use that local in your for loop instead:
locals {
role_policies = [
data.aws_iam_policy_document.role_1.json,
data.aws_iam_policy_document.role_2.json,
]
role_policy_docs = {
for s in local.role_policies :
index(local.role_policies, s) => s
}
}

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