diff options
-rw-r--r-- | manifests/classes/augeas.pp | 22 | ||||
-rw-r--r-- | manifests/init.pp | 1 | ||||
-rw-r--r-- | plugins/puppet/provider/augeas/augeas.rb | 291 | ||||
-rw-r--r-- | plugins/puppet/type/augeas.rb | 163 |
4 files changed, 477 insertions, 0 deletions
diff --git a/manifests/classes/augeas.pp b/manifests/classes/augeas.pp new file mode 100644 index 0000000..a093905 --- /dev/null +++ b/manifests/classes/augeas.pp @@ -0,0 +1,22 @@ +class augeas::base { + + case $operatingsystem { + RedHat: { + package { + ["augeas", "augeas-libs", "ruby-augeas"]: + ensure => "present"; + } + } + + Debian, Ubuntu: { + package { + ["augeas-lenses", "libaugeas0", "augeas-tools", "libaugeas-ruby1.8"]: + ensure => "present"; + } + } + } + + file {"/usr/share/augeas/lenses/contrib": + ensure => directory, + } +} diff --git a/manifests/init.pp b/manifests/init.pp new file mode 100644 index 0000000..b33bf58 --- /dev/null +++ b/manifests/init.pp @@ -0,0 +1 @@ +import "classes/*.pp" diff --git a/plugins/puppet/provider/augeas/augeas.rb b/plugins/puppet/provider/augeas/augeas.rb new file mode 100644 index 0000000..56e217a --- /dev/null +++ b/plugins/puppet/provider/augeas/augeas.rb @@ -0,0 +1,291 @@ +#-- +# Copyright (C) 2008 Red Hat Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Author: Bryan Kearney <bkearney@redhat.com> + +require 'augeas' if Puppet.features.augeas? + +Puppet::Type.type(:augeas).provide(:augeas) do + include Puppet::Util + + confine :true => Puppet.features.augeas? + + has_features :parse_commands, :need_to_run?,:execute_changes + + SAVE_NOOP = "noop" + SAVE_OVERWRITE = "overwrite" + + attr_accessor :aug + + # Extracts an 2 dimensional array of commands which are in the + # form of command path value. + # The input can be + # - A string with one command + # - A string with many commands per line + # - An array of strings. + def parse_commands(data) + commands = Array.new() + if data.is_a?(String) + data.each_line do |line| + cmd_array = Array.new() + single = line.index("'") + double = line.index('"') + tokens = nil + delim = " " + if ((single != nil) or (double != nil)) + single = 99999 if single == nil + double = 99999 if double == nil + delim = '"' if double < single + delim = "'" if single < double + end + tokens = line.split(delim) + # If the length of tokens is 2, thn that means the pattern was + # command file "some text", therefore we need to re-split + # the first line + if tokens.length == 2 + tokens = (tokens[0].split(" ")) << tokens[1] + end + cmd = tokens.shift().strip() + delim = "" if delim == " " + file = tokens.shift().strip() + other = tokens.join(" ").strip() + cmd_array << cmd if !cmd.nil? + cmd_array << file if !file.nil? + cmd_array << other if other != "" + commands << cmd_array + end + elsif data.is_a?(Array) + data.each do |datum| + commands.concat(parse_commands(datum)) + end + end + return commands + end + + + def open_augeas + if (@aug.nil?) + flags = 0 + (flags = 1 << 2 ) if self.resource[:type_check] == :true + root = self.resource[:root] + load_path = self.resource[:load_path] + debug("Opening augeas with root #{root}, lens path #{load_path}, flags #{flags}") + @aug = Augeas.open(root, load_path,flags) + + if (self.get_augeas_version() >= "0.3.6") + debug("Augeas version #{self.get_augeas_version()} is installed") + end + end + @aug + end + + def close_augeas + if (!@aug.nil?) + @aug.close() + debug("Closed the augeas connection") + @aug = nil; + end + end + + # Used by the need_to_run? method to process get filters. Returns + # true if there is a match, false if otherwise + # Assumes a syntax of get /files/path [COMPARATOR] value + def process_get(cmd_array) + return_value = false + + #validate and tear apart the command + fail ("Invalid command: #{cmd_array.join(" ")}") if cmd_array.length < 4 + cmd = cmd_array.shift() + path = cmd_array.shift() + comparator = cmd_array.shift() + arg = cmd_array.join(" ") + + #check the value in augeas + result = @aug.get(path) || '' + unless result.nil? + case comparator + when "!=": + return_value = true if !(result == arg) + when "=~": + regex = Regexp.new(arg) + loc = result=~ regex + return_value = true if ! loc.nil? + else + return_value = true if (result.send(comparator, arg)) + end + end + return_value + end + + # Used by the need_to_run? method to process match filters. Returns + # true if there is a match, false if otherwise + def process_match(cmd_array) + return_value = false + + #validate and tear apart the command + fail("Invalid command: #{cmd_array.join(" ")}") if cmd_array.length < 4 + cmd = cmd_array.shift() + path = cmd_array.shift() + verb = cmd_array.shift() + + #Get the values from augeas + result = @aug.match(path) || '' + # Now do the work + unless (result.nil?) + case verb + when "size": + fail("Invalid command: #{cmd_array.join(" ")}") if cmd_array.length != 2 + comparator = cmd_array.shift() + arg = cmd_array.shift().to_i + return_value = true if (result.size.send(comparator, arg)) + when "include": + arg = cmd_array.join(" ") + return_value = true if result.include?(arg) + when "==": + begin + arg = cmd_array.join(" ") + new_array = eval arg + return_value = true if result == new_array + rescue + fail("Invalid array in command: #{cmd_array.join(" ")}") + end + end + end + return_value + end + + def get_augeas_version + return @aug.get("/augeas/version") || "" + end + + def set_augeas_save_mode(mode) + return @aug.set("/augeas/save", mode) + end + + def files_changed? + saved_files = @aug.match("/augeas/events/saved") + return saved_files.size() > 0 + end + + # Determines if augeas acutally needs to run. + def need_to_run? + force = resource[:force] + return_value = true + self.open_augeas() + filter = resource[:onlyif] + unless (filter == "") + cmd_array = filter.split + command = cmd_array[0]; + cmd_array[1]= File.join(resource[:context], cmd_array[1]) + begin + data = nil + case command + when "get" then return_value = process_get(cmd_array) + when "match" then return_value = process_match(cmd_array) + end + rescue Exception => e + fail("Error sending command '#{command}' with params #{cmd_array[1..-1].inspect}/#{e.message}") + end + end + + unless (force) + # If we have a verison of augeas which is at least 0.3.6 then we + # can make the changes now, see if changes were made, and + # actually do the save. + if ((return_value) and (self.get_augeas_version() >= "0.3.6")) + debug("Will attempt to save and only run if files changed") + self.set_augeas_save_mode(SAVE_NOOP) + self.do_execute_changes() + save_result = @aug.save() + saved_files = @aug.match("/augeas/events/saved") + if ((save_result) and (not files_changed?)) + debug("Skipping becuase no files were changed") + return_value = false + else + debug("Files changed, should execute") + end + end + end + self.close_augeas() + return return_value + end + + def execute_changes + # Re-connect to augeas, and re-execute the changes + self.open_augeas() + if (self.get_augeas_version() >= "0.3.6") + self.set_augeas_save_mode(SAVE_OVERWRITE) + end + + self.do_execute_changes() + + success = @aug.save() + if (success != true) + fail("Save failed with return code #{success}") + end + self.close_augeas() + + return :executed + end + + # Actually execute the augeas changes. + def do_execute_changes + commands = resource[:changes].clone() + context = resource[:context] + commands.each do |cmd_array| + cmd_array = cmd_array.clone() + fail("invalid command #{cmd_array.join[" "]}") if cmd_array.length < 2 + command = cmd_array[0] + cmd_array.shift() + begin + case command + when "set": + cmd_array[0]=File.join(context, cmd_array[0]) + debug("sending command '#{command}' with params #{cmd_array.inspect}") + @aug.set(cmd_array[0], cmd_array[1]) + when "rm", "remove": + cmd_array[0]=File.join(context, cmd_array[0]) + debug("sending command '#{command}' with params #{cmd_array.inspect}") + @aug.rm(cmd_array[0]) + when "clear": + cmd_array[0]=File.join(context, cmd_array[0]) + debug("sending command '#{command}' with params #{cmd_array.inspect}") + @aug.clear(cmd_array[0]) + when "insert", "ins" + ext_array = cmd_array[1].split(" ") ; + if cmd_array.size < 2 or ext_array.size < 2 + fail("ins requires 3 parameters") + end + label = cmd_array[0] + where = ext_array[0] + path = File.join(context, ext_array[1]) + case where + when "before": before = true + when "after": before = false + else fail("Invalid value '#{where}' for where param") + end + debug("sending command '#{command}' with params #{[label, where, path].inspect()}") + aug.insert(path, label, before) + else fail("Command '#{command}' is not supported") + end + rescue Exception => e + fail("Error sending command '#{command}' with params #{cmd_array.inspect}/#{e.message}") + end + end + end + +end diff --git a/plugins/puppet/type/augeas.rb b/plugins/puppet/type/augeas.rb new file mode 100644 index 0000000..da9cff3 --- /dev/null +++ b/plugins/puppet/type/augeas.rb @@ -0,0 +1,163 @@ +#-- +# Copyright (C) 2008 Red Hat Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Author: Bryan Kearney <bkearney@redhat.com> + +Puppet::Type.newtype(:augeas) do + include Puppet::Util + + feature :parse_commands, "Parse the command string" + feature :need_to_run?, "If the command should run" + feature :execute_changes, "Actually make the changes" + + @doc = "Apply the changes (single or array of changes) to the filesystem + via the augeas tool. + + Requires: + - augeas to be installed (http://www.augeas.net) + - ruby-augeas bindings + + Sample usage with a string:: + + augeas{\"test1\" : + context => \"/files/etc/sysconfig/firstboot\", + changes => \"set RUN_FIRSTBOOT YES\", + onlyif => \"match other_value size > 0\", + } + + Sample usage with an array and custom lenses:: + + augeas{\"jboss_conf\": + context => \"/files\", + changes => [ + \"set /etc/jbossas/jbossas.conf/JBOSS_IP $ipaddress\", + \"set /etc/jbossas/jbossas.conf/JAVA_HOME /usr\" + ], + load_path => \"$/usr/share/jbossas/lenses\", + } + + " + + newparam (:name) do + desc "The name of this task. Used for uniqueness" + isnamevar + end + + newparam (:context) do + desc "Optional context path. This value is pre-pended to the paths of all changes" + defaultto "" + end + + newparam (:onlyif) do + desc "Optional augeas command and comparisons to control the execution of this type. + Supported onlyif syntax:: + + get [AUGEAS_PATH] [COMPARATOR] [STRING] + match [MATCH_PATH] size [COMPARATOR] [INT] + match [MATCH_PATH] include [STRING] + match [MATCH_PATH] == [AN_ARRAY] + + where:: + + AUGEAS_PATH is a valid path scoped by the context + MATCH_PATH is a valid match synatx scoped by the context + COMPARATOR is in the set [> >= != == <= <] + STRING is a string + INT is a number + AN_ARRAY is in the form ['a string', 'another']" + defaultto "" + end + + + newparam(:changes) do + desc "The changes which should be applied to the filesystem. This + can be either a string which contains a command or an array of commands. + Commands supported are:: + + set [PATH] [VALUE] Sets the value VALUE at loction PATH + rm [PATH] Removes the node at location PATH + remove [PATH] Synonym for rm + clear [PATH] Keeps the node at PATH, but removes the value. + ins [LABEL] [WHERE] [PATH] + Inserts an empty node LABEL either [WHERE={before|after}] PATH. + insert [LABEL] [WHERE] [PATH] + Synonym for ins + If the parameter 'context' is set that value is prepended to PATH" + + munge do |value| + provider.parse_commands(value) + end + end + + + newparam(:root) do + desc "A file system path; all files loaded by Augeas are loaded underneath ROOT" + defaultto "/" + end + + newparam(:load_path) do + desc "Optional colon separated list of directories; these directories are searched for schema definitions" + defaultto "" + end + + newparam(:force) do + desc "Optional command to force the augeas type to execute even if it thinks changes + will not be made. This does not overide the only setting. If onlyif is set, then the + foce setting will not override that result" + + defaultto false + end + + newparam(:type_check) do + desc "Set to true if augeas should perform typechecking. Optional, defaults to false" + newvalues(:true, :false) + + defaultto :false + end + + # This is the acutal meat of the code. It forces + # augeas to be run and fails or not based on the augeas return + # code. + newproperty(:returns) do |property| + include Puppet::Util + desc "The expected return code from the augeas command. Should not be set" + + defaultto 0 + + # Make output a bit prettier + def change_to_s(currentvalue, newvalue) + return "executed successfully" + end + + # if the onlyif resource is provided, then the value is parsed. + # a return value of 0 will stop exection becuase it matches the + # default value. + def retrieve + if @resource.provider.need_to_run?() + :need_to_run + else + 0 + end + end + + # Actually execute the command. + def sync + @resource.provider.execute_changes() + end + end + +end |