puppet class syntax clarification - puppet

Currently trying to understand the puppet manifests written by another person and met the following construction in the class:
postgres_helper::tablespace_grant { $tablespace_grants:
privilege => 'all',
require => [Postgresql::Server::Role[$rolename]]
}
what does $tablespace_grants: means in this case? First i suggested that is some kind of a title, however when i used notice to receive the value of it, it is hash:
Tablespace_grants value is [{name => TS_INDEX_01, role => developer},
{name => TS_DATA01_01, role => developer}]

what does $tablespace_grants: means in this case? First i suggested
that is some kind of a title,
It is a variable reference, used, yes, as the title of a postgres_helper::tablespace_grant resource declaration.
however when i used notice to receive
the value of it, it is hash:
Tablespace_grants value is [{name => TS_INDEX_01, role => developer},
{name => TS_DATA01_01, role => developer}]
Actually, it appears to be an array of hashes. An array may be used as the title of a resource declaration to compactly declare multiple resources, one for each array element. In Puppet 4, however, the elements are required to be strings. Earlier versions of Puppet would stringify hashes presented as resource titles; I am uncertain offhand whether Puppet 4 still falls back on this.
In any case, it is unlikely that the overall declaration means what its original author intended, in any version of Puppet. It looks like the intent is to declare multiple resources, each with properties specified by one of the hashes, but the given code doesn't accomplish that, and it's unclear exactly what the wanted code would be.

Related

Shall I set an empty string computed string attribute for Terraform resource?

