Conditonal resource attributes - conditional-statements

To install packages, I feed in data from Hiera into a for loop. Some packages require additional arguments. For packages that do not require an argument, I've set the value to undef, however, Chocolatey reads undef and complains.
How do I get the package resource to ignore the install_options attribute when it is blank or undef?
Hiera snippet:
profile::business::packages:
office365business:
version: latest
provider: chocolatey
arguments: ['/productid:O365BusinessRetail']
xmind:
version: latest
provider: chocolatey
arguments: undef
slack:
version: latest
provider: chocolatey
arguments: undef
Class example:
class profile::business(
Hash $packages,
){
if $::kernel == 'windows' {
$packages.each | $key, $value | {
package { "install_${key}" :
name => $key,
ensure => $value['version'],
provider => $value['provider'],
install_options => $value['arguments'],
notify => Reboot['after_profile_business'],
}
}
reboot { 'after_profile_business' :
apply => finished,
message => 'Reboot: Business profile applied.'
}
}
}
The best I can come up with is using an if clause to apply different instances of the package resource with or without install_options, depending on the value of arguments:
$packages.each | $key, $value | {
if $value['arguments'] != 'undef' {
package { "install_${key}" :
name => $key,
ensure => $value['version'],
provider => $value['provider'],
install_options => $value['arguments'],
notify => Reboot['after_profile_admin'],
}
} else {
package { "install_${key}" :
name => $key,
ensure => $value['version'],
provider => $value['provider'],
notify => Reboot['after_profile_admin'],
}
}
}
However, this seems rather clunky and I'm hoping someone might be able to show me a better way?
I've seen the Puppet Selector condition example, but I do not know if this will work for me.
T.I.A

