How to deal w/ mutually exclusive classes in roles/profiles

33 views
Skip to first unread message

Alan Evans

unread,
Nov 5, 2019, 1:09:22 AM11/5/19
to Puppet Users
I have run into the problem of mutually exclusive classes in the past and I have not found a great way to work it out.  Consider the module https://forge.puppet.com/wazuh/wazuh/readme which defines wazuh::agent and wazuh::manager.  The way the software is designed a node can be either an agent or a manager and the puppet module is setup similarly.

In short , all nodes must have the agent configured unless the node is a manager.

What is the best way to make the distinction?

Background
  • Using Puppet 6 OSS
  • Using a control repo
  • Using hiera
  • (Trying to) use roles/profiles methodology
  • Classification via manifests/site.pp
In the role?
node /^qqq/ { include role::qqq }
node
/^wazuh-manager/ {include role::wazuh::manager }

class profile::wazuh::agent {}
class profile::wazuh::manager {}
class role::qqq            { include profile::wazuh::agent } # every role must explicitly include one or the other, we can't just put it in profile::base
class role::zzz            { include profile::wazuh::agent }
class role::wazuh::manager { include::wazuh::manager }


This is the "proper" way I think, but it makes extra work.

In hiera? (Configuration)
# given a hierarchy:
#   - node/%{trusted.certname}.yaml
#   ...
#   - common.yaml

# nodes/wazuh-server.example.com.yaml
profile
::wazuh::manager: true
profile::wazuh::manager::ossec_emailnotification: true
... more settings

# common.yaml
profile::wazuh::agent::wazuh_reporting_endpoint: wazuh-manager.example.com
... more settings

# site-modules/{profile,role}/manifests/*
class profile::base include profile::wazuh }

class profile::wazuh (
 
Boolean $manager = False
) {
 
unless $manager include profile::wazuh::manager }
}

