diff --git a/ext/debian/mcollective-client.install b/ext/debian/mcollective-client.install index d7423250..3e57a2d2 100644 --- a/ext/debian/mcollective-client.install +++ b/ext/debian/mcollective-client.install @@ -2,3 +2,4 @@ usr/bin/mco usr/bin/ usr/sbin/mc-* usr/sbin/ etc/mcollective/client.cfg etc/mcollective usr/share/mcollective/plugins/mcollective/application usr/share/mcollective/plugins/mcollective +usr/share/mcollective/plugins/mcollective/pluginpackager usr/share/mcollective/plugins/mcollective diff --git a/ext/redhat/mcollective.spec b/ext/redhat/mcollective.spec index 3844cb22..1f0624f8 100644 --- a/ext/redhat/mcollective.spec +++ b/ext/redhat/mcollective.spec @@ -118,6 +118,7 @@ fi %config(noreplace)/etc/mcollective/client.cfg %config/etc/mcollective/rpc-help.erb /usr/libexec/mcollective/mcollective/application +/usr/libexec/mcollective/mcollective/pluginpackager %files %doc COPYING diff --git a/lib/mcollective.rb b/lib/mcollective.rb index 05ca8006..530e4cb9 100644 --- a/lib/mcollective.rb +++ b/lib/mcollective.rb @@ -58,6 +58,7 @@ class MsgDoesNotMatchRequestID < RuntimeError; end autoload :Applications, "mcollective/applications" autoload :Vendor, "mcollective/vendor" autoload :Shell, "mcollective/shell" + autoload :PluginPackager, "mcollective/pluginpackager" MCollective::Vendor.load_vendored diff --git a/lib/mcollective/monkey_patches.rb b/lib/mcollective/monkey_patches.rb index 1a26488e..acf26048 100644 --- a/lib/mcollective/monkey_patches.rb +++ b/lib/mcollective/monkey_patches.rb @@ -51,3 +51,54 @@ def in_groups_of(chunk_size, padded_with=nil, &block) end unless method_defined?(:in_groups_of) end +class Dir + def self.mktmpdir(prefix_suffix=nil, tmpdir=nil) + case prefix_suffix + when nil + prefix = "d" + suffix = "" + when String + prefix = prefix_suffix + suffix = "" + when Array + prefix = prefix_suffix[0] + suffix = prefix_suffix[1] + else + raise ArgumentError, "unexpected prefix_suffix: #{prefix_suffix.inspect}" + end + tmpdir ||= Dir.tmpdir + t = Time.now.strftime("%Y%m%d") + n = nil + begin + path = "#{tmpdir}/#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}" + path << "-#{n}" if n + path << suffix + Dir.mkdir(path, 0700) + rescue Errno::EEXIST + n ||= 0 + n += 1 + retry + end + + if block_given? + begin + yield path + ensure + FileUtils.remove_entry_secure path + end + else + path + end + end unless method_defined?(:mktmpdir) + + def self.tmpdir + tmp = '.' + for dir in [ENV['TMPDIR'], ENV['TMP'], ENV['TEMP'], '/tmp'] + if dir and stat = File.stat(dir) and stat.directory? and stat.writable? + tmp = dir + break + end rescue nil + end + File.expand_path(tmp) + end unless method_defined?(:tmpdir) +end diff --git a/lib/mcollective/pluginmanager.rb b/lib/mcollective/pluginmanager.rb index f1071026..8b873516 100644 --- a/lib/mcollective/pluginmanager.rb +++ b/lib/mcollective/pluginmanager.rb @@ -99,6 +99,65 @@ def self.create_instance(klass) end end + # Finds plugins in all configured libdirs + # + # find("agent") + # + # will return an array of just agent names, for example: + # + # ["puppetd", "package"] + # + # Can also be used to find files of other extensions: + # + # find("agent", "ddl") + # + # Will return the same list but only of files with extension .ddl + # in the agent subdirectory + def self.find(type, extension="rb") + extension = ".#{extension}" unless extension.match(/^\./) + + plugins = [] + + Config.instance.libdir.each do |libdir| + plugdir = File.join([libdir, "mcollective", type]) + + Dir.new(plugdir).grep(/#{extension}$/).map do |plugin| + plugins << File.basename(plugin, extension) + end + end + + plugins.sort.uniq + end + + # Finds and loads from disk all plugins from all libdirs that match + # certain criteria. + # + # find_and_load("pluginpackager") + # + # Will find all .rb files in the libdir/mcollective/pluginpackager/ + # directory in all libdirs and load them from disk. + # + # You can influence what plugins get loaded using a block notation: + # + # find_and_load("pluginpackager") do |plugin| + # plugin.match(/puppet/) + # end + # + # This will load only plugins matching /puppet/ + def self.find_and_load(type, extension="rb") + extension = ".#{extension}" unless extension.match(/^\./) + + klasses = find(type, extension).map do |plugin| + if block_given? + next unless yield(plugin) + end + + "%s::%s::%s" % [ "MCollective", type.capitalize, plugin.capitalize ] + end.compact + + klasses.sort.uniq.each {|klass| loadclass(klass, true)} + end + # Loads a class from file by doing some simple search/replace # on class names and then doing a require. def self.loadclass(klass, squash_failures=false) diff --git a/lib/mcollective/pluginpackager.rb b/lib/mcollective/pluginpackager.rb new file mode 100644 index 00000000..4693c5e4 --- /dev/null +++ b/lib/mcollective/pluginpackager.rb @@ -0,0 +1,15 @@ +module MCollective + module PluginPackager + # Plugin definition classes + autoload :AgentDefinition, "mcollective/pluginpackager/agent_definition" + + # Package implementation plugins + def self.load_packagers + PluginManager.find_and_load("pluginpackager") + end + + def self.[](klass) + const_get("#{klass}") + end + end +end diff --git a/lib/mcollective/pluginpackager/agent_definition.rb b/lib/mcollective/pluginpackager/agent_definition.rb new file mode 100644 index 00000000..06d574e9 --- /dev/null +++ b/lib/mcollective/pluginpackager/agent_definition.rb @@ -0,0 +1,91 @@ +module MCollective + module PluginPackager + # MCollective Agent Plugin package + class AgentDefinition + attr_accessor :path, :packagedata, :metadata, :target_path, :vendor, :iteration, :postinstall + + def initialize(path, name, vendor, postinstall, iteration) + @path = path + @packagedata = {} + @iteration = iteration || 1 + @postinstall = postinstall + @vendor = vendor || "Puppet Labs" + @target_path = File.expand_path(@path) + @metadata = get_metadata + @metadata[:name] = name if name + @metadata[:name] = @metadata[:name].downcase.gsub(" ", "_") + identify_packages + end + + # Identify present packages and populate packagedata hash. + def identify_packages + @packagedata[:common] = common + @packagedata[:agent] = agent + @packagedata[:client] = client + end + + # Obtain Agent package files and dependencies. + def agent + agent = {:files => [], + :dependencies => ["mcollective"], + :description => "Agent plugin for #{@metadata[:name]}"} + + agentdir = File.join(@path, "agent") + + if check_dir agentdir + ddls = Dir.glob "#{agentdir}/*.ddl" + agent[:files] = (Dir.glob("#{agentdir}/*") - ddls) + implementations = Dir.glob("#{@metadata[:name]}/**") + agent[:files] += implementations unless implementations.empty? + else + return nil + end + + agent[:dependencies] << "mcollective-#{@metadata[:name]}-common" if @packagedata[:common] + agent + end + + # Obtain client package files and dependencies. + def client + client = {:files => [], + :dependencies => ["mcollective-client"], + :description => "Client plugin for #{@metadata[:name]}"} + + clientdir = File.join(@path, "application") + bindir = File.join(@path, "bin") + ddldir = File.join(@path, "agent") + + client[:files] += Dir.glob("#{clientdir}/*") if check_dir clientdir + client[:files] += Dir.glob("#{bindir}/*") if check_dir bindir + client[:files] += Dir.glob("#{ddldir}/*.ddl") if check_dir ddldir + client[:dependencies] << "mcollective-#{@metadata[:name]}-common" if @packagedata[:common] + client[:files].empty? ? nil : client + end + + # Obtain common package files and dependencies. + def common + common = {:files =>[], + :dependencies => ["mcollective-common"], + :description => "Common libraries for #{@metadata[:name]}"} + + commondir = File.join(@path, "util") + common[:files] += Dir.glob("#{commondir}/*") if check_dir commondir + common[:files].empty? ? nil : common + end + + # Load plugin meta data from ddl file. + def get_metadata + ddl = MCollective::RPC::DDL.new("package", false) + ddl.instance_eval File.read(Dir.glob("#{@path}/agent/*.ddl").first) + ddl.meta + rescue + raise "error: could not read agent DDL File" + end + + # Check if directory is present and not empty. + def check_dir(path) + (File.directory?(path) && !Dir.glob(path).empty?) ? true : false + end + end + end +end diff --git a/plugins/mcollective/application/plugin.rb b/plugins/mcollective/application/plugin.rb new file mode 100644 index 00000000..fb580573 --- /dev/null +++ b/plugins/mcollective/application/plugin.rb @@ -0,0 +1,130 @@ +module MCollective + class Application::Plugin + mco plugin info + mco plugin doc + + info : Display plugin information including package details. + package : Create all available plugin packages. + doc : Application list and RPC agent help + END_OF_USAGE + + option :pluginname, + :description => "Plugin name", + :arguments => ["-n", "--name NAME"], + :type => String + + option :postinstall, + :description => "Post install script", + :arguments => ["--postinstall POSTINSTALL"], + :type => String + + option :iteration, + :description => "Iteration number", + :arguments => ["--iteration ITERATION"], + :type => String + + option :vendor, + :description => "Vendor name", + :arguments => ["--vendor VENDOR"], + :type => String + + option :format, + :description => "Package output format. Defaults to rpm or deb", + :arguments => ["--format OUTPUTFORMAT"], + :type => String + + option :plugintype, + :description => "Plugin type.", + :arguments => ["--plugintype PLUGINTYPE"], + :type => String + + option :rpctemplate, + :description => "RPC Template to use.", + :arguments => ["--template RPCHELPTEMPLATE"], + :type => String + + # Handle alternative format that optparser can't parse. + def post_option_parser(configuration) + if ARGV.length >= 1 + configuration[:action] = ARGV.delete_at(0) + + configuration[:target] = ARGV.delete_at(0) || "." + end + end + + # Display info about plugin + def info_command + plugin = prepare_plugin + packager = PluginPackager["#{configuration[:format].capitalize}Packager"] + packager.new(plugin).package_information + end + + # Package plugin + def package_command + plugin = prepare_plugin + packager = PluginPackager["#{configuration[:format].capitalize}Packager"] + packager.new(plugin).create_packages + end + + # Show application list and RPC agent help + def doc_command + if configuration.include?(:target) && configuration[:target] != "." + ddl = MCollective::RPC::DDL.new(configuration[:target]) + puts ddl.help(configuration[:rpctemplate] || Config.instance.rpchelptemplate) + else + puts "The Marionette Collective version #{MCollective.version}" + puts + + PluginManager.find("agent", "ddl").each do |ddl| + help = MCollective::RPC::DDL.new(ddl) + puts " %-15s %s" % [ddl, help.meta[:description]] + end + end + end + + # Creates the correct package plugin object. + def prepare_plugin + set_plugin_type unless configuration[:plugintype] + configuration[:format] = "ospackage" unless configuration[:format] + PluginPackager.load_packagers + plugin_class = PluginPackager[configuration[:plugintype]] + plugin_class.new(configuration[:target], configuration[:pluginname], configuration[:vendor], configuration[:postinstall], configuration[:iteration]) + end + + def directory_for_type(type) + File.directory?(File.join(configuration[:target], type)) + end + + # Identify plugin type if not provided. + def set_plugin_type + if directory_for_type("agent") || directory_for_type("application") + configuration[:plugintype] = "AgentDefinition" + else + raise "error. target directory is not a valid mcollective plugin" + end + end + + # Returns a list of available actions in a pretty format + def list_actions + methods.sort.grep(/_command/).map{|x| x.to_s.gsub("_command", "")}.join("|") + end + + def main + abort "No action specified" unless configuration.include?(:action) + + cmd = "#{configuration[:action]}_command" + + if respond_to? cmd + send cmd + else + abort "Invalid action #{configuration[:action]}. Valid actions are [#{list_actions}]." + end + end + end +end diff --git a/plugins/mcollective/pluginpackager/ospackage_packager.rb b/plugins/mcollective/pluginpackager/ospackage_packager.rb new file mode 100644 index 00000000..808fc445 --- /dev/null +++ b/plugins/mcollective/pluginpackager/ospackage_packager.rb @@ -0,0 +1,130 @@ +module MCollective + module PluginPackager + # MCollective plugin packager general OS implementation. + class OspackagePackager + + attr_accessor :package, :libdir, :package_type, :common_dependency, :tmpdir, :workingdir + + # Create packager object with package parameter containing list of files, + # dependencies and package metadata. We also identify if we're creating + # a RPM or Deb at object creation. + def initialize(package) + + if File.exists?("/etc/redhat-release") + @libdir = "usr/libexec/mcollective/mcollective/" + @package_type = "RPM" + raise "error: package 'rpm-build' is not installed." unless build_tool?("rpmbuild") + elsif File.exists?("/etc/debian_version") + @libdir = "usr/share/mcollective/plugins/mcollective" + @package_type = "Deb" + raise "error: package 'ar' is not installed." unless build_tool?("ar") + else + raise "error: cannot identify operating system." + end + + @package = package + end + + # Checks if rpmbuild executable is present. + def build_tool?(build_tool) + ENV["PATH"].split(File::PATH_SEPARATOR).each do |path| + builder = File.join(path, build_tool) + + if File.exists?(builder) + return true + end + end + false + end + + # Iterate package list creating tmp dirs, building the packages + # and cleaning up after itself. + def create_packages + gem 'fpm', '>= 0.4.1' + require 'fpm' + + @package.packagedata.each do |type, data| + next unless data + @tmpdir = Dir.mktmpdir("mcollective_packager") + @workingdir = File.join(@tmpdir, @libdir) + FileUtils.mkdir_p @workingdir + prepare_tmpdirs data + create_package type, data + cleanup_tmpdirs + end + end + + # Creates a system specific package with FPM + def create_package(type, data) + begin + dirpackage = FPM::Package::Dir.new + dirpackage.attributes[:chdir] = @tmpdir + dirpackage.input @libdir + rpmpackage = dirpackage.convert(FPM::Package.const_get(@package_type)) + params(rpmpackage, type, data) + rpmpackage.output("mcollective-#{package.metadata[:name]}-#{type}.#{@package_type.downcase}") + ensure + rpmpackage.cleanup if rpmpackage + dirpackage.cleanup if dirpackage + end + end + + # Constructs the list of FPM paramaters + def params(package, type, data) + package.name = "mcollective-#{@package.metadata[:name]}-#{type}" + package.maintainer = @package.metadata[:author] + package.version = @package.metadata[:version] + package.url = @package.metadata[:url] + package.license = @package.metadata[:license] + package.iteration = @package.iteration + package.vendor = @package.vendor + package.description = @package.metadata[:description] + "\n\n#{data[:description]}" + package.dependencies = data[:dependencies] + package.scripts["post-install"] = @package.postinstall if @package.postinstall + end + + # Creates temporary directories and sets working directory from which + # the packagke will be built. + def prepare_tmpdirs(data) + data[:files].each do |file| + targetdir = File.join(@workingdir, File.dirname(file).gsub(@package.target_path, "")) + target = FileUtils.mkdir(targetdir) unless File.directory? targetdir + FileUtils.cp_r(file, targetdir) + end + end + + # Remove temp directories created during packaging. + def cleanup_tmpdirs + FileUtils.rm_r @tmpdir if @tmpdir + end + + # Displays the package metadata and detected files + def package_information + puts + puts "%30s%s" % ["Plugin information : ", @package.metadata[:name]] + puts "%30s%s" % ["-" * 22, "-" * 22] + puts "%30s%s" % ["Plugin Type : ", @package.class.to_s.gsub(/^.*::/, "")] + puts "%30s%s" % ["Package Output Format : ", @package_type.upcase] + puts "%30s%s" % ["Version : ", @package.metadata[:version]] + puts "%30s%s" % ["Iteration : ", @package.iteration] + puts "%30s%s" % ["Vendor : ", @package.vendor] + puts "%30s%s" % ["Post Install Script : ", @package.postinstall] + puts "%30s%s" % ["Author : ", @package.metadata[:author]] + puts "%30s%s" % ["License : ", @package.metadata[:license]] + puts "%30s%s" % ["URL : ", @package.metadata[:url]] + + if @package.packagedata.size > 0 + @package.packagedata.each_with_index do |values, i| + next if values[1][:files].empty? + if i == 0 + puts "%30s%s" % ["Identified Packages : ", values[0]] + else + puts "%30s%s" % [" ", values[0]] + end + end + + end + end + end + end +end diff --git a/spec/unit/pluginmanager_spec.rb b/spec/unit/pluginmanager_spec.rb index 1b291545..55266a23 100755 --- a/spec/unit/pluginmanager_spec.rb +++ b/spec/unit/pluginmanager_spec.rb @@ -93,6 +93,43 @@ class MCollective::Foo; end end end + describe "#find" do + before do + @config.stubs(:libdir).returns(["/libdir/"]) + Config.stubs(:instance).returns(@config) + end + + it "should find all plugins in configured libdirs" do + File.expects(:join).with(["/libdir/", "mcollective", "test"]).returns("/plugindir/") + Dir.expects(:new).with("/plugindir/").returns(["plugin.rb"]) + PluginManager.find("test").should == ["plugin"] + end + + it "should find all plugins with a given file extension" do + File.expects(:join).with(["/libdir/", "mcollective", "test"]).returns("/plugindir/") + Dir.expects(:new).with("/plugindir/").returns(["plugin.ddl"]) + PluginManager.find("test", "ddl").should == ["plugin"] + end + end + + describe "#find_and_load" do + before do + @config.stubs(:libdir).returns(["/libdir/"]) + Config.stubs(:instance).returns(@config) + PluginManager.expects(:loadclass).with("MCollective::Test::Testplugin", true) + end + + it "should find and load all plugins from all libdirs that match the type" do + PluginManager.expects(:find).with("test", ".rb").returns(["testplugin"]) + PluginManager.find_and_load("test") + end + + it "should exclude plugins who do not match criteria if block is given" do + PluginManager.expects(:find).with("test", ".rb").returns(["testplugin", "failplugin"]) + PluginManager.find_and_load("test") {|plugin| plugin.match(/^test/)} + end + end + describe "#loadclass" do it "should load the correct filename given a ruby class name" do PluginManager.stubs(:load).with("mcollective/foo.rb").once diff --git a/spec/unit/pluginpackager/agent_spec.rb b/spec/unit/pluginpackager/agent_spec.rb new file mode 100644 index 00000000..d651fec8 --- /dev/null +++ b/spec/unit/pluginpackager/agent_spec.rb @@ -0,0 +1,152 @@ +#!/usr/bin/env rspec + +require 'spec_helper' + +module MCollective + module PluginPackager + describe AgentDefinition do + describe "#identify_packages" do + before do + AgentDefinition.any_instance.expects(:get_metadata).once.returns({:name=>"foo"}) + end + + it "should attempt to identify all agent packages" do + AgentDefinition.any_instance.expects(:common).once.returns(:check) + AgentDefinition.any_instance.expects(:agent).once.returns(:check) + AgentDefinition.any_instance.expects(:client).once.returns(:check) + + agent = AgentDefinition.new(".", nil, nil, nil, nil) + agent.packagedata[:common].should == :check + agent.packagedata[:agent].should == :check + agent.packagedata[:client].should == :check + end + end + + describe "#agent" do + before do + AgentDefinition.any_instance.expects(:get_metadata).once.returns({:name=>"foo"}) + AgentDefinition.any_instance.expects(:client).once + end + + it "should not populate the agent files if the agent directory is empty" do + AgentDefinition.any_instance.expects(:common).returns(nil) + AgentDefinition.any_instance.expects(:check_dir).returns(false) + agent = AgentDefinition.new(".", nil, nil, nil, nil) + agent.packagedata[:agent].should == nil + end + + it "should populate the agent files if the agent directory is present and not empty" do + AgentDefinition.any_instance.expects(:common).returns(nil) + AgentDefinition.any_instance.expects(:check_dir).returns(true) + File.expects(:join).returns("tmpdir") + Dir.expects(:glob).with("tmpdir/*.ddl").returns([]) + Dir.expects(:glob).with("tmpdir/*").returns(["file.rb"]) + Dir.expects(:glob).with("foo/**").returns(["implementation.rb"]) + + agent = AgentDefinition.new(".", nil, nil, nil, nil) + agent.packagedata[:agent][:files].should == ["file.rb", "implementation.rb"] + end + + it "should add common package as dependency if present" do + AgentDefinition.any_instance.expects(:common).returns(true) + AgentDefinition.any_instance.expects(:check_dir).returns(true) + File.expects(:join).returns("tmpdir") + Dir.expects(:glob).with("tmpdir/*.ddl").returns([]) + Dir.expects(:glob).with("tmpdir/*").returns(["file.rb"]) + Dir.expects(:glob).with("foo/**").returns(["implementation.rb"]) + + agent = AgentDefinition.new(".", nil, nil, nil, nil) + agent.packagedata[:agent][:dependencies].should == ["mcollective", "mcollective-foo-common"] + end + end + + describe "#common" do + before do + AgentDefinition.any_instance.expects(:get_metadata).once.returns({:name=>"foo"}) + AgentDefinition.any_instance.expects(:agent) + AgentDefinition.any_instance.expects(:client) + end + + it "should not populate the commong files if the util directory is empty" do + AgentDefinition.any_instance.expects(:check_dir).returns(false) + common = AgentDefinition.new(".", nil, nil, nil, nil) + common.packagedata[:common].should == nil + end + + it "should populate the agent files if the agent directory is present and not empty" do + AgentDefinition.any_instance.expects(:check_dir).returns(true) + File.expects(:join).returns("tmpdir") + Dir.expects(:glob).with("tmpdir/*").returns(["file.rb"]) + common = AgentDefinition.new(".", nil, nil, nil, nil) + common.packagedata[:common][:files].should == ["file.rb"] + end + end + + describe "#client" do + before do + AgentDefinition.any_instance.expects(:get_metadata).once.returns({:name=>"foo"}) + AgentDefinition.any_instance.expects(:agent).returns(nil) + File.expects(:join).with(".", "application").returns("clientdir") + File.expects(:join).with(".", "bin").returns("bindir") + File.expects(:join).with(".", "agent").returns("agentdir") + end + + it "should populate client files if all directories are present" do + AgentDefinition.any_instance.expects(:common).returns(nil) + AgentDefinition.any_instance.expects(:check_dir).times(3).returns(true) + Dir.expects(:glob).with("clientdir/*").returns(["client.rb"]) + Dir.expects(:glob).with("bindir/*").returns(["bin.rb"]) + Dir.expects(:glob).with("agentdir/*.ddl").returns(["agent.ddl"]) + + client = AgentDefinition.new(".", nil, nil, nil, nil) + client.packagedata[:client][:files].should == ["client.rb", "bin.rb", "agent.ddl"] + end + + it "should not populate client files if directories are not present" do + AgentDefinition.any_instance.expects(:common).returns(nil) + AgentDefinition.any_instance.expects(:check_dir).times(3).returns(false) + + client = AgentDefinition.new(".", nil, nil, nil, nil) + client.packagedata[:client].should == nil + end + + it "should add common package as dependency if present" do + AgentDefinition.any_instance.expects(:common).returns("common") + AgentDefinition.any_instance.expects(:check_dir).times(3).returns(true) + Dir.expects(:glob).with("clientdir/*").returns(["client.rb"]) + Dir.expects(:glob).with("bindir/*").returns(["bin.rb"]) + Dir.expects(:glob).with("agentdir/*.ddl").returns(["agent.ddl"]) + + client = AgentDefinition.new(".", nil, nil, nil, nil) + client.packagedata[:client][:dependencies].should == ["mcollective-client", "mcollective-foo-common"] + end + end + + describe "#get_metadata" do + it "should raise and exception if the ddl file is not present" do + expect { + AgentDefinition.new(".", nil, nil, nil, nil) + }.to raise_error "error: could not read agent DDL File" + end + end + + it "should load the ddl file if its present" do + File.stubs(:read).returns("metadata :name => \"testpackage\", + :description => \"Test Package\", + :author => \"Test\", + :license => \"Apache 2\", + :version => \"0\", + :url => \"foo.com\", + :timeout => 5") + testpackage = AgentDefinition.new(".", nil, nil, nil, nil) + testpackage.metadata.should == {:name => "testpackage", + :description => "Test Package", + :author => "Test", + :license => "Apache 2", + :version => "0", + :url => "foo.com", + :timeout => 5} + end + end + end +end diff --git a/spec/unit/plugins/mcollective/packagers/ospackage_spec.rb b/spec/unit/plugins/mcollective/packagers/ospackage_spec.rb new file mode 100644 index 00000000..c1c1d936 --- /dev/null +++ b/spec/unit/plugins/mcollective/packagers/ospackage_spec.rb @@ -0,0 +1,208 @@ +#!/usr/bin/env rspec + +require 'spec_helper' + +MCollective::PluginManager.clear +require File.dirname(__FILE__) + '/../../../../../plugins/mcollective/pluginpackager/ospackage_packager.rb' + +module MCollective + module PluginPackager + describe OspackagePackager do + before :all do + + class OspackagePackager + ENV = {"PATH" => "."} + end + + class TestPlugin + attr_accessor :path, :packagedata, :metadata, :target_path, :vendor, :iteration + attr_accessor :postinstall + + def initialize + @path = "/tmp" + @packagedata = {:testpackage => {:files => ["/tmp/test.rb"], + :dependencies => ["mcollective"], + :description => "testpackage"}} + @iteration = 1 + @postinstall = "/tmp/test.sh" + @metadata = {:name => "testplugin", + :description => "A Test Plugin", + :author => "Psy", + :license => "Apache 2", + :version => "0", + :url => "http://foo.bar.com", + :timeout => 5} + @vendor = "Puppet Labs" + @target_path = "/tmp" + end + end + + @testplugin = TestPlugin.new + end + + describe "#initialize" do + it "should correctly identify a RedHat system" do + File.expects(:exists?).with("/etc/redhat-release").returns(true) + OspackagePackager.any_instance.expects(:build_tool?).with("rpmbuild").returns(true) + + ospackage = OspackagePackager.new(@testplugin) + ospackage.libdir.should == "usr/libexec/mcollective/mcollective/" + ospackage.package_type.should == "RPM" + end + + it "should correctly identify a Debian System" do + File.expects(:exists?).with("/etc/redhat-release").returns(false) + File.expects(:exists?).with("/etc/debian_version").returns(true) + OspackagePackager.any_instance.expects(:build_tool?).with("ar").returns(true) + + ospackage = OspackagePackager.new(@testplugin) + ospackage.libdir.should == "usr/share/mcollective/plugins/mcollective" + ospackage.package_type.should == "Deb" + end + + it "should raise an exception if it cannot identify the operating system" do + File.expects(:exists?).with("/etc/redhat-release").returns(false) + File.expects(:exists?).with("/etc/debian_version").returns(false) + + expect{ + ospackage = OspackagePackager.new(@testplugin) + }.to raise_exception "error: cannot identify operating system." + end + + it "should identify if rpmbuild is present for RedHat systems" do + File.expects(:exists?).with("/etc/redhat-release").returns(true) + File.expects(:exists?).with("./rpmbuild").returns(true) + ospackage = OspackagePackager.new(@testplugin) + end + + it "should raise an exception if rpmbuild is not present for RedHat systems" do + File.expects(:exists?).with("/etc/redhat-release").returns(true) + File.expects(:exists?).with("./rpmbuild").returns(false) + expect{ + ospackage = OspackagePackager.new(@testplugin) + }.to raise_error "error: package 'rpm-build' is not installed." + end + + it "should identify if ar is present for Debian systems" do + File.expects(:exists?).with("/etc/redhat-release").returns(false) + File.expects(:exists?).with("/etc/debian_version").returns(true) + File.expects(:exists?).with("./ar").returns(true) + + ospackage = OspackagePackager.new(@testplugin) + end + + it "should raise an exception if the build tool is not present" do + File.expects(:exists?).with("/etc/redhat-release").returns(false) + File.expects(:exists?).with("/etc/debian_version").returns(true) + File.expects(:exists?).with("./ar").returns(false) + expect{ + ospackage = OspackagePackager.new(@testplugin) + }.to raise_error "error: package 'ar' is not installed." + end + end + + describe "#create_packages" do + it "should prepare temp directories, create a package and clean up when done" do + OspackagePackager.any_instance.stubs(:gem).with("fpm", ">= 0.4.1") + OspackagePackager.any_instance.stubs(:require).with("fpm") + OspackagePackager.any_instance.stubs(:require).with("tmpdir") + + File.expects(:exists?).with("/etc/redhat-release").returns(true) + OspackagePackager.any_instance.expects(:build_tool?).with("rpmbuild").returns(true) + Dir.expects(:mktmpdir).with("mcollective_packager").returns("/tmp/mcollective_packager") + FileUtils.expects(:mkdir_p).with("/tmp/mcollective_packager/usr/libexec/mcollective/mcollective/") + + ospackage = OspackagePackager.new(@testplugin) + ospackage.expects(:prepare_tmpdirs).once + ospackage.expects(:create_package).once + ospackage.expects(:cleanup_tmpdirs).once + + ospackage.create_packages + end + end + + describe "#create_package" do + before do + module FPM + module Package + class Dir;end + class RPM;end + end + end + end + + it "should run fpm with the correct parameters" do + File.expects(:exists?).with("/etc/redhat-release").returns(true) + OspackagePackager.any_instance.expects(:build_tool?).with("rpmbuild").returns(true) + + ospackage = OspackagePackager.new(@testplugin) + ospackage.expects(:params) + + attributes = {:chdir => nil} + fpm_dir = mock + fpm_rpm = mock + + FPM::Package::Dir.expects(:new).returns(fpm_dir).once + fpm_dir.expects(:attributes).returns(attributes) + fpm_dir.expects(:input).with("usr/libexec/mcollective/mcollective/") + FPM::Package.expects(:const_get).with("RPM").returns("FPM::Package::RPM") + fpm_dir.expects(:convert).with("FPM::Package::RPM").returns(fpm_rpm) + fpm_rpm.expects(:output).with("mcollective-testplugin-testpackage.rpm") + + fpm_rpm.expects(:cleanup) + fpm_dir.expects(:cleanup) + + ospackage.create_package(:testpackage, @testplugin.packagedata[:testpackage]) + end + end + + describe "#params" do + it "should create all paramaters needed by fpm" do + File.expects(:exists?).with("/etc/redhat-release").returns(true) + OspackagePackager.any_instance.expects(:build_tool?).with("rpmbuild").returns(true) + ospackage = OspackagePackager.new(@testplugin) + + fpm_type = mock + fpm_type.expects(:name=).with("mcollective-testplugin-testpackage") + fpm_type.expects(:maintainer=).with("Psy") + fpm_type.expects(:version=).with("0") + fpm_type.expects(:url=).with("http://foo.bar.com") + fpm_type.expects(:license=).with("Apache 2") + fpm_type.expects(:iteration=).with(1) + fpm_type.expects(:vendor=).with("Puppet Labs") + fpm_type.expects(:description=).with("A Test Plugin\n\ntestpackage") + fpm_type.expects(:dependencies=).with(["mcollective"]) + fpm_type.expects(:scripts).returns({"post-install" => nil}) + + ospackage.params(fpm_type,:testpackage, @testplugin.packagedata[:testpackage]) + + end + end + + describe "#prepare_tmpdirs" do + it "should create temp directories and copy package files" do + File.expects(:exists?).with("/etc/redhat-release").returns(true) + OspackagePackager.any_instance.expects(:build_tool?).with("rpmbuild").returns(true) + FileUtils.expects(:mkdir).with("/tmp/mcollective_packager/") + FileUtils.expects(:cp_r).with("/tmp/test.rb", "/tmp/mcollective_packager/") + + ospackage = OspackagePackager.new(@testplugin) + ospackage.workingdir = "/tmp/mcollective_packager/" + ospackage.prepare_tmpdirs(@testplugin.packagedata[:testpackage]) + end + end + + describe "#cleanup_tmpdirs" do + it "should remove temp directories" do + File.expects(:exists?).with("/etc/redhat-release").returns(true) + OspackagePackager.any_instance.expects(:build_tool?).with("rpmbuild").returns(true) + FileUtils.expects(:rm_r).with("/tmp/mcollective_packager/") + + ospackage = OspackagePackager.new(@testplugin) + ospackage.tmpdir = "/tmp/mcollective_packager/" + ospackage.cleanup_tmpdirs + end + end + end + end +end