Extracting an array of values from an array of hashes in Puppet - arrays

I have the following array of hashes in hiera:
corporate_roles:
- name: 'user.1'
system_administrator: true
global_administrator: false
password: TestPassword1234
- name: 'user.2'
system_administrator: true
global_administrator: true
password: TestPassword1234
I need to extract a list of users with a give role (eg global_administrator) to be assigned later on.
I managed to use the map function to extract the data I need:
$corporate_roles = lookup('corporate_roles')
$global_admins = $corporate_roles.map | $hash | { if ($hash['global']){$hash['name']}}
notify { "global admins are: ${global_admins}":
}
However this results in undef values seemingly making their way into the array for the users that don't match the criteria:
Notice: /Stage[main]/salesraft_test/Notify[global admins are: [, user.2]]/message: defined 'message' as 'global admins are: [, user.2]'
Notice: Applied catalog in 0.04 seconds
I can get around this by using the filter function as such:
$test = $global_admins.filter | $users | {$users =~ NotUndef}
Which results in clean output:
Notice: /Stage[main]/salesraft_test/Notify[global admins are: [user.2]]/message: defined 'message' as 'global admins are: [user.2]'
Notice: Applied catalog in 0.03 seconds
But I suspect there must be a better way of doing this and I am either missing some logic in my map or I am likely using the wrong function altogether for this.
I would like to know if there is a better way to achieve what I am trying to do?

But I suspect there must be a better way of doing this and I am either
missing some logic in my map or I am likely using the wrong function
altogether for this.
map() emits exactly one output item for each input item, so if your objective is to apply a single function to obtain your wanted output from your (lengthier) input, then indeed, map will not achieve that.
I would like to know if there is a better way to achieve what I am trying to do?
Personally, I would do the job by filtering out the hashes you want from your input and then mapping those to the wanted output form (as opposed to mapping and then filtering the result):
$global_admins = $corporate_roles.filter |$hash| {
$hash['global_administrator']
}.map |$hash| { $hash['name'] }
I like that because it's nice and clear, but if you want to do it with one function call instead of two then you're probably looking for reduce:
$global_admins = $corporate_roles.reduce([]) |$admins, $hash| {
$hash['global_admin'] ? {
true => $admins << $hash['name'],
default => $admins
}
}

Related

Create database seeder with same random value in a table row with Laravel database seeder

I am trying to create a table and populate the table with the following fields with the help of database seeder:
option a
option b
option c
option d
correct option
First four fields will be assigned random word, and the last field 'correct option' will contain any one of the first four.
I could not find any solution to do it with Laravel database seeder. Can anyone help?
Something like this?
use faker random element function in your factory or seeder.
$optionA = $faker->word;
$optionB = $faker->word;
$optionC = $faker->word;
$optionD = $faker->word;
return [
'option_a' => $optionA,
'option_b' => $optionB,
'option_c' => $optionC,
'option_d' => $optionD,
'correct_option' => $faker->randomElement([$optionA,$optionB,$optionC,$optionD]),
];
Create a factory and use Faker to generate the random words you're after
This sounds like an ideal use case for JSON columns (both for questions and answers). For instance, you might decide to have multiple valid answers to a single multiple choice question.
In your migration:
// create_questions_table.php
...
$table->json('choices')->default(new Expression('(JSON_ARRAY())'));
$table->json('answer')->default(new Expression('(JSON_ARRAY())'));
From https://laravel.com/docs/7.x/migrations#column-modifiers:
Using an Expression instance will prevent wrapping the value in quotes and allow you to use database specific functions. One situation where this is particularly useful is when you need to assign default values to JSON columns.
Then create a factory:
// QuestionFactory.php
$factory->define(Location::class, function (Faker $faker) {
$choices = $faker->words(4);
$answer = [ $choices[rand(1,4)] ];
return [
'choices' => $choices,
'answer' => $answer,
];
});
Using the Faker library included in Laravel, we can pick 4 words and randomly assign one of them to be the answer.

perl validate data structure syntax

