Puppet: set default values for nested hashes - puppet

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.

Related

Puppet: Access hashkey in dynamic variable path

I've got a puppet hash type data structure like this to store ssh keys:
class users::sshkeys {
$user1 = {
user1 => {
key => 'AAAAAA',
type => 'ssh-rsa'
}
}
$user2 = {
user2 => {
key => 'BBBBBB',
type => 'ed25519'
}
}
}
I can access a key like this:
$users::sshkeys::user1['user1']['key']
I would like to access a key dynamically (e.g. in a loop), like this:
$users = ['user1', 'user2']
$users.each |$user| {
$users::sshkeys::$user[$user]['key']
}
I found a solution to access a dynamic class/variable like this:
$mydynamicvar = getvar("users::sshkeys::${user1}")
notify{"$mydynamicvar":}
This seems to return a String, so that I can't access the hashkeys inside:
notify{"$mydynamicuser[$user]['key']":}
Any Ideas?
Ah, got it :) Putting all hashes into one variable (here: $all_users) and summarizing them into groups works like this:
$sysadmins = {
user1 => $all_users['user1'],
user2 => $all_users['user2']
}
Thanks for pointing me to the right direction #MattSchuchard
With a big hash even receiving ssh key and key type in a loop works:
$users = ['user1', 'user2']
$users.each |$user| {
profiles::users::sshkeys { $user:
user => $user,
group => 'sftpusers',
ssh_authorized_key => $users::sshkeys::all_keys::all_users[$user]['key'],
ssh_authorized_key_type => $users::sshkeys::all_keys::all_users[$user]['type'],
shell => $shell,
}
}

Array iteration with position in puppet

I'm planning to implement the possibility to add multiple ssh keys per user.
For a single key, I used:
if ($sshkey) {
ssh_authorized_key { $resourcename:
ensure => 'present',
type => 'ssh-rsa',
key => '$sshkey',
user => $title,
require => User[$title],
}
}
For multiple keys, i thought that this might work:
if ($sshkeyarray != []) {
$sshkeyarray.each |String $singlesshkey| {
ssh_authorized_key { $resourcename:
ensure => 'present',
type => 'ssh-rsa',
key => '$singlesshkey',
user => $title,
require => User[$title],
}
}
}
But the resourcename can only be used once, so I want to give names like "resourcename_1" for the first ssh key and "resourcename_n" for the n-th key.
How can I do this? Can i get the position of the singlesshkey from the array and add it to the resourdcename?
As described in the docs here you can do this:
$sshkeyarray.each |$index, String $singlesshkey| {
ssh_authorized_key { "${resourcename}_${index}":
ensure => 'present',
type => 'ssh-rsa',
key => $singlesshkey,
user => $title,
require => User[$title],
}
}
Notice that there's no need to test for an empty array either. Looping over an empty array causes nothing to happen anyway.

puppet creating relationship between file, class and define

I want to create relationship between file, class and define.... Please check the below code....
The problem I am facing is, even if there is no change in deploy.cfg file, class and nexus::artifact always runs...
class and nexus::artifact should execute only if it detects a change in file
I know that we need to make use of subscribe and refreshonly=true. But I have no idea where to put this...
file { 'deploy.cfg':
ensure => file,
path => '/home/deploy/deploy.cfg',
mode => '0644',
owner => 'root',
group => 'root',
content => "test",
notify => [Class['nexus'], Nexus::Artifact['nexus-artifact']],
subscribe => File['/home/deploy/dir'],
}
class { 'nexus':
url => $url,
username => $user,
password => $password,
}
nexus::artifact { "nexus-artifact":
gav => $gav,
packaging => $packaging,
output => $targetfilepath,
repository => $repository,
owner => $owner,
mode => $mode,
}
artifact.pp
define nexus::artifact (
$gav,
$repository,
$output,
$packaging = 'jar',
$classifier = undef,
$ensure = update,
$timeout = undef,
$owner = undef,
$group = undef,
$mode = undef
) {
include nexus
}
init.pp
class nexus (
$url,
$username = undef,
$password = undef,
$netrc = undef,
) {
}
even if there is no change in deploy.cfg file, class and nexus::artifact always runs
Well yes, every class and resource in your node's catalog is applied on every catalog run, unless a resource that is required to be applied before it fails. That is normal. The key thing to understand in this regard is that the first part of applying a catalog resource is determining whether the corresponding physical resource is already in sync; if it is, then applying the catalog resource has no further effect.
class and nexus::artifact should execute only if it detects a change in file
I know that we need to make use of subscribe and refreshonly=true.
Well, no. You may be able to modulate the effect of applying that class and resource, but you cannot use the result of syncing another resource to modulate whether they are applied at all. In any event, refreshonly is specific to Exec resources, and you don't have any of those in your code.

