Map within a map in terraform variables - 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

Related

Nextflow dynamic includes and output

I'm trying to use dynamic includes but I have problem to manage output files:
/*
* enables modules
*/
nextflow.enable.dsl = 2
include { requestData } from './modules/get_xapi_data'
include { uniqueActors } from './modules/unique_actors'
include { compileJson } from './modules/unique_actors'
if (params.user_algo) {
include { userAlgo } from params.user_algo
}
workflow {
dataChannel = Channel.from("xapi_data.json")
requestData(dataChannel)
uniqueActors(requestData.out.channel_data)
if (params.user_algo) {
user_algo = userAlgo(requestData.out.channel_data)
} else {
user_algo = null
}
output_json = [user_algo, uniqueActors.out]
// Filter output
Channel.fromList(output_json)
.filter{ it != null } <--- problem here
.map{ file(it) }
.set{jsonFiles}
compileJson(jsonFiles)
}
The problem is userAlgo can be dynamically loaded. And I don't know how I can take care of it. With this solution, I got a Unknown method invocation getFileSystem on ChannelOut type error.
The problem is that fromList expects a list of values, not a list of channels. If you use an empty Channel instead of checking for a null value, you could use:
if( params.user_algo ) {
user_algo = userAlgo(requestData.out.channel_data)
} else {
user_algo = Channel.empty()
}
user_algo
| concat( uniqueActors.out )
| map { file(it) }
| compileJson

Terraform Conditional Content Block Inside Resource

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 = {}
}

XMLPullParser returns only one element

I could not parse my XML file, it returns only one element instead of 4
Here's my XML file
<Quizzs>
<Quizz type="A">...</Quizz>
<Quizz type="B">...</Quizz>
<Quizz type="C">...</Quizz>
<Quizz type="D">...</Quizz>
</Quizzs>
It returns only the last one "D"
while (eventType != XmlPullParser.END_DOCUMENT) {
var eltName: String? = null
when (eventType) {
XmlPullParser.START_TAG -> {
eltName = parser.name
if ("Quizzs" == eltName) {
currentQuizz = Quizz()
quizz.add(currentQuizz)
} else if (currentQuizz != null) {
if ("Quizz" == eltName) {
currentQuizz.type = parser.getAttributeValue(null, "type")
}
}
}
}
eventType = parser.next()
}
printPlayers(quizz)
}
You need to .add() something to your currentQuizz for each "Quizz". With currentQuizz.type = ... you just overwrite each previous "Quizz" with the current one, so you end up with only the last one, which is D.
I think you are confused by your own code. For the "Quizzs" tag you create a Quizz() object instead of a QuizzList() object or something similar. It is for the "Quizz" tag you should create a new Quizz() object each time. And then you should add that object to your QuizzList.

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

Detecting a change in a compound property

Objective-C would not allow you to run the following code:
myShape.origin.x = 50
This made it easy to detect changes in the origin, since someone using your class was forced to write myShape.origin = newOrigin, and thus you could easily tie in to the setter of this property.
Swift now allows you to perform the original, formerly-disallowed code. Assuming the following class structure, how would you detect the change to the origin in order to execute your own code (e.g. to update the screen)?
struct Point {
var x = 0
var y = 0
}
class Shape {
var origin: Point = Point()
}
Update: Perhaps I should have been more explicit, but assume I don't want to modify the Point struct. The reason is that Shape is but one class that uses Point, there may very well be hundreds of others, not to mention that the origin is not the only way a Point may be used.
Property observers (willSet and didSet) do fire when sub-properties of that property are changed. In this case, when the x or y values of the Point structure change, that property will be set.
Here is my example playground code:
struct Point : Printable
{
var x = 0
var y = 0
var description : String {
{
return "(\(x), \(y))";
}
}
class Shape
{
var origin : Point = Point()
{
willSet(newOrigin)
{
println("Changing origin to \(newOrigin.description)!")
}
}
}
let circle = Shape()
circle.origin.x = 42
circle.origin.y = 77
And here is the console output:
Changing origin to (42, 0)!
Changing origin to (42, 77)!
Doesn't this work?
class Shape {
var origin: Point {
willSet(aNewValueForOrigin) {
// pre flight code
}
didSet(theOldValueOfOrigin) {
// post flight code
}
}
}
Edit: revisited code and added name of arguments to reflect what to expect.
You can use Property Observers also works for structs
Link to the part on the ebook
class StepCounter {
var totalSteps: Int = 0 {
willSet(newTotalSteps) {
println("About to set totalSteps to \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
println("Added \(totalSteps - oldValue) steps")
}
}
}
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// About to set totalSteps to 200
// Added 200 steps
stepCounter.totalSteps = 360
// About to set totalSteps to 360
// Added 160 steps
stepCounter.totalSteps = 896
// About to set totalSteps to 896
// Added 536 steps
Use didSet, e.g.,
struct Point {
var x = 0
var y: Int = 0 {
didSet {
println("blah blah")
}
}
}
class Shape {
var origin: Point = Point()
}
let s = Shape()
s.origin.y = 2