concatenate filepath prefix and file name in terraform code - loops

I'm trying to create policies in aws with terraform.
variable "path" {
type = "string"
}
variable "policies" {
type = list(object ({
name = string
plcyfilename = string
asmplcyfilename = string
desc = string
ownner = string}))
default = []
}
resource "aws_iam_policy" "policy" {
count = length(var.policies)
name = lookup(var.policies[count.index], "name")
policy = file(lookup(var.policies[count.index], concat("var.path","plcyfilename")))
description = "Policy for ${lookup(var.policies[count.index], "desc")}"
}
and this is how my tfvars looks like:
path = "./../t2/scripts/"
policies = [
{name = "cwpolicy", plcyfilename = "cw.json" , asmplcyfilename ="csasm.json", desc ="vpcflowlogs", ownner ="vpc"},
]
The error that is thrown while I do this is like this:
Error: Invalid function argument
on main.tf line 13, in resource "aws_iam_policy" "policy":
13: policy = file(lookup(var.policies[count.index], "${concat("${var.path}","plcyfilename")}"))
Invalid value for "seqs" parameter: all arguments must be lists or tuples; got
string.
I'm using terraform 0.12.
It works as expected if I change the variable to have complete file path:plcyfilename=./../t2/scripts/cw.json.
However I want to isolate the file path from the file names.
Can someone point me where I am going wrong.

The concat function is for concatenating lists, not for concatenating strings.
To concatenate strings in Terraform, we use template interpolation syntax:
policy = file("${var.path}/${var.policies[count.index].policy_filename}")
Since your collection of policies is not a sequence where the ordering is significant, I'd recommend also changing this to use resource for_each, which will ensure that Terraform tracks the policies using the policy name strings rather than using the positions in the list:
variable "policies" {
type = map(object({
policy_filename = string
assume_policy_filename = string
description = string
owner = string
}))
default = {}
}
resource "aws_iam_policy" "policy" {
for_each = var.policies
name = each.key
policy = file("${var.path}/${each.value.policy_filename}")
description = "Policy for ${each.value.description}"
}
In this case the policies variable is redefined as being a map, so you'd now present the name of each policy as the key within the map rather than as one of the attributes:
policies = {
cw = {
policy_filename = "cw.json"
assume_policy_filename = "csasm.json"
description = "vpcflowlogs"
owner = "vpc"
}
# ...
}
Because the for_each value is the policies map, each.key inside the resource block is a policy name and each.value is the object representing that policy, making the resulting expressions easier to read and understand.
By using for_each, we will cause Terraform to create resource instance addresses like aws_iam_policy.policy["cw"] rather than like aws_iam_policy.policy[1], and so adding and removing elements from the map will cause Terraform to add and remove corresponding instances from the resource, rather than try to update instances in-place to respect the list ordering as it would've done with your example.

Related

Terraform for Digital Ocean Managed Database Firewall