class profile::wazuh::agent { # do agent things }
class profile::wazuh::manager { # do manager things }
class role::wazuh::manager { include profile::wazuh::manager }

This way seems really readable to me and does not make a lot of extra work.  But I don't think it really fits w/ the idea that one node should have exactly one role.  In fact it doesn't really use roles at all and instead the "role" is replaced with configuration data in the profile.

Thoughts?

Have you had to decide how to implement this kind of thing before?  If so, how did you go about it?

Thanks,
-Alan

Karsten Heymann

unread,
Nov 5, 2019, 8:52:12 AM11/5/19
to puppet...@googlegroups.com
Hi Alan,

I would simply do

class profile::base { include profile::wazuh }

class profile::wazuh (
Boolean $manager = False
) {
if $manager {
include profile::wazuh::manager
}
else {
include profiles::wazuh::agent
}

and be set with it.

Then you don't need the following:
class role::wazuh::manager { include profile::wazuh::manager }
because every node already includes profile::wazuh via profile::base

Best
Karsten

jcbollinger

unread,
Nov 5, 2019, 3:11:19 PM11/5/19
to Puppet Users

On Monday, November 4, 2019 at 7:09:22 PM UTC-6, Alan Evans wrote:
I have run into the problem of mutually exclusive classes in the past and I have not found a great way to work it out.  Consider the module https://forge.puppet.com/wazuh/wazuh/readme which defines wazuh::agent and wazuh::manager.  The way the software is designed a node can be either an agent or a manager and the puppet module is setup similarly.

In short , all nodes must have the agent configured unless the node is a manager.

What is the best way to make the distinction?


"Best" in what sense?

You seem to be focusing on implementation details, but that is not the primary consideration of Roles & Profiles.  R&P does yield implementation benefits, but if you're looking to aggressively minimize code size and class count then R&P is probably the wrong direction for you.

On the other hand, if by "best" you mean most consistent with the R&P model then I think probably yes, distinguishing Wazuh agent nodes from manager nodes by assigning different roles to the two categories is the R&P way.  That does not necessarily mean that that distinction must be a defining characteristic of the roles involved, however.  It may be that you choose nodes as Wazuh managers because they also have other kinds of coordinating or service responsibilities that individually or collectively serve as more appropriate defining characteristics.  On the other hand, if Wazuh managers are devoted exclusively to that purpose then that's a clear sign that there should be one or more roles to represent them, specifically.

In the role?
node /^qqq/ { include role::qqq }
node
/^wazuh-manager/ {include role::wazuh::manager }

class profile::wazuh::agent {}
class profile::wazuh::manager {}
class role::qqq            { include profile::wazuh::agent } # every role must explicitly include one or the other, we can't just put it in profile::base
class role::zzz            { include profile::wazuh::agent }
class role::wazuh::manager { include::wazuh::manager }


This is the "proper" way I think, but it makes extra work.


By "extra work" I take you to be referring to the code comment: "every role must explicitly include one or the other, we can't just put it in profile::base".  Yes.  I mean, you could instead create a single profile::wazuh class that distinguished between agents and masters via a class parameter, but for role classes to provide the locus for that distinction you would need each one to declare that profile.  That's a pretty natural outcome of implementing R&P.  Indeed, your comment leads me to suspect that you would do well to have more of it.

That is, if you are using your profile::base to provide a hodge podge of disparate things that are common to all your nodes, and especially if you have an explicit goal of trying to fit as many such things as possible there instead of into their own profiles, then you are not really embracing R&P yet.  Remember that although each node should have exactly one role, that role can and often does encompass multiple profiles (otherwise, there would be no point to distinguishing roles from profiles).  Profiles should define coherent units of configuration, and roles should aggregate as many of those as are appropriate for the task.  That's not to say that there shouldn't be a profile for common miscellany, but the bias should be toward minimizing its scope, not maximizing it, and it should not be declared or inherited by other profiles.

If certain combinations of profiles recur together in multiple different roles, then the best way to DRY that out is to create one or more base roles that other roles declare and build upon.  But if you do that then don't go crazy with it.  There's nothing wrong with a little repetition.

 

In hiera? (Configuration)
 [...]
This way seems really readable to me and does not make a lot of extra work.

Neither does the other alternative make a lot of extra work.  And this variation is not nearly as readable to me as the purely role-based one, because the information is not centralized.
 
But I don't think it really fits w/ the idea that one node should have exactly one role. In fact it doesn't really use roles at all and instead the "role" is replaced with configuration data in the profile.

And that's right.  The role class is a lie because declaring it on a given node does not actually ensure that node will be configured as a Wazuh manager.  I would find that very surprising, myself.  If you choose to use Hiera to discriminate between managers and agents then do yourself a favor and go all with way with that.  But that's not the R&P way.


Overall, I think you will already have perceived my bias toward defining separate roles for Wazuh managers and Wazuh agents, with those roles in fact controlling the distinction via their choice of profiles or profile parameters.  At least, if you're really going to do Roles and Profiles, anyway.  Most of my role classes declare several profiles, and I have some duplication in those profile lists.  I do have a base role such as I described, however, that many (but not all) of my other roles include, and that itself is assigned to a few nodes.


John

Alan Evans

unread,
Nov 6, 2019, 12:27:33 AM11/6/19
to Puppet Users
Hey Karsten thank you for your thoughts.
The trouble here is that the way the module works is that a node can only be a wazuh::agent or wazuh::manager.  If a node had both it would have duplicate resource definitions.  For example File{'/var/ossec/ossec.conf':} is used by both the agent and the manager but they're different.

I think I mentioned this in a paragraph I edited out of my original email.
 
Best
Karsten

Alan Evans

unread,
Nov 6, 2019, 12:54:38 AM11/6/19
to Puppet Users
Sorry, I misread.  I see what you're saying.  This is like what I was proposing w/ hiera.  The trouble is that it's not really R&P then as has been brought up by jcbollinger.

Karsten Heymann

unread,
Nov 6, 2019, 9:55:02 AM11/6/19
to puppet...@googlegroups.com
Am Mi., 6. Nov. 2019 um 01:54 Uhr schrieb Alan Evans <alanw...@gmail.com>:
> Sorry, I misread.  I see what you're saying.  This is like what I was proposing w/ hiera.  The trouble is that it's not really R&P then as has been brought up by jcbollinger.

I think there are at least three ways to handle this, and all of them have their valid uses:

1) Explicitly include the worker profile in every role but the master role
  - pro: you need no logic to disable the worker profile in the master role
  - con: A lot of repetition in the other roles
2) Add a "disable switch" hiera variable to the worker profile and then include it in the base.pp. For the master node, you disable the worker profile via the hiera switch and explicitly include the master profile
  - pro: It is possible to include the 
  - con: You need to wrap the code in the worker profile into a large if not $disabled { ... } clause
3) Create a wrapper profile for master and worker that conditionally, depending on a hiera variable, load one class or the other. That variable could be a $service_type = master|worker enum or the fqdn of the master server to compare again in the wrapper class if the master name is already needed i.e. for configuring the workers: if $facts['fqdn'] == $mastername { include profile::service::master } else { include profile::service::slave }
  - pro: You need only a single include in the role or base.pp for every server somehow related to that service, be it master or worker
  - con: In the role class it is not directly visible if the server is a master or a worker.

Which one of these to use (if any) depends on your preferences and situation, of course.

Best regards
Karsten

Karsten Heymann

unread,
Nov 6, 2019, 9:56:39 AM11/6/19
to puppet...@googlegroups.com
The last part of that sentence was missing:

>   - pro: It is possible to include the 
... include the worker profile in the base.pp

Alan Evans

unread,
Nov 8, 2019, 9:49:22 PM11/8/19
to Puppet Users
As was discussed above it does not fit well w/ R&P but I think I am going to do something like #2.

class wazuh(
 
Boolean $manager = false,
) {
 
if $manager {
    include wazuh
::manager
 
} else {
    include wazuh
::agent
 
}
}

# data/nodes/my-manager-node.example.com.yaml
wazuh
::manager: true
Reply all
Reply to author
Forward
0 new messages