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?
Related
I've got the following variable in a module:
variable "container_registries" {
type = list(object({
name = string
addl_keys = list(string)
namespaces = set(string)
hostnames = list(string)
username = string
password = string
}))
default = []
}
I'm feeding the module variable as such:
container_registries = [
{
name : "server.example.com"
addl_keys : ["config.json"]
namespaces : ["flux-system", "tekton"]
hostnames : ["cr-lts.server.example.com", "cr-test.server.example.com"]
username : "foo"
password : "bar"
}
]
Now I need to create multiple Kubernetes Secrets, each in different namespaces - but with the same content. I need the Secrets in the flux-system and tekton namespace. I need the secret to look like this:
apiVersion: v1
kind: Secret
type: kubernetes.io/dockerconfigjson
metadata:
name: server.example.com
data:
.dockerconfigjson: eyJhdXRocyI6eyJjci1sdHMuc2VydmVyLmV4YW1wbGUuY29tIjp7ImF1dGgiOiJabTl2T21KaGNnbz0ifSwiY3ItdGVzdC5zZXJ2ZXIuZXhhbXBsZS5jb20iOnsiYXV0aCI6IlptOXZPbUpoY2dvPSJ9fX0K
config.json: eyJhdXRocyI6eyJjci1sdHMuc2VydmVyLmV4YW1wbGUuY29tIjp7ImF1dGgiOiJabTl2T21KaGNnbz0ifSwiY3ItdGVzdC5zZXJ2ZXIuZXhhbXBsZS5jb20iOnsiYXV0aCI6IlptOXZPbUpoY2dvPSJ9fX0K
Note that the Secret has two different keys, each with the same value. The .dockerconfigjson key is mandatory when the Secret type is set to kubernetes.io/dockerconfigjson, so it should always be included. The value is a base64 encoded JSON and the JSON looks as such:
{
"auths": {
"cr-lts.server.example.com": {
"auth": "Zm9vOmJhcgo="
},
"cr-test.server.example.com": {
"auth": "Zm9vOmJhcgo="
}
}
}
The value of auth is foo:bar (the username and password) in base64.
I've been trying and trying, but I am not getting any closer. All my attempts have felt like garbage 😰 How in the world can I achieve this with Terraform? 😅
Here's what I did to solve the problem, using #Kreetchy's answer as a base:
locals {
container_registries = toset(flatten([
for cr in var.container_registries : [
for ns in cr.namespaces : format("%s/%s", ns, cr.name)
]
]))
container_registry_data = {
for cr in var.container_registries : cr.name => {
for key in toset(concat([".dockerconfigjson"], cr.addl_keys)) : key => jsonencode({
auths = {
for hostname in cr.hostnames : hostname => {
auth = base64encode("${cr.username}:${cr.password}")
}
}
})
}
}
}
resource "kubernetes_secret" "container_registry" {
for_each = local.container_registries
metadata {
namespace = split("/", each.value)[0]
name = split("/", each.value)[1]
}
type = "kubernetes.io/dockerconfigjson"
data = local.container_registry_data[split("/", each.value)[1]]
}
Note the extra local.container_registries which is used for the loop in the resource. This local stores the namespace/name of the secret in the list. The drawback is that two secrets by the same name and different content can not be created in two namespaces. It's something I can live with :-)
I also renamed var.keys to var.addl_keys, because a key by the name of .dockerconfigjson always must exist in a kubernetes.io/dockerconfigjson kind of Secret.
#Kreetchy: Please feel free to copy the code above and put it into your answer and I'll mark it as accepted. Once done, I will edit my question to adapt the requirement to fit the answer :-)
You could achieve it this way in Terraform
locals {
secret_data = {
for registry in var.container_registries : {
for key in registry.keys : key => base64encode(jsonencode({
auths = {
for hostname in registry.hostnames : hostname => {
auth = base64encode("${registry.username}:${registry.password}")
}
}
}))
}
}
}
resource "kubernetes_secret" "example" {
for_each = flatten([for registry in var.container_registries : registry.namespaces])
metadata {
name = lookup(var.container_registries[each.key].name, each.value)
namespace = each.value
}
type = "kubernetes.io/dockerconfigjson"
data = {
for key, value in local.secret_data : key => value
}
}
This will handle a list of varying amount of entries in the container_registries variable, and create a separate kubernetes_secret resource for each namespace. The contents of the secrets will still be the encoded JSON, but this time generated dynamically based on the number of keys and hostnames specified in each entry in the container_registries variable.
This handles any number of keys, namespaces and hosts dynamically and will create separate kuberneteds_secret resource for each namespace. Content of secret is still encoded JSON, but generated dynamically based on number of keys and hostnames specified in container_registries
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 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.
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).
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.