How to understand plusignment operator in puppet - puppet

In URL https://docs.puppetlabs.com/references/glossary.html#plusignment-operator, here is its explanation:
The +> operator, which allows you to add values to resource attributes using the (‘plusignment’) syntax. Useful when you want to override resource attributes without having to respecify already declared values.
For example, I have code like this:
Package {
require => File['/etc/apt/apt.conf.d/no-cache'],
}
package { 'php5-cgi':
ensure => present,
}
Package[ 'php5-cli' ] {
require +> Package['php5-cgi'],
}
What did the operator +> mean here?
Other sample:
subscribe +> Sshkey['www.example.com']

The attribute in question takes a value that is composed of the right hand side and whatever it would have taken otherwise.
In your example, the package { 'php5-cgi' } would normally use a require value of File['/etc/apt/apt.conf.d/no-cache'], since it is the default for all package resources. Through the plusignment, you end up with a value of
require => [ File['/etc/apt/apt.conf.d/no-cache'], Package['php5-cgi'] ]
The php5-cli package builds a relation to both the referenced file and the php5-cgi package.
The same logic will apply to the subscribe metaparameter from your second example. I cannot comment on the whole semantics without more context.
The plusignment works for all resource attributes, although the resulting array values will not make sense for many of them.

Related

Terraform: output from multiple module calls

Pseudo-code:
module "foo-1" {
source="./foo"
input=1
}
module "foo-2" {
source="./foo"
input=2
}
module "foo-3"
source="./foo"
input=3
}
...etc...
(The module ./foo outputs a unique id based on the input value)
Problem:
I would like to be able to arbitrarily instantiate/call the ./foo module and have access to the unique id from each module instances. I can't see a way to do this with Terraform as the output syntax either requires a unique val=expression per module instantiation. Splat expressions on the module object (module.*.id) are unfortunately (and not surprisingly) not supported.
I'm guessing that this can't be done in terraform but would love to be wrong.
Because each of those modules is entirely separate from Terraform's perspective, to gather their results together into a single value will require writing an expression to describe that. For example:
locals {
foos = [
module.foo_1,
module.foo_2,
module.foo_3,
]
}
With a definition like that, elsewhere in your module you can then write an expression like local.foos[*].id to collect up all of the ids across all of the modules.

When is the equal sign (=) required in Terraform when assigning a map (TF 0.11)

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.

Puppet cron job -- ensure files exist

I'm trying to set up a Puppet cron job with the following structure:
file { '/usr/local/sbin/file.py':
mode => '0755',
source => 'puppet:///modules/file.py',
require => File['/usr/local/sbin']
}
cron { "cronjob":
require => "ALL_THE_FILES_ABOVE"
command => "...command_to_run_script..."
minute => '*/1'
}
All of the above is in one file run_script.pp. I'm wondering how I can code the require => "ALL_THE_FILES_ABOVE" part.
Thanks!
Based on the information provided in your question, I am going to make the assumption that the contents of run_script.pp is many file resources and the listed cron resource. You state that you want the cron resource there to require all of the file resources in that class. Based on this, here is a clean and efficient solution.
There are a few complicated/advanced ways to arrive at a clean and efficient solution, but the easiest to understand is to use a resource default: https://puppet.com/docs/puppet/5.3/lang_defaults.html
With this, we can establish attribute/value pair defaults for all file resources contained in that scope. This would make it easier to use the before metaparameter on the file resources instead: https://puppet.com/docs/puppet/5.3/metaparameter.html#before
This simplifies the solution to a one-liner in your class:
File { before => Cron['cronjob'] }
Note there will be a caveat to this method, which is that if you are declaring, requiring, or containing a class within this manifest, then this default could be expanded to that "area of effect" and cause a circular dependency. In that case, you should use a per-expression resource default attribute: https://puppet.com/docs/puppet/5.3/lang_resources_advanced.html#per-expression-default-attributes
You can use a multiple require
file{'path/foo':}
file{'path/bar':}
file{'~/foobar':
require => [ File['path/foo'], File['path/bar'] ]
}
or you can use the ordering arrow
-> (ordering arrow; a hyphen and a greater-than sign) — Applies the resource on the left before the resource on the right.
file{'path/foo':} ->
file{'path/bar':} ->
file{'~/foobar':}
Here is more information about relationships and ordering in Puppet

Puppet 2.7: Updating hash in the parent scope fails