context: I'm adding a new resource to TF Provider.
I've got an API that optionally return a string attribute so I represent it as:
"foo": {
Type: schema.TypeString,
Computed: true,
Optional: true,
},
Question: if an API returns value not set / empty string for response.foo, shall I still set an empty string for foo attribute or I shouldn't set any value instead (e.g., null)?
in my resource schema.
(Hello! I'm the same person who wrote the answer you included in your screenshot.)
If both approaches -- returning null or returning an empty string -- were equally viable from a technical standpoint then I would typically prefer to use null to represent the absence of a value, since that is clearly distinct from an empty string which for some situations would otherwise be a valid present value for the attribute.
However, since it seems like you are using the old SDK ("SDKv2") here, you will probably be constrained from a technical standpoint: SDKv2 was designed for Terraform v0.11 and earlier and so it predates the idea of attributes being null and so there is no way in its API to specify that. You may be able to "trick" the SDK into effectively returning null by not calling d.Set("foo", ...) at all in your Create function, but there is no API provided to unset an attribute and so once you've set it to something non-null there would typically be no way to get it to go back to being null again.
Given that, I'd suggest it better to be consistent and always use "" when using the old SDK, because that way users of the provider won't have to deal with the inconsistency of the value sometimes being null and sometimes being "" in this case.
When using the modern Terraform Plugin Framework this limitation doesn't apply, because that framework was designed with the modern Terraform language in mind. You aren't using that framework and so this part of the answer probably won't help you right now, but I'm mentioning it just in case someone else finds this answer in future who might already be using or be considering use of the new framework.

How to refer to alternate resources in Terraform?

Terraform (as of today) has the somewhat disturbing limitation, that you cannot create a resource with an interpolated (calcuted) lifecycle attribute prevent_destroy.
Terraform: How to get a boolean from interpolation?
https://github.com/hashicorp/terraform/issues/3116
The work-around is pretty simple to code, just create 2 resources with "alternating" counts. When you have 1 "production" resource which does not allow destroying you have 0 "testing" resources which can be destroyed. Or the other way round. (See the answer to the stackoverflow question linked
above for details.)
However, this brings up a new question. When I want to refer to "the one of the alternate resources that exists for this execution", how do I do that?
In pseudo code something like
"${local.production ? "${aws_eip.commander_production.public_ip}" : "${aws_eip.commander_testing.public_ip}" }"
This pseudo code cannot work for a couple of reasons:
aws_eip.commander_production is no longer a single resource, it is a list, so you need the * syntax
one of the lists is always empty and Terraform easily complains that it cannot determine the type of an empty list. (I guess because the ternary operator requires that the alternates have the same type)
when you access into an empty list you will get an error (With C semantics the unused alternate would not be evaluated, but Terraform seems to work differently and I got errors when trying to code this)
To work around those I came up with the following hacky solution:
Extend the lists with a dummy element in the end and then refer to the
first element of the extended list. The code for this is pretty
horrible to type, but it seems to work
locals {
dummy = [ "foo" ]
}
output "0101: address" {
value = "${format("Public IP is %s", "${local.production ? "${element("${concat("${aws_eip.commander_production.*.public_ip}", "${local.dummy}")}", 0)}" : "${element("${concat("${aws_eip.commander_testing.*.public_ip}", "${local.dummy}")}", 0)}" }")}"
}
Question: What is a shorter / more elegant way to code this?
Note: I have found one answer myself, but feel welcome to contribute even better ones.
A bit shorter code is
output "0101: address" {
value = "${format("Public IP is %s", "${element("${concat("${aws_eip.commander_production.*.public_ip}", "${aws_eip.commander_testing.*.public_ip}")}", 0)}")}"
}
In plain text: Concatenate the lists and take the first element of the the result. One list has one element and the other one zero, so the result will be what we want, regardless whether the element is in the first or second list.

Tell AutoMapper that a field is a top-level member

I am trying to take some of the pain out of creating mapping expressions in AutoMapper, using AutoMapper.QueryableExtensions
I have the following, which gives a critical performance gain:
private MapperConfiguration CreateConfiguration() {
return new MapperConfiguration(cfg => cfg.CreateMap<Widget, WidgetNameDto>()
.ForMember(dto => dto.Name,
conf => conf.MapFrom(w => w.Name)));
}
To understand the performance gain, see here: https://github.com/AutoMapper/AutoMapper/blob/master/docs/Queryable-Extensions.md The key is that the query is limited by field at the database level.
It's terrific that this works. But I anticipate needing to do a lot of this kind of projecting. I am trying to take some of the pain out of the syntax in the ForMember clause above.
For example, I've tried this:
public static IMappingExpression<TFrom, TTo> AddProjection<TFrom, TTo, TField>(this IMappingExpression<TFrom, TTo> expression,
Func<TFrom, TField> from,
Func<TTo, TField> to
)
=> expression.ForMember(t => to(t), conf => conf.MapFrom(f => from(f)));
The problem is that everything I do runs into an error:
AutoMapper.AutoMapperConfigurationException : Custom configuration for members is only supported for top-level individual members on a type.
Even if the passed in Funcs are top-level individual members, that fact is lost in the passing, so I hit the error. I've also tried changing Func<Whatever> to Expression<Func<Whatever>>. It doesn't help.
Is there any way I can simplify the syntax of the ForMember clause? Ideally, I would just pass in the two relevant fields.
First, there is no need to add mapping for the fields/properties that match by name - AutoMapper maps them automatically by convention (that's why it is called convention-based object-object mapper). And for including just some of the properties in the projection you could use the Explicit expansion feature.
Second, what you call a pain in the ForMember syntax is in fact a flexibility. For instance, the explicit expansion and other behaviors can be controlled by conf argument, so it's not only for specifying the source.
With that being said, what you ask is possible. You have to change the from/ to type to Expression:
Expression<Func<TFrom, TField>> from,
Expression<Func<TTo, TField>> to
and the implementation simply as follows:
=> expression.ForMember(to, conf => conf.MapFrom(from));

puppet resource command with title munge

I've created a puppet resource for interfaces. Most of the interface names on my switch are lowercase with the exception of Ethernet interfaces, so I munged the interface name to hopefully reduce errors in the manifest; e.g.:
manifest:
cisco_interface { 'Ethernet1/1': description => 'foo' }
type/cisco_interface.rb:
newparam(:name) do
munge { |value|
value.downcase
}
end
My provider code also downcases the interface names when I collect the list of interfaces with self.instances.
Okay, so this works great when I test with the manifest, but not so great with the puppet resource command which only works when I call it with the name already downcased:
switch# puppet resource cisco_interface 'Ethernet1/1'
cisco_interface { 'Ethernet1/1':
ensure => 'absent',
}
switch# puppet resource cisco_interface 'ethernet1/1'
cisco_interface { 'ethernet1/1':
ensure => 'present',
description => 'foo',
}
The puppet resource command name field seems to just be a simple filter so I think I'm stuck, but I thought I'd seen other resource types munging title values like this.
Is it possible to munge the title values in a way that works for both scenarios?
If not then I'm not sure whether it would be better to leave it case-sensitive since that is what users will see in the switch config, or to "help" them avoid errors in the manifest.
You are correct about what is happening here, puppet currently requires that the name passed into the command line exactly matches the name according to the type. It's buried a couple levels deep, but look at the find and resource_name methods of the RAL.
It doesn't seem to me like this would be a major change, so you might want to log a defect or make the change yourself!

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