dependency injection in puppet

179 views
Skip to first unread message

David Portabella

unread,
Dec 4, 2013, 12:48:50 PM12/4/13
to puppet...@googlegroups.com
it explains that "modules that have configuration should be configurable in a single way and single place",
and I agree.

However, as configuration grows in complexity, this means that the entry point would have an enourmous list of parameters,
that need to be propagated down to its dependencies.
for instance, myapp takes a $jmxtrans_output, which it passes to tomcat, which it passes to jmxtrans::connection.
and if I have more dependencies like this one, myapp would not take 4 parameters, but far too many.

what is a proper way to inject $jmxtrans_output to jmxtrans::connection without requiring to declare it in myapp nor tomcat?

I could declare to do $jmxtrans_output a global variable, but that is ugly.
what if I have myapp1, and myapp2, which uses two differents $jmxtrans_output?


node 'mynode' {
  $tomcat_conf = {
    hostname     => 'host1.example.com',
    port         => 8080,
    jmx_port     => 9200,
    jmx_username => 'my_tomcat_jmx_username',
    jmx_password => 'my_tomcat_jmx_password',
  }

  $jmxtrans_output = {
    host          => 'graphite_dev.example.com',
    port          => 2003,
    username      => 'my_graphite_username',
    password      => 'my_graphite_password',
  }

  class { myapp: 
    tomcat_conf       => $tomcat_conf,
    jmxtrans_output   => $jmxtrans_output,
    market            => 'US',
    products          => ['p1', 'p2', 'p3']
  }
}

class myapp($tomcat_conf, $jmxtrans_output, $market, $products) {
  # it installs several packages, as tomcat, imagemagick... and configuration files...

  class { tomcat: 
    tomcat_conf      => $tomcat_conf,
    jmxtrans_output  => $jmxtrans_output,
  }

  # package { imagemagik: ensure => installed }

  # ...
}


class tomcat($tomcat_conf, $jmxtrans_output) {
  # package { tomcat: ensure => installed }
  # config file, using $tomcat_conf.{hostname, port, jmx_port, jmx_username, jmx_password}
  #...

  $tomcat_jmx = {
    host          => $tomcat_conf[hostname],
    port          => $tomcat_conf[jmx_port],
    username      => $tomcat_conf[jmx_username],
    password      => $tomcat_conf[jmx_password],
  }

  jmxtrans::connection { $name:
    input            => $tomcat_jmx,
    output           => $jmxtrans_output,
    template_source  => "myapp/tomcat_jmxtrans.json.erb",
#   require         => Class[jmxtrans]
  }
}


# todo: replace $template_source with $objects (the objects to be monitored, instead of passing a full template)
define jmxtrans::connection ($input, $output, $template_source) {
  notify {"jmxtrans::connection::input: $input": }
  notify {"jmxtrans::connection::output: $output": }

  file { "/tmp/jmxtrans_${hostname}_${name}.json":
    content => "template that uses \ninput_host: ${input[host]}\ninput_port: ${input[port]}\ninput_username: ${input[username]}\ninput_password: ${input[password]}\noutput_host: ${output[host]}\noutput_port: ${output[port]}\noutput_username: ${output[username]}\noutput_password: ${output[password]}\n",
#   content => template(template_source),
  }
}


jcbollinger

unread,
Dec 4, 2013, 4:26:11 PM12/4/13
to puppet...@googlegroups.com


On Wednesday, December 4, 2013 11:48:50 AM UTC-6, David Portabella wrote:
it explains that "modules that have configuration should be configurable in a single way and single place",
and I agree.

However, as configuration grows in complexity, this means that the entry point would have an enourmous list of parameters,
that need to be propagated down to its dependencies.
for instance, myapp takes a $jmxtrans_output, which it passes to tomcat, which it passes to jmxtrans::connection.
and if I have more dependencies like this one, myapp would not take 4 parameters, but far too many.

what is a proper way to inject $jmxtrans_output to jmxtrans::connection without requiring to declare it in myapp nor tomcat?

I could declare to do $jmxtrans_output a global variable, but that is ugly.
what if I have myapp1, and myapp2, which uses two differents $jmxtrans_output?



You are running into one of the lesser evils of parameterized classes: they can lead you down the path of trying to push all your configuration data in at the front end.  You should be writing classes that pull their configuration data from hiera or another external source, so that your classes do not normally need to accept data that is intended only to be passed on to other classes.  Instead, each class pulls its own data and/or relies on data belonging to other classes and pulled by those.

Indeed, I have long argued, for a variety of reasons, that although parameterized classes themselves are not inherently harmful, you should avoid declaring them via parameterized-style class declarations (leaving you relying on automated data binding for class parameter customization).


