Construct mutable string from hash iteration - puppet

I'm stuck on the following puppet code from days, can you please give me an hand?
In hiera I have the following structure:
my_list::servers:
server1.subnet.env.com:
id: 0
server2.subnet.env.com:
id: 1
server3.subnet.env.com:
id: 2
server4.subnet.env.com:
id: 3
Where the various server1..n (n=>2) are the FQDN of the servers in the specific environment. The ID is always in order, but starting from 0.
I need to create a string that contains a comma separated list of string as broker-${id}-check, where id is different from the FQDN of the server where I'm running puppet, so for example if I'm running the script on server2.subnet.env.com the string should be broker-0-check,broker-2-check,broker-3-check. If I'm running on server1.subnet.env.com it will be broker-1-check,broker-2-check,broker-3-check, etc..
My last tentative is:
$servers_list = hiera('my_list::servers', {"${::fqdn}" => {'id' => 0 } })
$list_broker=''
$servers_list.each |$key, $value| {
if $key != $::fqdn {
$list_broker="${list_broker},broker-${value['id']}-check"
}
}
notify {"******* ${list_broker}": }
but list_broker is still empty and then I will have to fix the leading comma.
Is there a better way to do that?
I'm using Puppet 4.

The problem is that although Puppet has an iteration feature, it doesn't allow the reassignment of variables (ref).
For this reason, Puppet has a lot of functional programming features that allow you to solve problems like this without needing to reassign variables.
This works (Puppet < 5.5), where join() comes from stdlib:
$list_broker = join(
$servers_list
.filter |$key, $value| { $key != $::fqdn }
.map |$key, $value| { "broker-${value['id']}-check" },
','
)
Or in Puppet >= 5.5 (as suggested in comments), where the join command is built-in, the join can be chained too:
$list_broker = $servers_list
.filter |$key, $value| { $key != $::fqdn }
.map |$key, $value| { "broker-${value['id']}-check" }
.join(',')
If you'd prefer it in more steps:
$filtered = $servers_list.filter |$key, $value| { $key != $::fqdn }
$mapped = $filtered.map |$key, $value| { "broker-${value['id']}-check" }
$list_broker = join($mapped, ',')
Explanation:
The filter function selects elements from an Array or Hash on the basis of some criteria.
The map function performs a transformation on all elements of an Array or Hash and returns an Array of transformed data.
The join function (which prior to Puppet 5.5 comes from stdlib) joins an array of strings.

Related

Register array with a field in only one of the registers in DML 1.4

