diff --git a/lib/puppet/provider/augeas/augeas.rb b/lib/puppet/provider/augeas/augeas.rb index f70654438..5fa5ab409 100644 --- a/lib/puppet/provider/augeas/augeas.rb +++ b/lib/puppet/provider/augeas/augeas.rb @@ -1,208 +1,259 @@ #-- # 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 require 'augeas' if Puppet.features.augeas? Puppet::Type.type(:augeas).provide(:augeas) do -#class Puppet::Provider::Augeas < Puppet::Provider 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() tokens = line.split(" ") cmd = tokens.shift() file = tokens.shift() other = tokens.join(" ") 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 - 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}") - Augeas.open(root, load_path,flags) + 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 # 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 - aug = open_augeas() - result = aug.get(path) || '' + 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 - aug = open_augeas() - result = aug.match(path) || '' + result = @aug.match(path) || '' # Now do the work if (!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 + 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? + self.open_augeas() return_value = true 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 - return_value + + # 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 + + return return_value end + + def execute_changes + # if we have version 0.3.6 or greater we have already executed + # the changes. We just need to save them. If not, do the changes + if (self.get_augeas_version() >= "0.3.6") + self.set_augeas_save_mode(SAVE_OVERWRITE) + else + self.do_execute_changes() + end + + success = @aug.save() + if (success != true) + fail("Save failed with return code #{success}") + end + + return :executed + end # Actually execute the augeas changes. - def execute_changes - aug = open_augeas + def do_execute_changes commands = resource[:changes] context = resource[:context] commands.each do |cmd_array| 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]) + @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]) + @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]) + @aug.clear(cmd_array[0]) when "insert", "ins" if cmd_array.size < 3 fail("ins requires 3 parameters") end label = cmd_array[0] where = cmd_array[1] path = File.join(context, cmd_array[2]) 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) + @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 - success = aug.save() - if (success != true) - fail("Save failed with return code #{success}") - end - - return :executed end end diff --git a/spec/unit/provider/augeas/augeas.rb b/spec/unit/provider/augeas/augeas.rb index 2def0d0c4..3ce64576e 100644 --- a/spec/unit/provider/augeas/augeas.rb +++ b/spec/unit/provider/augeas/augeas.rb @@ -1,248 +1,255 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../../spec_helper' provider_class = Puppet::Type.type(:augeas).provider(:augeas) describe provider_class do describe "command parsing" do it "should break apart a single line into three tokens" do provider = provider_class.new() tokens = provider.parse_commands("set /Jar/Jar Binks") tokens.size.should == 1 tokens[0].size.should == 3 tokens[0][0].should == "set" tokens[0][1].should == "/Jar/Jar" tokens[0][2].should == "Binks" end it "should break apart a multiple line into six tokens" do provider = provider_class.new() tokens = provider.parse_commands("set /Jar/Jar Binks\nrm anakin skywalker") tokens.size.should == 2 tokens[0].size.should == 3 tokens[1].size.should == 3 tokens[0][0].should == "set" tokens[0][1].should == "/Jar/Jar" tokens[0][2].should == "Binks" tokens[1][0].should == "rm" tokens[1][1].should == "anakin" tokens[1][2].should == "skywalker" end it "should handle arrays" do provider = provider_class.new() commands = ["set /Jar/Jar Binks", "rm anakin skywalker"] tokens = provider.parse_commands(commands) tokens.size.should == 2 tokens[0].size.should == 3 tokens[1].size.should == 3 tokens[0][0].should == "set" tokens[0][1].should == "/Jar/Jar" tokens[0][2].should == "Binks" tokens[1][0].should == "rm" tokens[1][1].should == "anakin" tokens[1][2].should == "skywalker" end it "should concat the last values" do provider = provider_class.new() tokens = provider.parse_commands("set /Jar/Jar Binks is my copilot") tokens.size.should == 1 tokens[0].size.should == 3 tokens[0][0].should == "set" tokens[0][1].should == "/Jar/Jar" tokens[0][2].should == "Binks is my copilot" end end describe "get filters" do before do augeas_stub = stub("augeas", :get => "value") @provider = provider_class.new() - @provider.stubs(:open_augeas).returns(augeas_stub) + @provider.aug= augeas_stub end it "should return false for a = nonmatch" do command = ["get", "fake value", "==", "value"] @provider.process_get(command).should == true end it "should return true for a != match" do command = ["get", "fake value", "!=", "value"] @provider.process_get(command).should == false end it "should return true for a =~ match" do command = ["get", "fake value", "=~", "val*"] @provider.process_get(command).should == true end it "should return false for a == nonmatch" do command = ["get", "fake value", "=~", "num*"] @provider.process_get(command).should == false end end describe "match filters" do before do augeas_stub = stub("augeas", :match => ["set", "of", "values"]) @provider = provider_class.new() - @provider.stubs(:open_augeas).returns(augeas_stub) + @provider.aug= augeas_stub end it "should return true for size match" do command = ["match", "fake value", "size", "==", "3"] @provider.process_match(command).should == true end it "should return false for a size non match" do command = ["match", "fake value", "size", "<", "3"] @provider.process_match(command).should == false end it "should return true for includes match" do command = ["get", "fake value", "include", "values"] @provider.process_match(command).should == true end it "should return false for includes non match" do command = ["get", "fake value", "include", "JarJar"] @provider.process_match(command).should == false end it "should return true for an array match" do command = ["get", "fake value", "==", "['set', 'of', 'values']"] @provider.process_match(command).should == true end it "should return false for an array non match" do command = ["get", "fake value", "==", "['this', 'should', 'not', 'match']"] @provider.process_match(command).should == false end end - describe "need_to_run" do + describe "legacy need to run" do it "should handle no filters" do resource = stub("resource", :[] => "") + augeas_stub = stub("augeas", :match => ["set", "of", "values"]) provider = provider_class.new(resource) + provider.stubs(:get_augeas_version).returns("0.3.5") provider.need_to_run?.should == true end it "should return true when a get filter matches" do resource = stub("resource", :[] => "get path == value") provider = provider_class.new(resource) augeas_stub = stub("augeas", :get => "value") - provider.stubs(:open_augeas).returns(augeas_stub) + provider.aug= augeas_stub + provider.stubs(:get_augeas_version).returns("0.3.5") provider.need_to_run?.should == true end it "should return false when a get filter does not match" do resource = stub("resource", :[] => "get path == another value") provider = provider_class.new(resource) augeas_stub = stub("augeas", :get => "value") - provider.stubs(:open_augeas).returns(augeas_stub) + provider.aug= augeas_stub + provider.stubs(:get_augeas_version).returns("0.3.5") provider.need_to_run?.should == false end it "should return true when a match filter matches" do resource = stub("resource", :[] => "match path size == 3") provider = provider_class.new(resource) augeas_stub = stub("augeas", :match => ["set", "of", "values"]) - provider.stubs(:open_augeas).returns(augeas_stub) + provider.aug= augeas_stub + provider.stubs(:get_augeas_version).returns("0.3.5") provider.need_to_run?.should == true end it "should return false when a match filter does not match" do resource = stub("resource", :[] => "match path size == 2") provider = provider_class.new(resource) augeas_stub = stub("augeas", :match => ["set", "of", "values"]) - provider.stubs(:open_augeas).returns(augeas_stub) + provider.aug= augeas_stub + provider.stubs(:get_augeas_version).returns("0.3.5") provider.need_to_run?.should == false end end - describe "augeas integration" do + describe "legacy augeas integration" do before do @resource = stub("resource") @provider = provider_class.new(@resource) @augeas = stub("augeas") - @provider.stubs(:open_augeas).returns(@augeas) + @provider.aug= @augeas + @provider.stubs(:get_augeas_version).returns("0.3.5") end it "should handle set commands" do command = [["set", "/Jar/Jar", "Binks"]] context = "/some/path" @resource.expects(:[]).times(2).returns(command).then.returns(context) @augeas.expects(:set).with("/some/path/Jar/Jar", "Binks") @augeas.expects(:save).returns(true) @provider.execute_changes.should == :executed end it "should handle rm commands" do command = [["rm", "/Jar/Jar"]] context = "" @resource.expects(:[]).times(2).returns(command).then.returns(context) @augeas.expects(:rm).with("/Jar/Jar") @augeas.expects(:save).returns(true) @provider.execute_changes.should == :executed end it "should handle remove commands" do command = [["remove", "Jar/Jar"]] context = "" @resource.expects(:[]).times(2).returns(command).then.returns(context) @augeas.expects(:rm).with("/Jar/Jar") @augeas.expects(:save).returns(true) @provider.execute_changes.should == :executed end it "should handle clear commands" do command = [["clear", "/Jar/Jar"]] context = "/foo" @resource.expects(:[]).times(2).returns(command).then.returns(context) @augeas.expects(:clear).with("/foo/Jar/Jar") @augeas.expects(:save).returns(true) @provider.execute_changes.should == :executed end it "should handle ins commands with before" do command = [["ins", "Binks", "before /Jar/Jar"]] context = "/foo" @resource.expects(:[]).times(2).returns(command).then.returns(context) @augeas.expects(:insert).with("/foo/Jar/Jar", "Binks", true) @augeas.expects(:save).returns(true) @provider.execute_changes.should == :executed end it "should handle ins commands with before" do command = [["ins", "Binks", "after /Jar/Jar"]] context = "/foo" @resource.expects(:[]).times(2).returns(command).then.returns(context) @augeas.expects(:insert).with("/foo/Jar/Jar", "Binks", false) @augeas.expects(:save).returns(true) @provider.execute_changes.should == :executed end it "should handle ins with no context" do command = [["ins", "Binks", "after /Jar/Jar"]] context = "" # this is the default @resource.expects(:[]).times(2).returns(command).then.returns(context) @augeas.expects(:insert).with("/Jar/Jar", "Binks", false) @augeas.expects(:save).returns(true) @provider.execute_changes.should == :executed end it "should handle multiple commands" do command = [["ins", "Binks", "after /Jar/Jar"], ["clear", "/Jar/Jar"]] context = "/foo" @resource.expects(:[]).times(2).returns(command).then.returns(context) @augeas.expects(:insert).with("/foo/Jar/Jar", "Binks", false) @augeas.expects(:clear).with("/foo/Jar/Jar") @augeas.expects(:save).returns(true) @provider.execute_changes.should == :executed end end end