How to extract values from this complex Puppet Struct - puppet

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').

Related

Print Element GString Property in Human Readable format

I'm writing a rust application with a simple gstreamer pipeline. I would like to print the stats property of the appsink element in a human readable format.
With this code:
let stats = appsink.get_property("stats").unwrap();
println!("stats: {:?}", stats);
I get:
stats: Value(GString(Foreign(0x7f9c008f00, 101)))
Since that isn't human readable, I tried:
let stats = appsink.get_property("stats").unwrap().get::<GString>();
println!("stats: {:?}", stats);
but got:
stats: Err(GetError { actual: GstStructure, requested: gchararray })
I'm not sure how to interpret the output.
I've looked at this post: gstreamer rust get human readable output for bitrate set on x264enc but it doesn't show how to approach a GString.
I was able to sort of reproduce this using the following example:
use gstreamer::prelude::*;
fn main() {
gstreamer::init().unwrap();
let source = gstreamer::ElementFactory::make("videotestsrc", Some("source")).expect("Could not create source element.");
let val = source.get_property("pattern").unwrap();
println!("{:?}", val);
}
This will attempt to get the pattern property on a generic VideoTestSrc element, and it will print out the string address instead of the actual string. Adding .get::<GString>() to the let val statement will produce a runtime error:
Err(GetError { actual: GstVideoTestSrcPattern, requested: gchararray })
which is telling us that it tried to cast to gchararray but the actual data type of the property is a custom type, GstVideoTestSrcPattern, which is not a string. In your example, the property value has the type GstStructure. It might be possible to use .get::<GstVideoTestSrcPattern>() to get the value of the pattern property and manipulate it as such, but since we want a string here, there's another way using the .transform() method defined on a glib::Value:
let val = source.get_property("pattern").unwrap().transform::<String>().unwrap().get::<String>().unwrap().unwrap();
This is rather unwieldy and it would be advised to do a lot more error checking on the values returned here (for example using the ? operator instead of the .unwrap()s).
The .transform::<String>() call will try to give us a String representation of the property's value, but it gives us a Option<Value> which we must unwrap and convert into an actual String using .get::<String>(), which gives us a Result<Option<String>, GetError> (the inner option is because the string could be NULL). Unwrapping those values gives us a printable string.
There might be a simpler way, but this at least gives you the result. There is more documentation on how to deal with glib Value types here: https://gstreamer.pages.freedesktop.org/gstreamer-rs/glib/value/struct.Value.html
But unfortunately it's not very easy to read and doesn't have examples. It might be possible to glean more info from the rust port of the gstreamer tutorials: https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/tree/master/tutorials
Close to what transistor wrote, the correct way of doing this would be
let stats = appsink.get_property("stats").unwrap();
println!("stats: {:?}", stats.get::<gst::Structure>().expect("not a structure").expect("structure was None"));
You don't have to transform the glib::Value to a String, but you can directly get a gst::Structure from the glib::Value and work on that. Among other things it provides a Debug impl that allows direct printing of it, and various API for accessing the fields, etc. See https://gstreamer.pages.freedesktop.org/gstreamer-rs/gstreamer/structure/struct.StructureRef.htm

Read multiple hash keys and keep only unique values

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]

How to check if the first variable passed into a method is a string. Perl

