Terraform Conditional Content Block Inside Resource - dynamic

How do I make ip_configuration optional to turn the below:
resource "azurerm_firewall" "example" {
name = "testfirewall"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
ip_configuration {
name = "configuration"
subnet_id = azurerm_subnet.example.id
public_ip_address_id = azurerm_public_ip.example.id
}
}
In to something that OPTIONALLY accepts values in this:
variable "ip_configuration" {
type = object({
name = string // Specifies the name of the IP Configuration.
subnet_id = string // Reference to the subnet associated with the IP Configuration.
public_ip_address_id = string // The ID of the Public IP Address associated with the firewall.
})
description = "(Optional) An ip_configuration block as documented"
default = null
}
I was looking at dynamic blocks, lookups and try expressions but nothing seems to work. Can anyone help? I have spent days on it trying to figure it out
Edit:
There maybe a neater way to do it, but I have found something that works. If someone can improve on this that would be great, but thanks for those who have replied.
subnet_id should only appear in the first ip_configuration which is why I have decided to use the numbering system on the key.
resource "azurerm_firewall" "example" {
name = "testfirewall"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
dynamic "ip_configuration" {
for_each = var.ip_configuration
iterator = ip
content {
name = ip.value["name"]
subnet_id = ip.key == "0" ? ip.value["subnet_id"] : null
public_ip_address_id = ip.value["public_ip_address_id"]
}
}
}
variable "ip_configuration" {
type = map
description = <<-EOT
(Optional) An ip_configuration block is nested maps with 0, 1, 2, 3 as the name of the map as documented below:
name = string // Specifies the name of the IP Configuration.
public_ip_address_id = string // The ID of the Public IP Address associated with the firewall.
subnet_id = string // Reference to the subnet associated with the IP Configuration. The Subnet used for the Firewall must have the name AzureFirewallSubnet and the subnet mask must be at least a /26.
NOTE: Only the first map (with a key of 0) should have a subnet_id property.
EXAMPLE LAYOUT:
{
"0" = {
name = "first_pip_configuration"
public_ip_address_id = azurerm_public_ip.firstpip.id
subnet_id = azurerm_subnet.example.id
},
"1" = {
name = "second_pip_configuration"
public_ip_address_id = azurerm_public_ip.secondpip.id
},
"2" = {
name = "third_pip_configuration"
public_ip_address_id = azurerm_public_ip.thirdpip.id
}
}
EOT
default = {}
}

There maybe a neater way to do it, but I have found something that works. If someone can improve on this that would be great, but thanks for those who have replied.
subnet_id should only appear in the first ip_configuration which is why I have decided to use the numbering system on the key.
resource "azurerm_firewall" "example" {
name = "testfirewall"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
dynamic "ip_configuration" {
for_each = var.ip_configuration
iterator = ip
content {
name = ip.value["name"]
subnet_id = ip.key == "0" ? ip.value["subnet_id"] : null
public_ip_address_id = ip.value["public_ip_address_id"]
}
}
}
variable "ip_configuration" {
type = map
description = <<-EOT
(Optional) An ip_configuration block is nested maps with 0, 1, 2, 3 as the name of the map as documented below:
name = string // Specifies the name of the IP Configuration.
public_ip_address_id = string // The ID of the Public IP Address associated with the firewall.
subnet_id = string // Reference to the subnet associated with the IP Configuration. The Subnet used for the Firewall must have the name AzureFirewallSubnet and the subnet mask must be at least a /26.
NOTE: Only the first map (with a key of 0) should have a subnet_id property.
EXAMPLE LAYOUT:
{
"0" = {
name = "first_pip_configuration"
public_ip_address_id = azurerm_public_ip.firstpip.id
subnet_id = azurerm_subnet.example.id
},
"1" = {
name = "second_pip_configuration"
public_ip_address_id = azurerm_public_ip.secondpip.id
},
"2" = {
name = "third_pip_configuration"
public_ip_address_id = azurerm_public_ip.thirdpip.id
}
}
EOT
default = {}
}

Related

SwiftUI : Type of expression is ambiguous without more context when define data type

