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

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}

Related

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
}

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

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?

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.

How to exclude fields in get_fieldsets() based on user type in Django admin

I'm trying to use get_fieldsets to organize admin model pages. Using fieldsets is pretty satisfying, but I'm stuck with how to exclude some fields. Currently, I used if condition to check user type, and then return different fieldsets based on user type. I'm having the same codes to be repeated because of that. Is there a way to exclude a few fields in get_fieldsets?
admin.py
class StoreAdmin(admin.ModelAdmin):
...
def get_fieldsets(self, request, obj=None):
fieldsets = copy.deepcopy(super(StoreAdmin, self).get_fieldsets(request, obj))
if request.user.is_superuser:
return (
[
('Basic Information', {
'fields': (
('status', 'review_score', 'typ'),
('businessName', 'relatedName'),
('mKey'),
)
}),
('Additional Options', {
'fields': (
('affiliate_switch', 'is_affiliated', 'affiliate',),
)
}),
]
)
else:
return (
[
('Basic Information', {
'fields': (
('status', 'review_score', 'typ'),
('businessName', 'relatedName'),
('mKey'),
)
}),
]
)
If you only want to exclude fields you can use get_fields instead as following:
def get_fields(self, request, obj=None):
fields = super(ClientAdmin, self).get_fields(request, obj)
if obj:
fields_to_remove = []
if request.user.is_superuser:
fields_to_remove = ['field1', 'field2', 'etc', ]
for field in fields_to_remove:
fields.remove(field)
return fields
Edit:
Same logic could be used for get_fieldsets
My way to solve this:
def get_fieldsets(self, request, obj=None):
fieldsets = super(AccountInline, self).get_fieldsets(request, obj)
fields_to_remove = ['field1', 'field2', 'etc', ]
if request.user.is_superuser:
# select your way, my:[3][1]
fieldsets[3][1]['fields'] = tuple(x for x in fieldsets[3][1]['fields'] if not x in fields_to_remove)
return fieldsets

Using Curry to Define Grails Tags

I have a grails tag library TpTagLib and in it I want to define 4 new tags that differ only in one constant value, so I tried to use curry.
But there is an exception: groovy.lang.MissingPropertyException: No such property: attr for class: TpTagLib
Does anyone have any idea why this exception occurs?
Here is the code:
def ifPermsTag = { permissions, attr, body ->
def user = attr?.user ?: session.userInstance
if( !user ) return false
if( !securityService.hasPermissions(user,permissions) ) return false
out << body()
return true
}
def canAdminRequestmaps = ifPermsTag.curry(Permission.CAN_ADMIN_REQUESTMAPS)
def canAdminCorporations = ifPermsTag.curry(Permission.CAN_ADMIN_CORPS)
def canAdminUsers = ifPermsTag.curry(Permission.CAN_ADMIN_USERS)
def canAdminDevices = ifPermsTag.curry(Permission.CAN_ADMIN_DEVICES)
Cool technique. You just need to make ifPermsTag private so it's not considered a candidate to be a usable tag method:
private ifPermsTag = { permissions, attr, body ->
...
}
Tags can have no parameters, or an 'attr' parameter, or an 'attr' and 'body' parameters but other signatures are invalid.

Resources