Puppet 2.7: Updating hash in the parent scope fails - scope

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.

Related

How to reference a variable from an earlier line in the same block in terraform?

Suppose I had a module (or resource, or locals block, or whatever)
module "example" {
foo = "foo"
bar = "${foo}" # why wont' this work??
}
It's frustrating to me that the terraform "language" doesn't support this. How am I supposed to write simple DRY code without being able to reference variables?
Edit: I think that example is too contrived. Here's a better one:
module "example" {
domain = "example.com"
uri = "https://${domain}" # why wont' this work??
}
It is supported, but it depends on your module. Your module must output foo first. Then you can do:
module "exmaple" {
source = "./mymodule1"
foo = "dfsaf"
bar = module.exmaple.foo
}
The simplest example of mymodule1 would be:
variable "foo" {
default = 1
}
variable "bar" {
default = 2
}
output "foo" {
value = var.foo
}
But in your example it really does not make sense doing that, as bar is always same as foo, thus it shouldn't even be exposed.
The better and more natrual way would be:
locals {
foo = "foo"
}
module "exmaple" {
source = "./mymodule1"
foo = local.foo
bar = local.foo
}
Your question seems to be two different questions: how can you achieve the goal you described, and also why didn't the thing you tried first work.
Others already answered how you can factor out values to use in multiple locations, and so I wanted to just try to address the second question about why this didn't work, in case it's useful for your onward journey with Terraform.
The Terraform language is designed to appear as a heirarchical data structure rather than as linear code to be executed, which of course has some significant tradeoffs. In return for hopefully making a well-written Terraform configuration read like a manifest of what should exist rather than a program for creating it, the Terraform language does somewhat obscure the real control flow and symbol scoping that you might be accustomed to in other languages.
For your question in particular, I think it's important to recognize that the arguments inside a module block are not like declarations of variables, but are instead more like arguments passed to a function.
For your second example then, it might help to see it reworked into a function-call-like syntax:
module "example" {
source = "./mymodule1"
domain = "example.com"
uri = "https://${domain}"
}
# NOTE: pseudocode
example(domain: "example.com", uri: "https://#{domain}")
In most imperative-style languages, function arguments bind to symbols inside the module rather than outside of it, and so defining domain as "example.com" here doesn't make that symbol visible to other subsequent arguments in the same call.
Now taking Marcin's final example, adapted to resemble your second example:
locals {
domain = "example.com"
}
module "example" {
source = "./mymodule1"
domain = local.domain
url = "https://${local.domain}"
}
# NOTE: pseudocode
domain = "example.com"
example(domain: domain, uri: "https://#{domain}")
Of course this is also exposing another difference with Terraform compared to general-purpose languages, which is that it doesn't have a shared variable scope with everything assigned into it and instead places local variables inside a namespace local. But despite the difference in syntax, they are local values scoped to the module they are defined with, and so you can refer to local.domain anywhere else in that same module. (Note: this is a whole-module scope, and not a lexical scope as you might be accustomed to in other languages.)
A similar principle applies to resource, data, and provider blocks; those two are more like arguments to a function than they are variable declarations, but just shaped in a different syntax with the goal of it appearing as a series of declarations rather than as sequential code.

How to understand plusignment operator in 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.

XText: permit invalid cross reference

I need to build a grammer containing a cross reference, which may be invalid, i.e. points to a nonexisting target. A file containing such a reference should not yield an error, but only a warning. The generator would handle this as as a special case.
How can I do this with XText?
It's not possible to create valid cross references to non-existing targets in EMF.
I would suggest to go with EAttributes instead of EReferences:
Change the feature=[EClass|ID] by feature=ID in {YourDSL} grammar.
Provide a scope calculation utility like it's done in *scope_EClass_feature(context, reference)* method in the {YourDSL}ScopeProvider class. As this scoping methods simply use the eType of the given reference the reimplementation should be straightforward.
Use this scope calculation utility in {YourDSL}ProposalProvider to propose values for the introduced EAttribute.
Optionally you can use this utility in a validation rule to add a warning/info to this EAttribute if it's not "valid".
Finally use the utility in your generator to create output based on valid target eObjects.
I also ran into this problem when creating a DSL to provide declerations of variables for a none-declerative language for a transition pahse. This method works but ask yourself if you realy want to have those nasty may-references.
You can drop the auto generated error in you UI module only. To do so, provide an ILinkingDiagnosticMessageProvider and override the function getUnresolvedProxyMessage:
class DSLLinkingDiagnosticMessageProvider extends LinkingDiagnosticMessageProvider {
override getUnresolvedProxyMessage(ILinkingDiagnosticContext context) {
if(context.context instanceof YourReference) {
// return null so the your error is left out
null
} else {
// use super implementation for others
super.getUnresolvedProxyMessage(context)
}
}
}
All linker-errors for YourReference will be missed. But be aware that there will be a dummy referenced object with all fealds null. Exspecialy the name ist lost and you can not set it due to a CyclicLinkingException. But you may create a new method that sets the name directly.
Note that the dummy object will have the type you entered in your gramma. If its abstract you can easily check witch reference is not linked.

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

How to pass node specific information to class in puppet?

I want to pass node specific information to a class, which then could evaluate it for specific purposes. Actually this question consists of three parts.
Say, I have the following node:
node 'devbox' {
$serverType = 'something'
include someClass
someOtherClass { 'someOtherClass':
par1 => 'value',
}
targetClass { 'nodeInformationShouldGoHere': }
}
Inside targetClass, I want to evaluate if serverType, someClass or someOtherClass is set (e.g. with if-else). My questions now are:
Is setting and passing the variable suitable in puppet for this?
or should I use tags (as the classes are automatically tagged for this node)?
Are their further approaches and what are limitations to above ones (e.g. do they work for resource types?)?
You can absolutely use puppet this way. Read over the documentation for Parameterized Classes and see if that meets your needs.

Resources