I would like to run the following code in a sequential order so that the servers_string variable is computed before the script execution.
Unfortunately puppet failed with the following error :
Error: Could not retrieve catalog from remote server: Error 400 on SERVER: Evaluation Error: Illegal relationship operand, can not form a relationship with a Hash. A Catalog type is required.
The code snippet :
$servers = [{ name => 'toto', ip => '10.0.0.1'}, { name => 'titi', ip => '10.0.0.2' }]
$servers.each | Hash $server | {
if $servers_string != "" {
$servers_string = "${servers_string},"
}
$name = $server['name']
$servers_string = "${servers_string}${name}"
}->
file { '/my/path/myscript.sh':
ensure => file,
mode => '0700',
owner => 'root',
group => 'root',
source => "puppet:///modules/${module_name}/install.sh --servers '${servers_string}'"
}
Any idea ? Thanks
Resource relationships in general and the chain operators in particular are about the order in which resources are applied to the node. They have nothing whatever to do with the order in which the catalog builder evaluates manifest files.
Manifests are always evaluated in order, left-to-right, top-to-bottom. You do not need to use chain operators to ensure that, nor can you use them to change it. Just drop the chain operator, and you'll be fine (at least in this regard).
Related
I am creating a class which creates a ssh keys pair for users.
In Hiera it looks like this:
ssh::default_user_keys::entries:
root:
privkey: ENC[PKCS7,MIIH/QYJKoZIhvcNAQcDoIIH7jCCB+oCAQAx...]
pubkey: BiQYJKoZIhvcNAQcDoIIBejCCAXYC...
user1:
privkey: ENC[PKCS7,MIIH/Bf0mv0aa7JRbEWWONfVMJBOX2I7x...]
keydir: /srv/data/user1/.ssh
user2:
privkey: ENC[PKCS7,MIIBiQYJKoZIhvcNAQcDoIIBejCCAXYCAQAx...]
keytype: id_ecdsa
If some values are not specified in Hiera, they must be picked up from default values in the manifest.
So my question is how to write the manifest correctly?
This is my pseudo code I try to make working:
class ssh::default_user_keys_refact (
Hash $entries = lookup('ssh::default_user_keys_refact::entries', "merge" => 'hash'),
) {
$entries.each |String $user, Hash $item = {} | {
set_default { $item:
privkey => undef,
pubkey => undef,
keydir => "/home/${user}/.ssh",
keytype => id_rsa,
comment => undef,
}
$sshpriv = $item[privkey]
file { "${keydir}/$[keytype}":
ensure => file,
owner => $user,
mode => '0600',
content => $sshpriv,
}
}
}
Do you have ideas how to make this pseudo code working? Or in other words, how to set up default values for nested hiera hash in puppet manifest?
puppet versions is 6.
Thank you in advance.
Or in other words, how to set up default values for nested hiera hash in puppet manifest?
The fact that the hashes for which you want to provide defaults are nested inside others in the Hiera data isn't much relevant, because at the point of use they have been plucked out. With that being the case, you can accomplish this fairly easily by leveraging Puppet (not Hiera) hash merging. It might look something like this:
$entries.each |String $user, Hash $props| {
# Merge with default values
$full_props = {
'keydir' => "/home/${user}/.ssh",
'keytype' => 'id_rsa'
} + $props
file { "${full_props['keydir']}/${full_props['keytype']}":
ensure => 'file',
owner => $user,
mode => '0600',
content => $full_props['privkey'],
}
}
That's by no means the only way, but it's clean and clear, and it scales reasonably well.
I'm trying to automate the Prometheus node_exporter and my Prometheus Server.
For the node_exporter I've written a module to install all the needed packages, set the $::ipaddress based on facter and some more..
Now I'd like to make sure that the collected informations ($hostname, $job_name, [...]) from the applying node are exported into the respective remote Prometheus configfile, but I want to have this step done asynchronously, so for example with a puppet agent run afterwards on the Prometheus Server.
I've tried to orientate the classes towards the puppetlabs/logrotate module, which is basically doing the following:
logrotate/init.pp
class logrotate (
String $ensure = present,
Boolean $hieramerge = false,
Boolean $manage_cron_daily = true,
Boolean $create_base_rules = true,
Boolean $purge_configdir = false,
String $package = 'logrotate',
Hash $rules = {},
) {
do some stuff
}
logrotate/rules.pp
class logrotate::rules ($rules = $::logrotate::rules){
#assert_private()
create_resources('logrotate::rule', $rules)
}
logrotate/rule.pp
define logrotate::rule(
Pattern[/^[a-zA-Z0-9\._-]+$/] $rulename = $title,
Enum['present','absent'] $ensure = 'present',
Optional[Logrotate::Path] $path = undef,
(...)
) {
do some stuff
}
Shortened my ni_trending (node_exporter) & ni_prometheus modules currently look very similar to logrotate:
ni_trending/init.pp
class ni_trending (
$hostname = $::fqdn,
$listen_address = $::ipaddress,
$listen_port = 51118,
) {
) inherits ni_trending::params {
anchor { 'ni_trending::start': }
->class { 'ni_trending::package': }
->class { 'ni_trending::config':
(...)
listen_address => $listen_address,
listen_port => $listen_port,
(...)
}
->class { 'ni_trending::service': }
->class { ' ni_trending::prometheus':
(...)
hostname => $hostname,
listen_port => $listen_port,
(...)
}
->anchor { 'ni_trending::end': }
}
ni_trending/prometheus.pp
class ni_trending::prometheus (
Hash $options = {},
) {
ni_prometheus::nodeexporterrule { 'node_exporter' :
ensure => pick_default($options['ensure'], 'present'),
hostname => pick_default($options['hostname'], $ni_trending::hostname),
listen_port => pick_default($options['hostname'], $ni_trending::listen_port),
}
}
ni_prometheus/nodeexporterrules.pp
class ni_prometheus::nodeexporterrules ($rules = $::ni_prometheus::nodeexporterrules) {
create_resources('ni_prometheus::nodeexporterrule', $nodeexporterrules)
}
ni_prometheus/nodeexporterrule.pp
define ni_prometheus::nodeexporterrule (
$job_name = $title,
Enum['present','absent'] $ensure = 'present',
$hostname = $hostname,
$listen_port = $listen_port,
) {
file_line { "prometheus-${job_name}" :
path => "/etc/prometheus/${job_name}.list",
after => 'hosts:',
line => "${hostname}:${listen_port}",
}
}
But this will just work when I apply the node_exporter locally on the Prometheus Master - not in the case that an external machine has the ni_trending::prometheus class included, which makes sense to me - because it clearly feels that something is missing. :-) How can I get this working?
Thanks!
This sounds like a job for exported resources (that makes two in one day!). This is a facility for one node's catalog building to generate resources that can be applied to other nodes (and also, optionally, to the exporting node itself). I'm still not tracking the details of what you want to manage where, so here's a more generic example: maintaining a local hosts file.
Generic example
Suppose we want to automatically manage a hosts file listing all our nodes under management. Puppet has a built-in resource, Host, representing one entry in a hosts file. We make use of that by having every node under management export an appropriate host resource. Something like this would go inside a class included on every node:
##host { "$hostname": ip => $ipaddress; }
The ## prefix marks the resource as exported. It is not applied to the current target node, unless by the mechanism I will describe in a moment. the $hostname and $ipaddress are just facts presented by the target node, and they are resolved in that context. Note, too, that the resource title is globally unique: each target node has a different hostname, therefore all the exported Host resources that apply to different target nodes will have distinct titles.
Then, separately, every node that wants all those Host entries applied to it will import them in its own catalog by using an exported resource collector:
<<|Host|>>
The nodes that export those resources can also collect some or all of them. Additionally, there are ways to be more selective about which resources are collected; see the link above.
I want to inject some values from facter <prop> into a file content.
It works with $fqdn since facter fqdn returns string.
node default {
file {'/tmp/README.md':
ensure => file,
content => $fqdn, # $(facter fqdn)
owner => 'root',
}
}
However, it does not work with hash object (facter os):
node default {
file {'/tmp/README.md':
ensure => file,
content => $os, # $(facter os) !! DOES NOT WORK
owner => 'root',
}
}
And getting this error message when running puppet agent -t:
Error: Failed to apply catalog: Parameter content failed on
File[/tmp/README.md]: Munging failed for value
{"architecture"=>"x86_64", "family"=>"RedHat", "hardware"=>"x86_64",
"name"=>"CentOS", "release"=>{"full"=>"7.4.1708", "major"=>"7",
"minor"=>"4"}, "selinux"=>{"config_mode"=>"enforcing",
"config_policy"=>"targeted", "current_mode"=>"enforcing",
"enabled"=>true, "enforced"=>true, "policy_version"=>"28"}} in class
content: no implicit conversion of Hash into String (file:
/etc/puppetlabs/code/environments/production/manifests/site.pp, line:
2)
How to convert the hash to string inside the pp file?
If you have Puppet >= 4.5.0, it is now possible to natively convert various data types to strings in the manifests (i.e. in the pp files). The conversion functions are documented here.
This would do what you want:
file { '/tmp/README.md':
ensure => file,
content => String($os),
}
or better:
file { '/tmp/README.md':
ensure => file,
content => String($facts['os']),
}
On my Mac OS X, that leads to a file with:
{'name' => 'Darwin', 'family' => 'Darwin', 'release' => {'major' => '14', 'minor' => '5', 'full' => '14.5.0'}}
Have a look at all that documentation, because there are quite a lot of options that might be useful to you.
Of course, if you wanted the keys inside the $os fact,
file { '/tmp/README.md':
ensure => file,
content => $facts['os']['family'],
}
Now, if you don't have the latest Puppet, and you don't have the string conversion functions, the old way of doing this would be via templates and embedded Ruby (ERB), e.g.
$os_str = inline_template("<%= #os.to_s %>")
file { '/tmp/README.md':
ensure => file,
content => $os_str,
}
This actually leads to a slightly differently-formatted Hash since Ruby, not Puppet does the formatting:
{"name"=>"Darwin", "family"=>"Darwin", "release"=>{"major"=>"14", "minor"=>"5", "full"=>"14.5.0"}}
For resources where only one or two attributes are changing, i can use array and hashes respectively. For example, if i have to create files in different directories, i can store the file names and respective paths in a hash and apply them by iterating over the hash. In case if i have more than two attributes that are different, how do i store and iterate over those attributes?
For example, i am trying to create Active Directory groups and i have five attributes and all are different for each group as shown below:
Group_Name Display Name Path Description GroupCategory
"My Support" "Support" "OU=Groups,OU=DEF,DC=xyz,DC=Com" "Some decription" Security
"Prod DBA" "DBA" "OU=Groups,OU=XYZ,DC=xyz,DC=Com" "Different description" Distribution
...
...
UPDATE: Based on the suggestion, here's the code:
[root#myhost] cat params.pp
$ad_groups = {
'Group_Prod' => {
path => 'OU=Groups,OU=PROD,DC=TEST,DC=COM',
displayname => 'Prod Support',
description => 'Prod Support',
},
'Group_App' => {
path => 'OU=Groups,OU=APP,DC=TEST,DC=COM',
displayname => 'App Support',
description => 'App Support',
},
}
$ad_groups_defaults = {
'ensure' => present,
'groupscope' => 'Global',
'groupcategory' => 'Security',
},
[root#myhost] cat create_groups.pp
class infra::ad::create_groups (
$ad_groups = $infra::params::ad_groups,
$ad_groups_defaults = $infra::params::ad_groups_defaults,
) inherits infra::params {
create_resources(windows_ad::group,$ad_groups,$ad_groups_defaults)
}
Now when i try running it, i am getting the following error:
Could not retrieve catalog from remote server: Error 500 on SERVER: "message":"Server Error: Evaluation Error: Error while evaluating a Resource Statement, Windows_ad::Group[Group_Prod]: default expression for $groupname tries to illegally access not yet evaluated $groupname at /etc/puppetlabs/code/environments/production/modules/infra/manifests/ad/create_groups.pp:5 on node puppet.test.com","issue_kind":"RUNTIME_ERROR"}
Now if i also add groupname attribute in each hash block, the error is resolved. What i want to know is that if my group names are same as the hash keys (in this case, Group_Prod and Group_App), then can i somehow use those hash keys itself as groupname without adding groupname attribute in each hash block?
I have a global string variable that's actually an array of names:
"mongo1,mongo2,mongo3"
What I'm doing here is splitting them into an array using the "," as a delimiter and then feeding that array into a define to create all instances I need.
Problem is, every instance has a different port. I made a new stdlib function to get the index of a name in an array, and am feeding that to the port parameter.
This seems bad and I don't like having to alter stdlib.
So I'm wondering how I could do this using something like a nx2 array?
"mongo1,port1;mongo2,port2;mongo3,port3"
or two arrays
"mongo1,mongo2,mongo3" and "port1,port2,port3"
class site::mongomodule {
class { 'mongodb':
package_ensure => '2.4.12',
logdir => '/var/log/mongodb/'
}
define mongoconf () {
$index = array_index($::site::mongomodule::mongoReplSetName_array, $name)
mongodb::mongod { "mongod_${name}":
mongod_instance => $name,
mongod_port => 27017 + $index,
mongod_replSet => 'Shard1',
mongod_shardsvr => 'true',
}
}
$mongoReplSetName_array = split(hiera('site::mongomodule::instances', undef), ',')
mongoconf { $mongoReplSetName_array: }
}
the module I'm using is this one:
https://github.com/echocat/puppet-mongodb
using puppet 3.8.0
Hiera can give you a hash when you lookup a key, so you can have something like this in hiera:
mongoinstances:
mongo1:
port: 1000
mongo2:
port: 1234
Then you lookup the key in hiera to get the hash, and pass it to the create_resources function which will create one instance of a resource per entry in the hash.
$mongoinstances = hiera('mongoinstances')
create_resources('mongoconf', $mongoinstances)
You will need to change mongoconf for this to work by adding a $port parameter. Each time you want to pass an additional value from hiera, just add it as a parameter to your defined type.
If you are using puppet >= 4.0, use puppet hashes with each function.
Define hash e.g:
$my_hash = { mongo1 => port1,
mongo2 => port2, }
Next use each function on it e.g:
$my_hash.each |$key, $val| { some code }.
More about iteration in puppet here.