If I create a varaible definition like this:
variable "aws_ecs_config" {
type = object({
cpu = number
memory = number
ecs_image_address = string
})
logs = {
type = object({
group = string
region = string
stream_prefix = string
})
}
}
How can I reuse that definition in multiple places without copy-pasting?
It is not possible to re-use variable declarations in Terraform. If variables in different modules will have the same type, that type must be repeated in each module.
Terraform has a structural type system rather than a nominal type system, so types themselves are not named and instead are matched/constrained by their form. When passing values between modules, we can use type constraints to create conventions between a family of related modules, but there is no mechanism to define a type (or declare a variable) in one place and reuse it in other places.
Terraform's type constraint mechanism considers any object with at least the attributes in the type constraint to be valid, so it's not necessary to define an exhaustive object type every time.
For example, if you define a variable with the following type:
object({
name = string
})
The following object value is acceptable for that type constraint, because it has a name attribute of the correct type regardless of any other attributes it defines:
{
name = "foo"
other = "bar"
}
For that reason, it can be better to limit the variable declaration in each module to only the subset of attributes that particular module actually requires, which reduces the coupling between the modules: they only need to be compatible to the extent that their attribute names overlap, and don't need to directly bind to one another.
Workaround
This is possible without explicit Terraform support. (There are some discussions about types. Weigh in if you think it would be handy)
Terraform allows variables to be declared over multiple files, and if the modules live on a Linux filesystem, reuse is vastly simplified. (Windows folks... well, it is possible, just not pretty and may not play well with version control.)
How?
Move the variable definition to a file named variable.aws_ecs_config.tf
Place it in a well known place. Say, a directory called variables that lives next to modules.
Whenever the variable is needed in a module, create a symlink to the variable file: ln -s <path/to/variables>/variable.aws_ecs_config.tf
Related
When writing terraform modules, one is commonly writing pass through variables/inputs for dependent objects.
How can I write the variable so that the description/type just references the dependent description?
I imagine something like
variable "foo" {
type = dependant.resource.foo.var.type
description = dependant.resource.foo.var.description
default = "module default"
}
Variable descriptions are metadata used by Terraform itself (specifically: by documentation mechanisms like Terraform Registry) and are not data visible to your module code.
Each module's input variables and output values must be entirely self-contained. Mechanisms like the Terraform Registry rely on this so that they are able to generate the documentation for a module only by reference to that module, without any need to fetch and analyze any other modules or other dependencies.
If you do intend to have a variable just "pass through" to a child module or to a resource configuration then you will need to redeclare its type and description in your module.
I would also suggest considering the advice in the documentation section When to write a module; directly passing through a variable to a child object isn't necessarily a wrong thing to do, but it can result from a module not raising the level of abstraction and therefore not passing the test described there.
In such cases, it can be better to use module composition instead of nested modules.
In this case would mean that the caller of your module would themselves call the other module you are currently wrapping. Instead of your module taking that other module's input variables as its own, it would be declared to accept the other module's output values as its input, so that the caller can pass the object representing the first module result into the second module:
module "first" {
# ...
}
module "second" {
# ...
first = module.first
}
Inside this "second" module, the input variable first would be declared as requiring an object type with attributes matching whatever subset of the output values of "first" that the second module depends on.
Let's take the dns_a_record_set data source, for example, if one does:
data "dns_a_record_set" "test" {
## Purposely passing a DNS that would not resolve
host = "thisDnsDoesNotResolve.org"
}
output "test" {
value = data.dns_a_record_set.test.addrs
}
The error returned is, as expected:
Error: error looking up A records for "thisDnsDoesNotResolve.org": lookup thisDnsDoesNotResolve.org on 1.1.1.1:53: no such host
│
│ with data.dns_a_record_set.test,
│ on main.tf line 1, in data "dns_a_record_set" "test":
│ 1: data "dns_a_record_set" "test" {
But, is it possible to recover from this error and have the test output assigned with a default value?
A naive trial of mine was to use the try function, but that obviously does not work, as the error does not happens when data is accessed, but rather when fetched, as shown in the error above.
output "test" {
## Seems like it doesn't even reach here
value = try(data.dns_a_record_set.test.addrs, ["127.0.0.1"])
}
So, is this even possible to recover from this error?
And, then, how?
The purpose of data resources in Terraform is to declare dependencies on objects managed elsewhere and, as a side-effect, obtain information about them to use in other parts of your module in a similar way as you might with objects that are managed by your current module.
If your module only depends on this DNS record in certain situations then a typical approach would be to use either the count or for_each meta-argument to explicitly declare in which situations your module depends on this object and which situations it doesn't.
It's hard to show a specific example without more information about your underlying motivation for this, but here's a contrived example where the hostname can be directly provided by the module caller as an input variable, and the dependency on that external object is only relevant when that variable is set (non-null):
variable "hostname" {
type = string
default = null # this variable is optional
}
data "dns_a_record_set" "test" {
count = length(var.hostname[*])
host = var.hostname
}
output "ip_addresses" {
value = one(data.dns_a_record_set.test[*].addrs)
}
The above uses two additional language features I didn't mention yet in this comment, which I'll describe briefly for completeness:
Applying the splat operator [*] to a non-list value converts the value to be a list which either has one element (if the value is non-null) or zero elements (if the value is null). Therefore there will be one instance of this data resource if and only if var.hostname is non-null.
The one function does essentially the opposite of that: if given a one-element list it will return the first element, and if given a zero-element list it will return null.
These two features taken together allow you to concisely pivot between values that might be null and lists of zero or one element, which is useful in situations like this where the "null-ness" of a valid decides how many of something should be declared.
Note that the try function only "catches" expression evaluation failures. It isn't relevant to this situation because what failed was the declared dependency on the external object; the error was returned when Terraform asked the provider to retrieve the dns_a_record_set object, not in the downstream expression which referred to that.
(Indeed, Terraform would never actually evaluate that expression in your case, because when there's a dependency from object B to object A, a failure of object A halts processing before reaching object B.)
I have a module "base" with an init.pp class which has some parameters as such:
class base (
$listen_ip = "xx.xx.xx.xx",
$listen_port = 3306,
$admin_username = 'admin',
$admin_password = 'admin',
)
{
...
}
Then I have created a profile "base" where I want to set some of the parameters:
class profile::base {
class { 'base':
$listen_ip = "xxx.xxx.xx.xx",
$listen_port => 6033,
}
}
Then the is a secondary profile where I want to set the username and password:
class profile::department::sales::base {
class { '::profile::base':
$admin_username = "some_user",
$admin_password => "some_pw",
}
}
However it's not possible to set the parameters from the "sales" profile.
The idea is that some values will be always the same for the base class and that some differ based on the department.
However it's not possible to set the parameters from the "sales" profile.
Not exactly. What is not allowed is using two different resource-like declarations for the same class while building one manifest. If you use even one then you must make certain that it is the first (or only) declaration of that class that the catalog builder evaluates.
To understand this, you need to appreciate that assigning parameter values is not the principal purpose of declarations such you are using. The principal purpose is rather to specify that the class in question should be included in the catalog in the first place. In service to that goal, values are bound to all the parameters of a class at the point where its first declaration is evaluated. Thus, your two class declarations do not supplement each other. Instead, they conflict with each other.
Even if the parameter values it specified for class base were identical to those declared by class profile::base, however, Puppet would still object to all uses of class profile::department::sales::base. To simplify evaluation and be absolutely certain to avoid inconsistency, it implements a stronger constraint than is actually required: that only the first-evaluated declaration of any given class may be a resource-like one.
Note: the latest docs actually specify an even stronger constraint than that: "Resource-like class declarations require that you declare a given class only once." In practice, however, this is a simplification (in every version of Puppet so far released since the introduction of parameterized classes). It is likely inspired by the fact that the order in which Puppet manifests are evaluated can be difficult to predict, so if you use include-like declarations along with a resource-like declaration of the same class, in different manifests, then it can be hard to ensure that the resource-like one is always evaluated first.
The idea is that some values will be always the same for the base
class and that some differ based on the department.
For most purposes it is best to avoid resource-like class declarations altogether, relying instead on external data (Hiera) for binding values to class parameters. Hiera recognizes a hierarchy of data sources (hence the name) and supports specifying different parameters at different levels, and even overriding data from one level at a higher-priority level.
My suggestion, then, is to leverage Hiera to assign appropriate parameter values to class base. There are many ways the specifics could play out.
I wasn't able to solve this question by any other method, so I have to ask this here...
What is the logic behind using the equal sign (=) when assigning a map to a value in Terraform 0.11.
With =
resource "pseude_resource" "pseudo_name" {
value = {
key1 = value1
key2 = value2
}
}
Without =
resource "pseude_resource" "pseudo_name" {
value {
key1 = value1
key2 = value2
}
}
The = seems to be required when using arrays ([]), but isn't when using a map. What is the reason behind this and why on earth?? Can it just be omited?
In the Terraform language, there are two distinct constructs that have quite similar-looking syntax in the common case.
Arguments that expect maps
An argument in Terraform is a single name/value pair where the provider dictates (in the resource type schema) what type of value it expects. You are free to create a value of that expected type any way you like, whether it be as a literal value or as a complex expression.
The argument syntax is, in general:
name = value
If a particular argument is defined as being a map then one way you can set it is with a literal value, like this:
tags = {
Name = "foo bar baz"
}
...but you can also use a reference to some other value that is compatible with the map type:
# Tags are the same as on some other VPC object
tags = aws_vpc.example.tags
...or you can combine maps together using built-in Terraform functions:
tags = merge(local.default_tags, var.override_tags)
Generally speaking, you can use any expression whose result is a map with the expected element type.
In Terraform 0.11 these non-literal examples all need to be presented in the template interpolation syntax ${ ... }, but the principle is the same nonetheless.
Nested blocks
Whereas an argument sets some specific configuration setting for the object whose block it's embedded in, the nested block syntax conventionally declares the existence of another object that is related to the one the block is embedded in. Sometimes this really is a separate physical object, such as a rule associated with a security group, and other times it's a more conceptual "object", such as versioning in aws_s3_bucket which models the versioning feature as a separate "object" because the presence of it activates the feature.
The nested block syntax follows the same conventions as the syntax for the top-level resource, variable, terraform, etc blocks:
block_type "label" {
nested_argument = value
}
Each block type will have a fixed number of labels it expects, which in many cases is no labels at all. Because each block represents the declaration of a separate object, the block structure is more rigid and must always follow the shape above; it's not possible to use arbitrary expressions in this case because Terraform wants to validate that each of the blocks has correct arguments inside it during its static validation phase.
Because the block syntax and the map literal syntax both use braces { }, they have a similar look in the configuration, but they mean something quite different to Terraform. With the block syntax, you can expect the content of the block to have a fixed structure with a specific set of argument names and nested block types decided by the resource type schema. With a map argument, you are free to choose whichever map keys you like (subject to any validation rules the provider or remote system might impose outside of the Terraform schema).
Recognizing the difference
Unfortunately today the documentation for providers is often vague about exactly how each argument or nested block should be used, and sometimes even omits the expected type of an argument. The major providers are very large codebases and so their documentation has variable quality due to the fact that they've been written by many different people over several years and that ongoing improvements to the documentation can only happen gradually.
With that said, the provider documentation will commonly use the words "nested block" or "block type" in the description of something that is a nested block, and will refer to some definition elsewhere on the page for exactly which arguments and nested blocks belong inside that block. If the documentation states that a particular argument is a map or implies that the keys are free-form then that suggests that it's an argument expecting a map value. Another clue is that block type names are conventionally singular nouns (because each block describes a single object) while arguments that take maps and other collections conventionally use plural nouns.
If you find specific cases where the documentation is ambiguous about whether a particular name is a nested block type or an argument, it can be helpful to open an issue for it in the provider's repository to help improve the documentation. Terraform's documentation pages have an "Edit This Page" link in the footer which you can use to propose simple (single-page-only) edits as a pull request in the appropriate repository.
The longer-form explanation of these concepts is in the documentation section Arguments and Blocks.
The confusion comes from the behavior of the hcl language used by terraform. The functionality isn't very well documented in terraform but... In hcl, you can define a list by using repeating blocks, which is how resources like aws_route_table define inline routes, e.g.
resource "aws_route_table" "r" {
vpc_id = "${aws_vpc.default.id}"
route {
cidr_block = "10.0.1.0/24"
gateway_id = "${aws_internet_gateway.main.id}"
}
route {
ipv6_cidr_block = "::/0"
egress_only_gateway_id = "${aws_egress_only_internet_gateway.foo.id}"
}
tags = {
Name = "main"
}
}
which would be equivalent to
resource "aws_route_table" "r" {
vpc_id = "${aws_vpc.default.id}"
route = [
{
cidr_block = "10.0.1.0/24"
gateway_id = "${aws_internet_gateway.main.id}"
},
{
ipv6_cidr_block = "::/0"
egress_only_gateway_id = "${aws_egress_only_internet_gateway.foo.id}"
}
]
tags = {
Name = "main"
}
}
You want to make sure you're using the = when you're assigning a value to something, and only use the repeating block syntax when you're working with lists. Also, from my experience I recommend NOT using inlines when an individual resource is available.
Some very limited documentation for hcl can be found in the readme for the repo.
I am making a simple debugger window in ActionScript for myself where I can add and remove variables I want to track. I was to be able to add variables to the list by just doing something like
DebuggerMonitor.trackVar(variable).
My question is, is there any way I can turn "variable" itself (the name, not the value) into a String to be added into a text field?
Depending on how "intelligent" your debugger should be, you could just pass the name along:
DebuggerMonitor.trackVar( variable, "variable" );
since obviously, when used in a context like this, the name should be known at the time you are writing the program.
You can also do some reflection magic to get instance variable names, but it won't work for temp variables (their names are dropped at compilation time):
public function getVariableName( instance:*, match:* ):String {
var typeDescription:XML = describeType( instance );
var variables:XMLList = typeDescription..variable;
var accessors:XMLList = typeDescription..accessor;
for each(var variable:XML in variables)
if(matchesXMLName( instance, variable, match ))
return variable.#name;
for each(var accessor:XML in accessors)
if(matchesXMLName( instance, accessor, match ))
return accessor.#name;
return "No name found.";
}
private function matchesXMLName( instance:*, xml:XML, match:* ):Boolean {
return match == instance[xml.#name.toString()];
}
var varName:String = getVariableName ( myObject, variable );
Using reflections like this will also be quite costly, if used often - you will have to think of a way to cache the type descriptions.
I recommend you check out the as3commons reflections package - there is a lot of useful functionality in there...
Short answer - No :(
You can access the type name but not individual instance names, as these are lost at run-time.
There is a confusion caused by the keyword 'var' because it is used to create several types of bindings.
Lexical bindings (the keyword 'var' was used inside a function).
Dynamic bindings (the keyword 'var' was used to declare a class' field).
Lexical bindings are interpreted by the compiler at compile time as addresses of the registers of the registers space occupied by the function. The names given to lexical bindings perish at this time and it is not possible to restore them at runtime - therefore you can't get the "name" of the variable.
Dynamic bindings are a kind of "public API" of the objects that declare them, they may be accessed from the code that was not compiled together with the code that created them, this is why, for the purpose of reflection the names of these bindings are stored in compiled code. However, ActionScript has no way of referencing LHS values, so you cannot, even if you know the name of the variable and the object declaring it, pass it to another function. But you can look it up in the debugger or by calling describeType on the object declaring the variable. Note that describeType will not show information on private variables even if you are calling it from the scope of the object in question.