This YAML fragment ...
arguments: undef
... sets the value of the 'arguments' key to the string 'undef'. That doesn;t mean the same thing on the Puppet side as the Puppet literal undef.
There are solutions. All of the best, IMO, revolve around representing absence of data via bona fide absence of data. That avoids any need for special reserved words. So suppose your data looked like this, instead:
profile::business::packages:
office365business:
version: latest
provider: chocolatey
arguments: ['/productid:O365BusinessRetail']
xmind:
version: latest
provider: chocolatey
slack:
version: latest
provider: chocolatey
Note that there is no entry bearing the arguments key where there are in fact no arguments to specify. If you have been rigorous and thorough about defining data types, then you may need to adjust your data type for these data to accommodate that, but so much the better because that would better describe the actual data semantics. That data modification probably resolves your issue by itself, because looking up a key that does not exist in a hash that does exist should yield undef (and there's also dig() if the undefinedness can occur at a higher level of a deep data structure).
Consider also, however, that Puppet has a shortcut for declaring that resource property values are drawn from a hash. That won't quite fit your present data because your keys are not the same as the needed property names, but you could either change the keys in your data or map them at the Puppet level. The latter might look like this:
# Defining the key / property name mappings here makes them clear, and is easy to
# change if you need to update the mappings
$mappings = { 'version' => 'ensure', 'arguments' => 'install_options' }
$packages.each |$package, $properties| {
# map the keys appearing in the data to Puppet property names, based on
# the hash defined above
$filtered_props = $properties.reduce({}) |$memo, $pair| {
$mapped_key = $pair[0] in $mappings ? { true => $mappings[$pair[0]], default => $pair[0] }
$memo + { $mapped_key => $pair[1] }
}
# one declaration covering all cases
package { "install_${package}" :
name => $package,
provider => $value['provider'],
notify => Reboot['after_profile_admin'],
* => $filtered_props,
}
}

Related

Validate multiple properties with one message

I'm trying to validate a class that has three required properties.
If one or more of them is null it should trigger a single validation message.
Is there a idiomatic way to describe this in fluent validator?
I'm looking at dependant rules but the bottom of the documentation's page advises against using them.
Furthermore I still want to validate all three properties. I just don't want duplicate messages.
I noticed RuleSets, but these seem to serve a different purpose.
Alternatively I could create a validator specifically for these three options but without message and then chain the new validator in the original one. Then I think I can give that one a single message.
But that's a lot of ceremony for a system that is built around being readable.
So looking for a readable way to express validation for three fields with a single message as result.
There are 3 main ways you can do this with FluentValidation: Conditions, dependent rules or a custom rule.
Conditions
You can use 3 separate rule declarations with When conditions to ensure that you only ever get a single validation message.
RuleFor(x => x.Property1).NotNull()
.WithMessage("At least one is required");
RuleFor(x => x.Property2).NotNull()
.When(x => x.Property2 != null)
.WithMessage("At least one is required");
RuleFor(x => x.Property3).NotNull()
.When(x => x.Property1 != null && x.Property2 != null)
.WithMessage("At least one is required");
Dependent Rules
RuleFor(x => x.Property1).NotNull()
.WithMessage("At least one is required")
.DependentRules(() => {
RuleFor(x => x.Property2).NotNull()
.WithMessage("At least one is required")
.DependentRules(() => {
RuleFor(x => x.Property3).NotNull().WithMessage("At least one is required");
});
});
I don't particularly like this approach - I think it's hard to read (hence the warning in the documentation), but if you like this approach it'll work just fine.
Custom logic
RuleFor(x => x)
.Must(x => x.Property1 != null && x.Property2 != null && x.Property3 != null)
.WithMessage("At least one is required");
This approach is slightly different as it creates a model-level rule, so the error message will be associated with the entire model, rather than with a specific property.
Stop the validator when the first rule fails by setting the CascadeMode property:
public class MyClassValidator : AbstractValidator<MyClass>
{
public DestinationDeviceValidator()
{
this.CascadeMode = CascadeMode.Stop;
this.RuleFor(x => x.Property1)
.NotNull();
this.RuleFor(x => x.Property2)
.NotNull();
this.RuleFor(x => x.Property3)
.NotNull();
}
}

How to use existing data from the database in Codeception FactoryMuffin?

I'm trying to set up easy test data in my Acceptance tests:
public function shouldUseAFakeAccountHolder(AcceptanceTester $I) {
$I->have(AccountHolder::class);
// ...
}
I've copied the example code from the Codeception documentation and modified it with my entity names (as well as fixing the bugs).
<?php
public function _beforeSuite()
{
$factory = $this->getModule('DataFactory');
// let us get EntityManager from Doctrine
$em = $this->getModule('Doctrine2')->_getEntityManager();
$factory->_define(AccountHolder::class, [
'firstName' => Faker::firstName(),
// Comment out one of the below 'accountRole' lines before running:
// get existing data from the database
'accountRole' => $em->getRepository(AccountRole::class)->find(1),
// create a new row in the database
'accountRole' => 'entity|' . AccountRole::class,
]);
}
The relationship using existing data 'accountRole' => $em->getRepository(AccountRole::class)->find(1) always fails:
[Doctrine\ORM\ORMInvalidArgumentException] A new entity was found through the relationship 'HMRX\CoreBundle\Entity\AccountHolder#accountRole' that was not configured to cascade persist operations for entity: HMRX\CoreBundle\Entity\AccountRole#0000000062481e3f000000009cd58cbd. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example #ManyToOne(..,cascade={"persist"}). If you cannot find out which entity causes the problem implement 'HMRX\CoreBundle\Entity\AccountRole#__toString()' to get a clue.
If I tell it to create a new entry in the related table 'accountRole' => 'entity|' . AccountRole::class, it works, but then it adds rows to the table when it should be using an existing row. All the role types are known beforehand, and a new random role type makes no sense because there's nothing in the code it could match to. Creating a duplicate role works, but again it makes so sense to have a separate role type for each user since roles should be shared by users.
I've had this error before in Unit tests, not Acceptance tests, when not using Faker / FactoryMuffin, and it's been to do with accessing each entity of the relationship with a different instance of EntityManager. As soon as I got both parts using the same instance, it worked. I don't see how to override the native behaviour here though.
It works (at least in Codeception 4.x) by using a callback for the existing relation:
<?php
public function _beforeSuite()
{
$factory = $this->getModule('DataFactory');
$em = $this->getModule('Doctrine2')->_getEntityManager();
$factory->_define(AccountHolder::class, [
'firstName' => Faker::firstName(),
'accountRole' => function($entity) use ($em) {
$em->getReference(AccountRole::class)->find(1);
},
]);
}
I've found it here: https://github.com/Codeception/Codeception/issues/5134#issuecomment-417453633

In Puppet, how to use defined node variables in an if clause

In a puppet class how should I test if a variable has been set in a node?
I use a VM name (like server1) and a domain name (like example.org) where users can reach the page. "example.org" won't be conveyed via a fact, so I need to pass it via a class parameter. I came up with this way to define the variable in a node block and use it in my test class for my settings.
node "VM1" {
class { 'test':
domainname => "example.org",
}
[...]
class test ($domainname) {
ini_setting {
'set_property':
ensure => present,
path => '/tmp/test.ini',
section => 'main',
setting => 'url',
value => "https://$domainname";
}
[...]
But now I want to add a condition that if $domainname isn't set then the $hostname fact should be used in its place.
ini_setting {
'set_property':
ensure => present,
path => '/tmp/test.ini',
section => 'main',
setting => 'url',
if $domainname !~ $hostname {
value => "https://$domainname";
} else {
value => "https://$hostname";
}
But now I get an error like this every time:
Error: Could not retrieve catalog from remote server: Error 500 on SERVER: Server Error: Syntax error at 'domainname'
What should I do instead?
The error message is explaining to you that if statements cannot appear inside resource declarations. There is, however, a different conditional form, called a "selector" that can appear inside resource declarations. It is Puppet's analog of the ternary ?: operator that appears in several languages.
Stylistically, though, it is usually better form to keep resource declarations as simple as possible. To that end, you should probably set a variable, conditionally, outside the resource declaration, and then use its value inside. Using your own conditional, that might look like this:
if $domainname !~ $hostname {
$url_value = "https://$domainname";
} else {
$url_value = "https://$hostname";
}
ini_setting {
'set_property':
ensure => present,
path => '/tmp/test.ini',
section => 'main',
setting => 'url',
value => $url_value;
}
Additionally, however, I note that your particular condition, repeated above, is highly suspect. In recent Puppet (version 4 and above), you should be using Puppet data types to both declare your class parameters and check them. In particular, if it is permissible to declare class test without providing a $domainname parameter, then you would declare that class like so:
# Using the Puppet v4+ type system
class test(
Optional[String] $domainname = undef
) {
# ...
, and would test whether a value was provided for $domainname like so:
if $domainname =~ Undef {
# ...
}
You cannot use the type system in earlier Puppet, but there you can rely on undefined variables to expand to nothing when you interpolate them:
# Using the Puppet v3- behavior
class test(
$domainname = undef
) {
# ...
if "$domainname" == "" {
# ...
}
# ...
}

PHPSpec: How to handle data heavy mocks in spec?

I have a series of specs that are doing what I would like them to but I'm wondering if I'm overcomplicating things as my let function for some of them is rather large and cumbersome.
I have built specs for a series of classes that process responses from SQL or JSON API depending on the response. The specs are just checking the resultant object of the processes of each class. I've mocked the connection to return valid mock data for each type of request that the specs would trigger. I'm trying to think of a better way to provide this mock data than having rather large arrays and JSON strings just sitting in the spec files.
For example (simplified):
class CharacterProcessorSpec extends ObjectBehavior
{
public function let(AdapterInterface $adapter)
{
$characters = [
[
'name' => 'Timmy',
'class' => 'Fighter',
'level' => 1,
'race' => 'Elf',
'str' => 16,
'dex' => 14,
'con' => 18,
'int' => 10,
'wis' => 12,
'cha' => 11,
... // Rest of the minimally required fields
], [
... // Second character for processing multiple at once
]
];
$adapter->fetch(new CharacterRequest('Timmy'))->willReturn([$characters[0]]);
$adapter->fetch(new CharacterRequest('*'))->willReturn($characters);
$this->beConstructedWith($adapter);
}
public function it_should_build_requested_character_details()
{
$this->build('Timmy')->shouldReturnArrayOfCharacters();
}
public function it_should_build_all_character_details()
{
$this->buildAll()->shouldReturnArrayOfCharacters();
}
public function getMatchers()
{
return [
'returnArrayOfCharacters' => function($characters) {
foreach ($characters as $c) {
if (!$c instanceof Character) {
return false;
}
}
return true;
}
];
}
}
Is it worth me moving the arrays to a separate file and loading them in or is that a no no?
Note: The build functions are designed to not care if the adapter is for SQL or the API, it just converts the data into a consistant object. Therefore the spec does not actually define what the data is (in the example I have provided it's the same as an SQL response but I usually have the second entry formatted how the JSON response would be as it processes on a per entry basis).
Is it worth me moving the arrays to a separate file and loading them in or is that a no no?
No, is not worth this effort. You provide only data to constructor and with that it should be OK in direct definitions.

Option result in akka-http

I'm having an issue when trying to return an Option result from aka-http.
Basically it's a get that might have a 404.
pathPrefix("contacts" / Segment) { id =>
get {
contactService.getById(id).map {
case Some(c: ContactDto) => complete(OK -> toResource(c))
case None => complete(HttpResponse(NotFound))
}
}
}
Which gives me and error of:
[error] found : scala.concurrent.Future[akka.http.scaladsl.server.StandardRoute]
[error] required: akka.http.scaladsl.server.Route
[error] (which expands to) akka.http.scaladsl.server.RequestContext => scala.concurrent.Future[akka.http.scaladsl.server.RouteResult]
[error] contactService.getById(id).map {
Any help would be greatly appreciated.
The problem you are seeing here has to do with the fact that you are using a Future and not because of the Option. I'm going to assume that the call contactService.getById(id) returns a Future. As the result of any route within your routing tree needs to be a Route (short for RequestContext => Future[RouteResult]) and your Future is itself not a Route, then you need to make a small change to handle this situation. You should be able to use the onComplete directive in combination with your Future as follows:
pathPrefix("contacts" / Segment) { id =>
get {
val fut = contactService.getById(id)
onComplete(fut){
case util.Success(Some(c: ContactDto)) =>
complete(OK -> toResource(c))
case util.Success(None) =>
complete(HttpResponse(NotFound))
case util.Failure(ex) =>
complete(HttpResponse(InternalServerError))
}
}
}
This code now handles the 3 possible outcomes from the Future (success with a Some, success with a None and a failure), producing a Route for each of those cases. This should fix your problem.
#cmbaxter's answer is correct but if you're happy with the standard status codes for the three cases above (Ok, NotFound, InternalServerError) then you can simplify the code to just complete directly with your function that returns Future[Option[T]].
pathPrefix("contacts" / Segment) { id =>
get {
complete(contactService.getById(id).map(toResource))
}
}
That is assuming that toResource returns a type where a ToEntityMarshaller exists for the type returned by that function. Akka provdes the machinery for Future and Option so you just need to supply the T part. For example if you were returning json and using spray-json then you can define a JsonWriter[T] and the implicits in akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport will do the rest. See spray-json-support.
The map(toResource) may not actually be required, but I'm assuming that does additional conversion of ContactDto to some other type - if its just converting it to json or similar then you can drop it and use the in built marshalling support as described above.