Can you use the lookup function in terraform with map variables containing arrarys? - variables

I'm using map variables and the lookup function to configure aws differently depending on the workspace selected. It works fine when the variable contains string but I can't get it to work with arrays and I'm not sure if it's possible
I've poured over the terraform documentation but can't see to sort it out. It looks like it can't be down with a map of arrays. Maybe someone has sorted through this issue
variable "cidr" {
type = "map"
default = {
"prod" = ["10.7.3.0/24","10.7.4.0/24"]
"test" = ["10.8.3.0/24","10.8.4.0/24"]
}
}
cidr = ${lookup(var.cidr, terraform.workspace)}"
lookup() can only be used with maps of
primitive types.

If you are using Terraform v0.12.0 or later, the idiomatic way to access one of the lists from your map of lists is to use the index syntax:
cidr = var.cidr[terraform.workspace]
You can also use the index syntax in Terraform v0.11 or earlier, but it must be wrapped in a template string because that is how we indicate to Terraform that we intend to use an expression in those older versions:
cidr = "${var.cidr[terraform.workspace]}"
The lookup function is for situations where you don't know if the given key is present and want to provide a default value to use instead if it is not. Although lookup with only two arguments is still supported for backward-compatibillity, it should generally be used only in its three-argument form in modern Terraform:
# (this particular default is likely not a good idea, but this
# is just to illustrate the syntax.)
cidr = lookup(var.cidr, terraform.workspace, ["0.0.0.0/0"])
Until Terraform 0.12.7, the lookup function is indeed restricted to only work with maps of primitive types. In Terraform 0.12.7 it was generalized to behave the same way as the index operator, but with the extra rule of returning the default value if the requested key isn't present.
As a side note, if you are using Terraform v0.12.0 or later then you can provide a more specific type constraint on that variable:
variable "cidr" {
type = map(list(string))
default = {
"prod" = ["10.7.3.0/24","10.7.4.0/24"]
"test" = ["10.8.3.0/24","10.8.4.0/24"]
}
}
By telling Terraform exactly what element types are expected for the list and map type, Terraform can automatically check the value provided by the caller to make sure it conforms, and report a type error if not. If you just write "map" then that's a legacy shorthand for map(any), in which case Terraform can only check that it's a map of any single type, not specifically what the element type must be. I'd recommend always using exact type constraints in Terraform 0.12.0 or later.

Related

What is the correct way to reference variables in terraform? When to use interpolation ${var.} and when to directly call var.?

I am new to Terraform. I have a doubt regarding referencing variable in the main.tf file.
Is it ami = var.image or ami = "${var.image}". What is the difference, when to use directly
var. and when to use interpolation ${var.}
Example:
variable "instancetype" {
default = "t2.micro"
}
variable "image" {
default = "ami-<ami id>"
}
resource "aws_instance" "new"{
ami = var.image
instance_type = var.instancetype
tags = {
Name = "New-Instance"
}
}
The ${var.example} syntax is for including the result of an expression into a larger string. It is part of Terraform's string template syntax. For example, you might write "${var.example}-foo" to produce a string that consists of the value of var.example followed by the literal suffix -foo.
If you need only the value of the variable in isolation, without concatenating it with any other string values, there is no reason to use that interpolation syntax; var.example and "${var.example}" are exactly equivalent.
For simple situations that Terraform can understand via basic syntactic analysis, terraform fmt will replace a redundant expression like "${var.example}" with its simpler equivalent var.example. That tool encodes various idiomatic style conventions like this, and so it can be useful to apply the result of that tool (either directly by running it, or via its integration into plugins for editors like Visual Studio Code) to see if it makes an adjustment that would make your configuration style consistent with the usual idiomatic style.

Terraform module as "custom function"

It is possible to use some i.e. local module to return let' say same calculated output. But how can you pass some parameters? So each time you will ask for the output value you will get different value according to the parameter(ie different prefix)
Is it possible to pass resource to module and enhance it with tags?
I can imagine that both cases are more likely to be case for providers, but for some simple case it should work maybe. The best would be if they implemented some custom function that you will be able to call at will.
It is possible in principle to write a Terraform module that only contains "named values", which is the broad term for the three module features Input Variables (analogous to function arguments), Local Values (analogous to local declarations inside your function), and Output Values (analogous to return values).
Such a module would not contain any resource or data blocks at all and would therefore be a "computation-only" module, which therefore has all of the same capabilities as a function in a functional programming language.
variable "a" {
type = number
}
variable "b" {
type = number
}
locals {
sum = var.a + var.b
}
output "sum" {
value = local.sum
}
The above example is contrived just to show the principle. A "function" this simple doesn't really need the local value local.sum, because its expression could just be written inline in the value of output "sum", but I wanted to show examples of all three of the relevant constructs here.
You would "call the function" by declaring a module call referring to the directory containing the file with the above source code in it:
module "example" {
source = "./modules/sum"
a = 1
b = 2
}
output "result" {
value = module.example.sum
}
I included the output "result" block here to show how you can refer to the result of the "function" elsewhere in your module, as module.example.sum.
Of course, this syntax is much more "chunky" than a typical function call, so in practice Terraform module authors will use this approach only when the factored out logic is significant enough to justify it. Verbosity aside though, you can include as many module blocks referring to that same module as you like if you need to call the "function" with different sets of arguments. Each call to the module can take a different set of input variable values and therefore produce a different result.

Can we have a dynamic string input with as a variable present on the terraform resource block?

