So I have an issue, and need a bit of help. My knowledge of Terraform isn't all that great, and I need some advice on how to make this happen. I have a map variable with the following information in it:
In variables.tf:
variable "users" {
type = "map"
}
in the Terraform.tfvars:
users = {
"user1" = {
},
"user2" = {
instance = ["instance_size"]
},
"user3" = {
bucket = ["bucket1"]
},
"user4" = {
bucket = ["bucket1", "bucket2"]
},
"user5" = {
instance = ["instance_size"]
bucket = ["bucket1", "bucket2", "bucket3"]
}
And what I want to do is to take the bucket information out of that variable and apply a AWS policy kinda like so:
data "aws_iam_policy_document" "sm_s3_bucket" {
for_each = var.users
statement {
actions = [
"s3:GetObject",
"s3:PutObject",
"s3:ListBucket",
"s3:GetBucketLocation"
]
resources = formatlist("arn:aws:s3:::%s", each.value.bucket)
}
}
How do I go about getting only the bucket info for each user out of the variable and into the resource command? Thanks!
If the question is how to create a policy document for users who have bucket specified, it could be done for example like this:
for_each = {
for user, attr in var.users : user => attr if contains(keys(attr), "bucket")
}
Related
I recently started working with terraform and am currently working on adding people to groups for access management. I've got a bunch of user_principal_names, basically unique identifiers within AzureAD, of people that are already added to AzureAD. I now need to add these people to azuread_group objects. I've got it working by requesting each person's azuread_user object in the top of my main.tf file, and then adding them to groups. This isn't a really scalable solution however, and I was wondering if there is a more dynamic method, can I for example work with an array containing these user_principal_name objects? Or do I always first have individually get each azuread_user object?
data "azuread_user" "jack" {
user_principal_name = "JackDoe#Company.Com"
}
data "azuread_user" "john" {
user_principal_name = "JohnDoe#Company.Com"
}
resource "azuread_group" "admins" {
display_name = "admins"
owners = [
data.azuread_user.jack.object_id,
]
security_enabled = true
prevent_duplicate_names = true
members = [
data.azuread_user.john.object_id,
]
}
This is an example of how I've got it working right now.
main.tf:
data "azuread_users" "users" {
ignore_missing = false
user_principal_names = var.ad_users
}
resource "azuread_group" "ad_group" {
display_name = "New_AD_Group"
security_enabled = true
members = data.azuread_users.users.object_ids
}
terraform.tfvars.json:
{
"ad_users": [
"user_1#domain.com",
"user_2#domain.com"
]
}
variables.tf:
variable "ad_users" {
type = list(any)
}
Also, here could be more complex solution for variable construction like:
{
"assignments": {
"AD_GROUP_1": {
"users": [
"user_1#domain.com",
"user_2#domain.com"
]
},
"AD_GROUP_2": {
"users": [
"user_3#domain.com",
"user_4#domain.com"
]
}
}
}
For this way you have to use for_each
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))
I want to create a iam_policy_arn_list in terraform where the list consists of the "FullAccess" arns of existing AWS policies, and the arn of a policy that I create on the fly. (I'm trying to create a Lambda function that can read/write to only a specified bucket.) If I only use existing AWS policies, then the following ingredients in my setup work:
variable "iam_policy_arn_list" {
type = list(string)
description = "IAM Policies to be attached to role"
default = [
"arn:aws:iam::aws:policy/CloudWatchFullAccess",
"arn:aws:iam::aws:policy/AmazonSESFullAccess",
"arn:aws:iam::aws:policy/AmazonS3FullAccess"
]
}
resource "aws_iam_role_policy_attachment" "role-policy-attachment" {
role = "${var.prefix}${var.role_name}"
count = length(var.iam_policy_arn_list)
policy_arn = var.iam_policy_arn_list[count.index]
depends_on = [aws_iam_role.iam_for_lambda]
}
But now I want to remove "arn:aws:iam::aws:policy/AmazonS3FullAccess" and replace it with the arn of a policy that I create on the fly that lets the Lambda function only access a specified S3 bucket. Where I am stuck is how to end up with a list variable of the rough form:
variable "iam_policy_arn_list" {
type = list(string)
description = "IAM Policies to be attached to role"
default = [
"arn:aws:iam::aws:policy/CloudWatchFullAccess",
"arn:aws:iam::aws:policy/AmazonSESFullAccess",
arn_of_the_policy_I_create_on_the_fly
]
}
... because the concat function will not work when defining variables. I have tried using the concat function elsewhere, but nothing seems to work. E.g. I tried:
resource "aws_iam_policy" "specific_s3_bucket_policy" {
name = "my_name"
description = "Grant access to one specific S3 bucket"
policy = jsonencode({
"Version" : "2012-10-17",
"Statement" : [
{
"Effect" : "Allow",
"Action" : [
"s3:ListAllMyBuckets",
"s3:ListBucket"
],
"Resource" : "*"
},
{
"Effect" : "Allow",
"Action" : [
"s3:PutObject",
"s3:PutObjectAcl",
"s3:GetObject",
"s3:GetObjectAcl",
"s3:DeleteObject"
],
"Resource" : "arn:aws:s3:::${var.S3_BUCKET_NAME}/*"
}
]
})
}
resource "aws_iam_role_policy_attachment" "role-policy-attachment" {
role = "${var.prefix}${var.role_name}"
count = length(var.iam_policy_arn_list)
policy_arn = concat(var.iam_policy_arn_list, [aws_iam_policy.specific_s3_bucket_policy.arn])[count.index]
depends_on = [aws_iam_role.iam_for_lambda]
}
... but this does not work. Suggestions?
Given the following iam_policy_arn_list:
variable "iam_policy_arn_list" {
type = list(string)
description = "IAM Policies to be attached to role"
default = [
"arn:aws:iam::aws:policy/CloudWatchFullAccess",
"arn:aws:iam::aws:policy/AmazonSESFullAccess",
]
}
Then create a local value like this:
locals {
combined_iam_policy_arn_list = concat(var.iam_policy_arn_list, [aws_iam_policy.specific_s3_bucket_policy.arn])
}
And then apply it like this:
resource "aws_iam_role_policy_attachment" "role-policy-attachment" {
role = "${var.prefix}${var.role_name}"
count = length(local.combined_iam_policy_arn_list)
policy_arn = local.combined_iam_policy_arn_list[count.index]
depends_on = [aws_iam_role.iam_for_lambda]
}
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
}
}
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}"
}