I have been trying to dynamically create the Terraform code for a managed Digital Ocean database I have. I am trying to achieve that I have some lists of FW entries like:
locals {
####################################################################################
## DO object ids (the different ID's for the Postgres databases in Digital Ocean
####################################################################################
id_postgres_application_dev = "12345"
id_postgres_application_stg = "23456"
id_postgres_application_prd = "34567"
# Map to fw for Postgres
pg-application_id = {
"dev" = id_postgres_application_dev
"stg" = id_postgres_application_stg
"prd" = id_postgres_application_prd
}
####################################################################################
## Outside IP addresses
####################################################################################
fw_ip_peter = "4.100.123.140"
fw_ip_sunshine = "152.120.106.102"
####################################################################################
## Postgres Application
####################################################################################
# Map to fw for Postgres
pg-application_fw_rules_ip = {
"dev" = [
local.fw_ip_peter,
local.fw_ip_sunshine]
"stg" = [
local.fw_ip_peter]
"prd" = [
local.fw_ip_peter]
}
long_key = {
type = "string"
default = <<EOF
rule = {
type = "KEY"
value = "VALUE"
}
EOF
}
fw_rules = toset(lookup(local.pg-application_fw_rules_ip, var.environment))
}
Now what I want to achieve is to dynamically generate the FW rule entries (these are described in the Digital Ocean documentation here: https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/resources/database_firewall
So the result would be something like for the dev environment:
id_postgres_application = lookup(local.pg-application_id, var.environment)
resource "digitalocean_database_firewall" "example-fw" {
cluster_id = id_postgres_application
rule {
type = "ip_addr"
value = "4.100.123.140" // Peter
}
rule {
type = "ip_addr"
value = "152.120.106.102" // Sunshine (for dev only)
}
}
So the problem lies in the rule sections - to repeat these per entry in the fw_rules variable.
Does anyone have specific advice on how to do this? I have tried many different solutions, and I think my basic problem is to understand which method to apply?
Generally, you would use dynamic blocks for that. Thus, your code could look like the following:
resource "digitalocean_database_firewall" "example-fw" {
cluster_id = id_postgres_application
dynamic "rule" {
for_each = local.application_fw_rules_ip[var.environment]
content {
type = "ip_addr"
value = rule.key
}
}
}
Treat the code as an example, as probably some further adjustments specific to your setup may be required.

Get oauth2_permissions from azuread_application using Terraform

I have an app registration which defines two oauth2_permissions blocks, e.g. (other details elided)
resource "azuread_application" "myapp" {
oauth2_permissions {
is_enabled = true
type = "User"
value = "Permission.One"
}
oauth2_permissions {
is_enabled = true
type = "User"
value = "Permission.Two"
}
}
Which, when applied,works just fine. I then want to refer to those permissions in another app registration, e.g.
resource "azuread_application" "myotherapp" {
required_resource_access {
resource_app_id = azuread_application.myapp.application_id
resource_access {
id = ??
type = "Scope"
}
}
}
For the id here, I have tried:
id = lookup(azuread_application.myapp.oauth2_permissions[0], "id")
which gives This value does not have any indices. As does
id = azuread_application.myapp.oauth2_permissions.0.id
I can define a data block and get the output of oauth2_permissions from myapp:
data "azuread_application" "myapp" {
application_id = azuread_application.myapp.application_id
}
output "myapp-perms" {
value = data.azuread_application.myapp.oauth2_permissions
}
And on apply, that will correctly show an array of the two permission blocks. If I try to refer to the data block instead of the application block, i.e.
id = lookup(data.azuread_application.myapp.oauth2_permissions[0], "id")
This gives me a different error: The given key does not identify an element in this collection value
If I apply those two permissions manually on the console, everything works fine. From reading around I was fairly sure that at least one of the above methods should work but I am clearly missing something.
For completeness, provider definition:
provider "azurerm" {
version = "~> 2.12"
}
provider "azuread" {
version = "~> 0.11.0"
}
Based on comments.
The solution is to use tolist. The reason is that the multiple oauth2_permissions blocks will be represented as sets of objects, which can't be accessed using indices.
id = tolist(azuread_application.myapp.oauth2_permissions)[0].id
However, the sets don't have guaranteed order. Thus a special attention should be payed to this.

How can I associate NSG's and Subnets being created by loops in Terraform?

Here is the code I am using to create subnets and nsgs I want to associate the NSG and subnet in the same script but I am unable to understand how can I get subnet IDs and NSG IDs which are being produced here and use them in the association resource. Thanks in advance for the help !
First part of code this is being used to create n no of Subnets and NSGs depends upon the parameter
provider "azurerm" {
version = "2.0.0"
features {}
}
resource "azurerm_resource_group" "new-rg" {
name = var.rg_name
location = "West Europe"
}
resource "azurerm_virtual_network" "new-vnet" {
name = var.vnet_name
address_space = ["${var.vnet_address_space}"]
location = azurerm_resource_group.new-rg.location
resource_group_name = azurerm_resource_group.new-rg.name
}
resource "azurerm_subnet" "test" {
count = "${length(var.subnet_prefix)}"
name = "${element(var.subnet_subnetname, count.index)}"
resource_group_name = azurerm_resource_group.new-rg.name
virtual_network_name = azurerm_virtual_network.new-vnet.name
address_prefix = "${element(var.subnet_prefix, count.index)}"
}
resource "azurerm_network_security_group" "new-nsg" {
count = "${length(var.subnet_prefix)}"
name = "${element(var.subnet_subnetname, count.index)}-nsg"
location = azurerm_resource_group.new-rg.location
resource_group_name = azurerm_resource_group.new-rg.name
}
Below is the resource where i have to pass the parameters to create the association for the above subnets and nsgs being created.
Second Part of code Need to make the below code usable for above solution for n no of associations.
resource "azurerm_subnet_network_security_group_association" "example" {
subnet_id = azurerm_subnet.example.id
network_security_group_id = azurerm_network_security_group.example.id
}
How can associate the n number of subnets and nsgs being created by using 2nd part of code, I cant find my way to that
This seems like a good case for for_each. Here is some code I'm using for AWS (the same logic applies as far as I can tell)-
(var.nr_azs is just an int, formatlist is used because for_each only likes strings)
locals {
az_set = toset(formatlist("%s", range(var.nr_azs))) # create a list of numbers and convert them to strings)
}
resource "aws_subnet" "private" {
for_each = local.az_set
availability_zone = random_shuffle.az.result[each.key]
cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, each.key)
vpc_id = aws_vpc.main.id
map_public_ip_on_launch = false
}
resource "aws_eip" "nat_gw" {
vpc = true
}
resource "aws_nat_gateway" "gw" {
for_each = aws_subnet.private
allocation_id = aws_eip.nat_gw.id
subnet_id = each.value.id
}
resource "aws_route_table" "private_egress" {
for_each = aws_nat_gateway.gw
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = each.value.id
}
}
resource "aws_route_table_association" "private" {
for_each = local.az_set
subnet_id = aws_subnet.private[each.key].id
route_table_id = aws_route_table.private_egress[each.key].id
}
So i was able to solve the issue mentioned by me above the following code contains the solution for the mentioned scenario for the problem.
resource "azurerm_subnet_network_security_group_association" "snet-nsg-association" {
count = length(var.subnet_subnetname)
subnet_id = element(azurerm_subnet.multi-snet.*.id, count.index)
network_security_group_id = element(azurerm_network_security_group.new-nsg.*.id, count.index)
}

