(Sorry, long post, and .. I'm replying via copy & paste from Google Groups)

On Wed, 15 Sep 2010 19:59:35 -0700 (PDT), Patrick wrote:
>Sounds like a job for Augeas or puppet-concat.  I think puppet_concat =
>would be a better fit.

>Augeas: http://docs.puppetlabs.com/references/stable/type.html#augeas

>puppet_concat: http://github.com/ripienaar/puppet-concat=


Thank you Patrick for the suggestions. 
I explored puppet_concat further, and found that it:
- Requires files to be split into several pieces to be useful i.e. the head, 
body, then tail.
- Leaves fragment files (turds?) on client filesystems, however 
well-categorized they may be.

So I became motivated to see if I could develop an improved solution that did 
the string processing server-side, then returned the finished results to the 
client.

I wrote a custom type (for Puppet 0.25.5) named "concat". In short, you can 
make line-based contributions scattered across many places, and the concat type 
will join them together with newlines and *somehow* make the resulting string 
available. My first use-case is configuring an SNMP daemon.

But it's incomplete:
- Resulting concatenated strings are not available from within the Puppet DSL.
- I'm not confident concatenation is being done before Puppet ultimately 
provides the resulting string values for use.

The code is at the bottom of this message.

Let's look at how to use it. Although unnecessary, I envisioned the resulting 
concatenated strings would be defined in arbitrary scopes, like this:

# In file modules/jvm/manifests/init.pp :
concat { "JVM SNMP":
    variable => "snmp::snmpd_conf_contributions",
    lines => "proxy -m ${mib_file} -v 2c -c ${snmp_community} localhost:1161 
.1.3.6.1.4.1.42.2.145" ,
    before => Class["snmp"],
}

# In file modules/postgres/manifests/init.pp :
concat { "PostgreSQL postmaster SNMP":
    variable => "snmp::snmpd_conf_contributions",
    lines => "proc postmaster",
    before => Class["snmp"],
}

In one ideal, the custom type would:
1. Gather all lines for each given distinct variable,
2. Perform concatenation, joining lines with UNIX newlines "\n" into a single 
string value, and
3. Set the appropriate variable in the appropriate scope to the final value

Continuing the above example, $snmp::snmpd_conf_contributions would have the 
value "proxy -m ${mib_file} -v 2c -c ${snmp_community} localhost:1161 
.1.3.6.1.4.1.42.2.145\nproc postmaster". Note that the lines could be reversed; 
this implementation doesn't consider the order of multiple concat resources, 
but if the lines param is an array, order within that array is preserved.

Now, the value could be used:

# In file modules/snmp/manifests/init.pp:
file { "/etc/snmpd.conf":
    content => "blah blah\n<%= snmpd_conf_contributions %>\nblah blah", # 
Easily extends to the use of template()
}

# Or, using some mechanism from within an ERB document:
<%= concatenated_value('snmpd_conf_contributions') %>

Well, even after studying type.rb, scope.rb, setvar.rb, and friends, I don't 
know how to finish the implementation.

Given my current level of understanding, this is about as far as I can take the 
code today.
Any hints, suggestions, ideas? Even without scoping? Can this be made to work 
at all?
Better yet, I hope some enterprising soul takes it from here ^_^

Ty

* I sense that modifying variable values, or appending to the value of 
variables out-of-scope, might be perceived as being counter to the spirit of 
Puppet. But this approach would cause me less pain and give greater control 
over the contents of configuration files in a way Augeas & other methods don't. 
Although, it might all be subjective, or I might be missing something .. It 
might even be a Bad Idea.

---- 8< ---- modules/common/lib/puppet/type/concat.rb ---- 8< ----

module Puppet

    # Note: The following global variables cause namespace pollution

    $concat_raw_map = {} # Maps identifiers to arrays of a mix of strings and 
arrays of strings
    $concat_processed_map = {} # Maps identifiers to each of their final string 
representations

    $concat_finalized = false
    $concat_instance_count = 0
    $concat_total_rule_count = 0

    newtype(:concat) do
        @doc = "Sets the string value of *TODO* to the concatenation of all
                contributors. With multiple concat resources, order of 
appearance in the
                final concatenated string is unspecified. Multiple content 
values are
                concatenated by being joined together with UNIX newline \\n 
characters.
                The implementation is partially patterned after iptables.rb."

        newparam(:name) do
            desc "The name of this concat resource. Does not influence 
concatenation behavior."
            isnamevar
        end

        newparam(:variable) do
            desc "Content is concatenated to the value of this variable 
identifier, typed as a string."
        end

        newparam(:lines) do
            desc "A string, or an array of strings, the value of which is 
concatenated to the value of the variable identifier. In the case of an array 
of strings, line order is preserved."
        end

        def initialize(args)
            super(args)

            $concat_total_rule_count += 1

            # Memorize these lines
            var = value(:variable).to_s
            if not $concat_raw_map.has_key?(var)
                # Create a new entry for this variable
                $concat_raw_map[var] = []
            end
            lines = value(:lines)
            $concat_raw_map[var].unshift(lines)

            debug("Concatenate to #{var}: '#{[lines].flatten * '\', \''}'") # 
Enable flatten to handle even a non-array type
        end

        def evaluate
            $concat_instance_count += 1
            if $concat_instance_count == $concat_total_rule_count
                self.finalize unless self.finalized?
            end
            return super
        end

        # finalize() runs once, after all concat resources have been declared.
        # For each identifier in the raw map, do the concatenation operation, 
and set the appropriate variable *TODO* to this value.
        def finalize
            # Any of the supplied :lines parameter values may have been arrays 
themselves, so flatten them
            $concat_processed_map = Hash[* $concat_raw_map.map{|k, v| [k, 
v.flatten * "\n"]}.flatten]

            $concat_processed_map.each { |k, v| debug("Assign #{k} = '#{v}'") }

            $concat_finalized = true
        end

        def finalized?
            if defined? $concat_finalized
                return $concat_finalized
            else
                return false
            end
        end

        # Reset class variables to their initial value
        def self.clear
            $concat_raw_map = {}
            $concat_processed_map = {}
            $concat_finalized = false
            $concat_instance_count = 0
            $concat_total_rule_count = 0

            super
        end

    end

end

-- 
You received this message because you are subscribed to the Google Groups 
"Puppet Users" group.
To post to this group, send email to puppet-us...@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.

Reply via email to