This is a first version that does very little - it has a few fields, and allows speciification of dependencies with other modules as well as compatibility with individual Puppet versions.
It's not really sufficient, because it only allows specific versions, rather than a range of versions, but it's a good demo of what it takes and what we provide. Signed-off-by: Luke Kanies <[email protected]> --- lib/puppet/module.rb | 67 +++++++++++++ spec/unit/module.rb | 265 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 332 insertions(+), 0 deletions(-) diff --git a/lib/puppet/module.rb b/lib/puppet/module.rb index 9aa634b..8f638aa 100644 --- a/lib/puppet/module.rb +++ b/lib/puppet/module.rb @@ -2,6 +2,12 @@ require 'puppet/util/logging' # Support for modules class Puppet::Module + class MissingModule < Puppet::Error; end + class IncompatibleModule < Puppet::Error; end + class UnsupportedPlatform < Puppet::Error; end + class IncompatiblePlatform < Puppet::Error; end + class MissingMetadata < Puppet::Error; end + include Puppet::Util::Logging class InvalidName < ArgumentError @@ -35,6 +41,14 @@ class Puppet::Module attr_reader :name, :environment attr_writer :environment + attr_accessor :source, :author, :version, :license, :puppetversion + + def has_metadata? + return false unless metadata_file + + FileTest.exist?(metadata_file) + end + def initialize(name, environment = nil) @name = name @@ -45,6 +59,11 @@ class Puppet::Module else @environment = Puppet::Node::Environment.new(environment) end + + load_metadata if has_metadata? + + validate_puppet_version + validate_dependencies end FILETYPES.each do |type| @@ -86,6 +105,18 @@ class Puppet::Module subpath("files") end + def load_metadata + data = JSON.parse File.read(metadata_file) + [:source, :author, :version, :license, :puppetversion].each do |attr| + unless value = data[attr.to_s] + unless attr == :puppetversion + raise MissingMetadata, "No #{attr} module metadata provided for #{self.name}" + end + end + send(attr.to_s + "=", value) + end + end + # Return the list of manifests matching the given glob pattern, # defaulting to 'init.pp' for empty modules. def match_manifests(rest) @@ -100,6 +131,13 @@ class Puppet::Module result.flatten.compact end + def metadata_file + return @metadata_file if defined?(@metadata_file) + + return @metadata_file = nil unless path + @metadata_file = File.join(path, "metadata.json") + end + # Find this module in the modulepath. def path environment.modulepath.collect { |path| File.join(path, name) }.find { |d| FileTest.exist?(d) } @@ -110,6 +148,16 @@ class Puppet::Module subpath("plugins") end + def requires(name, version = nil) + @requires ||= [] + @requires << [name, version] + end + + def supports(name, version = nil) + @supports ||= [] + @supports << [name, version] + end + def to_s result = "Module %s" % name if path @@ -118,6 +166,25 @@ class Puppet::Module result end + def validate_dependencies + return unless defined?(@requires) + + @requires.each do |name, version| + unless mod = environment.module(name) + raise MissingModule, "Missing module #{name} required by #{self.name}" + end + + if version and mod.version != version + raise IncompatibleModule, "Required module #{name} is version #{mod.version} but #{self.name} requires #{version}" + end + end + end + + def validate_puppet_version + return unless puppetversion and puppetversion != Puppet.version + raise IncompatibleModule, "Module #{self.name} is only compatible with Puppet version #{puppetversion}, not #{Puppet.version}" + end + private def find_init_manifest diff --git a/spec/unit/module.rb b/spec/unit/module.rb index 4fa6ec5..869a2b2 100755 --- a/spec/unit/module.rb +++ b/spec/unit/module.rb @@ -25,6 +25,185 @@ describe Puppet::Module do Puppet::Module.find("mymod", "myenv").should be_nil end + it "should support a 'version' attribute" do + mod = Puppet::Module.new("mymod") + mod.version = 1.09 + mod.version.should == 1.09 + end + + it "should support a 'source' attribute" do + mod = Puppet::Module.new("mymod") + mod.source = "http://foo/bar" + mod.source.should == "http://foo/bar" + end + + it "should support an 'author' attribute" do + mod = Puppet::Module.new("mymod") + mod.author = "Luke Kanies <[email protected]>" + mod.author.should == "Luke Kanies <[email protected]>" + end + + it "should support a 'license' attribute" do + mod = Puppet::Module.new("mymod") + mod.license = "GPL2" + mod.license.should == "GPL2" + end + + it "should support specifying a compatible puppet version" do + mod = Puppet::Module.new("mymod") + mod.puppetversion = "0.25" + mod.puppetversion.should == "0.25" + end + + it "should validate that the puppet version is compatible" do + mod = Puppet::Module.new("mymod") + mod.puppetversion = "0.25" + Puppet.expects(:version).returns "0.25" + mod.validate_puppet_version + end + + it "should fail if the specified puppet version is not compatible" do + mod = Puppet::Module.new("mymod") + mod.puppetversion = "0.25" + Puppet.stubs(:version).returns "0.24" + lambda { mod.validate_puppet_version }.should raise_error(Puppet::Module::IncompatibleModule) + end + + describe "when specifying required modules" do + it "should support specifying a required module" do + mod = Puppet::Module.new("mymod") + mod.requires "foobar" + end + + it "should support specifying multiple required modules" do + mod = Puppet::Module.new("mymod") + mod.requires "foobar" + mod.requires "baz" + end + + it "should support specifying a required module and version" do + mod = Puppet::Module.new("mymod") + mod.requires "foobar", 1.0 + end + + it "should fail when required modules are missing" do + mod = Puppet::Module.new("mymod") + mod.requires "foobar" + + mod.environment.expects(:module).with("foobar").returns nil + + lambda { mod.validate_dependencies }.should raise_error(Puppet::Module::MissingModule) + end + + it "should fail when required modules are present but of the wrong version" do + mod = Puppet::Module.new("mymod") + mod.requires "foobar", 1.0 + + foobar = Puppet::Module.new("foobar") + foobar.version = 2.0 + + mod.environment.expects(:module).with("foobar").returns foobar + + lambda { mod.validate_dependencies }.should raise_error(Puppet::Module::IncompatibleModule) + end + + it "should have valid dependencies when no dependencies have been specified" do + mod = Puppet::Module.new("mymod") + + lambda { mod.validate_dependencies }.should_not raise_error + end + + it "should fail when some dependencies are present but others aren't" do + mod = Puppet::Module.new("mymod") + mod.requires "foobar" + mod.requires "baz" + + mod.environment.expects(:module).with("foobar").returns Puppet::Module.new("foobar") + mod.environment.expects(:module).with("baz").returns nil + + lambda { mod.validate_dependencies }.should raise_error(Puppet::Module::MissingModule) + end + + it "should have valid dependencies when all dependencies are met" do + mod = Puppet::Module.new("mymod") + mod.requires "foobar", 1.0 + mod.requires "baz" + + foobar = Puppet::Module.new("foobar") + foobar.version = 1.0 + + baz = Puppet::Module.new("baz") + + mod.environment.expects(:module).with("foobar").returns foobar + mod.environment.expects(:module).with("baz").returns baz + + lambda { mod.validate_dependencies }.should_not raise_error + end + + it "should validate its dependendencies on initialization" do + Puppet::Module.any_instance.expects(:validate_dependencies) + Puppet::Module.new("mymod") + end + end + + describe "when managing supported platforms" do + it "should support specifying a supported platform" do + mod = Puppet::Module.new("mymod") + mod.supports "solaris" + end + + it "should support specifying a supported platform and version" do + mod = Puppet::Module.new("mymod") + mod.supports "solaris", 1.0 + end + + it "should fail when not running on a supported platform" do + pending "Not sure how to send client platform to the module" + mod = Puppet::Module.new("mymod") + Facter.expects(:value).with("operatingsystem").returns "Solaris" + + mod.supports "hpux" + + lambda { mod.validate_supported_platform }.should raise_error(Puppet::Module::UnsupportedPlatform) + end + + it "should fail when supported platforms are present but of the wrong version" do + pending "Not sure how to send client platform to the module" + mod = Puppet::Module.new("mymod") + Facter.expects(:value).with("operatingsystem").returns "Solaris" + Facter.expects(:value).with("operatingsystemrelease").returns 2.0 + + mod.supports "Solaris", 1.0 + + lambda { mod.validate_supported_platform }.should raise_error(Puppet::Module::IncompatiblePlatform) + end + + it "should be considered supported when no supported platforms have been specified" do + pending "Not sure how to send client platform to the module" + mod = Puppet::Module.new("mymod") + lambda { mod.validate_supported_platform }.should_not raise_error + end + + it "should be considered supported when running on a supported platform" do + pending "Not sure how to send client platform to the module" + mod = Puppet::Module.new("mymod") + Facter.expects(:value).with("operatingsystem").returns "Solaris" + Facter.expects(:value).with("operatingsystemrelease").returns 2.0 + + mod.supports "Solaris", 1.0 + + lambda { mod.validate_supported_platform }.should raise_error(Puppet::Module::IncompatiblePlatform) + end + + it "should be considered supported when running on any of multiple supported platforms" do + pending "Not sure how to send client platform to the module" + end + + it "should validate its platform support on initialization" do + pending "Not sure how to send client platform to the module" + end + end + it "should return nil if asked for a module whose name is 'nil'" do Puppet::Module.find(nil, "myenv").should be_nil end @@ -245,3 +424,89 @@ describe Puppet::Module, "when finding matching manifests" do @mod.match_manifests("yay/*.pp").should == [] end end + +describe Puppet::Module do + before do + Puppet::Module.any_instance.stubs(:path).returns "/my/mod/path" + @module = Puppet::Module.new("foo") + end + + it "should 'metadata.json' in its current path as its metadata file" do + @module.metadata_file.should == "/my/mod/path/metadata.json" + end + + it "should return nil as its metadata file when the module has no path" do + Puppet::Module.any_instance.stubs(:path).returns nil + Puppet::Module.new("foo").metadata_file.should be_nil + end + + it "should cache the metadata file" do + Puppet::Module.any_instance.expects(:path).once.returns nil + mod = Puppet::Module.new("foo") + mod.metadata_file.should == mod.metadata_file + end + + it "should know if it has a metadata file" do + FileTest.expects(:exist?).with(@module.metadata_file).returns true + + @module.should be_has_metadata + end + + it "should know if it is missing a metadata file" do + FileTest.expects(:exist?).with(@module.metadata_file).returns false + + @module.should_not be_has_metadata + end + + it "should be able to parse its metadata file" do + @module.should respond_to(:load_metadata) + end + + it "should parse its metadata file on initialization if it is present" do + Puppet::Module.any_instance.expects(:has_metadata?).returns true + Puppet::Module.any_instance.expects(:load_metadata) + + Puppet::Module.new("yay") + end + + describe "when loading the medatada file" do + confine "Cannot test module metadata without json" => Puppet.features.json? + + before do + @data = { + :license => "GPL2", + :author => "luke", + :version => "1.0", + :source => "http://foo/", + :puppetversion => "0.25" + } + @text = @data.to_json + + @module = Puppet::Module.new("foo") + @module.stubs(:metadata_file).returns "/my/file" + File.stubs(:read).with("/my/file").returns @text + end + + %w{source author version license}.each do |attr| + it "should set #{attr} if present in the metadata file" do + @module.load_metadata + @module.send(attr).should == @data[attr.to_sym] + end + + it "should fail if #{attr} is not present in the metadata file" do + @data.delete(attr.to_sym) + @text = @data.to_json + File.stubs(:read).with("/my/file").returns @text + lambda { @module.load_metadata }.should raise_error(Puppet::Module::MissingMetadata) + end + end + + it "should set puppetversion if present in the metadata file" do + @module.load_metadata + @module.puppetversion.should == @data[:puppetversion] + end + + + it "should fail if the discovered name is different than the metadata name" + end +end -- 1.6.1 --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Puppet Developers" group. To post to this group, send email to [email protected] To unsubscribe from this group, send email to [email protected] For more options, visit this group at http://groups.google.com/group/puppet-dev?hl=en -~----------~----~----~----~------~----~------~--~---