Scenario: to have multiple Kubernetes deployments I have a skeleton.tf file that could create an app as per requirement with minimum variable changes and in different namespaces and I do not want to provide a default name so that I will give the input everytime I do a Terraform plan and apply
like
resource "kubernetes_deployment" "${var.deployment-1}" {
...
...
namespace= var.namespace_1
...
}
how do I achieve this? Is this supported, because I face a syntax interpolation error,
or
Invalid string literal: Template sequences are not allowed in this string. To include a literal "$", double it (as "$$") to escape it.
or
Invalid character: This character is not used within the language.
or
Invalid block definition: Either a quoted string block label or an opening brace ("{") is expected here.
I have read about the terraform workspaces but then, it would be a tedious task to be able to get the resource name as a dynamic input. any help or workarounds to this is appreciated.
The name given in the second label of a resource block is used only within the current Terraform module, so there is no need for it to be dynamically customizable. If you don't have a specific name to use then you can use a generic name like "main".
Because this is a Terraform-only name, Terraform will automatically deal with it appearing in possibly several different instances of your module, because the full address of a resource includes the address of the module that contains it. The whole result is therefore unique with in a Terraform configuration, and the resource's local name is unique within the module that declares it.

Modeshape configuration - combine XML + programmatic?

I have configured a Modeshape workspace on my dev box using XML, pointing to:
workspaceRootPath="C:/jcr/modeshape/dev/..."
I will deploy to Linux with a workspace mounted on a different volume:
workspaceRootPath="/jcr/modeshape/prod/..."
Is it possible to use an environment variable to configure this or do I need to resort to programmatic configuration? Is there an approach recommended by the Modeshape team?
Thanks
If you're using later versions of ModeShape, you can use a variable in the configuration file that will be replaced at configuration load time with the value of the System property of the same name. For example, if you use the following:
workspaceRootPath="${myWorkspaceDirectory}"
and have a System property "myWorkspaceDirectory" set to "/foo/bar", then when ModeShape loads the configuration it will resolve the variable into the equivalent:
workspaceRootPath="/foo/bar"
Of course, the variable can be just a part of the attribute value, and you can even use multiple variables (as long as they're not nested). For example, this is valid, too:
workspaceRootPath="${my.system.root.path}/modeshape/${my.system.deploymentType}"
Finally, the grammar of each variable is:
"${" systemPropName { "," systemPropName } [ ":" defaultValue ] "}"
This allows 1 or more System property names and an optional default value to be specified within a single variable. The System property names are evaluated from left to right, and the first to have a corresponding real system property will be used. Here's another contrived example:
workspaceRootPath="${my.system.path1,my.system.path2,my.system.path3:/default/path}/modeshape/${my.system.deploymentType}"

Lambdas with captured variables

Consider the following line of code:
private void DoThis() {
int i = 5;
var repo = new ReportsRepository<RptCriteriaHint>();
// This does NOT work
var query1 = repo.Find(x => x.CriteriaTypeID == i).ToList<RptCriteriaHint>();
// This DOES work
var query1 = repo.Find(x => x.CriteriaTypeID == 5).ToList<RptCriteriaHint>();
}
So when I hardwire an actual number into the lambda function, it works fine. When I use a captured variable into the expression it comes back with the following error:
No mapping exists from object type
ReportBuilder.Reporter+<>c__DisplayClass0
to a known managed provider native
type.
Why? How can I fix it?
Technically, the correct way to fix this is for the framework that is accepting the expression tree from your lambda to evaluate the i reference; in other words, it's a LINQ framework limitation for some specific framework. What it is currently trying to do is interpret the i as a member access on some type known to it (the provider) from the database. Because of the way lambda variable capture works, the i local variable is actually a field on a hidden class, the one with the funny name, that the provider doesn't recognize.
So, it's a framework problem.
If you really must get by, you could construct the expression manually, like this:
ParameterExpression x = Expression.Parameter(typeof(RptCriteriaHint), "x");
var query = repo.Find(
Expression.Lambda<Func<RptCriteriaHint,bool>>(
Expression.Equal(
Expression.MakeMemberAccess(
x,
typeof(RptCriteriaHint).GetProperty("CriteriaTypeID")),
Expression.Constant(i)),
x)).ToList();
... but that's just masochism.
Your comment on this entry prompts me to explain further.
Lambdas are convertible into one of two types: a delegate with the correct signature, or an Expression<TDelegate> of the correct signature. LINQ to external databases (as opposed to any kind of in-memory query) works using the second kind of conversion.
The compiler converts lambda expressions into expression trees, roughly speaking, by:
The syntax tree is parsed by the compiler - this happens for all code.
The syntax tree is rewritten after taking into account variable capture. Capturing variables is just like in a normal delegate or lambda - so display classes get created, and captured locals get moved into them (this is the same behaviour as variable capture in C# 2.0 anonymous delegates).
The new syntax tree is converted into a series of calls to the Expression class so that, at runtime, an object tree is created that faithfully represents the parsed text.
LINQ to external data sources is supposed to take this expression tree and interpret it for its semantic content, and interpret symbolic expressions inside the tree as either referring to things specific to its context (e.g. columns in the DB), or immediate values to convert. Usually, System.Reflection is used to look for framework-specific attributes to guide this conversion.
However, it looks like SubSonic is not properly treating symbolic references that it cannot find domain-specific correspondences for; rather than evaluating the symbolic references, it's just punting. Thus, it's a SubSonic problem.