Better handling of multiple loops within 1 resource with Terraform - loops

I'm trying to create multiple CNAME resources based on multiple A record resources and am struggling to find a way to achieve this.
I currently have the following in my variable.tfvars file:
hostnames = [
"webserver1",
"webserver2"
]
domain = domain.com
subdomain_addresses = [
"www-1",
"www-2",
"www-3"
]
I have the following output from my ec2 instance module which is based off the length of var.hostnames: (in this instance there will be 2 private IPs)
output "server_ip_addresses" {
value = aws_instance.this.*.private_ip
}
I currently have the following in my resource.tf file:
resource "aws_route53_record" "instance_record" {
for_each = toset(var.hostnames)
zone_id = data.aws_route53_zone.route53_zone.zone_id
name = "${each.value}.${var.domain}"
type = "A"
ttl = "300"
records = module.ec2_instance.server_ip_addresses
}
resource "aws_route53_record" "webservice_records" {
for_each = toset(var.subdomain_addresses)
zone_id = data.aws_route53_zone.route53_zone.zone_id
name = "${each.value}.${var.domain}"
type = "CNAME"
ttl = "300"
records = [values(aws_route53_record.instance_record)[*].name]
}
What I need is a way to have the webservice_records resource create a subdomain for each entry in var.subdomain_addresses but associate each subdomain with each aws_route53_record.instance_record.name output.
So for example instance_record creates the following A records:
webserver1.domain.com = 10.1.1.18
webserver2.domain.com = 10.1.1.19
and webservice_records creates the following CNAMEs:
www-1.domain.com = webserver1.domain.com, webserver2.domain.com
www-2.domain.com = webserver1.domain.com, webserver2.domain.com
www-3.domain.com = webserver1.domain.com, webserver2.domain.com
I am currently getting the following error:
╷
│ Error: Incorrect attribute value type
│
│ on resource.tf line 62, in resource "aws_route53_record" "webservice_records":
│ 62: records = [values(aws_route53_record.instance_record)[*].name]
│ ├────────────────
│ │ aws_route53_record.instance_record is object with 2 attributes
│
│ Inappropriate value for attribute "records": element 0: string required.
╵
Does someone have a better idea of how this can be achieved?

you can use the setproduct function to achieve that output like below:
so it would be like something like this to create a map:
locals {
hostnames = [
"webserver1",
"webserver2"
]
domain = "domain.com"
subdomain_addresses = [
"www-1",
"www-2",
"www-3"
]
webserver_list = [ for web_list in setproduct(local.hostnames, [local.domain]) : join(".",web_list) ]
cname_map = {for sub_domain in local.subdomain_addresses: sub_domain => local.webserver_list}
}
resource "aws_route53_record" "cnames" {
for_each = local.cname_map
zone_id = data.aws_route53_zone.route53_zone.zone_id
name = each.key
type = "CNAME"
ttl = "300"
records = each.value
}
cname_map looks like this:
cname_map= {
www-1 = [
"webserver1.domain.com",
"webserver2.domain.com",
]
www-2 = [
"webserver1.domain.com",
"webserver2.domain.com",
]
www-3 = [
"webserver1.domain.com",
"webserver2.domain.com",
]
}

Related

Terraform - Adding specific users to a specific group

