summaryrefslogtreecommitdiffstats
path: root/ext
diff options
context:
space:
mode:
authorJames Turnbull <james@lovedthanlost.net>2009-08-03 23:12:13 +1000
committerJames Turnbull <james@lovedthanlost.net>2009-08-03 23:12:13 +1000
commitea34ee6ce64436b41ddb5a9bf71ef311fdf8b92b (patch)
treee6478766e79cded4f19e4e7730640e23d00d17ae /ext
parent36d3f588b7807a9dab26c27c3118c3ced62d75c2 (diff)
downloadpuppet-ea34ee6ce64436b41ddb5a9bf71ef311fdf8b92b.tar.gz
puppet-ea34ee6ce64436b41ddb5a9bf71ef311fdf8b92b.tar.xz
puppet-ea34ee6ce64436b41ddb5a9bf71ef311fdf8b92b.zip
Added R.I.Pienaar's extlookup.rb to the ext directory
Diffstat (limited to 'ext')
-rw-r--r--ext/extlookup.rb183
1 files changed, 183 insertions, 0 deletions
diff --git a/ext/extlookup.rb b/ext/extlookup.rb
new file mode 100644
index 000000000..b0978ef12
--- /dev/null
+++ b/ext/extlookup.rb
@@ -0,0 +1,183 @@
+# Puppet External Data Sources
+#
+# This is a parser function to read data from external files, this version
+# uses CSV files but the concept can easily be adjust for databases, yaml
+# or any other queryable data source.
+#
+# The object of this is to make it obvious when it's being used, rather than
+# magically loading data in when an module is loaded I prefer to look at the code
+# and see statements like:
+#
+# $snmp_contact = extlookup("snmp_contact")
+#
+# The above snippet will load the snmp_contact value from CSV files, this in its
+# own is useful but a common construct in puppet manifests is something like this:
+#
+# case $domain {
+# "myclient.com": { $snmp_contact = "John Doe <john@myclient.com>" }
+# default: { $snmp_contact = "My Support <support@my.com>" }
+# }
+#
+# Over time there will be a lot of this kind of thing spread all over your manifests
+# and adding an additional client involves grepping through manifests to find all the
+# places where you have constructs like this.
+#
+# This is a data problem and shouldn't be handled in code, a using this function you
+# can do just that.
+#
+# First you configure it in site.pp:
+# $extlookup_datadir = "/etc/puppet/manifests/extdata"
+# $extlookup_precedence = ["%{fqdn}", "domain_%{domain}", "common"]
+#
+# The array tells the code how to resolve values, first it will try to find it in
+# web1.myclient.com.csv then in domain_myclient.com.csv and finally in common.csv
+#
+# Now create the following data files in /etc/puppet/manifests/extdata
+#
+# domain_myclient.com.csv:
+# snmp_contact,John Doe <john@myclient.com>
+# root_contact,support@%{domain}
+# client_trusted_ips,192.168.1.130,192.168.10.0/24
+#
+# common.csv:
+# snmp_contact,My Support <support@my.com>
+# root_contact,support@my.com
+#
+# Now you can replace the case statement with the simple single line to achieve
+# the exact same outcome:
+#
+# $snmp_contact = extlookup("snmp_contact")
+#
+# The obove code shows some other features, you can use any fact or variable that
+# is in scope by simply using %{varname} in your data files, you can return arrays
+# by just having multiple values in the csv after the initial variable name.
+#
+# In the event that a variable is nowhere to be found a critical error will be raised
+# that will prevent your manifest from compiling, this is to avoid accidentally putting
+# in empty values etc. You can however specify a default value:
+#
+# $ntp_servers = extlookup("ntp_servers", "1.${country}.pool.ntp.org")
+#
+# In this case it will default to "1.${country}.pool.ntp.org" if nothing is defined in
+# any data file.
+#
+# You can also specify an additional data file to search first before any others at use
+# time, for example:
+#
+# $version = extlookup("rsyslog_version", "present", "packages")
+#
+# package{"rsyslog": ensure => $version }
+#
+# This will look for a version configured in packages.csv and then in the rest as configured
+# by $extlookup_precedence if it's not found anywhere it will default to "present", this kind
+# of use case makes puppet a lot nicer for managing large amounts of packages since you do not
+# need to edit a load of manifests to do simple things like adjust a desired version number.
+#
+# For more information on installing and writing your own custom functions see:
+# http://reductivelabs.com/trac/puppet/wiki/WritingYourOwnFunctions
+#
+# For further help contact Volcane on #puppet
+require 'csv'
+
+module Puppet::Parser::Functions
+ newfunction(:extlookup, :type => :rvalue) do |args|
+ key = args[0]
+ default = "_ExtUNSET_"
+ datafile = "_ExtUNSET_"
+
+ default = args[1] if args[1]
+ datafile = args[2] if args[2]
+
+ extlookup_datadir = lookupvar('extlookup_datadir')
+ extlookup_precedence = Array.new
+
+ # precedence values can have variables embedded in them
+ # in the form %{fqdn}, you could for example do
+ #
+ # $extlookup_precedence = ["hosts/%{fqdn}", "common"]
+ #
+ # this will result in /path/to/extdata/hosts/your.box.com.csv
+ # being searched.
+ #
+ # we parse the precedence here because the best place to specify
+ # it would be in site.pp but site.pp is only evaluated at startup
+ # so $fqdn etc would have no meaning there, this way it gets evaluated
+ # each run and has access to the right variables for that run
+ lookupvar('extlookup_precedence').each do |prec|
+ while prec =~ /%\{(.+?)\}/
+ prec.gsub!(/%\{#{$1}\}/, lookupvar($1))
+ end
+
+ extlookup_precedence << prec
+ end
+
+
+ datafiles = Array.new
+
+ # if we got a custom data file, put it first in the array of search files
+ if datafile != ""
+ if File.exists?(extlookup_datadir + "/#{datafile}.csv")
+ datafiles << extlookup_datadir + "/#{datafile}.csv"
+ end
+ end
+
+ extlookup_precedence.each do |d|
+ datafiles << extlookup_datadir + "/#{d}.csv"
+ end
+
+ desired = "_ExtUNSET_"
+
+ datafiles.each do |file|
+ parser.watch_file(file) if File.exists?(file)
+
+ if desired == "_ExtUNSET_"
+ if File.exists?(file)
+ result = CSV.read(file).find_all do |r|
+ r[0] == key
+ end
+
+
+ # return just the single result if theres just one,
+ # else take all the fields in the csv and build an array
+ if result.length > 0
+ if result[0].length == 2
+ val = result[0][1].to_s
+
+ # parse %{}'s in the CSV into local variables using lookupvar()
+ while val =~ /%\{(.+?)\}/
+ val.gsub!(/%\{#{$1}\}/, lookupvar($1))
+ end
+
+ desired = val
+ elsif result[0].length > 1
+ length = result[0].length
+ cells = result[0][1,length]
+
+ # Individual cells in a CSV result are a weird data type and throws
+ # puppets yaml parsing, so just map it all to plain old strings
+ desired = cells.map do |c|
+ # parse %{}'s in the CSV into local variables using lookupvar()
+ while c =~ /%\{(.+?)\}/
+ c.gsub!(/%\{#{$1}\}/, lookupvar($1))
+ end
+
+ c.to_s
+ end
+ end
+ end
+ end
+ end
+ end
+
+ # don't accidently return nil's and such rather throw a parse error
+ if desired == "_ExtUNSET_" && default == "_ExtUNSET_"
+ raise Puppet::ParseError, "No match found for '#{key}' in any data file during extlookup()"
+ else
+ desired = default if desired == "_ExtUNSET_"
+ end
+
+ desired
+ end
+end
+
+# vi:tabstop=4:expandtab:ai