Search code examples
dnspuppetpuppet-enterprisepuppetlabs-aws

Puppet calls same service in multiple module?


I have a puppet module A. In that module I have a service restart for a change in file.

class A::test1 { 
  include ::corednsclient
  service { 'sshd':
    ensure => running,
    enable => true,
  }
}

Now , I have a different puppet module B. In that module also I have to restart the same service for a change in another file.

Now , the problem is that I'm getting the following:

Duplicate declaration error

when I'm doing /opt/puppetlabs/bin/puppet apply --modulepath=/abc xyz/site.pp

If I runs each module independently as puppet apply -e 'include moduleA' and puppet apply -e 'include moduleB' , Both would works fine. But puppet apply globally seems to be failing.

Any help would be highly appreciated !

Error: Evaluation Error: Error while evaluating a Resource Statement,
 Duplicate declaration: Service[sshd] is already declared in file 
 /export/content/ucm/puppet/modules/coresshd/manifests/configure.pp:28; cannot
 redeclare at 
 /export/content/ucm/puppet/modules/corednsclient/manifests/daemon_reload.pp:10 at 
 /export/content/ucm/puppet/modules/corednsclient/manifests/daemon_reload.pp:10:3 on 
 node lor1-0002276.int.xxx.com .

Solution

  • Yes, this is normal. Puppet only allows resources to be declared once. In general, if you have code like:

    class aaa {
      notify { 'xxx': message => 'yyy' }
    }
    
    class bbb {
      notify { 'xxx': message => 'yyy' }
    }
    
    include aaa
    include bbb
    

    Puppet apply that and you will see an error like this:

    Error: Evaluation Error: Error while evaluating a Resource Statement,
     Duplicate declaration: Notify[xxx] is already declared at (file: ...test.pp, 
     line: 2); cannot redeclare (file: ...test.pp, line: 6) (file: ...test.pp, line: 6,
     column: 3) on node ...
    

    Workarounds

    Solution 1 Refactor so both classes inherit a third class

    Generally, the best way to resolve this is to refactor your code so that there is a third class that contains the duplicated resource, and other classes include that using the include function, like this:

    class ccc {
      notify { 'xxx': message => 'yyy' }
    }
    
    class aaa {
      include ccc
    }
    
    class bbb {
      include ccc
    }
    
    include aaa
    include bbb
    

    That works fine.

    Note that this only works because the include function can be called any number of times, unlike a resource declaration - also unlike the resource-like class declarations.

    You can read more about "include-like v resource-like class declarations" here.

    Solution 2 Use virtual resources

    You can also use virtual resources. Refactor like this:

    class ccc {
      @notify { 'xxx': message => 'yyy' }
    }
    
    class aaa {
      include ccc
      realize Notify['xxx']
    }
    
    class bbb {
      include ccc
      realize Notify['xxx']
    }
    
    include aaa
    include bbb
    

    An added advantage of this one is you can use resource collectors and select only specific resources from a set of virtual resources, like this:

    class ccc {
      @notify { 'ppp': message => 'xxx' }
      @notify { 'qqq': message => 'yyy' }
      @notify { 'rrr': message => 'zzz' }
    }
    
    class aaa {
      include ccc
      Notify <| message == 'xxx' |>
    }
    
    class bbb {
      include ccc
      Notify <| message == 'xxx' or message == 'yyy' |>
    }
    
    include aaa
    include bbb
    

    If you don't need this functionality here, as appears the case, you probably should use the first proposal.

    Solution 3 Use ensure resource

    Another option is the ensure_resources function in stdlib:

    class aaa {
      ensure_resources('notify', {'xxx' => {'message' => 'yyy'}})
    }
    
    class bbb {
      ensure_resources('notify', {'xxx' => {'message' => 'yyy'}})
    }
    
    include aaa
    include bbb
    

    Solution 4 Use defined

    Historically this is strongly advised against, although the docs do not mention any reason to not use it. It is possible to use the defined like this:

    class aaa {
      if ! defined(Notify['xxx']) {
        notify { 'xxx': message => 'yyy' }
      }
    }
    
    class bbb {
      if ! defined(Notify['xxx']) {
        notify { 'xxx': message => 'yyy' }
      }
    }
    
    include aaa
    include bbb
    

    This way, the resource is added to the catalog only if it is not already in there.