How to check an array with regular expression in GraphQL

I need to check the existence of some elements in an array as such
I have an array as such
ar = ['one','two','three']
I want to know how I can individually check the elements in the regular expression code below instead of "/something/" that would map through my array and check if they exist in graphQL one by one.
similar : allCockpitHello (filter: {Association : {value : {regex: "\/something/" }}} limit:2){
nodes{
Name{
value
}
}
You need to have the regex string as an input parameter to be used by the resolver, GraphQL is not going to do the filter for you, you need to do/call that logic in the resolver based on your inputs.
Based on your example, you could have something like this on the schema and resolver:
type Node {
name: String!
}
type NodeQueries {
nodes (filterRegEx :String): [Node]!
}
Once you have the input string on the resolver, the implementation of the filter mechanism is up to you.
const resolvers = {
...
NodeQueries: {
nodes: (parent, params) => {
const {filterRegEx} = params; // regex input string
const ar = ['one','two','three'];
// Create a RegExp based on the input,
// Compare the with the elements in ar and store the result...
// You might end up with ... res = ['one', 'three'];
// Now map the result to match your schema:
return _.map(res, name => ({name}) ); // to end up with [{name: 'one'}, {name: 'three'}]
}
}
...
}
GraphQL is not a magic bullet - it's only a query language, it 'transports' your needs to the engine (local client, remote server ...) where all the necessary processing takes place.
In this case you probably need to pass your array and expression as variables to the server (resolver). If processing is expensive results (similar relation) should be already defined, cached, preprocessed, etc.
If dataset is small you can do this entirely client-side - iterate over an array (fetched using graphql).

Laravel 5.4 - Array returning NULL instead of a numeric value

Here's a problem that is bothering me for a while.
I have a service provider to pass data to all views, everytime the sidebar is rendered. like this:
`
public function boot()
{
$userrole = array (DB::table('users')->where('id','=', Auth::id())->value('role'));
$menucase1 = [3,4,9,10];
$menucase2 = [1,2,3,10];
$menucase3 = [1,3,4,9,10];
$menucase4 = [4,9];
$commondata = compact('userrole','menucase1','menucase2','menucase3','menucase4');
view()->share('commondata', $commondata);
View::composer('sidebar', function ($view) {
$userrole = array (DB::table('users')->where('id','=', Auth::id())->value('role'));
$menucase1 = [3,4,9,10];
$menucase2 = [1,2,3,10];
$menucase3 = [1,3,4,9,10];
$menucase4 = [4,9];
$commondata = compact('userrole','menucase1','menucase2','menucase3','menucase4');
$view->with('commondata', $commondata);
});
}`
Doing a {{ dd($commondata) }} returns the correct values for the menucase arrays, but NULL for the $userrole
If i declare the same $userrole variable in a controller and call the variable in the view, the received data is correct.
Why is this happening?
Thanks in advance
Can't understand what are you actually trying to do.
If you want get user role as array, you can using pluck method:
$userRole = User::where('id', Auth::id())->pluck('role')->toArray();
But for current user you can just get the role
$userRole = [Auth::user()->role];
UPD: you also can do it in view without any sharing
{{ Auth::user()->role }}
If your user has many roles from a different table, and you have the relationship defined, you could do
$userrole = Auth::user()->roles->pluck('name');
//will return all the roles names in an array
//Replace name by the actual column you want from 'roles' table.

Resources