diff --git a/lib/puppet/face/module/build.rb b/lib/puppet/face/module/build.rb index f1d006a74..5ee8b2363 100644 --- a/lib/puppet/face/module/build.rb +++ b/lib/puppet/face/module/build.rb @@ -1,63 +1,63 @@ Puppet::Face.define(:module, '1.0.0') do action(:build) do summary "Build a module release package." description <<-EOT Prepares a local module for release on the Puppet Forge by building a ready-to-upload archive file. This action uses the Modulefile in the module directory to set metadata used by the Forge. See for more about writing modulefiles. After being built, the release archive file can be found in the module's `pkg` directory. EOT returns "Pathname object representing the path to the release archive." examples <<-EOT Build a module release: $ puppet module build puppetlabs-apache notice: Building /Users/kelseyhightower/puppetlabs-apache for release Module built: /Users/kelseyhightower/puppetlabs-apache/pkg/puppetlabs-apache-0.0.1.tar.gz Build the module in the current working directory: $ cd /Users/kelseyhightower/puppetlabs-apache $ puppet module build notice: Building /Users/kelseyhightower/puppetlabs-apache for release Module built: /Users/kelseyhightower/puppetlabs-apache/pkg/puppetlabs-apache-0.0.1.tar.gz EOT arguments "[]" when_invoked do |*args| options = args.pop if options.nil? or args.length > 1 then raise ArgumentError, "puppet module build only accepts 0 or 1 arguments" end module_path = args.first if module_path.nil? pwd = Dir.pwd module_path = Puppet::ModuleTool.find_module_root(pwd) if module_path.nil? - raise "Unable to find module root at #{pwd} or parent directories" + raise "Unable to find metadata.json or Modulefile in module root #{pwd} or parent directories. See for required file format." end else unless Puppet::ModuleTool.is_module_root?(module_path) - raise "Unable to find module root at #{module_path}" + raise "Unable to find metadata.json or Modulefile in module root #{module_path}. See for required file format." end end Puppet::ModuleTool.set_option_defaults options Puppet::ModuleTool::Applications::Builder.run(module_path, options) end when_rendering :console do |return_value| # Get the string representation of the Pathname object. "Module built: " + return_value.expand_path.to_s end end end diff --git a/lib/puppet/module_tool/applications/application.rb b/lib/puppet/module_tool/applications/application.rb index d175f563c..c94990b56 100644 --- a/lib/puppet/module_tool/applications/application.rb +++ b/lib/puppet/module_tool/applications/application.rb @@ -1,99 +1,98 @@ require 'net/http' require 'semver' require 'json' require 'puppet/util/colors' module Puppet::ModuleTool module Applications class Application include Puppet::Util::Colors def self.run(*args) new(*args).run end attr_accessor :options def initialize(options = {}) @options = options end def run raise NotImplementedError, "Should be implemented in child classes." end def discuss(response, success, failure) case response when Net::HTTPOK, Net::HTTPCreated Puppet.notice success else errors = JSON.parse(response.body)['error'] rescue "HTTP #{response.code}, #{response.body}" Puppet.warning "#{failure} (#{errors})" end end def metadata(require_metadata = false) return @metadata if @metadata @metadata = Puppet::ModuleTool::Metadata.new unless @path raise ArgumentError, "Could not determine module path" end + if require_metadata && !Puppet::ModuleTool.is_module_root?(@path) + raise ArgumentError, "Unable to find metadata.json or Modulefile in module root at #{@path} See http://links.puppetlabs.com/modulefile for required file format." + end + modulefile_path = File.join(@path, 'Modulefile') metadata_path = File.join(@path, 'metadata.json') if File.file?(metadata_path) File.open(metadata_path) do |f| begin @metadata.update(JSON.load(f)) rescue JSON::ParserError => ex raise ArgumentError, "Could not parse JSON #{metadata_path}", ex.backtrace end end end if File.file?(modulefile_path) if File.file?(metadata_path) Puppet.warning "Modulefile is deprecated. Merging your Modulefile and metadata.json." else Puppet.warning "Modulefile is deprecated. Building metadata.json from Modulefile." end Puppet::ModuleTool::ModulefileReader.evaluate(@metadata, modulefile_path) end - has_metadata = File.file?(modulefile_path) || File.file?(metadata_path) - if !has_metadata && require_metadata - raise ArgumentError, "No metadata found for module #{@path}" - end - return @metadata end def load_metadata! @metadata = nil metadata(true) end def parse_filename(filename) if match = /^((.*?)-(.*?))-(\d+\.\d+\.\d+.*?)$/.match(File.basename(filename,'.tar.gz')) module_name, author, shortname, version = match.captures else raise ArgumentError, "Could not parse filename to obtain the username, module name and version. (#{@release_name})" end unless SemVer.valid?(version) raise ArgumentError, "Invalid version format: #{version} (Semantic Versions are acceptable: http://semver.org)" end return { :module_name => module_name, :author => author, :dir_name => shortname, :version => version } end end end end diff --git a/spec/unit/face/module/build_spec.rb b/spec/unit/face/module/build_spec.rb index 02bbb7e9d..72ba5e1f1 100644 --- a/spec/unit/face/module/build_spec.rb +++ b/spec/unit/face/module/build_spec.rb @@ -1,69 +1,69 @@ require 'spec_helper' require 'puppet/face' require 'puppet/module_tool' describe "puppet module build" do subject { Puppet::Face[:module, :current] } describe "when called without any options" do it "if current directory is a module root should call builder with it" do Dir.expects(:pwd).returns('/a/b/c') Puppet::ModuleTool.expects(:find_module_root).with('/a/b/c').returns('/a/b/c') Puppet::ModuleTool.expects(:set_option_defaults).returns({}) Puppet::ModuleTool::Applications::Builder.expects(:run).with('/a/b/c', {}) subject.build end it "if parent directory of current dir is a module root should call builder with it" do Dir.expects(:pwd).returns('/a/b/c') Puppet::ModuleTool.expects(:find_module_root).with('/a/b/c').returns('/a/b') Puppet::ModuleTool.expects(:set_option_defaults).returns({}) Puppet::ModuleTool::Applications::Builder.expects(:run).with('/a/b', {}) subject.build end it "if current directory or parents contain no module root, should return exception" do Dir.expects(:pwd).returns('/a/b/c') Puppet::ModuleTool.expects(:find_module_root).returns(nil) - expect { subject.build }.to raise_error RuntimeError, "Unable to find module root at /a/b/c or parent directories" + expect { subject.build }.to raise_error RuntimeError, "Unable to find metadata.json or Modulefile in module root /a/b/c or parent directories. See for required file format." end end describe "when called with a path" do it "if path is a module root should call builder with it" do Puppet::ModuleTool.expects(:is_module_root?).with('/a/b/c').returns(true) Puppet::ModuleTool.expects(:set_option_defaults).returns({}) Puppet::ModuleTool::Applications::Builder.expects(:run).with('/a/b/c', {}) subject.build('/a/b/c') end it "if path is not a module root should raise exception" do Puppet::ModuleTool.expects(:is_module_root?).with('/a/b/c').returns(false) - expect { subject.build('/a/b/c') }.to raise_error RuntimeError, "Unable to find module root at /a/b/c" + expect { subject.build('/a/b/c') }.to raise_error RuntimeError, "Unable to find metadata.json or Modulefile in module root /a/b/c. See for required file format." end end describe "with options" do it "should pass through options to builder when provided" do Puppet::ModuleTool.stubs(:is_module_root?).returns(true) Puppet::ModuleTool.expects(:set_option_defaults).returns({}) Puppet::ModuleTool::Applications::Builder.expects(:run).with('/a/b/c', {:modulepath => '/x/y/z'}) subject.build('/a/b/c', :modulepath => '/x/y/z') end end describe "inline documentation" do subject { Puppet::Face[:module, :current].get_action :build } its(:summary) { should =~ /build.*module/im } its(:description) { should =~ /build.*module/im } its(:returns) { should =~ /pathname/i } its(:examples) { should_not be_empty } %w{ license copyright summary description returns examples }.each do |doc| context "of the" do its(doc.to_sym) { should_not =~ /(FIXME|REVISIT|TODO)/ } end end end end