I have a 2 csv files named "_member.csv" and "_groups.csv".
Content from _member.csv :
first_name,last_name,department,job_title
Albator,Albator,Exploitation,Integrateur d'exploitation
Bingo,Bingo,Exploitation,Ingenieur de production
Cresus,Cresus,Etudes et developpements, Analyste developpeur
Content from _groups.csv :
members_groups
adm-xxx
exp-xxx
cod-xxx
tec-xxx
met-xxx
svc-xxx
My resource to create users :
resource "azuread_user" "Terra-Aad-User-Member" {
for_each = { for user in local.azure_members : user.first_name => user }
user_principal_name = format(
"%s%s#%s",
substr(lower(each.value.first_name), 0, 1),
lower(each.value.last_name),
local.domain_name
)
password = format(
"%s%s%s!",
lower(each.value.last_name),
substr(lower(each.value.first_name), 0, 1),
length(each.value.first_name)
)
force_password_change = true
display_name = "${each.value.first_name} ${each.value.last_name}"
department = each.value.department
job_title = each.value.job_title
}
My resource to create groups :
resource "azuread_group" "Terra-Aad-Group" {
for_each = { for group in local.azure_groups : group.members_groups => group }
display_name = format("%s", lower(each.value.members_groups))
security_enabled = true
}
My resource to add users to groups :
resource "azuread_group_member" "Terra-Aad-Member-In-Group" {
for_each = { for u in azuread_user.Terra-Aad-User-Member : u.department => u... if u.department == "Exploitation" }
group_object_id = values(azuread_group.Terra-Aad-Group).0.object_id
member_object_id = each.value.id
}
The provider :
terraform {
required_providers {
azuread = {
source = "hashicorp/azuread"
version = "=2.28.1"
}
}
}
provider "azuread" {
tenant_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
data "azuread_domains" "Terra-Aad" {
only_initial = true
}
My variables.tf
locals {
domain_name = data.azuread_domains.Terra-Aad.domains.0.domain_name
azure_members = csvdecode(file("${path.module}/_member.csv"))
azure_groups = csvdecode(file("${path.module}/_groups.csv"))
}
When I launch 'terraform plan', I have this error :
│ Error: Unsupported attribute
│
│ on groupmember.tf line 5, in resource "azuread_group_member" "Terra-Aad-Member-In-Group":
│ 5: member_object_id = each.value.id
│ ├────────────────
│ │ each.value is tuple with 2 elements
│
│ This value does not have any attributes.
Is it possible to automatically add all users who have the department 'Exploitation' to the group 'adm-xxx'? The problem is in the line 'member_object_id = each.value.id' I may need a conditional expression but I don't know exactly what that line is.
Does anyone have an idea?
Ok. So I added a null_resource to get all the ids of my users who are on the "Exploitation" department like this :
resource "null_resource" "Id-Department" {
provisioner "local-exec" {
command = "az ad user list --filter \"department eq 'Exploitation'\" --query \"[].id\" -o tsv"
interpreter = ["PowerShell", "-Command"]
}
depends_on = [azuread_user.Terra-Aad-User-Member]
}
I modified my resource azuread_group_member like this :
resource "azuread_group_member" "Terra-Aad-Member-In-Group" {
for_each = { for u in azuread_user.Terra-Aad-User-Member : u.department => u... if u.department == "Exploitation" }
group_object_id = values(azuread_group.Terra-Aad-Group).0.object_id
member_object_id = null_resource.Id-Department
depends_on = [null_resource.Id-Department]
}
Now I get this error :
│ Error: Incorrect attribute value type
│
│ on groupmember.tf line 13, in resource "azuread_group_member" "Terra-Aad-Member-In-Group":
│ 13: member_object_id = null_resource.Id-Department
│ ├────────────────
│ │ null_resource.Id-Department is object with 2 attributes
│
│ Inappropriate value for attribute "member_object_id": string required.
What I need is to take all the results of the command in null_resource to this: member_object_id = null_resource.Id-Department
Is there a way to do this ?
For starters, it seems most simple just to extract the groups to a list. Then for azuread_group, you can just use toset. Once that is done, you can reference this group by the group name in the object. So within azuread_group_member you use the for expression to get only members that are have "Exploitation" - you can reference their member_object_id by each.value.object_id. And you can get the group_object_id from the set-built azuread_group objects.
locals {
azure_groups = [for v in csvdecode(file("${path.module}/_groups.csv")) : v.members_groups]
azure_members = csvdecode(file("${path.module}/_member.csv"))
}
resource "azuread_user" "Terra-Aad-User-Member" {
for_each = { for user in local.azure_members : "${user.first_name}-${user.last_name}" => user }
user_principal_name = format(
"%s%s#%s",
substr(lower(each.value.first_name), 0, 1),
lower(each.value.last_name),
local.domain_name
)
password = format(
"%s%s%s!",
lower(each.value.last_name),
substr(lower(each.value.first_name), 0, 1),
length(each.value.first_name)
)
force_password_change = true
display_name = "${each.value.first_name} ${each.value.last_name}"
department = each.value.department
job_title = each.value.job_title
}
resource "azuread_group" "Terra-Aad-Group" {
for_each = toset(local.azure_groups)
display_name = lower(each.value)
security_enabled = true
}
resource "azuread_group_member" "Terra-Aad-Member-In-Group" {
for_each = {
for k, v in azuread_user.Terra-Aad-User-Member :
k => v if v.department == "Exploitation"
}
group_object_id = azuread_group.Terra-Aad-Group["adm-xxx"].object_id
member_object_id = each.value.object_id
}
As an aside, I have no idea how a null_resource is relevant here. You should have all the information you need without it.

Terraform for_each, Count Index

I am trying to access all the values from a for_each statement in google_compute_instance resource
I want to get all the values [dev-1, dev-2] in thename attribute and parse it to vm_name in my metadata_startup_script
resource "google_compute_instance" "compute_instance" {
project = var.project_id
for_each = toset(["1", "2"])
name = "dev-${each.key}"
machine_type = "e2-micro"
zone = "${var.region}-b"
boot_disk {
initialize_params {
image = "ubuntu-os-cloud/ubuntu-1804-lts"
}
}
network_interface {
network = "default"
access_config {
}
}
lifecycle {
ignore_changes = [attached_disk]
}
metadata_startup_script = templatefile("copy.tftpl", {
vm_name = "${google_compute_instance.compute_instance.0.name}"
nfs_ip = "${google_filestore_instance.instance.networks[0].ip_addresses[0]}"
file_share_name = "${google_filestore_instance.instance.file_shares[0].name}"
zone = "${var.region}-b"
})
}
I am unable to get all compute instance from the name argument
I get this error message
╷
│ Error: Cycle: google_compute_instance.compute_instance["2"], google_compute_instance.compute_instance["1"]
│
│
How do I resolve this issue so I can get all the virtual machine name and parse it to vm_name variable?
I would change the hardcoded elements in your for_each to a variable,
and pass that to your vm_name, something like this:
locals {
compute_names = ["dev-1", "dev-2"]
}
resource "google_compute_instance" "compute_instance" {
project = var.project_id
for_each = toset(local.compute_names)
name = each.key
machine_type = "e2-micro"
zone = "${var.region}-b"
...
metadata_startup_script = templatefile("copy.tftpl", {
vm_name = local.compute_names
...
zone = "${var.region}-b"
})
}

Terraform nested loop array of objects with an array in object

Just having this input in my module
databases = [
{
db_name = "test_0"
db_owner = "testu_user_0",
extensions = ["unaccent"]
},
{
db_name = "test_db"
db_owner = "test_user"
extensions = ["uuid_ossp","pg_trgm"]
}
]
And then i need to loop through and make specifiec extensions. How can i achieve that?
For the database creation it was pretty straightforward
resource "postgresql_database" "db" {
for_each = {for db in var.databases : db.db_name => db}
name = each.key
owner = postgresql_role.specific_role["${each.value.db_owner}"].name
lifecycle {
prevent_destroy = false
}
}
But now when it comes to extensions im having a hard time to make it happen. I can see examples online, but they all use object with an array, not an array filled with objects and plus nested array inside.
# resource "postgresql_extension" "uuid_ossp" {
# for_each = {for db in var.databases : db.db_name => db}
# name = "uuid-ossp"
# database = each.key
# }
Please help
You have to flatten your data structure, in locals for instance, and then use that in postgresql_extension:
locals {
db_extentions = merge([
for db in var.databases :
{
for ext in db.extensions:
"${db.db_name}-${ext}" => {
db_name = db.db_name
db_owner = db.db_owner
extension = ext
}
}
]...) # <-- the dots are important! Don't remove them
}
resource "postgresql_extension" "uuid_ossp" {
for_each = local.db_extentions
name = each.value.extension
database = each.value.db_name
}
The three dots are for Expanding Function Arguments.

Create database schema with terraform

I created RDS instance using aws_db_instance (main.tf):
resource "aws_db_instance" "default" {
identifier = "${module.config.database["db_inst_name"]}"
allocated_storage = 20
storage_type = "gp2"
engine = "mysql"
engine_version = "5.7"
instance_class = "db.t3.micro"
name = "${module.config.database["db_name_prefix"]}${terraform.workspace}"
username = "${module.config.database["db_username"]}"
password = "${module.config.database["db_password"]}"
parameter_group_name = "default.mysql5.7"
skip_final_snapshot = true
}
Can I also create database schemas from file schema.sql with terraform apply?
$ tree -L 1
.
├── main.tf
└── schema.sql
You can use a provisioner (https://www.terraform.io/docs/provisioners/index.html) for that:
resource "aws_db_instance" "default" {
identifier = module.config.database["db_inst_name"]
allocated_storage = 20
storage_type = "gp2"
engine = "mysql"
engine_version = "5.7"
instance_class = "db.t3.micro"
name = "${module.config.database["db_name_prefix"]}${terraform.workspace}"
username = module.config.database["db_username"]
password = module.config.database["db_password"]
parameter_group_name = "default.mysql5.7"
skip_final_snapshot = true
provisioner "local-exec" {
command = "mysql --host=${self.address} --port=${self.port} --user=${self.username} --password=${self.password} < ./schema.sql"
}
}
#Apply scheme by using bastion host
resource "aws_db_instance" "default_bastion" {
identifier = module.config.database["db_inst_name"]
allocated_storage = 20
storage_type = "gp2"
engine = "mysql"
engine_version = "5.7"
instance_class = "db.t3.micro"
name = "${module.config.database["db_name_prefix"]}${terraform.workspace}"
username = module.config.database["db_username"]
password = module.config.database["db_password"]
parameter_group_name = "default.mysql5.7"
skip_final_snapshot = true
provisioner "file" {
connection {
user = "ec2-user"
host = "bastion.example.com"
private_key = file("~/.ssh/ec2_cert.pem")
}
source = "./schema.sql"
destination = "~"
}
provisioner "remote-exec" {
connection {
user = "ec2-user"
host = "bastion.example.com"
private_key = file("~/.ssh/ec2_cert.pem")
}
command = "mysql --host=${self.address} --port=${self.port} --user=${self.username} --password=${self.password} < ~/schema.sql"
}
}
mysql client needs to be installed on your device.
If you don't have direct access to your DB, there is also a remote-exec provisioner, where you can use a bastion host (transfer file to remote place with file provisioner first).
If your schema is not to complex, you could also use the MySQL provider of terraform:
https://www.terraform.io/docs/providers/mysql/index.html

cakephp: Acl problem: not able to populate aros_acos table with initdb function in users controller

When I try to run app/users/initdb, I'm getting warning 512 and i'm not able to populate the aros_acos table. Does anyone know on i'm doing wrong:
Warning (512): DbAcl::allow() - Invalid node [CORE\cake\libs\controller\components\acl.php, line 361]Code | Context
if ($perms == false) {
trigger_error(__('DbAcl::allow() - Invalid node', true), E_USER_WARNING);$aro = Group
Group::$name = "Group"
Group::$validate = array
Group::$hasMany = array
Group::$actsAs = array
Group::$useDbConfig = "default"
Group::$useTable = "groups"
Group::$displayField = "name"
Group::$id = 7
Group::$data = array
Group::$table = "groups"
Group::$primaryKey = "id"
Group::$_schema = array
Group::$validationErrors = array
Group::$tablePrefix = ""
Group::$alias = "Group"
Group::$tableToModel = array
Group::$logTransactions = false
Group::$cacheQueries = false
Group::$belongsTo = array
Group::$hasOne = array
Group::$hasAndBelongsToMany = array
Group::$Behaviors = BehaviorCollection object
Group::$whitelist = array
Group::$cacheSources = true
Group::$findQueryType = NULL
Group::$recursive = 1
Group::$order = NULL
Group::$virtualFields = array
Group::$__associationKeys = array
Group::$__associations = array
Group::$__backAssociation = array
Group::$__insertID = NULL
Group::$__numRows = NULL
Group::$__affectedRows = NULL
Group::$_findMethods = array
Group::$User = User object
Group::$Aro = Aro object
$aco = "controllers"
$actions = "*"
$value = 1
$perms = false
$permKeys = array(
"_create",
"_read",
"_update",
"_delete"
)
$save = array()DbAcl::allow() - CORE\cake\libs\controller\components\acl.php, line 361
AclComponent::allow() - CORE\cake\libs\controller\components\acl.php, line 106
UsersController::initdb() - APP\controllers\users_controller.php, line 82
Dispatcher::_invoke() - CORE\cake\dispatcher.php, line 204
Dispatcher::dispatch() - CORE\cake\dispatcher.php, line 171
[main] - APP\webroot\index.php, line 83
The following is my users_controller.php
class UsersController extends appController{
var $name = 'Users';
function beforeFilter(){
parent::beforeFilter();
$this->Auth->allow(array('*'));
}
function index() {
$this->User->recursive = 0;
$this->set('users', $this->paginate());
}
function login(){
}
function logout(){
}
.........
function initdb(){
//gets reference of Group model and modify its id to be able to specify the ARO we wanted, this is due to how AclBehavior works. AclBehavior does not set the alias field in the aros table so we must use an object reference or an array to reference the ARO we want.
$group=& $this->User->Group;
//Allow admins to everything
$group->id=7;
$this->Acl->allow($group,'controllers');
//allow managers to posts and widgets
$group->id=8;
$this->Acl->deny($group,'controllers');
$this->Acl->allow($group,'controllers/Posts');
$this->Acl->allow($group, 'controllers/Widgets');
//allow users to only add and edit on posts and widgets
$group->id=9;
$this->Acl->deny($group,'controllers');
$this->Acl->allow($group,'controllers/Posts/add');
$this->Acl->allow($group,'controllers/Posts/edit');
$this->Acl->allow($group,'controllers/Widgets/add');
$this->Acl->allow($group,'controllers/Widgets/edit');
//we add an exit to avoid an ugly "missing views" error message
echo "all done";
exit;
}
any help is much appreciated. Thanks.

Resources