How to iterate on puppet? Or how to avoid it?

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.

update Conf File using perl

I am using perl script to update sections in conf file . my script i working properly but when i execute script the ordered on sections changed automatically while i dont want to change the order.script is following .
#!/usr/bin/perl
use Config::Tiny;
$file = 'sss.conf';
my $Config = Config::Tiny->new;
$Config = Config::Tiny->read( $file );
my $rootproperty = $Config->{_}->{rootproperty};
$Config->{amrit}->{host} = 'Not Bar!';
$Config->write( $file );
and file contents following line;
[amrit]
type=friend
host=111.118.253.145
port=2776
username=amrit
secret=password
disallow=all
allow=gsm
context=sip-calling
qualify=yes
call-limit=22
Please help me here i dont want to change order of fields
From the documentation for Config::Tiny:
...Config::Tiny does not preserve your comments, whitespace, or the order of your config file.
See Config::Tiny::Ordered (and possibly others) for the preservation of the order of the entries in the file.
So, do as the documentation suggests, and see Config::Tiny::Ordered.
Update: Based on the comments I'll try to provide additional help here.
First, your existing script is mostly just a copy-paste from the Config::Tiny synopsis, without any deep understanding of what most of it does. The relevant parts... or at least those parts you should keep are:
use Config::Tiny;
$file = 'sss.conf';
$Config = Config::Tiny->read( $file );
$Config->{amrit}->{host} = 'Not Bar!';
$Config->write( $file );
If you add use Data::Dumper; at the top of the script, and immediately after reading the config file you add print Dumper $Config;, the structure would look like this:
{
'amrit' => {
'call-limit' => '22',
'host' => '111.118.253.145',
'secret' => 'password',
'context' => 'sip-calling',
'port' => '2776',
'username' => 'amrit',
'allow' => 'gsm',
'qualify' => 'yes',
'type' => 'friend',
'disallow' => 'all'
}
}
Modifying the structure with the script I've posted above would work if you don't mind the key/value pairs to have their orders rearranged. But you need to preserve order. So the suggestion was to switch to Config::Tiny::Ordered. That module preserves order by rearranging the structure differently. If you change the script to look like this:
use Data::Dumper;
use Config::Tiny::Ordered;
$file = 'conf.conf';
$Config = Config::Tiny->read( $file );
print Dumper $Config;
You will see that the structure now looks like this:
{
'amrit' =>
[
{
'value' => 'friend',
'key' => 'type'
},
{
'key' => 'host',
'value' => '111.118.253.145'
},
{
'value' => '2776',
'key' => 'port'
},
{
'value' => 'amrit',
'key' => 'username'
},
{
'value' => 'password',
'key' => 'secret'
},
{
'value' => 'all',
'key' => 'disallow'
},
{
'key' => 'allow',
'value' => 'gsm'
},
{
'key' => 'context',
'value' => 'sip-calling'
},
{
'key' => 'qualify',
'value' => 'yes'
},
{
'value' => '22',
'key' => 'call-limit'
}
]
}
Or in other words, the internal structure of the Config::Tiny::* object has changed from a hash of hashes, to a hash of arrays of hashes. ("All problems in computer science can be solved by another level of indirection" -- David Wheeler) This change in the shape of the datastructure exists to move away from the problem of hashes being unordered containers.
So now instead of convenient hash lookups for the key named "host", you have to iterate through your structure to find the array element that has an anonymous hash with a key field named host. More work:
use List::Util qw(first);
use Config::Tiny::Ordered;
$file = 'sss.conf';
$Config = Config::Tiny::Ordered->read( $file );
my $want_ix = first {
$Config->{amrit}[$_]{key} eq 'host'
} 0 .. $#{$Config->{amrit}};
$Config->{amrit}[$want_ix]{value} = 'Not Bar!';
$Config->write( $file );
This works by running through the amrit section of the config file looking for the element in the structure's anonymous array that has an anonymous hash with a field named 'key', and once that array element is found, modifying the 'value' hash element to 'Not Bar!'
If this is a one time thing for you, you're done. If you want to be able to do it yourself next time, read perldoc perlreftut, perldoc perldsc, as well as documentation for any of the functions used herein that aren't immediately clear.
From the CPAN page:
Lastly, Config::Tiny does not preserve your comments, whitespace, or the order of your config file.
Use COnfig::Tiny::Ordered instead
See Config::Tiny::Ordered (and possibly others) for the preservation of the order of the entries in the file.

Resources