I want to make a register array, where one of the registers should include a field in bit 0 with a value of 1.
I have tried using a conditional without any success.
register feature_bits[i < N_FEATURE_SELECT_REGS] size 4 is unmapped {
#if (i == 1) {
field virtio_f_version_1 # [0] "VIRTIO_F_VERSION_1" {
param init_val = 1;
}
}
}
I have also tried indexing the register element and set the field accordingly
register feature_bits[i < N_FEATURE_SELECT_REGS] size 4 is unmapped;
register feature_bits[1] {
field VIRTIO_F_VERSION_1 # [0] {
param init_val = 1;
}
}
None of these approaches worked.
If we take a step back and look at what you're trying to accomplish here, it's possible that a third option can be usable. If it's only the init-value of some fields that differs, you can create a template type and iterate over it like this:
template feature_bit {
param lsb : uint64;
}
bank regs is (init, hard_reset) {
register features[i < 4] size 4 # unmapped;
method init() {
foreach f in (each feature_bit in (this)) {
features[f.lsb / 32].val[f.lsb % 32] = 1;
}
}
method hard_reset() {
init();
}
group featA is feature_bit { param lsb = 42; }
group featB is feature_bit { param lsb = 3; }
}
The init-template is depth-first recursive, so the feature registers will be initialized first, and then the init() method of the bank will run and set the value of the feature-registers by iterating over all instances of the feature_bit template. We also call init from hard_reset, which is also depth-first recursive, otherwise the register will be 0 after reset.
Arrays in DML are homogeneous; every subobject must exist for all indices. This is because when you write a method inside an array, each array index translates to an implicit method argument, so in your case, if a method in your register calls this.virtio_f_version.read(), this translates to something like regs__feature_bits__virtio_f_version_1__read(_dev, _i). This function exists for all values of i, therefore the field must exist for all values of i.
There are two approaches to solve your problem. The first is to let the field exist in all indices, and add code to make it pretend to not exist in indices other than 1:
register feature_bits[...]; {
field VIRTIO_F_VERSION_1 # [0] {
param init_val = i == 1 ? 1 : 0;
method write(uint64 value) {
if (i == 1) {
return set_f_version_1(value);
} else if (value != this.val) {
log spec_viol: "write outside fields";
}
}
}
}
The second approach is to accept that your set of registers is heterogeneous, and use a template to share code between them instead:
template feature_bit {
param i;
#if (i == 1) {
field virtio_f_version ... { ... }
}
... lots of common fields here ...
}
register feature_bits_0 is feature_bit { param index = 0; }
register feature_bits_1 is feature_bit { param index = 1; }
register feature_bits_2 is feature_bit { param index = 2; }
...
Which approach to choose is a trade-off; the first solution is more economic in terms of memory use and compile speed (because DML doesn't need to create almost-identical copies of methods across register indices), whereas the second solution gives a model that more accurately reflects the specification upon inspection (because the model will declare to the simulator that virtio_f_version_1 is a field only for one of the registers).
If you read the spec and you get the feeling that this field is a singular exception to an otherwise homogeneous array, then the first approach is probably better, but if registers vary wildly across indices then the second approach probably makes more sense.

Why are these values sometimes undefined?

I'm fairly new to Perl and am working on a project to further my learning. It's a little console word game (translated from a python project of mine), and part of the logic requires to draw a random letter from a pool that is 98 characters long.
Running the functions individually, I've never had an issue, but when I try to loop it into a list it occasionally fails. Running with warnings on tells me that some of these are undefined, but I can't for the life of me figure out why. Here's an MRE:
package Random;
sub choice {
shift;
my ($str) = #_;
my $random_index = int(rand(length($str)));
return substr($str,$random_index,1); #fixed variable name
}
package Player;
sub new {
my $class = shift;
my $self = { "name" => shift, "letters" => {fillList()} };
bless $self, $class;
return $self;
}
sub drawCharacter {
my $freq = "aaaaaaaaabbccddddeeeeeeeeeeeeffggghhiiiiiiiiijkllllmmnnnnnnooooooooppqrrrrrrssssttttttuuuuvvwwxyyz";
my $choice = Random -> choice($freq);
return $choice;
}
sub fillList {
my #ls = ();
for (0..6) {
push #ls, drawCharacter();
}
return #ls;
}
sub getLetters {
my ($self) = #_;
my $arr = $self -> {letters};
return %$arr;
}
package Main;
my #players = ();
for (0..12){
my $player = Player -> new("Foo");
print($player->getLetters(),"\n");
}
BIG EDIT: Adding the object I'm using. This is verifiably not working. Warnings:
"Use of uninitialized value in print" and "Odd number of elements in anonymous hash". This is where I think the issue lies.
The list returned by fillList sometimes is missing an item or 2, and in some circumstances even 3 or 4 items are missing. Does anybody know what's going on here? The python one hasn't failed once.
If the python analogue would be helpful, I can include that here too.
The error comes from using a hash ref where you should have an array ref:
my $self = { "name" => shift, "letters" => {fillList()} };
# ^ ^-- wrong brackets
This is what the warning talks about:
Odd number of elements in anonymous hash at foo.pl line 22.
You want to change that to:
my $self = { "name" => shift, "letters" => [fillList()] };
# ^ ^--- creates array ref
And also the line which uses this array
return %$arr;
Where you need to change % to #.
return #$arr;
After those fixes, the code runs without errors for me.

If statements not working with JSON array

I have a JSON file of 2 discord client IDs `{
{
"premium": [
"a random string of numbers that is a client id",
"a random string of numbers that is a client id"
]
}
I have tried to access these client IDs to do things in the program using a for loop + if statement:
for(i in premium.premium){
if(premium.premium[i] === msg.author.id){
//do some stuff
}else{
//do some stuff
When the program is ran, it runs the for loop and goes to the else first and runs the code in there (not supposed to happen), then runs the code in the if twice. But there are only 2 client IDs and the for loop has ran 3 times, and the first time it runs it goes instantly to the else even though the person who sent the message has their client ID in the JSON file.
How can I fix this? Any help is greatly appreciated.
You may want to add a return statement within your for loop. Otherwise, the loop will continue running until a condition has been met, or it has nothing else to loop over. See the documentation on for loops here.
For example, here it is without return statements:
const json = {
"premium": [
"aaa-1",
"bbb-1"
]
}
for (i in json.premium) {
if (json.premium[i] === "aaa-1") {
console.log("this is aaa-1!!!!")
} else {
console.log("this is not what you're looking for-1...")
}
}
And here it is with return statements:
const json = {
"premium": [
"aaa-2",
"bbb-2"
]
}
function loopOverJson() {
for (i in json.premium) {
if (json.premium[i] === "aaa-2") {
console.log("this is aaa-2!!!!")
return
} else {
console.log("this is not what you're looking for-2...")
return
}
}
}
loopOverJson()
Note: without wrapping the above in a function, the console will show: "Syntax Error: Illegal return statement."
for(i in premium.premium){
if(premium.premium[i] === msg.author.id){
//do some stuff
} else{
//do some stuff
}
}
1) It will loop through all your premium.premium entries. If there are 3 entries it will execute three times. You could use a break statement if you want to exit the loop once a match is found.
2) You should check the type of your msg.author.id. Since you are using the strict comparison operator === it will evaluate to false if your msg.author.id is an integer since you are comparing to a string (based on your provided json).
Use implicit casting: if (premium.premium[i] == msg.author.id)
Use explicit casting: if (premium.premium[i] === String(msg.author.id))
The really fun and easy way to solve problems like this is to use the built-in Array methods like map, reduce or filter. Then you don't have to worry about your iterator values.
eg.
const doSomethingAuthorRelated = (el) => console.log(el, 'whoohoo!');
const authors = premiums
.filter((el) => el === msg.author.id)
.map(doSomethingAuthorRelated);
As John Lonowski points out in the comment link, using for ... in for JavaScript arrays is not reliable, because its designed to iterate over Object properties, so you can't be really sure what its iterating on, unless you've clearly defined the data and are working in an environment where you know no other library has mucked with the Array object.

Case insensitive hash keys perl

Problem
I have a hash/array structure, some of the hash keys are not in the same case though.
I would like to know if there is a way to handle this case without manually checking the keys of every hash.
In the example below i would like all ID/iD/id/Id fields to be printed.
Example code
use warnings;
use strict;
my $Hash = {
Server => [
{
Id=>123
},
{
iD=>456
},
{
ID=>789
}
]
};
for (#{$Hash->{Server}}){
print "$_->{ID}\n"
#This is the problematic part
}
Other
perl version: v5.10.0
This data is recieved from elsewhere and must remain the same case, the example above is minimal and i cannot just simply change them all to the same case.
Any more info needed let me know.
Well, it depends a little bit on your source of information. This looks like you've parsed something, so there may be a better solution.
However, with what we've got here, I'd do it like this:
for my $entry (#{$Hash->{Server}}){
#grep, find first match. Dupes discarded.
my ( $key ) = grep { /^id$/i } keys %$entry;
print "$key => ",$entry -> {$key},"\n";
}
This works by using grep with an i regex for case insensitive on keys, and grabbing whatever comes out first. So if you have multiple matches for /id/i then it'll be random which one you get. (sort could help with that though)
Given you're working with XML though, I'd probably backtrack a bit, throw out XML::Simple and do it like this instead:
#!/usr/bin/perl
use strict;
use warnings;
use XML::Twig;
my $twig = XML::Twig -> new ( twig_handlers => { '_all_' => sub { $_ -> lc_attnames }} );
$twig -> parse ( \*DATA );
print "XML looks like:\n";
$twig -> set_pretty_print ( 'indented_a');
$twig -> print;
print "Output:\n";
foreach my $server ( $twig -> get_xpath('//Server') ) {
print $server -> att('id'),"\n";
}
__DATA__
<XML>
<Server ID="123" />
<Server Id="456" />
<Server id="789" />
</XML>
Or you can just:
foreach my $server ( $twig -> get_xpath('//Server') ) {
$server -> lc_attnames;
print $server -> att('id'),"\n";
}
in lieu of doing it in the twig handlers. The first answer will 'fix' all of your XML to having lower case attributes, which might not be what you want. But then, it might be useful for other scenarios, which is why I've given two examples.
There is no built-in way to do that. What you could do is use List::Util's first to at least get less checks, and then still try until one fits for each of the keys.
use strict;
use warnings;
use feature 'say';
use List::Util 'first';
my $Hash = {
Server => [
{
Id => 123
},
{
iD => 456
},
{
ID => 789
}
]
};
foreach my $thing ( #{ $Hash->{Server} } ) {
# this returns the first match in the list, like grep
# so we need to use it here to return the actual value
say $thing->{ first { $thing->{$_} } qw/id ID iD Id/ };
}
If there are a lot of other keys in the data structure, this is cheaper than looking at all the keys, because you at max look up all possible id keys plus one, and at best two.
If you want the list of possible keys to auto-generate and the uppercase and lowercase letters can be arbitrarily mixed, take a look at this answer.
I would suggest you to use regex to ignore case of keys using i flag.
for my $item ( # { $Hash->{Server} }) {
for(keys %{$item}) {
print $item -> {$_},"\n" if /^ID$/i;
}
}

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.

Resources