I need to recursively insert entries in a hash based on some logic. The state of the hash gets updated inside the defined type loop, but not in the outer scope. The following should clarify:
class Test {
$config = {}
define my_loop()
{
$config['a'] = 'b'
notify { "1) config = $config": } # shows that $config has a=>b
}
my_loop { 'loop' : }
notify { "2) config = $config":
require => My_loop['loop'] # shows that $config is empty
}
}
So, the problem is that $config inside the loop() contains a=>b, but outside the loop() it doesn't. I must be bumping against some scoping rules here.
Thoughts?
The values of Puppet variables are set once, and they do not change thereafter. In those few places that present the appearance of behaving differently, what actually happens is either that a modified local copy or an entirely independent variable is created.
Additionally, do not nest classes or defined types inside classes. Puppet allows it for historical reasons, but it doesn't have the semantics you probably expect, and it makes the nested class / type hard to find.
Consider writing a custom function to perform your computation and return the needed hash.
Also consider whether upgrading to a supported version of Puppet would be feasible. Version 2.7 is very old.

How to iterate over an array in Puppet

I would like to iterate over an array that is stored as a Facter fact, and for each element of the array create a new system user and a directory, and finally make API calls to AWS.
Example of the fact: my_env => [shared1,shared2,shared3]
How can I iterate over an array in Puppet?
This might work, depending on what you are doing
# Assuming fact my_env => [ shared1, shared2, shared3 ]
define my_resource {
file { "/var/tmp/$name":
ensure => directory,
mode => '0600',
}
user { $name:
ensure => present,
}
}
my_resource { $my_env: }
It will work if your requirements are simple, if not, Puppet makes this very hard to do. The Puppet developers have irrational prejudices against iteration based on a misunderstanding about how declarative languages work.
If this kind of resource doesn't work for you, perhaps you could give a better idea of which resource properties you are trying to set from your array?
EDIT:
With Puppet 4, this lamentable flaw was finally fixed. Current state of affairs documented here. As the documentation says, you'll find examples of the above solution in a lot of old code.
As of puppet 3.2 this is possible using the "future" parser like so:
$my_env = [ 'shared1', 'shared2', 'shared3', ]
each($my_env) |$value| {
file { "/var/tmp/$value":
ensure => directory,
mode => 0600,
}
user { $value:
ensure -> present,
}
}
See also: http://docs.puppetlabs.com/puppet/3/reference/lang_experimental_3_2.html#background-the-puppet-future-parser
Puppet 3.7 released earlier this month have the new DSL, which one feature is the iteration, check the following URL https://docs.puppetlabs.com/puppet/latest/reference/experiments_lambdas.html#enabling-lambdas-and-iteration
these new features can be enabled with the :
Setting parser = future in your puppet.conf file
or adding the command line switch --parser=future
hope that helps
As of latest Puppet (6.4.2), and since Puppet 4, iteration over arrays is supported in a few ways:
$my_arr = ['foo', 'bar', 'baz']
Each function:
$my_arr.each |$v| {
notice($v)
}
Each function alternative syntax:
each($my_arr) |$v| {
notice($v)
}
To get the index:
Pass a second argument to each:
$my_arr.each |$i, $v| {
notice("Index: $i, value: $v")
}
Comparison with Ruby:
Note that this grammar is inspired by Ruby but slightly different, and it's useful to show the two side by side to avoid confusion. Ruby would allow:
my_arr.each do |v|
notice(v)
end
Or:
my_arr.each { |v|
notice(v)
}
Other iteration functions:
Note that Puppet provides a number of other iteration functions:
each - Repeats a block of code a number of times, using a collection of values to provide different parameters each time.
slice - Repeats a block of code a number of times, using groups of values from a collection as parameters.
filter - Uses a block of code to transform a data structure by removing non-matching elements.
map - Uses a block of code to transform every value in a data structure.
reduce - Uses a block of code to create a new value, or data structure, by combining values from a provided data structure.
with - Evaluates a block of code once, isolating it in its own local scope. It doesn’t iterate, but has a family resemblance to the iteration functions.
Puppet 3 and earlier:
If you have inherited old code still using Puppet 3, the accepted answer is still correct:
define my_type {
notice($name)
}
my_type { $my_arr: }
Note however that this is usually considered bad style in modern Puppet.
itsbruce's answer is probably the best for now, but there is an iteration proposal going through puppetlabs' armatures process for possible implementation in future.
There is a "create_resources()" function in puppet. that will be very helpful while iterating over the list of itmes

Resources