Read multiple hash keys and keep only unique values - puppet

If I have data in Hiera like:
resource_adapter_instances:
'Adp1':
adapter_plan_dir: "/opt/weblogic/middleware"
adapter_plan: 'Plan_DB.xml'
'Adp2':
adapter_plan_dir: "/opt/weblogic/middleware"
adapter_plan: 'ODB_Plan_DB.xml'
'Adp3':
adapter_plan_dir: "/opt/weblogic/middleware"
adapter_plan: 'Plan_DB.xml'
And I need to transform this into an array like this, noting duplicates are removed:
[/opt/weblogic/middleware/Plan_DB.xml, /opt/weblogic/middleware/ODB_Plan_DB.xml]
I know I have to use Puppet's map but I am really struggling with it.
I tried this:
$resource_adapter_instances = hiera('resource_adapter_instances', {})
$resource_adapter_paths = $resource_adapter_instances.map |$h|{$h['adapter_plan_dir']},{$h['adapter_plan']}.join('/').uniq
notice($resource_adapter_instances)
But that doesn't work, and emits syntax errors. How do I do this?

You are on the right track. A possible solution is as follows:
$resource_adapter_instances = lookup('resource_adapter_instances', {})
$resource_adapter_paths =
$resource_adapter_instances.map |$x| {
[$x[1]['adapter_plan_dir'], $x[1]['adapter_plan']].join('/')
}
.unique
notice($resource_adapter_paths)
A few further notes:
The hiera function is deprecated so I rewrote using lookup and you should too.
Puppet's map function can be a little confusing - especially if you need to iterate with it through a nested Hash, as in your case. On each iteration, Puppet passes each key and value pair as an array in the form [key, value]. Thus, $x[0] gets your Hash key (Adp1 etc) and $x[1] gets the data on the right hand side.
Puppet's unique function is not uniq as in Bash, Ruby etc but actually is spelt out as unique.
Note I've rewritten it without the massively long lines. It's much easier to read.
If you puppet apply that you'll get:
Notice: Scope(Class[main]): [/opt/weblogic/middleware/Plan_DB.xml,
/opt/weblogic/middleware/ODB_Plan_DB.xml]

Related

Filter the list generated by a Gremlin traversal and Groovy

I'm doing the following traversal:
g.V().has('Transfer','eventName','Airdrop').as('t1').
outE('sent_to').
inV().dedup().as('a2').
inE('sent_from').
outV().as('t2').
where('t1',eq('t2')).by('address').
outE('sent_to').
inV().as('a3').
select('a3','a2').
by('accountId').toList().groupBy { it.a3 }.collectEntries { [(it.key): [a2 : it.value.a2]]};
So as you can see I'm basically doing a traversal and at the end I'm using groovy with collectEntries to aggregate the results like I need them, which is aggregated by a3 in this case. The results look like this:
==>0xfe43502662ce2adf86d9d49f25a27d65c70a709d={a2=[0x99feb505a8ed9976cf19e757a9536117e6cdc5ba, 0x22019ad32ea3adabae68003bdefd099d7e5e3886]}
(This is GOOD, because the number of values in a2 is at least 2)
==>0x129e0131ea3cc16fe5252d7280bd1258f629f20f={a2=[0xf7958fad496d15cf9fd9e54c0012504f4fdb96ff]}
(This is NOT GOOD, I want to return in my list only those combinations where there are at lest 2 values for a2)
I have tried using filters and an additional where step in the traversal itself but I haven't been able to do it. I'm not sure if this is something I should skip using Groovy in my last line. Any help or orientation would be very much appreciated
I don't think you need to drop into Groovy to get the answer you want. It would be preferable to do this all in Gremlin especially since you intend to filter results which could yield some performance benefit. Gremlin has it's own group() step as well as methods for filtering the resulting Map:
g.V().has('Transfer','eventName','Airdrop').as('t1').
out('sent_to').
dedup().as('a2').
in('sent_from').as('t2').
where('t1',eq('t2')).by('address').
out('sent_to').inV().as('a3').
select('a3','a2').
by('accountId').
group().
by('a3').
by('a2').
unfold().
where(select(values).limit(local,2).count(local).is(gte(2)))
The idea is to build your Map with group() then deconstruct it to entries with unfold(). You the filter each entry with where() by selecting the values of the entry, which is a List of "a2" then counting the items locally in that List. I use limit(local,2) to avoid unnecessary iteration beyond 2 since the filter is gte(2).
The easiest way to do this is with findAll { }.
.groupBy { it.a3 }
.findAll { it.value.a2.size() > 1 }
.collectEntries { [(it.key): [a2: it.value.a2]] }
if some a2 are null, then value.a2 also evaluates to null and filters the results without the need for explicit nullchecks

