for_each for aws_iam_policy data block - not working (dynamic code inside loop) - foreach

I am using terraform 0.14
Here is a working version of my code:
#MODULE DEFINITION (in a folder named "iam-group-with-policies")
resource "aws_iam_policy" "custom" {
count = length(var.custom_group_policies)
name = var.custom_group_policies[count.index]["name"]
policy = var.custom_group_policies[count.index]["policy"]
description = lookup(var.custom_group_policies[count.index], "description", null)
tags = var.tags
}
#RESOURCE DEFINITION
module "iam_group_with_custom_policies_S3" {
source = "../modules/iam-group-with-policies"
name = "S3_viewer"
group_users = ["user8", "user9"]
custom_group_policies = [
{
name = "Custom_S3viewer"
policy = data.aws_iam_policy_document.sample.json
},
]
}
#DATA BLOCK
data "aws_iam_policy_document" "sample" {
statement {
actions = [
"s3:ListBuckets",
]
resources = ["*"]
}
}
using that data block defined above is mentioned here - https://learn.hashicorp.com/tutorials/terraform/aws-iam-policy?in=terraform/aws#refactor-your-policy
It is working perfectly fine, I can have multiple resource definitions pointing to different data blocks.
Now I wanted to extend this code to add more variables to it to support multiple policy creation using single block of resource, so I thought of using for_each:
#MODULE DEFINITION
resource "aws_iam_policy" "custom" {
count = length(var.custom_group_policies)
name = var.custom_group_policies[count.index]["name"]
policy = var.custom_group_policies[count.index]["policy"]
description = lookup(var.custom_group_policies[count.index], "description", null)
tags = var.tags
}
resource "aws_iam_group" "this" {
count = var.create_group ? 1 : 0
name = var.name
}
resource "aws_iam_group_membership" "this" {
count = length(var.group_users) > 0 ? 1 : 0
group = local.group_name
name = var.name
users = var.group_users
}
################################
# IAM group policy attachements
################################
resource "aws_iam_group_policy_attachment" "custom_arns" {
count = length(var.custom_group_policy_arns)
group = local.group_name
policy_arn = element(var.custom_group_policy_arns, count.index)
}
resource "aws_iam_group_policy_attachment" "custom" {
count = length(var.custom_group_policies)
group = local.group_name
policy_arn = element(aws_iam_policy.custom.*.arn, count.index)
}
#RESOURCE DEFINITION
module "iam_group_with_custom_policies_looptest" {
source = "../modules/iam-group-with-policies"
for_each = var.user_groups
name = each.key
group_users = each.value.user_list
custom_group_policies = [
{
name = each.value.policy_name
policy = each.value.policy
# policy = lookup(var.user_groups_policies, each.key, [""])
# policy = {trim(each.value.policy, "\"")}
},
]
}
#VARIABLE DEFINITION
variable "user_groups" {
description = "Map of user groups and associated custom policies"
type = map(any)
default = {
EB_viewer = {
policy_name = "Custom_EB"
user_list = ["user7", "user9"]
policy = data.aws_iam_policy_document.custom_eb.json
},
EC2_viewer = {
policy_name = "Custom_EC2viewer"
user_list = ["user8", "user9"]
policy = data.aws_iam_policy_document.custom_ec2viewer.json
}
}
}
#DATA BLOCK
data "aws_iam_policy_document" "custom_ec2viewer" {
statement {
actions = [
"ec2:DescribeFastLaunchImages",
"ec2:DescribeInstances"
"ec2:DescribeConversionTasks"
]
resources = ["*"]
}
}
data "aws_iam_policy_document" "custom_eb" {
statement {
actions = [
"elasticbeanstalk:DescribePlatformVersion",
"elasticbeanstalk:DescribeAccountAttributes",
"elasticbeanstalk:RetrieveEnvironmentInfo"
]
resources = ["*"]
}
}
but this is failing with following error:
Error: "policy" contains an invalid JSON policy
on ../modules/iam-group-with-policies/main.tf line 45, in resource "aws_iam_policy" "custom":
45: policy = var.custom_group_policies[count.index]["policy"]
Error: "policy" contains an invalid JSON policy
on ../modules/iam-group-with-policies/main.tf line 45, in resource "aws_iam_policy" "custom":
45: policy = var.custom_group_policies[count.index]["policy"]
I feel the problem is with the line in RESOURCE BLOCK where I mention:
policy = each.value.policy
here it is considering the literal string that will come out as a value of "each.value.policy" i.e. "data.aws_iam_policy_document.custom_eb.json" OR "data.aws_iam_policy_document.custom_ec2viewer.json". but I want it to execute this and get the value that resides in that data block.
To all the terraform gods out there, any suggestions on how to achieve this?