This warning is really weird to me because I was able to create one of the data type but the other 2 are not. You will see the implement + init are mostly the same.
1 small question, if my graphql API does not have id, which is the best way to implement the id? I'm currently use country name for id. which I think is a pretty bad practice
Thank you in advance!
typealias CountryData = ScpecificCountryQuery.Data
struct Countries: Decodable {
var countries: [Country]
init(_ countries: CountryData?) {
self.countries = countries?.countries.map({ country -> Country in
Country(country)
}) ?? []
}
struct Country: Identifiable, Decodable {
var id: String {
return currency
}
var code: String
var name: String
var phone: String
var capital: String
var currency : String
var continent: Continent ( Does not have any warning)
var languages : Lan ("Type of expression is ambiguous without more context")
var states: State ("Type of expression is ambiguous without more context")
init(_ country: ScpecificCountryQuery.Data.Country?) {
self.name = country?.name ?? ""
self.code = country?.code ?? ""
self.phone = country?.phone ?? ""
self.capital = country?.capital ?? ""
self.currency = country?.currency ?? ""
self.emoji = country?.emoji ?? ""
self.continent = Continent(country?.continent)
self.languages = Lan(country?.languages)
self.states = State(country?.states)
}
struct Lan: Decodable {
var code: String
var name: String
init(_ lan: ScpecificCountryQuery.Data.Country.Language?) {
self.code = lan?.code ?? ""
self.name = lan?.name ?? ""
}
}
struct Continent: Decodable {
var code: String
var name: String
init (_ continent: ScpecificCountryQuery.Data.Country.Continent?) {
self.code = continent?.code ?? ""
self.name = continent?.name ?? ""
}
}
struct State {
var code: String
var name: String
init (_ state: ScpecificCountryQuery.Data.Country.State?) {
self.code = state?.code ?? ""
self.name = state?.name ?? ""
}
}
}
}

Issues with creating multiple vm's with Terraform for Azure