John

David Portabella

unread,
Dec 4, 2013, 4:35:05 PM12/4/13
to puppet...@googlegroups.com
calling hiera from inside jmxtrans::connection would contradict the argument:
  "modules that have configuration should be configurable in a single way and single place",

I agree with that argument, because it makes clear what parameters a class needs,
and it makes it easier to unit test,
and it allows to have two apps, myapp1, and myapp2, which uses two differents $jmxtrans_outputs.

and that's not with case calling hiera from inside jmxtrans::connection.

R.I.Pienaar

unread,
Dec 4, 2013, 4:42:39 PM12/4/13
to puppet...@googlegroups.com


----- Original Message -----
> From: "David Portabella" <david.po...@gmail.com>
> To: puppet...@googlegroups.com
> Sent: Wednesday, December 4, 2013 9:35:05 PM
> Subject: [Puppet Users] Re: dependency injection in puppet
>
> calling hiera from inside jmxtrans::connection would contradict the
> argument:
> "modules that have configuration should be configurable in a single way
> and single place",
> explained here:
> http://www.devco.net/archives/2012/12/13/simple-puppet-module-structure-redux.php

It also says:

"As before I will show a simple module for a common scenario. Rather than
considering this module a blueprint for every module out there you should
instead study its design and use it as a starting point when writing your
own modules. You can build on it and adapt it but the basic approach should
translate well to more complex modules."

so if you feel your module has too many arguments, adjust the pattern but try to
understand why it recommends what it does and try to stay within the overall goals


you do not need to pass in all the arguments, your inner modules can reference
like $jmxtrans::foo for example this removes a lot of the duplication if you
go this route.

David Portabella

unread,
Dec 4, 2013, 4:53:35 PM12/4/13
to puppet...@googlegroups.com
> so if you feel your module has too many arguments, adjust the pattern but try to 
> understand why it recommends what it does and try to stay within the overall goals 

sure, and again, what i like about it is that it allows to have two apps, myapp1, and myapp2, which uses two differents $jmxtrans_outputs.


you mean something like this?
again, this does not allow to have two apps, myapp1, and myapp2, with two differents $jmxtrans::outputs.

node 'mynode' {
  $tomcat_conf = {
    hostname     => 'host1.example.com',
    port         => 8080,
    jmx_port     => 9200,
    jmx_username => 'my_tomcat_jmx_username',
    jmx_password => 'my_tomcat_jmx_password',
  }

  class {jmxtrans::output:
    host          => 'graphite_dev.example.com',
    port          => 2003,
    username      => 'my_graphite_username',
    password      => 'my_graphite_password',
  }

  class { myapp: 
    tomcat_conf       => $tomcat_conf,
    market            => 'US',
    products          => ['p1', 'p2', 'p3']
  }
}

class myapp($tomcat_conf, $market, $products) {
  # it installs several packages, as tomcat, imagemagick... and configuration files...

  class { tomcat: 
    tomcat_conf      => $tomcat_conf,
  }

  # package { imagemagik: ensure => installed }

  # ...
}


class tomcat($tomcat_conf) {
  # package { tomcat: ensure => installed }
  # config file, using $tomcat_conf.{hostname, port, jmx_port, jmx_username, jmx_password}
  #...

  $tomcat_jmx = {
    host          => $tomcat_conf[hostname],
    port          => $tomcat_conf[jmx_port],
    username      => $tomcat_conf[jmx_username],
    password      => $tomcat_conf[jmx_password],
  }

  jmxtrans::connection { $name:
    input            => $tomcat_jmx,
    template_source  => "myapp/tomcat_jmxtrans.json.erb",
#   require         => Class[jmxtrans]
  }
}


# todo: replace $template_source with $objects (the objects to be monitored, instead of passing a full template)
define jmxtrans::connection ($input, $template_source) {
  notify {"jmxtrans::connection::input: $input": }
  notify {"jmxtrans::connection::output: $jmxtrans::output": }

  file { "/tmp/jmxtrans_${hostname}_${name}.json":
    content => "template that uses \ninput_host: ${input[host]}\ninput_port: ${input[port]}\ninput_username: ${input[username]}\ninput_password: ${input[password]}\noutput_host: ${jmxtrans::output[host]}\noutput_port: ${jmxtrans::output[port]}\noutput_username: ${jmxtrans::output[username]}\noutput_password: ${jmxtrans::output[password]}\n",
#   content => template(template_source),
  }
}

class jmxtrans::output ($host, $port, $username, $password) {
Reply all
Reply to author
Forward
0 new messages