2

I'm having trouble with puppet (version 5.5) code design. I've written a component module to deal with ucarp. It make use of the module eyp-systemd to register an ucarp service within systemd. Now I would like to consume the ucarp module from two independant profile modules that manage different services (in my case actually haproxy and bind9). Essentially this looks like this:

class ucarp {
  systemd::service { 'ucarp':
    # list of parameters
  }
}

define ucarp::vip {
  # defines a virtual IP to be shared among several nodes
}

# ====================

class profile_haproxy {
  include ::ucarp
  ::ucarp::vip { 'haproxy': }
  # setup haproxy
}

# =====================

class profile_bind9 {
  include ::ucarp
  ::ucarp::vip { 'bind9': }
  # setup bind9
}

This is straight forward and works well.

Now the actual issue: it is best practice to order the ucarp service after the services that are run over ucarp. This is possible using the after parameter:

class ucarp(
  Array[String] $after,
) {
  systemd::service { 'ucarp':
    after => $after.join(' '),
    # list of other parameters
  }
}

This requires replacement of include ::ucarp by

class { '::ucarp':
  after => ['haproxy'],
}

or

class { '::ucarp':
  after => ['bind9'],
}

respectively. Of course this would immediately lead to a "duplicate declaration" error.

What I actually would like to have is a single instantiation of the class ucarp that collects all after parameters into one single string that can be passed to systemd::service. How would I do this?

Currently two possible solutions come to my mind:

  1. Fork eyp-systemd, remove the after parameter and replace it by a defined type, e.g. systemd::service::after that manages the corresponding entry in the service definition file. This is something I really don't want to do. Generally I shy away from modifying forge modules as I this forces me to maintain them on my own. In this case the change also seems to be rather big (including an interface change).
  2. Introduce my own defined type in the ucarp module ucarp::order_after which does not do anything. The profile modules would define virtual instances of this type. The ucarp class could then use a puppetdb query to collect all instances of ucarp::order_after. The big drawback here is that I'm only dealing with virtual resources not with exported resources. So actually there is no need at all to involve puppetdb, rendering this approach to an ugly work-around.

A further solution is inspired by c4f4t0r:

  1. Introduce an ucarp profile module that single task is to instantiate the ucarp component class with the correct after services. The list of after services is provided by hiera:
    class profile_ucarp (
      Array[String] $after,
    ) {
      class { '::ucarp':
        after => $after,
      }
    }
    profile_ucarp.after:
        - 'haproxy'
        - 'bind9'
    
    There is no need any more for the other profile classes to instantiate the ucarp class - removing the potential duplicate declaration issues. I consider this solution superior to the two above. Still I'm not content as using hiera to fix an issue that is solely related to code is a misuse of hiera.

I hope there are other possibilities I can't think of right now.

1 Answers1

2

You need to use contain function and put the class parameters in hiera.

In your profile module, your classes profile::haproxy and profile::bind, can be only one, because your dupplicating classes, because you are not using hiera to store the class parameters

class profile::ucarp {
  contain ::ucarp
  contain ::ucarp::vip
}

class profile::haproxy {
  contain ::haproxy
}

 #now I can create a role using this profiles

class role::elb {
  contain ::profile::ucarp
  contain ::profile::haproxy
}

Now inside hiera you can storage parameters based on the host funcionality, if you want to avoid the error, try to check your design using puppet doc roles and profiles

From puppet documentation

Having classes contain other classes can be very useful, especially in larger modules where you want to improve code readability by moving chunks of implementation into separate files.

    However, unlike resources, Puppet does not automatically contain classes when they are declared inside another class. This is because classes can be declared in several places via include and similar functions. Most of these places shouldn’t contain the class, and trying to contain it everywhere would cause huge problems.

    Instead, you must manually contain any classes that need to be contained.
c4f4t0r
  • 5,149
  • 3
  • 28
  • 41
  • sorry, I don't get what you mean. I don't have an ucarp profile as ucarp on it's own is useless. It only makes sense if used together with another service (like haproxy). Now I would like to run two services on the same machine - each is having it's own profile that in turn makes use of the ucarp component module. Of course the profile parameters are stored in hiera, not so the ucarp componenent class parameters. But you're right: defining an ucarp profile and configuring the profile via hiera would also be a possibility. – C.Scharfenberg Mar 11 '19 at 14:48
  • I've edited my question to offer a new solution inspired by c4f4t0r. Is this what you suggested, c4f4t0r? – C.Scharfenberg Mar 11 '19 at 15:13
  • you are trying to manage the class ordering using after, you need to use contain function – c4f4t0r Mar 11 '19 at 15:36
  • `after` != `require`. The former is a systemd construct. – bodgit Mar 11 '19 at 15:44
  • I'm not using class ordering. 'after' is a parameter to the class systemd::service that deals with systemd service ordering. It is completely unrelated to puppet class ordering with 'require' or 'before' – C.Scharfenberg Mar 11 '19 at 15:48
  • ok, but if you put that parameter in hiera and use contain your classes will be execute in the correct order. – c4f4t0r Mar 11 '19 at 15:56
  • 1
    This is a good answer. It follows both the "roles and profiles" puppet documentation and the "Puppet Best Practices" book: "Intermodule relationships like this are typically domain-logic handled in your profiles rather than hardcoded into your modules." – Mark Wagner Mar 11 '19 at 17:43
  • 1
    i have found out that my preferred solution - almost - can be implemented using the richardc/datacat module. It cannot only target files but also attributes of other resources. So it is possible to define datacat_fragments in different profiles and combine them into an attribute of a single shared resource. But this only works with native types not with defined types. Unfortunately systemd::service is a defined type. – C.Scharfenberg Mar 12 '19 at 15:39