I am having issues with creating multiple virtual machines in Azure using Terraform. While creating the network interfaces I run into an error regarding the creation of the public ip address id:
I assume that I am using the count function incorrectly, or need a different approach entirely.
Code:
provider "azurerm" {
version = "~>2.0"
features {}
subscription_id = "XXXX"
client_id = "XXXX"
client_secret = "XXXX"
tenant_id = "XXXX"
}
resource "azurerm_resource_group" "rg" {
name = "${var.prefix}test_project"
location = var.location
tags = var.tags
}
resource "azurerm_virtual_network" "vnet" {
name = "${var.prefix}Vnet"
address_space = ["10.0.0.0/16"]
location = var.location
resource_group_name = azurerm_resource_group.rg.name
tags = var.tags
}
resource "azurerm_subnet" "subnet" {
name = "${var.prefix}Subnet"
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefix = "10.0.1.0/24"
}
resource "azurerm_public_ip" "publicip" {
name = "${var.prefix}PublicIP${count.index}"
location = var.location
resource_group_name = azurerm_resource_group.rg.name
allocation_method = "Dynamic"
tags = var.tags
count = 2
}
resource "azurerm_network_security_group" "nsg" {
name = "${var.prefix}NetworkSecurityGroup"
location = var.location
resource_group_name = azurerm_resource_group.rg.name
tags = var.tags
security_rule {
name = "SSH"
priority = 1001
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "22"
source_address_prefix = "*"
destination_address_prefix = "*"
}
}
resource "azurerm_network_interface" "nic" {
name = "${var.prefix}NIC${count.index}"
location = var.location
resource_group_name = azurerm_resource_group.rg.name
tags = var.tags
count = 2
ip_configuration {
name = "${var.prefix}NICConfig${count.index}"
subnet_id = azurerm_subnet.subnet.id
private_ip_address_allocation = "Dynamic"
public_ip_address_id = ["${element(azurerm_public_ip.publicip.id, count.index)}"]
}
}
resource "azurerm_network_interface_security_group_association" "example" {
count = length(azurerm_network_interface.nic)
network_interface_id = "${azurerm_network_interface.nic[count.index]}"
network_security_group_id = azurerm_network_security_group.nsg.id
}
resource "azurerm_linux_virtual_machine" "vm" {
count = 2
name = "${var.prefix}VM${count.index}"
location = var.location
resource_group_name = azurerm_resource_group.rg.name
network_interface_ids = azurerm_network_interface.nic[count.index]
size = "Standard_DS1_v2"
tags = var.tags
os_disk {
name = "${var.prefix}OsDisk${count.index}"
caching = "ReadWrite"
storage_account_type = "Premium_LRS"
}
source_image_reference {
publisher = "Canonical"
offer = "UbuntuServer"
sku = lookup(var.sku, var.location)
version = "latest"
}
computer_name = "${var.computer_name}-${count.index}"
admin_username = var.admin_username
admin_password = var.admin_password
disable_password_authentication = false
}
Can anyone help me resolve this issue??
I'm pretty sure all you need to do is change
public_ip_address_id = ["${element(azurerm_public_ip.publicip.id, count.index)}"]
to
public_ip_address_id = ["${azurerm_public_ip.publicip[count.index].id}"]
In general, references like azurerm_public_ip.publicip.id work for singular resources (i.e. those that don't use count). So the use of element is kind of assuming a singular resource. As soon as count is used, resources start behaving like lists and need to be treated as such.

Map within a map in terraform variables

Does anyone know if it's possible with possibly code snipits representing whether I can create a map variable within a map variable in terraform variables?
variable "var" {
type = map
default = {
firstchoice = {
firstAChoice ="foo"
firstBChoice = "bar"
}
secondchoice = {
secondAChoice = "foobar"
secondBChoice = "barfoo"
}
}
}
If anyone has any insight to whether this is possible or any documentation that elaborates that would be great.
Yes, it's possible to have map variable as value of map variable key. Your variable just needed right indentation. Also I am putting ways to access that variable.
variable "var" {
default = {
firstchoice = {
firstAChoice = "foo"
firstBChoice = "bar"
}
secondchoice = {
secondAChoice = "foobar"
secondBChoice = "barfoo"
}
}
}
To access entire map value of a map key firstchoice, you can try following
value = "${var.var["firstchoice"]}"
output:
{
firstAChoice = foo
firstBChoice = bar
}
To access specific key of that map key (example firstAChoice), you can try
value = "${lookup(var.var["firstchoice"],"firstAChoice")}"
output: foo
Would this syntax be possible? ${var.var[firstchoice[firstAchoice]]}
With Terraform 0.12+ nested blocks are supported seamlessly. Extending #Avichal Badaya's answer to explain it using an example:
# Nested Variable
variable "test" {
default = {
firstchoice = {
firstAChoice = "foo"
firstBChoice = "bar"
}
secondchoice = {
secondAChoice = "foobar"
secondBChoice = "barfoo"
}
thirdchoice = {
thirdAChoice = {
thirdBChoice = {
thirdKey = "thirdValue"
}
}
}
}
}
# Outputs
output "firstchoice" {
value = var.test["firstchoice"]
}
output "FirstAChoice" {
value = var.test["firstchoice"]["firstAChoice"]
}
output "thirdKey" {
value = var.test["thirdchoice"]["thirdAChoice"]["thirdBChoice"]["thirdKey"]
}
Applying the above, you can verify that Terraform map nesting is now quite powerful and this makes a lot of things easier.
# Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
# Outputs:
firstchoice = {
"firstAChoice" = "foo"
"firstBChoice" = "bar"
}
thirdKey = thirdValue
For more complex structures and Rich Value Types, see HashiCorp Terraform 0.12 Preview: Rich Value Types

Using Terraform to add Storage Virtual Network Rule

Is there a way to add a Virtual Network Rule to a storage account like there is with Azure SQL? There is the azurerm_sql_virtual_network_rule but there does not appear to be an equivalent for storage accounts.
Here is a sample of Usage with Network Rules, you could refer to it.
resource "azurerm_resource_group" "testrg" {
name = "resourceGroupName"
location = "westus"
}
resource "azurerm_virtual_network" "test" {
name = "virtnetname"
address_space = ["10.0.0.0/16"]
location = "${azurerm_resource_group.testrg.location}"
resource_group_name = "${azurerm_resource_group.testrg.name}"
}
resource "azurerm_subnet" "test" {
name = "subnetname"
resource_group_name = "${azurerm_resource_group.testrg.name}"
virtual_network_name = "${azurerm_virtual_network.test.name}"
address_prefix = "10.0.2.0/24"
service_endpoints = ["Microsoft.Sql","Microsoft.Storage"]
}
resource "azurerm_storage_account" "testsa" {
name = "storageaccountname"
resource_group_name = "${azurerm_resource_group.testrg.name}"
location = "${azurerm_resource_group.testrg.location}"
account_tier = "Standard"
account_replication_type = "LRS"
network_rules {
ip_rules = ["127.0.0.1"]
virtual_network_subnet_ids = ["${azurerm_subnet.test.id}"]
}
tags {
environment = "staging"
}
}

Groovy - Define variable where the variable name is passed by another variable

I want define a variable in groovy with where the variable name is passed by another variable.
Something like.
def runExtFunc(varName){
def varName // => def abc
varName = load 'someFile.groovy' // abc = load 'someFile.groovy'
varName."$varName"() // -> abc.abc() (run function defined in File)
}
[...]
runExtFunc('abc') // -> abc.abc() (Function abc defined in File)
[...]
runExtFunc('xyz') // -> xyz.xyz() (Function xyz defined in File)
[...]
Sadly def varName defines the variable varName and not abc. When I call runExtFunc twice an error occoures bacause varName is already defined.
I also tried
def runExtFunc(varName){
def "${varName}" // => def abc
[...]
"${varName}" = load 'someFile.groovy'
[...]
}
which doesn't work either.
Any suggestions?
This is the wrong approach. Normally you would use List, Map or Set data structures, which allow you to save a collection and access specific elements in the collection.
List allows you to hold specific values (unique or non-unique). Set allows you to hold specific values (all unique). Map allows you to have Key, Value pairs (Key must be unique) .
Read more here
groovy list,
groovy map
Try this (if I understand you correctly):
def dummyFunc(varName) {
new GroovyShell(this.binding).evaluate("${varName}")
}
dummyFunc('abc')
abc = "Hello there"
println abc
Prints
Hello there
See here
https://godless-internets.org/2020/02/14/extracting-jenkins-credentials-for-use-in-another-place/
secret_var="SECRET_VALUE_${secret_index}"
aws ssm put-parameter --name ${param_arn} --type "SecureString" --value ${!secret_var} --region us-east-2 --overwrite
I'm entering here a code sample we've done.
Please, feel free to comment.
http://groovy-lang.org/syntax.html
https://godless-internets.org/2020/02/14/extracting-jenkins-credentials-for-use-in-another-place/
def int fileContentReplaceDynamic(String filePathVar, String envTail = "",
String [] keysToIgnore = new String[0]){
def filePath = readFile filePathVar
def lines = filePath.readLines()
//def regex = ~/\b__\w*\b/
String regex = "__(.*?)__"
ArrayList credentialsList = new ArrayList();
ArrayList<String> keysToIgnoreList = new ArrayList<String>(Arrays.asList(keysToIgnore));
for (line in lines){
Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE)
Matcher matcher = pattern.matcher(line)
while (matcher.find()){
String credKeyName = matcher.group().replaceAll("__","")
if ((! credentialsList.contains(credKeyName)) &&
(! keysToIgnoreList.contains(credKeyName))) {
credentialsList.add(credKeyName)
} else {
log.info("Credencial ignorada o ya incluida: ${credKeyName}")
}
}
}
if(credentialsList.size() <= 0){
log.info("No hay variables para aplicar transformada")
return 0
}
log.info("Numero de credenciales encontradas a sustituir: " + credentialsList.size())
String credentialsListString = String.join(", ", credentialsList);
log.info("Credenciales: " + credentialsListString)
def credsRequest = null
for(def credKeyName in credentialsList){
// Retrieve the values of the variables by environment tail name.
String credKeyNameByEnv = "${credKeyName}";
if ((envTail != null) && (! envTail.trim().isEmpty())) {
credKeyNameByEnv = credKeyNameByEnv + "-" + envTail.trim().toUpperCase();
}
// Now define the name of the variable we'll use
// List<org.jenkinsci.plugins.credentialsbinding.MultiBinding>
// Tip: java.lang.ClassCastException:
// org.jenkinsci.plugins.credentialsbinding.impl.BindingStep.bindings
// expects class org.jenkinsci.plugins.credentialsbinding.MultiBinding
String varName = "var_${credKeyNameByEnv}"
if (credsRequest == null) {
// Initialize
credsRequest = [string(credentialsId: "${credKeyNameByEnv}", variable: "${varName}")]
} else {
// Add element
credsRequest << string(credentialsId: "${credKeyNameByEnv}", variable: "${varName}")
}
}
int credsProcessed = 0
def passwordsRequest = null
StringBuilder sedReplacements = new StringBuilder();
// Now ask jenkins to fill in all variables with values
withCredentials(credsRequest) {
for(def credKeyName in credentialsList){
String credKeyVar = "var_${credKeyName}"
log.info("Replacing value for credential ${credKeyName} stored in ${credKeyVar}")
String credKeyValueIn = "${!credKeyVar}"
String credKeyValue = null;
if ("empty_string_value".equals(credKeyValueIn.trim())) {
credKeyValue = "";
} else {
credKeyValue = credKeyValueIn.replaceAll(/(!|"|#|#|\$|%|&|\/|\(|\)|=|\?)/, /\\$0/)
}
if (passwordsRequest == null) {
// Initialize
passwordsRequest = [[password: "${credKeyValue}" ]]
} else {
// Add element
passwordsRequest << [password: "${credKeyValue}" ]
}
sedReplacements.append("s/__${credKeyName}__/${credKeyValue}/; ")
credsProcessed++
}
}
wrap([$class: "MaskPasswordsBuildWrapper", varPasswordPairs: passwordsRequest ]){
String sedReplacementsString = sedReplacements.toString().trim();
if (sedReplacementsString.endsWith(";")) {
sedReplacementsString = sedReplacementsString.substring(0, sedReplacementsString.length() -1);
sedReplacementsString = sedReplacementsString + "g;"
}
sh """sed -i "${sedReplacementsString}" ${filePathVar}"""
}
log.info("Finaliza la transformada. Transformados: ${credsProcessed}/${credentialsList.size()} ")
if (credsProcessed != credentialsList.size()) {
log.info("Hay credenciales que no se han podido encontrar en el vault de Jenkins.")
log.info("Si estas guardando cadenas vacias en credenciales, guarda en su lugar el valor 'empty_string_value'.");
return -1
}
return 0;
}