i have configuration files for my scripts containing huge data structures, e.g:
foo => 'bar',
one => 'two',
bla => [
'something',
'else',
],
bli => {
here => {
'and' => 'there',
'also' => 'here',
},
},
etc.
i'm loading these configuration files using a simple e.g. :
%hash = do 'config.file';
my problem is : when users modify this configuration file, they can mess it up, forgetting a comma, bracket or anything else, it can have terrible impacts as the variables will appear empty afterwards in the script (i don't want to check for the presence of every variable..)
is there any way to "validate" the syntax of such a file/structure at loading time or right after loading it ? any module doing this ?
i suppose i could add a value at the bottom and check for the presence of this value but it's a bit dirty.
thank you !
Is this supposed to be readable with Perl? (then shouldn't there be a comma after the b1 entry?) Then the way to validate it is pass it to eval and then check the value of $#. The less insecure way is to format your configuration in another popular, readable, writeable, comprehendable data serialization format. The JSON format meets those criteria.
Rx is intended to validate the schema of JSON/YAML documents but because it runs on the in-memory data structure rather than the file it's usable with arbitrary config file formats. (It also means that it can't make certain distinctions like the use of true vs. 1 in YAML/JSON.)

CakePHP3: How to get the query result count if the result is a collection?

I have the following code:
$sites = $this->Sites->find()
->contain([
'Sitecategories',
'Sitedescriptions.Languages',
'Countries',
'Users'
])
->where([
'postcode LIKE' => $this->request->data['numero'] . '%'
])
->sortBy(function($row) { return substr($row->postcode, 0, 2); }, SORT_ASC);
debug($sites); displays:
object(Cake\Collection\Iterator\SortIterator) {
'count' => (int) 428
}
But I don't understand how to access to that count var.
I tried to access $sites->count() but I get the following error message:
Error: You cannot issue a count on a Collection.
Counting collections is kinda unreliable, given than they can mutate when being iterated, ie the count and the content can change, and with every iteration, changes may be reapplied for certain operations, causing the collection to possibly mutate in an unwated way (filtering, reducing, modifying contained objects, etc). Another unwanted effect may be unrewindable collections, ie they could not be iterated anymore. So that's basically why no count method is implemented.
The debug info shown there is the return value of iterator_count(), it accepts traversable objects and returns the number of elements in the iterator. Calling this will have the aforementioned side effects, the collection will be iterated and possibly mutated.
If you really need to know the count, and want to keep the collection, then you could for example compile it in beforehand, that would create a new collection based on the source data with all modifications applied, like:
$sites = $sites->compile();
$count = iterator_count($sites);
$sites can safely be reused after that point.
If you don't need the collection anymore, you could always simply convert it into an array and count that:
$sites = $sites->toArray();
$count = count($sites);
See also
PHP Manual > iterator_count()
Cookbook > Collections > Collection::compile()
Another possible solution is to use buffered iterator.
$collection = new Collection([1,2,3]);
$collection->count(); //throws exception
$collection->buffered()->count(); //gives 3
You have to use count($sites->toArray()).

Drupal-7 how to get hook_field_[formatter_]prepare_view() invoked without overwriting existing formatter

From my module, I'm looking for a way to change text-fields value during rendering process, but WITHOUT creating a new formatter, and BEFORE the currently affected formatter works.
In other words I want my changes always made on any text-field, as a generic preparatory step, regardless of which formatter will work afterwards.
For this to work:
I first considered using hook_field_formatter_prepare_view().
To get it invoked, I wanted to use hook_field_formatter_info_alter()
to add my module name to each involved formatter found here. But it
appears that the "module" index only accepts a unique module-name,
not an array.
BTW I'm quite surprised by this lack: I seem it should make sense to allow a sequence of formatters, like are allowed a sequence of
filters!
Then I considered using hook_field_prepare_view(), which seemed to
be the best candidate since the doc sayd it runs before the
formatters own hook_field_formatter_prepare_view(). But that
doesn't work either: this hook is invoked only for a field created by
the involved module (this issue had been discussed here).
Any idea? Thanks in advance.
I actually found a pretty way to do what I looked for.
The method is quite invasive but works fine and may be re-used for different cases.
1. To be as clear as possible, first I rephrase my question in terms of a
general use case:
In the rendering process, how to permit a module to change value of one or more
fields (given field-id, given field-type...) before the formatter (if any) do its own job?
2. The problem to accomplish this:
We can't make the module define a new formatter, because only one may
be defined at the same time for the same field
3. The strategy which led me to the desired result:
use hook_field_formatter_info_alter() to run through existing formatters and "graft" my module inside of those where I wish to intervene
(see detail under 4 below)
use hook_field_formatter_prepare_view() to:
(a) execute the required changes in field values
(the job my module is intended to: here it may be done or not, upon all fields of a given type or precisely identified fiels and so on, depending on any detailed needs)
(b) again run through formatters list and, when involved, fire their own hook_field_formatter_prepare_view() if it exists
(see detail under 5 below)
do the same job as in (b) above, successively for each of the other possibly involved hooks of any formatter:
hook_field_formatter_view()
hook_field_formatter_setting_form()
hook_field_formatter_setting_summary()
4. Detail about how to graft my module in the process:
Whith hook_field_formatter_info_alter(&$info) we face the following $info structure:
$info = array(
['formatter machine name'] = array(
['label'] => 'Human readable formatter description',
['field types'] => array(
[0] => 'a_field_type,
[1] => 'another_field_type',
# ...
),
['settings'] => array(
['option A'] => 'option A value',
['option B'] => 'option B value',
# ...
),
['module'] => 'formatter_module_name',
),
['formatter machine name'] = array(
# ...
),
# ...
);
We can easily run through the formatters list and look at "field types" index to select which ones are concerned by our needs.
Then for each involved one, we can:
substitute our own module name to formatter module name in "module" index
add a new sub-index in "settings" index (say "our module graft") to register the original formatter module name
So our hook_field_formatter_info_alter() will be something like:
function mymodule_field_formatter_info_alter(&$info) {
if($info) {
foreach($info as $name=>$formatter) {
if(
!#$formatter['settings']['mymodule graft'] # or already grafted
and
array_intersect($formatter['field types'],
array('text','text_long','text_with_summary')) # here it is for text fields only
) {
# substitute mymodule to original module:
$info[$name]['settings']['mymodule graft']=$formatter['module'];
$info[$name]['module']='mymodule';
}
}
}
}
Once flushing class registry, now all involved fields have their formatting phase redirected to our own module.
NOTE: installing a new formatter now requires flushing class registry again, in order our module to take it in hand also.
5. Detail about how to make original formatters to work after us:
As stated above, now it is our own module which is notified when a field has to been formatted, rather than the originally affected formatter.
So we must react in our hook_field_formatter_prepare_view(), which should look like:
function mymodule_field_formatter_prepare_view(
$entity_type,$entities,$field,$instances,$langcode,&$items,$displays
) {
# here we do our own job with field values:
if($items) {
foreach($items as $nid=>$node_data) {
# ...
}
}
# then we give original formatter a chance to execute its own hook:
foreach($displays as $display) {
$hook=
$display['settings']['mymodule graft'].'_field_formatter_prepare_view';
if(function_exists($hook)) {
$hook(
$entity_type,$entities,$field,$instances,$langcode,$items,$displays
);
}
}
}
Finally we also must give a chance to other formatters hooks to execute.
For each one, it should look like (replace HOOK and ARGS by the right data for each hook):
function mymodule_field_formatter_HOOK(ARGS) {
$hook=$display['settings']['mymodule graft'].'_field_formatter_HOOK';
if(function_exists($hook)) {
return $hook(ARGS);
}
}
Hope this helps...

In Puppet, can I realize a defined type for each element in an array?

After removing my variable names and everything extraneous for readability, my code comes down to this. I already set up a custom fact that returns an array of users that I need to set up some configuration files for. I'm trying to use a defined resource type and realize it with an array argument to do the configs for each user because Puppet lacks a basic for loop, so the code I have simplifies to this:
define modulename::pushconfigs{
user {"$name":
ensure => present
parameter => value
parameter => value
parameter => value
}
}
modulename::setconfigs{$::userlist: }
# $::userlist is an array of users, in the form [user1 user2 user3...]
for a couple parameters. However, when I try to run it, it says couldn't[do configs]for user user1 user2 user3. In other words, it's realizing the defined type only once, and it's trying to do so for a user whose name is the concatenated array.
How can I instead realize the defined type for each one in the array?
This should work...
define createuser{
user { $name :
ensure => present,
group => "gp",
home => "/home/$name",
shell => "/bin/bash",
}
}
$allusers = [ "user1", "user2", "user3", "user4" ]
createuser{$allusers:}
$::userlist is a string.
Its a fact, and facts are strings.
You need to turn it into an array before you can iterate over it.
If $::userlist = "user1,user2,user3"
$a_userlist=split($::userlist,',')
modulename::setconfigs{$a_userlist: }

Resources