diff --git a/lib/puppet/module_tool/applications/searcher.rb b/lib/puppet/module_tool/applications/searcher.rb index c47b1a735..f8e4a66aa 100644 --- a/lib/puppet/module_tool/applications/searcher.rb +++ b/lib/puppet/module_tool/applications/searcher.rb @@ -1,40 +1,40 @@ module Puppet::Module::Tool module Applications class Searcher < Application def initialize(term, options = {}) @term = term super(options) end def run request = Net::HTTP::Get.new("/modules.json?q=#{URI.escape(@term)}") - response = repository.contact(request) + response = repository.make_http_request(request) case response when Net::HTTPOK matches = PSON.parse(response.body) else raise SystemExit, "Could not execute search (HTTP #{response.code})" matches = [] end # Return a list of module metadata hashes that match the search query. # This return value is used by the module_tool face install search, # and displayed to on the console. # # Example return value: # # [ # { # "name" => "nginx", # "project_url" => "http://github.com/puppetlabs/puppetlabs-nginx", # "version" => "0.0.1", # "full_name" => "puppetlabs/nginx" # } # ] # matches end end end end diff --git a/lib/puppet/module_tool/repository.rb b/lib/puppet/module_tool/repository.rb index 55be8531e..dcbd7f4ef 100644 --- a/lib/puppet/module_tool/repository.rb +++ b/lib/puppet/module_tool/repository.rb @@ -1,79 +1,79 @@ require 'net/http' require 'digest/sha1' module Puppet::Module::Tool # = Repository # # This class is a file for accessing remote repositories with modules. class Repository include Utils::URI include Utils::Interrogation attr_reader :uri, :cache # Instantiate a new repository instance rooted at the optional string # +url+, else an instance of the default Puppet modules repository. def initialize(url=Puppet[:module_repository]) @uri = normalize(url) @cache = Cache.new(self) end # Return a Net::HTTPResponse read for this +request+. # # Options: # * :authenticate => Request authentication on the terminal. Defaults to false. - def contact(request, options = {}) + def make_http_request(request, options = {}) if options[:authenticate] authenticate(request) end if ! @uri.user.nil? && ! @uri.password.nil? request.basic_auth(@uri.user, @uri.password) end - return read_contact(request) + return read_response(request) end # Return a Net::HTTPResponse read from this HTTPRequest +request+. - def read_contact(request) + def read_response(request) begin Net::HTTP::Proxy( Puppet::Module::Tool::http_proxy_host, Puppet::Module::Tool::http_proxy_port ).start(@uri.host, @uri.port) do |http| http.request(request) end rescue Errno::ECONNREFUSED, SocketError raise SystemExit, "Could not reach remote repository" end end # Set the HTTP Basic Authentication parameters for the Net::HTTPRequest # +request+ by asking the user for input on the console. def authenticate(request) Puppet.notice "Authenticating for #{@uri}" email = prompt('Email Address') password = prompt('Password', true) request.basic_auth(email, password) end # Return the local file name containing the data downloaded from the # repository at +release+ (e.g. "myuser-mymodule"). def retrieve(release) return cache.retrieve(@uri + release) end # Return the URI string for this repository. def to_s return @uri.to_s end # Return the cache key for this repository, this a hashed string based on # the URI. def cache_key return @cache_key ||= [ @uri.to_s.gsub(/[^[:alnum:]]+/, '_').sub(/_$/, ''), Digest::SHA1.hexdigest(@uri.to_s) ].join('-') end end end diff --git a/spec/integration/module_tool_spec.rb b/spec/integration/module_tool_spec.rb index 8b03942c6..a26f06c9e 100644 --- a/spec/integration/module_tool_spec.rb +++ b/spec/integration/module_tool_spec.rb @@ -1,477 +1,477 @@ require 'spec_helper' require 'tmpdir' require 'fileutils' # FIXME This are helper methods that could be used by other tests in the # future, should we move these to a more central location def stub_repository_read(code, body) kind = Net::HTTPResponse.send(:response_class, code.to_s) response = kind.new('1.0', code.to_s, 'HTTP MESSAGE') response.stubs(:read_body).returns(body) - Puppet::Module::Tool::Repository.any_instance.stubs(:read_contact).returns(response) + Puppet::Module::Tool::Repository.any_instance.stubs(:read_response).returns(response) end def stub_installer_read(body) Puppet::Module::Tool::Applications::Installer.any_instance.stubs(:read_match).returns(body) end def stub_cache_read(body) Puppet::Module::Tool::Cache.any_instance.stubs(:read_retrieve).returns(body) end # Return path to temparory directory for testing. def testdir return @testdir ||= tmpdir("module_tool_testdir") end # Create a temporary testing directory, change into it, and execute the # +block+. When the block exists, remove the test directory and change back # to the previous directory. def mktestdircd(&block) previousdir = Dir.pwd rmtestdir FileUtils.mkdir_p(testdir) Dir.chdir(testdir) block.call ensure rmtestdir Dir.chdir previousdir end # Remove the temporary test directory. def rmtestdir FileUtils.rm_rf(testdir) if File.directory?(testdir) end # END helper methods # Directory that contains sample releases. RELEASE_FIXTURES_DIR = File.join(PuppetSpec::FIXTURE_DIR, "releases") # Return the pathname string to the directory containing the release fixture called +name+. def release_fixture(name) return File.join(RELEASE_FIXTURES_DIR, name) end # Copy the release fixture called +name+ into the current working directory. def install_release_fixture(name) release_fixture(name) FileUtils.cp_r(release_fixture(name), name) end describe "module_tool" do include PuppetSpec::Files before do @tmp_confdir = Puppet[:confdir] = tmpdir("module_tool_test_confdir") @tmp_vardir = Puppet[:vardir] = tmpdir("module_tool_test_vardir") Puppet[:module_repository] = "http://forge.puppetlabs.com" @mytmpdir = Pathname.new(tmpdir("module_tool_test")) @options = {} @options[:install_dir] = @mytmpdir @options[:module_repository] = "http://forge.puppetlabs.com" end def build_and_install_module Puppet::Module::Tool::Applications::Generator.run(@full_name) Puppet::Module::Tool::Applications::Builder.run(@full_name) FileUtils.mv("#{@full_name}/pkg/#{@release_name}.tar.gz", "#{@release_name}.tar.gz") FileUtils.rm_rf(@full_name) Puppet::Module::Tool::Applications::Installer.run("#{@release_name}.tar.gz", @options) end # Return STDOUT and STDERR output generated from +block+ as it's run within a temporary test directory. def run(&block) mktestdircd do block.call end end before :all do @username = "myuser" @module_name = "mymodule" @full_name = "#{@username}-#{@module_name}" @version = "0.0.1" @release_name = "#{@full_name}-#{@version}" end before :each do Puppet.settings.stubs(:parse) Puppet::Module::Tool::Cache.clean end after :each do Puppet::Module::Tool::Cache.clean end describe "generate" do it "should generate a module if given a dashed name" do run do Puppet::Module::Tool::Applications::Generator.run(@full_name) File.directory?(@full_name).should == true modulefile = File.join(@full_name, "Modulefile") File.file?(modulefile).should == true metadata = Puppet::Module::Tool::Metadata.new Puppet::Module::Tool::Modulefile.evaluate(metadata, modulefile) metadata.full_name.should == @full_name metadata.username.should == @username metadata.name.should == @module_name end end it "should fail if given an undashed name" do run do lambda { Puppet::Module::Tool::Applications::Generator.run("invalid") }.should raise_error(SystemExit) end end it "should fail if directory already exists" do run do Puppet::Module::Tool::Applications::Generator.run(@full_name) lambda { Puppet::Module::Tool::Applications::Generator.run(@full_name) }.should raise_error(SystemExit) end end it "should return an array of Pathname objects representing paths of generated files" do run do return_value = Puppet::Module::Tool::Applications::Generator.run(@full_name) return_value.each do |generated_file| generated_file.should be_kind_of(Pathname) end return_value.should be_kind_of(Array) end end end describe "build" do it "should build a module in a directory" do run do Puppet::Module::Tool::Applications::Generator.run(@full_name) Puppet::Module::Tool::Applications::Builder.run(@full_name) File.directory?(File.join(@full_name, "pkg", @release_name)).should == true File.file?(File.join(@full_name, "pkg", @release_name + ".tar.gz")).should == true metadata_file = File.join(@full_name, "pkg", @release_name, "metadata.json") File.file?(metadata_file).should == true metadata = PSON.parse(File.read(metadata_file)) metadata["name"].should == @full_name metadata["version"].should == @version metadata["checksums"].should be_a_kind_of(Hash) metadata["dependencies"].should == [] metadata["types"].should == [] end end it "should build a module's checksums" do run do Puppet::Module::Tool::Applications::Generator.run(@full_name) Puppet::Module::Tool::Applications::Builder.run(@full_name) metadata_file = File.join(@full_name, "pkg", @release_name, "metadata.json") metadata = PSON.parse(File.read(metadata_file)) metadata["checksums"].should be_a_kind_of(Hash) modulefile_path = Pathname.new(File.join(@full_name, "Modulefile")) metadata["checksums"]["Modulefile"].should == Digest::MD5.hexdigest(modulefile_path.read) end end it "should build a module's types and providers" do run do name = "jamtur01-apache" install_release_fixture name Puppet::Module::Tool::Applications::Builder.run(name) metadata_file = File.join(name, "pkg", "#{name}-0.0.1", "metadata.json") metadata = PSON.parse(File.read(metadata_file)) metadata["types"].size.should == 1 type = metadata["types"].first type["name"].should == "a2mod" type["doc"].should == "Manage Apache 2 modules" type["parameters"].size.should == 1 type["parameters"].first.tap do |o| o["name"].should == "name" o["doc"].should == "The name of the module to be managed" end type["properties"].size.should == 1 type["properties"].first.tap do |o| o["name"].should == "ensure" o["doc"].should =~ /present.+absent/ end type["providers"].size.should == 1 type["providers"].first.tap do |o| o["name"].should == "debian" o["doc"].should =~ /Manage Apache 2 modules on Debian-like OSes/ end end end it "should build a module's dependencies" do run do Puppet::Module::Tool::Applications::Generator.run(@full_name) modulefile = File.join(@full_name, "Modulefile") dependency1_name = "anotheruser-anothermodule" dependency1_requirement = ">= 1.2.3" dependency2_name = "someuser-somemodule" dependency2_requirement = "4.2" dependency2_repository = "http://some.repo" File.open(modulefile, "a") do |handle| handle.puts "dependency '#{dependency1_name}', '#{dependency1_requirement}'" handle.puts "dependency '#{dependency2_name}', '#{dependency2_requirement}', '#{dependency2_repository}'" end Puppet::Module::Tool::Applications::Builder.run(@full_name) metadata_file = File.join(@full_name, "pkg", "#{@full_name}-#{@version}", "metadata.json") metadata = PSON.parse(File.read(metadata_file)) metadata['dependencies'].size.should == 2 metadata['dependencies'].sort_by{|t| t['name']}.tap do |dependencies| dependencies[0].tap do |dependency1| dependency1['name'].should == dependency1_name dependency1['version_requirement'].should == dependency1_requirement dependency1['repository'].should be_nil end dependencies[1].tap do |dependency2| dependency2['name'].should == dependency2_name dependency2['version_requirement'].should == dependency2_requirement dependency2['repository'].should == dependency2_repository end end end end it "should rebuild a module in a directory" do run do Puppet::Module::Tool::Applications::Generator.run(@full_name) Puppet::Module::Tool::Applications::Builder.run(@full_name) Puppet::Module::Tool::Applications::Builder.run(@full_name) end end it "should build a module in the current directory" do run do Puppet::Module::Tool::Applications::Generator.run(@full_name) Dir.chdir(@full_name) Puppet::Module::Tool::Applications::Builder.run(Puppet::Module::Tool.find_module_root(nil)) File.file?(File.join("pkg", @release_name + ".tar.gz")).should == true end end it "should fail to build a module without a Modulefile" do run do Puppet::Module::Tool::Applications::Generator.run(@full_name) FileUtils.rm(File.join(@full_name, "Modulefile")) lambda { Puppet::Module::Tool::Applications::Builder.run(Puppet::Module::Tool.find_module_root(@full_name)) }.should raise_error(ArgumentError) end end it "should fail to build a module directory that doesn't exist" do run do lambda { Puppet::Module::Tool::Applications::Builder.run(Puppet::Module::Tool.find_module_root(@full_name)) }.should raise_error(ArgumentError) end end it "should fail to build a module in the current directory that's not a module" do run do lambda { Puppet::Module::Tool::Applications::Builder.run(Puppet::Module::Tool.find_module_root(nil)) }.should raise_error(ArgumentError) end end it "should return a Pathname object representing the path to the release archive." do run do Puppet::Module::Tool::Applications::Generator.run(@full_name) Puppet::Module::Tool::Applications::Builder.run(@full_name).should be_kind_of(Pathname) end end end describe "search" do it "should display matching modules" do run do stub_repository_read 200, <<-HERE [ {"full_name": "cli", "version": "1.0"}, {"full_name": "web", "version": "2.0"} ] HERE Puppet::Module::Tool::Applications::Searcher.run("mymodule", @options).size.should == 2 end end it "should display no matches" do run do stub_repository_read 200, "[]" Puppet::Module::Tool::Applications::Searcher.run("mymodule", @options).should == [] end end it "should fail if can't get a connection" do run do stub_repository_read 500, "OH NOES!!1!" lambda { Puppet::Module::Tool::Applications::Searcher.run("mymodule", @options) }.should raise_error(SystemExit) end end it "should return an array of module metadata hashes" do run do results = <<-HERE [ {"full_name": "cli", "version": "1.0"}, {"full_name": "web", "version": "2.0"} ] HERE expected = [ {"version"=>"1.0", "full_name"=>"cli"}, {"version"=>"2.0", "full_name"=>"web"} ] stub_repository_read 200, results return_value = Puppet::Module::Tool::Applications::Searcher.run("mymodule", @options) return_value.should == expected return_value.should be_kind_of(Array) end end end describe "install" do it "should install a module to the puppet modulepath by default" do myothertmpdir = Pathname.new(tmpdir("module_tool_test_myothertmpdir")) run do @options[:install_dir] = myothertmpdir Puppet::Module::Tool.unstub(:install_dir) build_and_install_module File.directory?(myothertmpdir + @module_name).should == true File.file?(myothertmpdir + @module_name + 'metadata.json').should == true end end it "should install a module from a filesystem path" do run do build_and_install_module File.directory?(@mytmpdir + @module_name).should == true File.file?(@mytmpdir + @module_name + 'metadata.json').should == true end end it "should install a module from a webserver URL" do run do Puppet::Module::Tool::Applications::Generator.run(@full_name) Puppet::Module::Tool::Applications::Builder.run(@full_name) stub_cache_read File.read("#{@full_name}/pkg/#{@release_name}.tar.gz") FileUtils.rm_rf(@full_name) stub_installer_read <<-HERE {"file": "/foo/bar/#{@release_name}.tar.gz", "version": "#{@version}"} HERE Puppet::Module::Tool::Applications::Installer.run(@full_name, @options) File.directory?(@mytmpdir + @module_name).should == true File.file?(@mytmpdir + @module_name + 'metadata.json').should == true end end it "should install a module from a webserver URL using a version requirement" # TODO it "should fail if module isn't a slashed name" do run do lambda { Puppet::Module::Tool::Applications::Installer.run("invalid") }.should raise_error(SystemExit) end end it "should fail if module doesn't exist on webserver" do run do stub_installer_read "{}" lambda { Puppet::Module::Tool::Applications::Installer.run("not-found", @options) }.should raise_error(SystemExit) end end it "should fail gracefully when receiving invalid PSON" do pending "Implement PSON error wrapper" # TODO run do stub_installer_read "1/0" lambda { Puppet::Module::Tool::Applications::Installer.run("not-found") }.should raise_error(SystemExit) end end it "should fail if installing a module that's already installed" do run do name = "myuser-mymodule" Dir.mkdir name lambda { Puppet::Module::Tool::Applications::Installer.run(name) }.should raise_error(SystemExit) end end it "should return a Pathname object representing the path to the installed module" do run do Puppet::Module::Tool::Applications::Generator.run(@full_name) Puppet::Module::Tool::Applications::Builder.run(@full_name) stub_cache_read File.read("#{@full_name}/pkg/#{@release_name}.tar.gz") FileUtils.rm_rf(@full_name) stub_installer_read <<-HERE {"file": "/foo/bar/#{@release_name}.tar.gz", "version": "#{@version}"} HERE Puppet::Module::Tool::Applications::Installer.run(@full_name, @options).should be_kind_of(Pathname) end end end describe "clean" do require 'puppet/module_tool' it "should clean cache" do run do build_and_install_module Puppet::Module::Tool::Cache.base_path.directory?.should == true Puppet::Module::Tool::Applications::Cleaner.run Puppet::Module::Tool::Cache.base_path.directory?.should == false end end it "should return a status Hash" do run do build_and_install_module return_value = Puppet::Module::Tool::Applications::Cleaner.run return_value.should include(:msg) return_value.should include(:status) return_value.should be_kind_of(Hash) end end end describe "changes" do it "should return an array of modified files" do run do Puppet::Module::Tool::Applications::Generator.run(@full_name) Puppet::Module::Tool::Applications::Builder.run(@full_name) Dir.chdir("#{@full_name}/pkg/#{@release_name}") File.open("Modulefile", "a") do |handle| handle.puts handle.puts "# Added" end return_value = Puppet::Module::Tool::Applications::Checksummer.run(".") return_value.should include("Modulefile") return_value.should be_kind_of(Array) end end end end diff --git a/spec/unit/module_tool/repository_spec.rb b/spec/unit/module_tool/repository_spec.rb index e0feecf37..2b36902a5 100644 --- a/spec/unit/module_tool/repository_spec.rb +++ b/spec/unit/module_tool/repository_spec.rb @@ -1,54 +1,54 @@ require 'spec_helper' require 'net/http' require 'puppet/module_tool' describe Puppet::Module::Tool::Repository do describe 'instances' do before do @repository = described_class.new('http://fake.com') end - describe '#contact' do + describe '#make_http_request' do before do # Do a mock of the Proxy call so we can do proper expects for # Net::HTTP Net::HTTP.expects(:Proxy).returns(Net::HTTP) Net::HTTP.expects(:start) end context "when not given an :authenticate option" do it "should authenticate" do @repository.expects(:authenticate).never - @repository.contact(nil) + @repository.make_http_request(nil) end end context "when given an :authenticate option" do it "should authenticate" do @repository.expects(:authenticate) - @repository.contact(nil, :authenticate => true) + @repository.make_http_request(nil, :authenticate => true) end end end describe '#authenticate' do before do @request = stub @repository.expects(:prompt).twice end it "should set basic auth on the request" do @request.expects(:basic_auth) @repository.authenticate(@request) end end describe '#retrieve' do before do @uri = URI.parse('http://some.url.com') @repository.cache.expects(:retrieve).with(@uri) end it "should access the cache" do @repository.retrieve(@uri) end end end end