Hi all, We at Reductive Labs keep running into clients who need something like an attribute class - that is, for a given module, they want a single class that handles all of the variable setting and overriding, and then they want that attribute class to be merged into all or some of the classes in the module. (Teyo has especially been pushing me to solve this problem.)
This looks a lot like composition, which Puppet doesn't currently support - you include one class's behaviour in another, rather than inheriting. This lack of composition is, I think, partially why people keep trying to do variable inheritance and getting frustrated that it doesn't work well. While doing a code audit for a client this week, though, I found their solution to this problem to be close enough that I was able to take it almost the rest of the of way. They had what amounts to a 'data' module with a class for each class that needed external data, and then they defined all of the attributes in that data class. That is, if you have an 'apache' class, you'd also have a 'data::apache' class with a bunch of attributes. Then all of your usage of those attributes would say '$data::apache::variable'. The big benefit of this model for them is that it allows clean change control of all of the important configuration data (as opposed to manifest structure and resources), and, again, it looks a lot like composition. In their case, they branch this module for all of their environments, but none of the other modules. As soon as I saw it, though, I thought of a way that might make it better, so I wrote a function that enables that way (and a converter for their existing data). Basically, I created (and have attached) a simple function that knows how to find and load a yaml file from a data directory, and it loads that file as a hash of parameters that should be set as local variables in the class. For instance, say you have this class apache; you'd create this file: data/apache.yaml And put all of the attributes you care about in that file. Then, in your apache class, you'd say: class apache { load_data() ... } It would pick the right file based on the class name (although the current function allows you to specify the class, also), load it, and set all of the contained attributes as local variables in the class. So, if your apache.yaml file looks like this: --- host: myhost.com port: 80 Then this 'load_data' call is equivalent to this Puppet code: class apache { $host = "myhost.com" $port = 80 } The function is currently set up to support one big 'data' directory for all of your modules. One could argue that it should instead support a data file per module, but the benefit of this one big data directory is that it makes it *much* easier to write sharable modules - you extract all of your site-specific data into this data dir, and you share the module with essentially no site data. It's probably most reasonable to support an in-module data file and a site-wide data directory to make it easy to provide defaults. I'm beginning to think that this, or a function a lot like this, should be included directly into Puppet, and data should get loaded automatically, rather than requiring the call to the 'load_data' function. What do you think? -- Fallacies do not cease to be fallacies because they become fashions. --G. K. Chesterton --------------------------------------------------------------------- Luke Kanies | http://reductivelabs.com | http://madstop.com --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Puppet Users" group. To post to this group, send email to puppet-users@googlegroups.com To unsubscribe from this group, send email to puppet-users+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/puppet-users?hl=en -~----------~----~----~----~------~----~------~--~---
# Copyright 2009 Reductive Labs # Licensed under the GPLv2 Puppet::Parser::Functions.newfunction :load_data, :type => :statement do |args| if args.length == 1 klass = args[0] else klass = resource.title end datadir = "/REPLACE/THIS/VALUE" dirs = datadir.split("/") + klass.split("::") name = dirs.pop + ".yaml" dir = dirs.join("/") path = File.join(dir, name) unless FileTest.exist?(path) raise ArgumentError, "Could not find data file for class %s" % klass end params = YAML.load_file(path) raise ArgumentError, "Data for %s is not a hash" % klass unless params.is_a?(Hash) params.each do |param, value| setvar(param, strinterp(value)) end end