Get resource from symbolic name array in Bicep - azure

In Bicep I am creating an array of origin groups with a for loop. I want to be able to reference specific values in this array as a parent for another resource.
I'm creating the array like this:
var originGroups = [
{
name: 'firstOriginGroup'
}
{
name: 'secondOriginGroup'
}
]
resource origin_groups 'Microsoft.Cdn/profiles/originGroups#2021-06-01' = [for group in originGroups :{
name: group.name
other properties...
}
Then I have an array of origin groups, "Microsoft.Cdn/profiles/originGroups#2021-06-01[]". I then want to make a origin with a parent. secondOriginGroup.
resource my_origin 'Microsoft.Cdn/profiles/originGroups/origins#2021-06-01' = {
name: 'myOrigin'
parent: origin_groups[ //select specific name here ]
other parameters...
}
Is it posible in bicep to select a specific indexer here or am i only able to index on ordinal numbers? Am I able to do, where name == 'secondOriginGroup'?

You could loop through the originGroups again and filter on 'secondOriginGroup':
resource my_origin 'Microsoft.Cdn/profiles/originGroups/origins#2021-06-01' = [for (group, i) in originGroups: if (group.name == 'secondOriginGroup') {
name: 'myOrigin'
parent: origin_groups[i]
other parameters...
}]

You'll need to use indexes in your loop, like this:
var originGroups = [
{
name: 'firstOriginGroup'
}
{
name: 'secondOriginGroup'
}
]
resource origin_groups 'Microsoft.Cdn/profiles/originGroups#2021-06-01' = [for (group, index) in originGroups :{
name: group.name
other properties...
}
resource my_origin 'Microsoft.Cdn/profiles/originGroups/origins#2021-06-01' = {
name: 'myOrigin'
parent: origin_groups[index]
other parameters...
}

Related

Bicep - Pair arrays with loops

My case is that I have a list of companies, as well as a list of queues that I want to pair together in my bicep file. I want to use the result to add queues to an service bus namespace.
This is an example of the queue array:
`var queues = [
'first-queue'
'second-queue'
'third-queue'
]``
And this is an example of the company array:
`var companies = [
'apple'
'intel'
'blizzard'
]``
I would like to loop through the company list with bicep syntax and insert the company name before each queue. In this case i want to have a result like bwlow:
`var res = [
'apple-first-queue'
'apple-second-queue'
'apple-third-queue'
'intel-first-queue'
'intel-second-queue'
'intel-third-queue'
'blizzard-first-queue'
'blizzard-second-queue'
'blizzard-third-queue'
]`
I've tried a few different ways but can't get it to work. This is my latest attempt below where I get an error message regarding the syntax of "queuesToCreate" in the form of* "Directly referencing a resource or module collection is not currently supported. Apply an array indexer to the expression"*.
Does anyone see what I'm doing wrong here and can point me in the right direction?
`// ## Service bus multiple companies module ## //
module queuesToCreate 'service-bus-loop.bicep' = [for company in companies: {
name: 'multipleQueues-${company}'
params: {
company: company
}
}]
`
``// ## Service bus namespace ## //
resource serviceBusNamespace 'Microsoft.ServiceBus/namespaces#2022-01-01-preview' = {
name: serviceBusNamespaceName
location: location
sku: {
name: skuName
}
}
// ## Queues ## //
resource queues 'Microsoft.ServiceBus/namespaces/queues#2022-01-01-preview' = [for queueName in queuesToCreate: {
parent: serviceBusNamespace
name: queueName
properties: {
maxDeliveryCount: 1
lockDuration: 'PT5M'
}
}]`
This service-bus-loop.bicep file look like below and just returns an array as output
param company string
`// Create an array with the names of the queues
var queues = [
'${company}-queue'
'${company}-queue'
'${company}-queue'
]
output res array = queues`
queuesToCreate variable has in above case an syntax error like "Directly referencing a resource or module collection is not currently supported. Apply an array indexer to the expression."
As suggested in the comment section, you could always use a module to create all the queues for a specific company.
// service-bus-queues.bicep
param serviceBusNamespaceName string
param company string
param queues array
resource serviceBusNamespace 'Microsoft.ServiceBus/namespaces#2022-01-01-preview' existing = {
name: serviceBusNamespaceName
}
resource queuesToCreate 'Microsoft.ServiceBus/namespaces/queues#2022-01-01-preview' = [for queue in queues: {
parent: serviceBusNamespace
name: '${company}-${queue}'
properties: {
maxDeliveryCount: 1
lockDuration: 'PT5M'
}
}]
Then from you main, loop through companies.
// main.bicep
param serviceBusNamespaceName string
param location string
param skuName string
var queues = [
'first-queue'
'second-queue'
'third-queue'
]
var companies = [
'apple'
'intel'
'blizzard'
]
resource serviceBusNamespace 'Microsoft.ServiceBus/namespaces#2022-01-01-preview' = {
name: serviceBusNamespaceName
location: location
sku: {
name: skuName
}
}
module queuesToCreate 'service-bus-queues.bicep' = [for company in companies: {
name: 'multipleQueues-${company}'
params: {
serviceBusNamespaceName: serviceBusNamespace.name
company: company
queues: queues
}
}]

Array of objects in terraform

I would like to have such a array of objects in terraform:
param ArrayOfRules array = [
{
name: '1stRule'
startIpAddress: '0.0.0.0'
endIpAddress: '0.0.0.0'
}
{
name: '2ndRule'
startIpAddress: '0.0.0.1'
endIpAddress: '0.0.0.1'
}
]
On which i would like to simply iterare in order to create firewall rules.
resource sqlServerFirewallRules 'Microsoft.Sql/servers/firewallRules#2022-02-01-preview' = [for rule in ArrayOfRules: {
parent: serverName_resource
name: rule.name
properties: {
startIpAddress: rule.startIpAddress
endIpAddress: rule.endIpAddress
}
}]
I know that i could to something like this in bicep but I don't know how to do it in terraform.
You need to create variable like
variable "ArrayOfRules" {
type = list(map(string))
}
You need to assign variable value like this
var.ArrayOfRules = [
{
name: '1stRule'
startIpAddress: '0.0.0.0'
endIpAddress: '0.0.0.0'
},
{
name: '2ndRule'
startIpAddress: '0.0.0.1'
endIpAddress: '0.0.0.1'
}
]
You need to call dynamic block in your resource..
resource sqlServerFirewallRules 'Microsoft.Sql/servers/firewallRules#2022-02-01-preview' {
parent: serverName_resource
name: rule.name
dynamic "eachElementinArray" {
for_each = each.value.eachElementinArray
properties {
name = name.value.type
startIpAddress = eachElementinArray.value.startIpAddress
endIpAddress = eachElementinArray.value.endIpAddress
}
}
}
You may have to change some syntax... but on a high level, your terraform will look like the above..

How to create a nested resource based on a condition in a bicep file?

I have a bicep file that accepts a param topic object object that looks like this:
name: 'topic name'
subscriptions: [
{
name: 'subscriptionName'
ruleName: 'name of the rule'
}
]
Next I am going to create the resources:
resource servicebus 'Microsoft.ServiceBus/namespaces#2021-11-01' existing = {
name: 'products'
}
resource topicResource 'Microsoft.ServiceBus/namespaces/topics#2021-11-01' = {
parent: servicebus
name: topic.topicName
}
resource subscriptions 'Microsoft.ServiceBus/namespaces/topics/subscriptions#2021-11-01' = [for obj in topic.subscriptions: {
parent: topicResource
name: obj.name
}]
resource rules 'Microsoft.ServiceBus/namespaces/topics/subscriptions/rules#2021-11-01' = [for (obj, i) in topic.subscriptions: {
parent: subscriptions[i]
name: obj.ruleName
properties: {
filterType: 'CorrelationFilter'
correlationFilter: {
label: obj.ruleName
}
}
}]
In some situations I don't want to create the rules resource. This is based on the ruleName property. If this property is empty, I don't want to create the resource.
But when I try to apply this condition, I get an error message:
{"code": "InvalidTemplate", "target": "[...]", "message": "Deployment template validation failed: 'The template resource '[..]' for type 'Microsoft.ServiceBus/namespaces/topics/subscriptions/rules' at line '1' and column '1763' has incorrect segment lengths. A nested resource type must have identical number of segments as its resource name. A root resource type must have segment length one greater than its resource name. Please see https://aka.ms/arm-template/#resources for usage details.'.", "additionalInfo": [{"type": "TemplateViolation", "info": {"lineNumber": 1, "linePosition": 1763, "path": "properties.template.resources[2].type"}}]}
What I think this error message is telling me, is that you need the same number of iterations as the parent, which in this case is subscriptions and because of the loop condition of the 'child' resource, the number now is decreased by one (which is odd to me, because why not continue with the next item in the array and increase the index with one... but ok).
So my question is, how can I skip the resource creation when ruleName is empty?
This is what I've tried and what gives the error above:
resource rules 'Microsoft.ServiceBus/namespaces/topics/subscriptions/rules#2021-11-01' = [for (obj, i) in topic.subscriptions: if (!empty(obj.ruleName)) {
The condition if (!empty(obj.ruleName)) will be converted to an ARM condition property.
Even if the resource won't be deployed it is still present in the generated ARM.
This mean the name property in the generated ARM is missing a segment:
topic-name/subscription-name/empty-rule-name
You will have to add the same condition (as a workaround) on the name property as well:
name: !empty(obj.ruleName) ? obj.ruleName : 'whatever'
In your code, it should look like that:
resource rules 'Microsoft.ServiceBus/namespaces/topics/subscriptions/rules#2021-11-01' = [for (obj, i) in topic.subscriptions: if (!empty(obj.ruleName)) {
parent: subscriptions[i]
name: !empty(obj.ruleName) ? obj.ruleName : 'whatever'
properties: {
filterType: 'CorrelationFilter'
correlationFilter: {
label: obj.ruleName
}
}
}]

How to reference an object in an array of objects using Bicep

I am trying to output the referenceId of each subnet in a module that creates a virtual network with 4 subnets. I can get the first one, [0], but when I try output the others, [1], [2], [3] the deployment fails and throws the error:
The language expression property array index "1" is out of bounds
Below is the code to create the virtualNetwork and the subnets:
resource virtualNetwork 'Microsoft.Network/virtualNetworks#2018-11-01' = {
name: vNetName
location: location
tags: tags
properties: {
addressSpace: {
addressPrefixes: [
addressPrefix
]
}
subnets: subnets
}
}
subnets is a variable of type array:
var subnets = [
{
name: mgmtSubnetName
properties: {
addressPrefix: mgmtSubnetAddressPrefix
}
}
{
name: intSubnetName
properties: {
addressPrefix: intSubnetAddressPrefix
}
}
{
name: extSubnetName
properties: {
addressPrefix: extSubnetAddressPrefix
}
}
{
name: vdmsSubnetName
properties: {
addressPrefix: vdmsSubnetAddressPrefix
}
}
]
When I use the output line below, it returns and array that has 4 objects...one for each subnet created:
output subnets array = virtualNetwork.properties.subnets
Each object has the format below:
{
"name":"<value>",
"id":"<value>",
"etag":"<value>",
"properties":{
"provisioningState":"Succeeded",
"addressPrefix":"<value>",
"ipConfigurations":[
{
"id":"<value>"
}
],
"delegations":[]
},
"type":"Microsoft.Network/virtualNetworks/subnets"
}
When I use the output line below, it returns the first object in the subnets array:
output subnet1 object = virtualNetwork.properties.subnets[0]
When I use the output line below, it returns the resourceId of the first subnet:
output subnet1 string = virtualNetwork.properties.subnets[0].id
I am unable to retrieve the other objects in the array using indexes 1, 2, or 3.
I have also tried the resourceId function (example below) but the behavior is exactly the same for indexes 1, 2, or 3:
output subnet1Id string = resourceId('Microsoft.Network/VirtualNetworks/subnets', name, subnets[0].name)
You can use the below bicep template to deploy the vnet and subnets and output the subnets and subnet id's like below :
var subnets = [
{
name: 'vm-subnet'
properties: {
addressPrefix:'10.0.0.0/24'
}
}
{
name: 'webapp-subnet'
properties: {
addressPrefix:'10.0.1.0/24'
}
}
{
name: 'appgw-subnet'
properties: {
addressPrefix:'10.0.2.0/24'
}
}
{
name: 'bastion-subnet'
properties: {
addressPrefix:'10.0.3.0/24'
}
}
]
resource virtualNetwork 'Microsoft.Network/virtualNetworks#2018-11-01' = {
name: 'ansuman-vnet'
location: 'east us'
properties: {
addressSpace: {
addressPrefixes: [
'10.0.0.0/16'
]
}
subnets: subnets
}
}
output subnets array = [for (name, i) in subnets :{
subnets : virtualNetwork.properties.subnets[i]
}]
output subnetids array = [for (name, i) in subnets :{
subnets : virtualNetwork.properties.subnets[i].id
}]
output subnetappgw string = virtualNetwork.properties.subnets[2].id
output webappsubnet object = virtualNetwork.properties.subnets[1]
Outputs:
Note: I am using the latest Bicep version i.e. Bicep CLI version 0.4.1124

Iterate over list of list of maps in terraform

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}"
}

Resources