Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use Puppet parameterized classes to force order of resources applied?

Tags:

puppet

The Puppetlabs docs state that in order for one class to require another class you should use the relationship chaining syntax and declare both classes in your outer nodes.

I have a repo class which creates the yum repo definition that many packages in each modole depend on. In each module I have a Class['repo'] -> Class['modulename'] statement and both classes are declared in the node. However, when puppet runs it doesn't always execute the repo class before the module class as expected. Why not? Example below (puppet 2.6.16):

EDIT: It appears there are 3 basic solutions to this problem.

  1. Replace the class dependencies with resources dependencies using before/require metaparameters (as shown in turingmachine's answer).
  2. Remove exterior class dependencies and explicitly state dependencies between inner classes.
  3. Use anchor type as provided by Puppetlabs in the stdlib module to contain a class allowing the depending class to create a reference to the external class using the chaining syntax.

So which of these approaches is best, considering Puppet v3 and the desire to keep refactoring to a minimum going forward'?

Manifest puppettest.pp:

class { 'repo': }
class { 'maradns': }

class repo {
  class { 'repo::custom': }
}

class repo::custom {
  yumrepo {'custom':
    enabled  => 1,
    gpgcheck => 0,
    descr    => "Local respository - ${::architecture}",
    baseurl  => 'http://repo.nike.local/CentOS/\$releasever/\$basearch';
  }
}

class maradns {
  Class['repo'] -> Class['maradns::install']
  Class['maradns::install'] -> Class['maradns::config']
  Class['maradns::config'] ~> Class['maradns::service']
  class { 'maradns::install': }
  class { 'maradns::config':  }
  class { 'maradns::service': }
}

class maradns::install {
  package { 'maradns':
    ensure  => present,
  }
}

class maradns::config {
  file { 'mararc':
    ensure  => present,
    path    => '/etc/mararc',
    mode    => '0644',
    owner   => root,
    group   => root,
  }
}

class maradns::service {
  service { 'maradns':
    ensure     => running,
    enable     => true,
    hasrestart => true,
  }
}

Output:

puppet apply puppettest.pp    
err: /Stage[main]/Maradns::Install/Package[maradns]/ensure: change from absent to present failed: Execution of '/usr/bin/yum -d 0 -e 0 -y install maradns' returned 1: Error: Nothing to do

notice: /Stage[main]/Maradns::Config/File[mararc]: Dependency Package[maradns] has failures: true
warning: /Stage[main]/Maradns::Config/File[mararc]: Skipping because of failed dependencies
notice: /Stage[main]/Maradns::Service/Service[maradns]: Dependency Package[maradns] has failures: true
warning: /Stage[main]/Maradns::Service/Service[maradns]: Skipping because of failed dependencies
notice: /Stage[main]/Repo::Custom/Yumrepo[custom]/descr: descr changed '' to 'Local respository - x86_64'
notice: /Stage[main]/Repo::Custom/Yumrepo[custom]/baseurl: baseurl changed '' to 'http://repo.test.com/CentOS/\$releasever/\$basearch'
notice: /Stage[main]/Repo::Custom/Yumrepo[custom]/enabled: enabled changed '' to '1'
notice: /Stage[main]/Repo::Custom/Yumrepo[custom]/gpgcheck: gpgcheck changed '' to '0'
notice: Finished catalog run in 2.15 seconds
like image 679
Michelle Avatar asked Jul 24 '12 17:07

Michelle


People also ask

What is the benefit of grouping resources into classes when using puppet?

In Puppet, classes are code blocks that can be called in a code elsewhere. Using classes allows you reuse Puppet code, and can make reading manifests easier.

Is it compulsory to precede class parameters with its data type in puppet?

Each parameter can be preceeded by an optional data type. If you include one, Puppet will check the parameter's value at runtime to make sure that it has the right data type, and raise an error if the value is illegal. If no data type is provided, the parameter will accept values of any data type.

In which puppet artifact can you declare classes?

You can declare classes in node definitions, at top scope in the site manifest, and in other classes or defined types.

What is init PP in puppet?

The init. pp manifest is the main class of a module and, unlike other classes or defined types, it is referred to only by the name of the module itself. For example, the class in init. pp in the puppetlabs-motd module is the mot d class.


2 Answers

A good starting point for debugging dependency issues is to instruct puppet to generate a dependency graph.

puppet apply --graph --noop manifest.pp
dot -Tpng /var/lib/puppet/state/graphs/relationships.dot -o relationships.png

By doing this you would see that the class repo:custom has no dependency information at all.

maradns::install sure has a dependency on the repo class but not on the repo::custom class, because repo::custom has no dependency on repo.

The new class declaration syntax class {'classname':} does not set any dependencies, it behaves just like the include classname syntax.

So either you set a dependency from repo::custom to repo or you instruct the maradns::install class to directly depend on the repo:custom class.

But you will run into more trouble. A dependency on class will only make sure that this class is applied. However, there will be no dependencies set on containing resources.

I would model your case like this:

class { 'repo:custom': }
class { 'maradns': }

class repo {
}

class repo::custom {
  yumrepo {'custom':
    enabled  => 1,
    gpgcheck => 0,
    descr    => "Local respository - ${::architecture}",
    baseurl  => 'http://repo.nike.local/CentOS/\$releasever/\$basearch';
  }
}

class maradns {
  class{[
    'maradns::package',
    'maradns::config',
    'maradns::service',
  ]:}
}

class maradns::package {
  package { 'maradns':
    ensure  => present,
    require => Yumrepo['custom'],
  }
}

class maradns::config {
  file { 'marac:config':
    ensure  => present,
    mode    => '0644',
    owner   => root,
    group   => root,
  }
}

class maradns::service {
  service { 'maradns':
    ensure     => running,
    enable     => true,
    hasrestart => true,
    require => [
      Package['maradns'],
      File['mararc:config'],
    ],
  }
}
like image 91
turingmachine Avatar answered Oct 23 '22 14:10

turingmachine


From the puppetlabs stdlib documentation

In Puppet 2.6, when a class declares another class, the resources in the interior class are not contained by the exterior class. This interacts badly with the pattern of composing complex modules from smaller classes, as it makes it impossible for end users to specify order relationships between the exterior class and other modules.

The anchor type lets you work around this. By sandwiching any interior classes between two no-op resources that are contained by the exterior class, you can ensure that all resources in the module are contained.

Based on the posted manifest, an example would be:

Manifest puppettest.pp:

class { 'repo': }
class { 'maradns': }

class repo {
  anchor { 'repo::begin': } ->
  class { 'repo::custom': } ->
  anchor { 'repo::end': }
}

class repo::custom {
  yumrepo {'custom':
    enabled  => 1,
    gpgcheck => 0,
    descr    => "Local respository - ${::architecture}",
    baseurl  => 'http://repo.nike.local/CentOS/\$releasever/\$basearch';
  }
}

class maradns {
  Class['repo'] -> Class['maradns::install']
  Class['maradns::install'] -> Class['maradns::config']
  Class['maradns::config'] ~> Class['maradns::service']
  class { 'maradns::install': }
  class { 'maradns::config':  }
  class { 'maradns::service': }
}

class maradns::install {
  package { 'maradns':
    ensure  => present,
  }
}

class maradns::config {
  file { 'mararc':
    ensure  => present,
    path    => '/etc/mararc',
    mode    => '0644',
    owner   => root,
    group   => root,
  }
}

class maradns::service {
  service { 'maradns':
    ensure     => running,
    enable     => true,
    hasrestart => true,
  }
}
like image 30
Michelle Avatar answered Oct 23 '22 15:10

Michelle