How to extract values from this complex Puppet Struct

I have this data structure in puppet:
Struct[
'ssh_keys' => Hash[
String,
Struct[
'path' => String,
'content' => String,
]
]
] $myStructure
And I would like to extract all the 'path' values into an Array.
I got as far as mapping the inner Struct using
$testvariable = $myStructure['ssh_keys'].map |$items| { $items[1] }
But a bit suck here, any help would be much appreciated.
It's not clear what you're hung up on, as you are indeed most of the way to a solution that should work. For hashes, however, I do usually prefer the form of the map() function in which the lambda takes two parameters, a separate key and value. That will read more clearly in this case:
$testvariable = $myStructure['ssh_keys'].map |$unused, $ssh_key| { $ssh_key['path'] }
But you should also be able in your original code to index $items[1] as the hash (Struct) it is: $items[1]['path'].
You could also use the dig() function if you cannot abide the mixture of array and hash indexing in the above: $items.dig(1, 'path').

Dict key getting overwritten when created in a loop

I'm trying to create individual dictionary entries while looping through some input data. Part of the data is used for the key, while a different part is used as the value associated with that key. I'm running into a problem (due to Python's "everything is an object, and you reference that object" operations method) with this as ever iteration through my loop alters the key set in previous iterations, thus overwriting the previously set value, instead of creating a new dict key and setting it with its own value.
popcount = {}
for oneline of datafile:
if oneline[:3] == "POP":
dat1, dat2, dat3, dat4, dat5, dat6 = online.split(":")
datid = str.join(":", [dat2, dat3])
if datid in popcount:
popcount[datid] += int(dat4)
else:
popcount = { datid : int(dat4) }
This iterates over seven lines of data (datafile is a list containing that information) and should create four separate keys for datid, each with their own value. However, what ends up happening is that only the last value for datid exist in the dictionary when the code is run. That happens to be the one that has duplicates, and they get summed properly (so, at least i know that part of the code works, but the other key entries just are ... gone.
The data is read from a file, is colon (:) separated, and treated like a string even when its numeric (thus the int() call in the if datid in popcount).
What am I missing/doing wrong here? So far I haven't been able to find anything that helps me out on this one (though you folks have answered a lot of other Python questions i've run into, even if you didn't know it). I know why its failing; or, i think i do -- it is because when I update the value of datid the key gets pointed to the new datid value object even though I don't want it to, correct? I just don't know how to fix or work around this behavior. To be honest, its the one thing I dislike about working in Python (hopefully once I grok it, I'll like it better; until then...).
Simply change your last line
popcount = { datid : int(dat4) } # This does not do what you want
This creates a new dict and assignes it to popcount, throwing away your previous data.
What you want to do is add an entry to your dict instead:
popcount[datid] = int(dat4)

Updating a single field in a record with Haskell #

I need to update one field of a very large default record.
As the default may change I don't want to rebuild the entire record manually.
Now I have come across the following way of doing this, but I am not sure how it works:
unaggregate :: MyResult -> MyResult
unaggregate calc#MyResult{..} = calc{ the_defaults = the_override
`mappend` the_defaults }
where
the_override = create ("aggregation" := False)
I have tried searching for 'Haskell # operator' in Google but it does not return immediately useful information.
I saw somewhere calc#MyResult{..} does pattern matching on variables but I don't see what variable calc does for the MyResult record...
Also I have looked up mappend (and Monoids) and I am not sure how these work either...
Thank you for any help
The # symbol is called an "as-pattern". In the example above, you can use calc to mean the whole record. Usually you'd use it like this: calc#(MyResult someResult) -- so you can have both the whole thing and the pieces that you're matching. You can do the same thing with lists (myList#(myHead:myTail)) or tuples (myTuple#(myFst, mySnd). It's pretty handy!
MyResult{..} uses RecordWildcards. Which is a neat extension! BUT RecordWildcards doesn't help you update just one field of a record.
You can do this instead: calc { theFieldYouWantToUpdate = somethingNew }.

Passing an array with facter

Let us say I have an array of values
["node1.example.org", "node2.example.org"]
And I want to pass this using facter, and use it in puppet at such:
host {
$nodes:
...
}
How can I do this?
Facter 1.x cannot pass structured data as fact values. All facts will be coerced into String format. This is especially unfortunate for Arrays, because those will have their elements concatenated without a join marker.
It is advisable to make your fact return a [comma] seperated list, e.g. instead of returning Array result, do
result * ","
In your manifest, turn this back into an array
$nodes_array = split($nodes, ',')
host { $nodes_array: }
See the Puppet function reference and Ruby's Array methods.
Facter 2 does support Boolean, Hash and Array facts, but this may not yet be readily available.

Resources