I have no idea how to check for this. My method(if condition in method) should only work (execute) if the first argument passed in is a string. I know how to check other types, but I can't seem to find anything for checking for a string.
For a hash I would do something like;
if(ref eq 'HASH') {...}
If someone could provide a simple example I'm sure I would be able to apply it to what I'm doing. I will put up the code for the method and an explanation for the whole operational details of the method if needed.
Added Information
This is a method for handling different types of errors in the software, here are the 3 possible input formats:
$class->new("error string message")
$class->new("error string message", code => "UNABLE_TO_PING_SWITCH_ERROR")
$class->new("error string message", code => "UNABLE_TO_PING_SWITCH_ERROR", switch_ip => $ip3, timeout => $timeout)
There will always be an error message string first.
With the 1st case there is also a hashref to an error hash structure that is located in a library,
this method new will go into a template processing if the word "code" exists as an arg. where the longer detailed error message is constructed. (I already have the logic for this).
But I have to add logic so that the error message string is added to the hash, so the output is one hash, and not strings.
The second case is very similar to the first, where there are parameters eg. switch_ip , which are inserted into the string using a similar template processing logic, (already have this too).
So I think the first and second cases can be handled in the same way, but I'm not sure, so separated them in this question.
The last case is just can error message string by itself, which at the minute I just insert it into a one key message hash { message => "error string}.
So after all that how should I be checking or dividing up these error cases, At the minute my idea for the ones with code , is to dump the arguments into a hash and just use something like:
if(exists($param{code}) { doTemplateProcess()...}
I need to ensure that there is a string passed in first though. Which was my original question. Does any of my context information help? I hope I didn't go off the topic of my question, if so I'll open this a new question. Thanks.
Error hash - located in Type.pm
use constant ERROR_CODE => {
UNABLE_TO_PING_SWITCH_ERROR => {
category => 'Connection Error:',
template => 'Could not ping switch %s in %s minutes',
tt => {template => 'disabled'},
fatal => 1,
wiki_page => www.error-solution.com/,
},
}
From comments:
These will be called in the software's code like so
ASC::Builder::Error->new(
"Phase x this occured because y was happening:",
code => UNABLE_TO_PING_SWITCH_ERROR,
switch_ip => $ip3,
timeout => 30,
);
Putting the wisdom of your particular problem aside and channeling Jeff Foxworthy:
If you have a scalar and it's not a reference, you might have a string.
If your non-reference scalar doesn't look like a number, it might be a string.
If your non-reference scalar looks like a number, it can still be a string.
If your non-reference scalar has a different string and number value, it might be a dualvar.
You know that your argument list is just that: a list. A list is a collection of scalar values. A scalar can be a reference or not a reference. I think you're looking for the not a reference case:
die "You can't do that" if ref $first_argument;
Past that, you'd have to do fancier things to determine if it's the sort of value that you want. This might also mean that you reject objects that pretend to be strings through overloading and whatnot.
Perhaps you can make the first argument part of the key-value pairs that you pass. You can then access that key to extract the value and delete it before you use the remaining pairs.
You may easily check only whether the error string is a simple scalar value or a reference. You would do that with ref, but you must consider what you want to do if the first parameter isn't a string
You should write your constructor in the ASC::Builder::Error package along these lines
sub new {
my $class = shift;
my ($error, %options) = #_;
die if ref $error;
bless { string => $error }, $class;
}
This example simply dies, and so kills the program, if it is called with anything other than a simple string or number as the first parameter
You may call it as
ASC::Builder::Error->new('error')
or
ASC::Builder::Error->new(42)
and all will be well. If you try
ASC::Builder::Error->new('message', 'code')
then you will see a warning
Odd number of elements in hash assignment
And you may make that warning fatal
If there is anything more then you should explain
Supporting all of the following is simple:
$class->new("s")
$class->new("s", code => "s")
$class->new("s", code => "s", switch_ip => "s", timeout => "s")
All you need is the following:
sub new {
my ($class, $msg, %opts) = #_;
...
}
You can checks such as the following to examine what the called provided:
if (exists($opts{code}))
if (defined($opts{code}))
if ($opts{code})
Despite saying that the string will always be provided, you now ask how to check if was provided. As such, you are probably trying to perform validation rather than polymorphism. You shouldn't waste your time doing this.
Let's look at the hash reference example you gave. ref($arg) eq 'HASH' is wrong. That returns false for some hash references, and it returns false for some things that act like a reference to a hash. The following is a more proper check:
eval { %$arg; 1 }
The equivalent for strings would be the following:
eval { "$arg"; 1 }
Unfortunately, it will always return true! Every value can act as a string. That means the best thing you can do is simply to check if any argument is provided.
use Carp qw( croak );
croak("usage") if !#_;
It's rare for Perl subs to perform argument validation. Not only is it tricky, it's also expensive. It also provides very little benefits. Bad or missing arguments usually results in exceptions or warnings shortly after.
You might see suggestions to use croak("usage") if ref($arg); (or worse, die if ref($arg);), but keep in mind that those will cause the rejection of perfectly fine objects that overload stringification (which is somewhat common), and they will fail to detect the problem with ASC::Builder::Error->new(code => ...) because code produces a string. Again, performing type-based argument validation is an expensive and buggy practice in Perl.

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.

Custom MongoDB search query

In my database I have documents which all contain the property foo. For each value of foo I have a function that either returns true or false. How can I query for all the documents for which the value of foo makes the function return true?
If you need to check if your string field's value is one of several, you need the $in modifier.
db.collection.find( { field : { $in : array } } );
It works fast and uses index (if possible).
If your field is an array and you pass a string, use this syntax.
db.collection.find({array_field : string_value});
It will check every element in the array and, if any of them matches your string, it will return the document.
You could use $where.
Example:
db.myCollection.find( { $where: "this.a > 3" });
db.myCollection.find( "this.a > 3" );
db.myCollection.find( { $where: function() { return this.a > 3;}});
Note, this is run in Javascript. This means two things.
You can put arbitrary Javacript into $where expression (the function form).
It'll be significantly slower than regular queries.
It really depends on what the function is and how you are using it. Is the function constant for any given record? Is it even a function you can evaluate on the database server? ...
In the extreme, if you need to check this value often, you might, for example, create a field that exists only when f(foo) is true and then create a sparse index on that field.
$where may well be the solution you are looking for, but depending on the access patterns there may be a better solution.

Resources