Related

Is there a way to simplify nested for-in expressions used in a for_each Terraform attribute?

Given the following code:
locals {
roles = [<a list of aws_iam_role resource objects...>]
policies = [
"AmazonEC2ContainerRegistryReadOnly",
"AmazonS3FullAccess",
<...>
]
}
# Attach all policies to all roles
resource "aws_iam_role_policy_attachment" "role_policy_attachments" {
for_each = { for role_policy in flatten([
for role in local.roles : [
for policy in local.policies : {
role = role.name
policy = policy
}
]
]) :
"${role_policy.role}-${role_policy.policy}" => role_policy
}
role = each.value.role
policy_arn = "arn:aws:iam::aws:policy/${each.value.policy}"
}
Is there a way to simplify the expression inside the for_each attribute of aws_iam_role_policy_attachment.role_policy_attachments?
E.g., in Python I would write the following more simple expression for which I cannot find the equivalent Terraform/HCL:
for_each = {f"{role.name}-{policy}": {"role": role.name, "policy": policy} for role in roles for policy in policies}

Terraform virtual wan azurerm_vpn_gateway_connection

Looking to create dynamic Azure vwan vpn site connections. I am using this map to define the sites and links. I would like to be able to add and remove sites and links as my requirements change.
virtual_wan_vpn_sites = {
vwan_site_dc = {
name = "site-shr-infra-dc"
location_map_key = "primary"
resource_group_key = "rg_vwan"
vwan_key = "vwan"
device_vendor = "Fortigate"
device_model = "FGT60F"
links = {
link_1 = {
name = "link-shr-infra-dc-1"
bgp_asn = "64512"
public_ip_address = "1.1.1.1"
bgp_peering_ip = "10.10.100.1"
}
link_2 = {
name = "link-shr-infra-dc-2"
bgp_asn = "64513"
public_ip_address = "2.2.2.2"
bgp_peering_ip = "10.10.100.100"
}
}
}
}
and this code to create the sites, links, and connections.
# Create vpn site(s)
module "virtualWanVpnSites" {
source = "../../modules/networking/virtual_wan_vpn_site"
for_each = var.virtual_wan_vpn_sites
name = each.value.name
location = var.location_map[each.value.location_map_key]
resource_group_name = azurerm_resource_group.resource_group[each.value.resource_group_key].name
virtual_wan_id = module.virtualWan[each.value.vwan_key].virtual_wan_id
vwan_key = each.value.vwan_key
vwan_sites = each.value.links
device_vendor = each.value.device_vendor
device_model = each.value.device_model
vpn_gateways = values(module.virtualHubVpn)[*].virtual_hub_vpn_gateway_id
tags = merge(lookup(each.value, "tags", {}), local.tags)
../../modules/networking/virtual_wan_vpn_site
# Virtual Wan vpn site
resource "azurerm_vpn_site" "vwan_vpn_site" {
name = var.name
location = var.location
resource_group_name = var.resource_group_name
virtual_wan_id = var.virtual_wan_id
device_vendor = var.device_vendor
device_model = var.device_model
tags = local.tags
dynamic "link" {
for_each = try(var.vwan_sites, {})
content {
name = link.value.name
ip_address = link.value.public_ip_address
bgp {
asn = link.value.bgp_asn
peering_address = link.value.bgp_peering_ip
}
}
}
}
# vhub vpn gateway connection
resource "azurerm_vpn_gateway_connection" "vhub_vpn_gateway_connection" {
for_each = toset(var.vpn_gateways)
name = "example"
vpn_gateway_id = each.key
remote_vpn_site_id = azurerm_vpn_site.vwan_vpn_site.id
*dynamic "vpn_link" {
for_each = try(azurerm_vpn_site.vwan_vpn_site.link, [])
content {
name = vpn_link.value.name
vpn_site_link_id = vpn_link.value.id
bgp_enabled = true
}
}*
}
The vwan site and links get created successfully, however, the link connections error out with this:
Error: Missing required argument
│
│ with module.virtualWanVpnSites["vwan_site_dc"].azurerm_vpn_gateway_connection.vhub_vpn_gateway_connection["/subscriptions/xxx/resourceGroups/xxx/providers/Microsoft.Network/vpnGateways/vpn-shr-infra-usce"],
│ on ../../modules/networking/virtual_wan_vpn_site/module.tf line 25, in resource "azurerm_vpn_gateway_connection" "vhub_vpn_gateway_connection":
│ 25: resource "azurerm_vpn_gateway_connection" "vhub_vpn_gateway_connection" {
│
│ The argument "vpn_link.1.vpn_site_link_id" is required, but no definition
│ was found.
╵
╷
│ Error: Missing required argument
│
│ with module.virtualWanVpnSites["vwan_site_dc"].azurerm_vpn_gateway_connection.vhub_vpn_gateway_connection["/subscriptions/xxx/resourceGroups/xxx/providers/Microsoft.Network/vpnGateways/vpn-shr-infra-use2"],
│ on ../../modules/networking/virtual_wan_vpn_site/module.tf line 25, in resource "azurerm_vpn_gateway_connection" "vhub_vpn_gateway_connection":
│ 25: resource "azurerm_vpn_gateway_connection" "vhub_vpn_gateway_connection" {
│
│ The argument "vpn_link.1.vpn_site_link_id" is required, but no definition
│ was found.
It appears that the resource azurerm_vpn_gateway_connection wants the vpn_site_link_id like this vpn_site_link_id = azurerm_vpn_site.example.link[0].id.
https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/vpn_gateway_connection
I can see the links in the state file like this:
"module": "module.virtualWanVpnSites[\"vwan_site_dc\"]",
"mode": "managed",
"type": "azurerm_vpn_site",
"name": "vwan_vpn_site",
"provider": "provider[\"registry.terraform.io/hashicorp/azurerm\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"address_cidrs": [],
"device_model": "FGT60F",
"device_vendor": "Fortigate",
"id": "/subscriptions/xxx/resourceGroups/xxx/providers/Microsoft.Network/vpnSites/site-shr-infra-dc",
"link": [
{
"bgp": [
{
"asn": 64512,
"peering_address": "10.10.100.1"
}
],
"fqdn": "",
"id": "/subscriptions/xxx/resourceGroups/xxx/providers/Microsoft.Network/vpnSites/site-shr-infra-dc/vpnSiteLinks/link-shr-infra-dc-1",
"ip_address": "1.1.1.1",
"name": "link-shr-infra-dc-1",
"provider_name": "",
"speed_in_mbps": 0
},
{
"bgp": [
{
"asn": 64513,
"peering_address": "10.10.100.100"
}
],
"fqdn": "",
"id": "/subscriptions/xxx/resourceGroups/xxx/providers/Microsoft.Network/vpnSites/site-shr-infra-dc/vpnSiteLinks/link-shr-infra-dc-2",
"ip_address": "2.2.2.2",
"name": "link-shr-infra-dc-2",
"provider_name": "",
"speed_in_mbps": 0
}
],
Any ideas would be greatly appreciated. Thanks.
Please check this terraform-azurerm-virtual-wan · GitHub
Initially , check if the version is the issue and see with v2.78.0 version of the Terraform Provider.
Try also setting address_cidrs to create an address space
I have used address_cidrs = ["10.0.0.0/16"] for azurerm_vpn_site
And address_prefix = "10.0.0.0/24" in azurerm_virtual_hub.
vpn_sites = [
{
name = "site1"
links = [
{
name = "site1-primary-endpoint"
ip_address = "20.20.20.20"
bgp = [
{
asn = 65530
peering_address = "169.254.21.2"
}
]
},
{
name = "site1-secondary-endpoint"
ip_address = "21.21.21.21"
bgp = [
{
asn = 65530
peering_address = "169.254.22.2"
}
]
}
]
}
]
As you said vpn_link is expected in the format vpn_site_link_id = vpn_link.value[0].id
Please also check the Reference : Terraform for-each with list of objects - Stack Overflow

Create multiple postgres objects using Terraform and for_each meta argument

I would like to create several postgres users, databases and passwords and output them with terraform. As of now, what I'm doing is this (for one of the application database):
main.tf
resource "random_password" "secret_password_1" {
length = 16
special = true
override_special = "!#$%&*()-_=+[]{}<>:?"
}
resource "postgresql_role" "application_db_role_1" {
name = "db_user_1"
login = true
password = random_password.secret_password_1.result
encrypted_password = true
}
resource "postgresql_database" "application_db_1" {
name = "db_name_1"
owner = postgresql_role.application_db_role_1.name
allow_connections = true
}
outputs.tf
output "db_name_1" {
value = postgresql_database.application_db_1.name
}
output "db_user_1" {
value = postgresql_role.application_db_role_1.name
}
output "db_1_password" {
value = random_password.secret_password_1.result
sensitive = true
}
Would it be possible to use for_each meta argument to make this code simpler when having several databases, starting from a list of DB names such as:
locals {
list_of_databases = ["db_1", "db_2"]
}
Finally found a clean solution:
main.tf
locals {
list_of_databases = ["one_database_example"]
}
resource "random_password" "password" {
for_each = toset(local.list_of_databases)
length = 16
special = true
override_special = "!#$%&*()-_=+[]{}<>:?"
}
resource "postgresql_role" "application_db_role" {
for_each = toset(local.list_of_databases)
name = "${each.value}_user"
login = true
password = random_password.password[each.value].result
encrypted_password = true
}
resource "postgresql_database" "application_db" {
for_each = toset(local.list_of_databases)
name = "${each.value}_db"
owner = postgresql_role.application_db_role[each.value].name
allow_connections = true
}
And outputs
output "shared_db_application_db_names" {
value = {
for v in local.list_of_databases : v => postgresql_database.application_db[v].name
}
}
output "shared_db_application_db_roles" {
value = {
for v in local.list_of_databases : v => postgresql_role.application_db_role[v].name
}
}
output "shared_db_application_db_passwords" {
value = {
for v in local.list_of_databases : v => random_password.password[v].result
}
sensitive = true
}

Array element from lua

I'm making a fivem server. But when i tru to pick the job.grade.name he says No grades.
QBShared.Jobs = {
["unemployed"] = {
label = "Werkloos",
grades = {
[0] = {
name = 'Werkloos',
payment = 10,
},
},
defaultDuty = true,
},
["police"] = {
label = "Politie",
grades = {
[0] = {
name = "Politie - Student", **Want to pick this**
payment = 200,
},
[1] = {
name = 'Aspirant',
payment = 300,
},
[2] = {
name = 'Agent',
payment = 400,
},
[3] = {
name = 'Hoofd Agent',
payment = 400,
},
[4] = {
name = 'Brigadier',
payment = 400,
},
[5] = {
name = 'Inspecteur',
payment = 400,
},
[6] = {
name = 'Hoofd Inspecteur',
payment = 400,
},
[7] = {
name = 'Commissaris',
payment = 400,
},
[8] = {
name = 'Hoofd Commissaris',
payment = 400,
},
[9] = {
name = 'Eerste Hoofd Commissaris',
isboss = true,
payment = 400,
},
},
defaultDuty = true,
So people can type /baan and then the see Baan: Politie
What i want is The must see Baan: Politie - Politie Student
QBCore.Commands.Add("baan", "Kijk wat je baan is", {}, false, function(source, args)
local Player = QBCore.Functions.GetPlayer(source)
TriggerClientEvent('chatMessage', source, "SYSTEM", "warning", "Baan: "..Player.PlayerData.job.label .. ' - ' ..Player.PlayerData.job.grade.name)
end)
Someone can help me? Because i want to learn more about lua but dont get this to work..
'Jobs' needs a string key to access it
and 'grades' needs a numerical index
Player .PlayerData .Jobs [job] .grades [grade] .name
TriggerClientEvent('chatMessage', source, "SYSTEM", "warning", "Baan: "..Player.PlayerData.job.label .. ' - ' ..Player.PlayerData.Jobs[job].grades[grade].name)
I'm assuming that somehow within your game engine, these values get parsed into PlayerData. That'll depend on the functions contained within fivem, and that you've used them properly. Otherwise, to access the raw table data, it's more like this:
print( QBShared.Jobs['police'].label )
Politie
print( QBShared.Jobs['police'].grades[0].name )
Politie - Student
print( QBShared.Jobs['police'].grades[0].payment )
200
if the game rearranges those during import into PlayerData, it might be
Player.PlayerData[job][grade].name
but it's very likely it remains in the original syntax illustrated above.

Module: S3 bucket for_each with different policy on each

I am trying to see if this is possible
Terraform 0.12.28
bucket_names = {
"bucket1" = "test_temp"
"bucket2" = "test_jump"
"bucket3" = "test_run"
}
module "s3" {
source = "./modules/s3"
name = var.bucket_names
region = var.region
tags = var.tags
}
module
resource "aws_s3_bucket" "s3" {
for_each = var.name
bucket = "${each.value}"
region = var.region
request_payer = "BucketOwner"
tags = var.tags
versioning {
enabled = false
mfa_delete = false
}
}
Works JUST fine, each bucket is created. BUT now my question is how can i keep a clean way of applying specific policies to each bucket in the list?
policy 1 => test1_temp
policy 2 => test2_jump
policy 3 => test2_run
Each policy will be slightly different
My thoughts, regex to find _temp and apply policy1 etc
I'm with #ydaetkocR on this. It's complexity for no gain in a real system, but it could be interesting for learning.
terraform.tfvars
bucket_names = {
"bucket1" = "test_temp"
"bucket2" = "test_jump"
"bucket3" = "test_run"
}
bucket_policy = {
"bucket1" = <<POLICY
{
"Version": "2012-10-17",
"Id": "MYBUCKETPOLICY",
"Statement": [
{
"Sid": "IPAllow",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": "arn:aws:s3:::test_temp/*",
"Condition": {
"IpAddress": {"aws:SourceIp": "8.8.8.8/32"}
}
}
]
}
POLICY
"bucket2" = "..."
"bucket3" = "..."
}
root module
module "s3" {
source = "./modules/s3"
name = var.bucket_names
policy = var.bucket_policies
region = var.region
tags = var.tags
}
modules/s3
resource "aws_s3_bucket" "s3" {
for_each = var.name
bucket = each.value
region = var.region
request_payer = "BucketOwner"
tags = var.tags
versioning {
enabled = false
mfa_delete = false
}
}
resource "aws_s3_bucket_policy" "s3" {
for_each = var.policy
bucket = aws_s3_bucket.s3[each.key].id
policy = each.value
}
The trick here is having the same keys for names, policies, and resource instances. You don't have to use two maps, but it's likely the simplest example.
As you can see above it would be a nuisance doing this because you'd have to manually synch the bucket names in the policies or write some very elaborate code to substitute the values in your module.

Resources