Please review pull request #477: (#12244) All forge interactions should be centralized opened by (kelseyhightower)
Description:
Before this patch each module application makes direct connection to the
Puppet Forge. This is a maintenance nightmare, any changes to the
Forge API requires changes to all the module applications.
This patch re-factors how we communicate with the Forge. All module
application now use the interface exposed by the lib/puppet/forge.rb
module.
This patch also includes tests for the new forge.rb module, and updates
others to reflect the new behaviour.
- Opened: Wed Feb 08 20:51:47 UTC 2012
- Based on: puppetlabs:2.7.x (0b26142d19f72f17a9fe0f9dc6fe6031befc1909)
- Requested merge: kelseyhightower:ticket/2.7.x/12244_centralize_forge_interation (e8c84e2b278874f3cce0daea6ce92404cbac7a2d)
Diff follows:
diff --git a/lib/puppet/forge.rb b/lib/puppet/forge.rb
new file mode 100644
index 0000000..0d3ff31
--- /dev/null
+++ b/lib/puppet/forge.rb
@@ -0,0 +1,150 @@
+require 'net/http'
+require 'open-uri'
+require 'pathname'
+require 'uri'
+require 'puppet/forge/cache'
+require 'puppet/forge/repository'
+
+module Puppet::Forge
+ class Forge
+ def initialize(url=""
+ @uri = URI.parse(url)
+ end
+
+ def search(term)
+ request = Net::HTTP::Get.new("/modules.json?q=#{URI.escape(term)}")
+ response = repository.make_http_request(request)
+
+ case response.code
+ when "200"
+ matches = PSON.parse(response.body)
+ else
+ raise RuntimeError, "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:
+ #
+ # [
+ # {
+ # "author" => "puppetlabs",
+ # "name" => "bacula",
+ # "tag_list" => ["backup", "bacula"],
+ # "releases" => [{"version"=>"0.0.1"}, {"version"=>"0.0.2"}],
+ # "full_name" => "puppetlabs/bacula",
+ # "version" => "0.0.2",
+ # "project_url" => "http://github.com/puppetlabs/puppetlabs-bacula",
+ # "desc" => "bacula"
+ # }
+ # ]
+ #
+ matches
+ end
+
+ def get_release_package(params)
+ cache_path = nil
+ case params[:source]
+ when :repository
+ if not (params[:author] && params[:modname])
+ raise ArgumentError, ":author and :modename required"
+ end
+ cache_path = get_release_package_from_repository(params[:author], params[:modname], params[:version])
+ when :filesystem
+ if not params[:filename]
+ raise ArgumentError, ":filename required"
+ end
+ cache_path = get_release_package_from_filesystem(params[:filename])
+ else
+ raise ArgumentError, "Could not determine installation source"
+ end
+
+ # Return a Pathname object representing the path to the module
+ # release package in the `Puppet.settings[:module_working_dir]`.
+ cache_path
+ end
+
+ def get_releases(author, modname)
+ request_string = "/#{author}/#{modname}"
+
+ begin
+ response = repository.make_http_request(request_string)
+ rescue => e
+ raise ArgumentError, "Could not find a release for this module (#{e.message})"
+ end
+
+ results = PSON.parse(response.body)
+ # At this point releases look like this:
+ # [{"version" => "0.0.1"}, {"version" => "0.0.2"},{"version" => "0.0.3"}]
+ #
+ # Lets fix this up a bit and return something like this to the caller
+ # ["0.0.1", "0.0.2", "0.0.3"]
+ results["releases"].collect {|release| release["version"]}
+ end
+
+ private
+
+ # Locate and download a module release package from the remote forge
+ # repository into the `Puppet.settings[:module_working_dir]`. Do not
+ # unpack it, just return the location of the package on disk.
+ def get_release_package_from_repository(author, modname, version=nil)
+ release = get_release(author, modname, version)
+ if release['file']
+ begin
+ cache_path = repository.retrieve(release['file'])
+ rescue OpenURI::HTTPError => e
+ raise RuntimeError, "Could not download module: #{e.message}"
+ end
+ else
+ raise RuntimeError, "Malformed response from module repository."
+ end
+
+ cache_path
+ end
+
+ # Locate a module release package on the local filesystem and move it
+ # into the `Puppet.settings[:module_working_dir]`. Do not unpack it, just
+ # return the location of the package on disk.
+ def get_release_package_from_filesystem(filename)
+ if File.exist?(File.expand_path(filename))
+ repository = Repository.new('file:///')
+ uri = URI.parse("file://#{URI.escape(File.expand_path(filename))}")
+ cache_path = repository.retrieve(uri)
+ else
+ raise ArgumentError, "File does not exists: #{filename}"
+ end
+
+ cache_path
+ end
+
+ def repository
+ @repository ||= Puppet::Forge::Repository.new(@uri)
+ end
+
+ # Connect to the remote repository and locate a specific module release
+ # by author/name combination. If a version requirement is specified, search
+ # for that exact version, or grab the latest release available.
+ def get_release(author, modname, version_requirement=nil)
+ request_string = "/users/#{author}/modules/#{modname}/releases/find.json"
+ if version_requirement
+ request_string + "?version=#{URI.escape(version_requirement)}"
+ end
+ request = Net::HTTP::Get.new(request_string)
+
+ begin
+ response = repository.make_http_request(request)
+ rescue => e
+ raise ArgumentError, "Could not find a release for this module (#{e.message})"
+ end
+
+ # Return the following response to the caller:
+ #
+ # {"file"=>"/system/releases/p/puppetlabs/puppetlabs-apache-0.0.3.tar.gz", "version"=>"0.0.3"}
+ #
+ PSON.parse(response.body)
+ end
+ end
+end
+
diff --git a/lib/puppet/forge/cache.rb b/lib/puppet/forge/cache.rb
new file mode 100644
index 0000000..253f247
--- /dev/null
+++ b/lib/puppet/forge/cache.rb
@@ -0,0 +1,55 @@
+require 'uri'
+
+module Puppet::Forge
+ # = Cache
+ #
+ # Provides methods for reading files from local cache, filesystem or network.
+ class Cache
+
+ # Instantiate new cahe for the +repositry+ instance.
+ def initialize(repository, options = {})
+ @repository = repository
+ @options = options
+ end
+
+ # Return filename retrieved from +uri+ instance. Will download this file and
+ # cache it if needed.
+ #
+ # TODO: Add checksum support.
+ # TODO: Add error checking.
+ def retrieve(url)
+ (path + File.basename(url.to_s)).tap do |cached_file|
+ uri = url.is_a?(::URI) ? url : ::URI.parse(url)
+ unless cached_file.file?
+ if uri.scheme == 'file'
+ FileUtils.cp(URI.unescape(uri.path), cached_file)
+ else
+ # TODO: Handle HTTPS; probably should use repository.contact
+ data = ""
+ cached_file.open('wb') { |f| f.write data }
+ end
+ end
+ end
+ end
+
+ # Return contents of file at the given URI's +uri+.
+ def read_retrieve(uri)
+ return uri.read
+ end
+
+ # Return Pathname for repository's cache directory, create it if needed.
+ def path
+ return @path ||= (self.class.base_path + @repository.cache_key).tap{ |o| o.mkpath }
+ end
+
+ # Return the base Pathname for all the caches.
+ def self.base_path
+ Pathname(Puppet.settings[:module_working_dir]) + 'cache'
+ end
+
+ # Clean out all the caches.
+ def self.clean
+ base_path.rmtree if base_path.exist?
+ end
+ end
+end
diff --git a/lib/puppet/forge/repository.rb b/lib/puppet/forge/repository.rb
new file mode 100644
index 0000000..2e10149
--- /dev/null
+++ b/lib/puppet/forge/repository.rb
@@ -0,0 +1,121 @@
+require 'net/http'
+require 'digest/sha1'
+require 'uri'
+
+require 'puppet/module_tool/utils'
+
+module Puppet::Forge
+ # Directory names that should not be checksummed.
+ ARTIFACTS = ['pkg', /^\./, /^~/, /^#/, 'coverage']
+ FULL_MODULE_NAME_PATTERN = /\A([^-\/|.]+)[-|\/](.+)\z/
+ REPOSITORY_URL = Puppet.settings[:module_repository]
+
+ # = Repository
+ #
+ # This class is a file for accessing remote repositories with modules.
+ class Repository
+ include Puppet::Module::Tool::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=""
+ @uri = url.is_a?(::URI) ? url : ::URI.parse(url)
+ @cache = Cache.new(self)
+ end
+
+ # Read HTTP proxy configurationm from Puppet's config file, or the
+ # http_proxy environment variable.
+ def http_proxy_env
+ proxy_env = ENV["http_proxy"] || ENV["HTTP_PROXY"] || nil
+ begin
+ return URI.parse(proxy_env) if proxy_env
+ rescue URI::InvalidURIError
+ return nil
+ end
+ return nil
+ end
+
+ def http_proxy_host
+ env = http_proxy_env
+
+ if env and env.host then
+ return env.host
+ end
+
+ if Puppet.settings[:http_proxy_host] == 'none'
+ return nil
+ end
+
+ return Puppet.settings[:http_proxy_host]
+ end
+
+ def http_proxy_port
+ env = http_proxy_env
+
+ if env and env.port then
+ return env.port
+ end
+
+ return Puppet.settings[:http_proxy_port]
+ end
+
+ # Return a Net::HTTPResponse read for this +request+.
+ #
+ # Options:
+ # * :authenticate => Request authentication on the terminal. Defaults to false.
+ 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_response(request)
+ end
+
+ # Return a Net::HTTPResponse read from this HTTPRequest +request+.
+ def read_response(request)
+ begin
+ Net::HTTP::Proxy(
+ http_proxy_host,
+ http_proxy_port
+ ).start(@uri.host, @uri.port) do |http|
+ http.request(request)
+ end
+ rescue Errno::ECONNREFUSED, SocketError
+ raise RuntimeError, "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/lib/puppet/module_tool.rb b/lib/puppet/module_tool.rb
index c11fd58..6d8d4db 100644
--- a/lib/puppet/module_tool.rb
+++ b/lib/puppet/module_tool.rb
@@ -37,42 +37,6 @@ def self.username_and_modname_from(full_module_name)
end
end
- # Read HTTP proxy configurationm from Puppet's config file, or the
- # http_proxy environment variable.
- def self.http_proxy_env
- proxy_env = ENV["http_proxy"] || ENV["HTTP_PROXY"] || nil
- begin
- return URI.parse(proxy_env) if proxy_env
- rescue URI::InvalidURIError
- return nil
- end
- return nil
- end
-
- def self.http_proxy_host
- env = http_proxy_env
-
- if env and env.host then
- return env.host
- end
-
- if Puppet.settings[:http_proxy_host] == 'none'
- return nil
- end
-
- return Puppet.settings[:http_proxy_host]
- end
-
- def self.http_proxy_port
- env = http_proxy_env
-
- if env and env.port then
- return env.port
- end
-
- return Puppet.settings[:http_proxy_port]
- end
-
def self.find_module_root(path)
for dir in [path, Dir.pwd].compact
if File.exist?(File.join(dir, 'Modulefile'))
@@ -87,11 +51,11 @@ def self.find_module_root(path)
# Load remaining libraries
require 'puppet/module_tool/applications'
-require 'puppet/module_tool/cache'
require 'puppet/module_tool/checksums'
require 'puppet/module_tool/contents_description'
require 'puppet/module_tool/dependency'
require 'puppet/module_tool/metadata'
require 'puppet/module_tool/modulefile'
-require 'puppet/module_tool/repository'
require 'puppet/module_tool/skeleton'
+require 'puppet/forge/cache'
+require 'puppet/forge'
diff --git a/lib/puppet/module_tool/applications/application.rb b/lib/puppet/module_tool/applications/application.rb
index 5f8c0c4..43d5c04 100644
--- a/lib/puppet/module_tool/applications/application.rb
+++ b/lib/puppet/module_tool/applications/application.rb
@@ -16,10 +16,6 @@ def initialize(options = {})
@options = options
end
- def repository
- @repository ||= Repository.new(@options[:module_repository])
- end
-
def run
raise NotImplementedError, "Should be implemented in child classes."
end
diff --git a/lib/puppet/module_tool/applications/cleaner.rb b/lib/puppet/module_tool/applications/cleaner.rb
index c42687f..b811983 100644
--- a/lib/puppet/module_tool/applications/cleaner.rb
+++ b/lib/puppet/module_tool/applications/cleaner.rb
@@ -2,7 +2,7 @@ module Puppet::Module::Tool
module Applications
class Cleaner < Application
def run
- Puppet::Module::Tool::Cache.clean
+ Puppet::Forge::Cache.clean
# Return a status Hash containing the status of the clean command
# and a status message. This return value is used by the module_tool
diff --git a/lib/puppet/module_tool/applications/installer.rb b/lib/puppet/module_tool/applications/installer.rb
index ad423bd..d76e0e3 100644
--- a/lib/puppet/module_tool/applications/installer.rb
+++ b/lib/puppet/module_tool/applications/installer.rb
@@ -7,22 +7,26 @@ module Applications
class Installer < Application
def initialize(name, options = {})
+ @forge = Puppet::Forge::Forge.new
+ @install_params = {}
+
if File.exist?(name)
if File.directory?(name)
# TODO Unify this handling with that of Unpacker#check_clobber!
raise ArgumentError, "Module already installed: #{name}"
end
- @source = :filesystem
@filename = File.expand_path(name)
+ @install_params[:source] = :filesystem
+ @install_params[:filename] = @filename
parse_filename!
else
- @source = :repository
+ @install_params[:source] = :repository
begin
- @username, @module_name = Puppet::Module::Tool::username_and_modname_from(name)
+ @install_params[:author], @install_params[:modname] = Puppet::Module::Tool::username_and_modname_from(name)
rescue ArgumentError
raise "Could not install module with invalid name: #{name}"
end
- @version_requirement = options[:version]
+ @install_params[:version_requirement] = options[:version]
end
super(options)
end
@@ -32,27 +36,9 @@ def force?
end
def run
- case @source
- when :repository
- if match['file']
- begin
- cache_path = repository.retrieve(match['file'])
- rescue OpenURI::HTTPError => e
- raise RuntimeError, "Could not install module: #{e.message}"
- end
- module_dir = Unpacker.run(cache_path, options)
- else
- raise RuntimeError, "Malformed response from module repository."
- end
- when :filesystem
- repository = Repository.new('file:///')
- uri = URI.parse("file://#{URI.escape(File.expand_path(@filename))}")
- cache_path = repository.retrieve(uri)
- module_dir = Unpacker.run(cache_path, options)
- else
- raise ArgumentError, "Could not determine installation source"
- end
+ cache_path = @forge.get_release_package(@install_params)
+ module_dir = Unpacker.run(cache_path, options)
# Return the Pathname object representing the path to the installed
# module. This return value is used by the module_tool face install
# action, and displayed to on the console.
@@ -63,27 +49,6 @@ def run
#
module_dir
end
-
- private
-
- def match
- return @match ||= begin
- url = "" + "/users/#{@username}/modules/#{@module_name}/releases/find.json"
- if @version_requirement
- url.query = "version=#{URI.escape(@version_requirement)}"
- end
- begin
- raw_result = read_match(url)
- rescue => e
- raise ArgumentError, "Could not find a release for this module (#{e.message})"
- end
- @match = PSON.parse(raw_result)
- end
- end
-
- def read_match(url)
- return url.read
- end
end
end
end
diff --git a/lib/puppet/module_tool/applications/searcher.rb b/lib/puppet/module_tool/applications/searcher.rb
index 0a2267b..97028cd 100644
--- a/lib/puppet/module_tool/applications/searcher.rb
+++ b/lib/puppet/module_tool/applications/searcher.rb
@@ -4,36 +4,12 @@ class Searcher < Application
def initialize(term, options = {})
@term = term
+ @forge = Puppet::Forge::Forge.new
super(options)
end
def run
- request = Net::HTTP::Get.new("/modules.json?q=#{URI.escape(@term)}")
- response = repository.make_http_request(request)
- case response
- when Net::HTTPOK
- matches = PSON.parse(response.body)
- else
- raise RuntimeError, "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" # full_name comes back from
- # } # API all to the forge.
- # ]
- #
- matches
+ @forge.search(@term)
end
end
end
diff --git a/lib/puppet/module_tool/applications/unpacker.rb b/lib/puppet/module_tool/applications/unpacker.rb
index 6dd1fca..119eaf3 100644
--- a/lib/puppet/module_tool/applications/unpacker.rb
+++ b/lib/puppet/module_tool/applications/unpacker.rb
@@ -36,7 +36,7 @@ def tag_revision
def extract_module_to_install_dir
delete_existing_installation_or_abort!
- build_dir = Puppet::Module::Tool::Cache.base_path + "tmp-unpacker-#{Digest::SHA1.hexdigest(@filename.basename.to_s)}"
+ build_dir = Puppet::Forge::Cache.base_path + "tmp-unpacker-#{Digest::SHA1.hexdigest(@filename.basename.to_s)}"
build_dir.mkpath
begin
Puppet.notice "Installing #{@filename.basename} to #{@module_dir.expand_path}"
diff --git a/lib/puppet/module_tool/cache.rb b/lib/puppet/module_tool/cache.rb
deleted file mode 100644
index b254780..0000000
--- a/lib/puppet/module_tool/cache.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-require 'uri'
-
-module Puppet::Module::Tool
-
- # = Cache
- #
- # Provides methods for reading files from local cache, filesystem or network.
- class Cache
-
- # Instantiate new cahe for the +repositry+ instance.
- def initialize(repository, options = {})
- @repository = repository
- @options = options
- end
-
- # Return filename retrieved from +uri+ instance. Will download this file and
- # cache it if needed.
- #
- # TODO: Add checksum support.
- # TODO: Add error checking.
- def retrieve(url)
- (path + File.basename(url.to_s)).tap do |cached_file|
- uri = url.is_a?(::URI) ? url : ::URI.parse(url)
- unless cached_file.file?
- if uri.scheme == 'file'
- FileUtils.cp(URI.unescape(uri.path), cached_file)
- else
- # TODO: Handle HTTPS; probably should use repository.contact
- data = ""
- cached_file.open('wb') { |f| f.write data }
- end
- end
- end
- end
-
- # Return contents of file at the given URI's +uri+.
- def read_retrieve(uri)
- return uri.read
- end
-
- # Return Pathname for repository's cache directory, create it if needed.
- def path
- return @path ||= (self.class.base_path + @repository.cache_key).tap{ |o| o.mkpath }
- end
-
- # Return the base Pathname for all the caches.
- def self.base_path
- Pathname(Puppet.settings[:module_working_dir]) + 'cache'
- end
-
- # Clean out all the caches.
- def self.clean
- base_path.rmtree if base_path.exist?
- end
- end
-end
diff --git a/lib/puppet/module_tool/dependency.rb b/lib/puppet/module_tool/dependency.rb
index bb55f59..d0eead1 100644
--- a/lib/puppet/module_tool/dependency.rb
+++ b/lib/puppet/module_tool/dependency.rb
@@ -10,7 +10,7 @@ def initialize(full_module_name, version_requirement = nil, repository = nil)
# TODO: add error checking, the next line raises ArgumentError when +full_module_name+ is invalid
@username, @name = Puppet::Module::Tool.username_and_modname_from(full_module_name)
@version_requirement = version_requirement
- @repository = repository ? Repository.new(repository) : nil
+ @repository = repository ? Puppet::Forge::Repository.new(repository) : nil
end
# Return PSON representation of this data.
diff --git a/lib/puppet/module_tool/repository.rb b/lib/puppet/module_tool/repository.rb
deleted file mode 100644
index 7f0ad84..0000000
--- a/lib/puppet/module_tool/repository.rb
+++ /dev/null
@@ -1,79 +0,0 @@
-require 'net/http'
-require 'digest/sha1'
-require 'uri'
-
-module Puppet::Module::Tool
-
- # = Repository
- #
- # This class is a file for accessing remote repositories with modules.
- class Repository
- 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=""
- @uri = url.is_a?(::URI) ? url : ::URI.parse(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 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_response(request)
- end
-
- # Return a Net::HTTPResponse read from this HTTPRequest +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 RuntimeError, "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 1067bfa..bd3b126 100644
--- a/spec/integration/module_tool_spec.rb
+++ b/spec/integration/module_tool_spec.rb
@@ -8,7 +8,7 @@ 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_response).returns(response)
+ Puppet::Forge::Repository.any_instance.stubs(:read_response).returns(response)
end
def stub_installer_read(body)
@@ -16,7 +16,7 @@ def stub_installer_read(body)
end
def stub_cache_read(body)
- Puppet::Module::Tool::Cache.any_instance.stubs(:read_retrieve).returns(body)
+ Puppet::Forge::Cache.any_instance.stubs(:read_retrieve).returns(body)
end
# Return path to temparory directory for testing.
@@ -98,11 +98,11 @@ def run(&block)
before :each do
Puppet.settings.stubs(:parse)
- Puppet::Module::Tool::Cache.clean
+ Puppet::Forge::Cache.clean
end
after :each do
- Puppet::Module::Tool::Cache.clean
+ Puppet::Forge::Cache.clean
end
describe "generate" do
@@ -376,9 +376,8 @@ def run(&block)
stub_cache_read File.read("#{@full_module_name}/pkg/#{@release_name}.tar.gz")
FileUtils.rm_rf(@full_module_name)
- stub_installer_read <<-HERE
- {"file": "/foo/bar/#{@release_name}.tar.gz", "version": "#{@version}"}
- HERE
+ release = {"file" => "/foo/bar/#{@release_name}.tar.gz", "version" => "#{@version}"}
+ Puppet::Forge::Forge.any_instance.stubs(:get_release).returns(release)
Puppet::Module::Tool::Applications::Installer.run(@full_module_name, @options)
@@ -426,9 +425,8 @@ def run(&block)
stub_cache_read File.read("#{@full_module_name}/pkg/#{@release_name}.tar.gz")
FileUtils.rm_rf(@full_module_name)
- stub_installer_read <<-HERE
- {"file": "/foo/bar/#{@release_name}.tar.gz", "version": "#{@version}"}
- HERE
+ release = {"file" => "/foo/bar/#{@release_name}.tar.gz", "version" => "#{@version}"}
+ Puppet::Forge::Forge.any_instance.stubs(:get_release).returns(release)
Puppet::Module::Tool::Applications::Installer.run(@full_module_name, @options).should be_kind_of(Pathname)
end
@@ -441,9 +439,9 @@ def run(&block)
it "should clean cache" do
run do
build_and_install_module
- Puppet::Module::Tool::Cache.base_path.directory?.should == true
+ Puppet::Forge::Cache.base_path.directory?.should == true
Puppet::Module::Tool::Applications::Cleaner.run
- Puppet::Module::Tool::Cache.base_path.directory?.should == false
+ Puppet::Forge::Cache.base_path.directory?.should == false
end
end
diff --git a/spec/unit/forge/repository_spec.rb b/spec/unit/forge/repository_spec.rb
new file mode 100644
index 0000000..6d8ce38
--- /dev/null
+++ b/spec/unit/forge/repository_spec.rb
@@ -0,0 +1,86 @@
+require 'spec_helper'
+require 'net/http'
+require 'puppet/forge/repository'
+require 'puppet/forge/cache'
+
+describe Puppet::Forge::Repository do
+ describe 'instances' do
+
+ let(:repository) { Puppet::Forge::Repository.new('http://fake.com') }
+
+ 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.make_http_request(nil)
+ end
+ end
+ context "when given an :authenticate option" do
+ it "should authenticate" do
+ repository.expects(:authenticate)
+ repository.make_http_request(nil, :authenticate => true)
+ end
+ end
+ end
+
+ describe '#authenticate' do
+ it "should set basic auth on the request" do
+ authenticated_request = stub
+ authenticated_request.expects(:basic_auth)
+ repository.expects(:prompt).twice
+ repository.authenticate(authenticated_request)
+ end
+ end
+
+ describe '#retrieve' do
+ before do
+ @uri = URI.parse('http://some.url.com')
+ end
+
+ it "should access the cache" do
+ repository.cache.expects(:retrieve).with(@uri)
+ repository.retrieve(@uri)
+ end
+ end
+
+ describe 'http_proxy support' do
+ before :each do
+ ENV["http_proxy"] = nil
+ end
+
+ after :each do
+ ENV["http_proxy"] = nil
+ end
+
+ it "should support environment variable for port and host" do
+ ENV["http_proxy"] = "http://test.com:8011"
+ repository.http_proxy_host.should == "test.com"
+ repository.http_proxy_port.should == 8011
+ end
+
+ it "should support puppet configuration for port and host" do
+ ENV["http_proxy"] = nil
+ Puppet.settings.stubs(:[]).with(:http_proxy_host).returns('test.com')
+ Puppet.settings.stubs(:[]).with(:http_proxy_port).returns(7456)
+
+ repository.http_proxy_port.should == 7456
+ repository.http_proxy_host.should == "test.com"
+ end
+
+ it "should use environment variable before puppet settings" do
+ ENV["http_proxy"] = "http://test1.com:8011"
+ Puppet.settings.stubs(:[]).with(:http_proxy_host).returns('test2.com')
+ Puppet.settings.stubs(:[]).with(:http_proxy_port).returns(7456)
+
+ repository.http_proxy_host.should == "test1.com"
+ repository.http_proxy_port.should == 8011
+ end
+ end
+ end
+end
diff --git a/spec/unit/forge_spec.rb b/spec/unit/forge_spec.rb
new file mode 100644
index 0000000..905f1bd
--- /dev/null
+++ b/spec/unit/forge_spec.rb
@@ -0,0 +1,114 @@
+require 'spec_helper'
+require 'puppet/forge'
+require 'net/http'
+
+describe Puppet::Forge::Forge do
+ before do
+ Puppet::Forge::Repository.any_instance.stubs(:make_http_request).returns(response)
+ Puppet::Forge::Repository.any_instance.stubs(:retrieve).returns("/tmp/foo")
+ end
+
+ let(:forge) { forge = Puppet::Forge::Forge.new('http://forge.puppetlabs.com') }
+
+ describe "the behavior of the search method" do
+ context "when there are matches for the search term" do
+ before do
+ Puppet::Forge::Repository.any_instance.stubs(:make_http_request).returns(response)
+ end
+
+ let(:response) { stub(:body => response_body, :code => '200') }
+ let(:response_body) do
+ <<-EOF
+ [
+ {
+ "author": "puppetlabs",
+ "name": "bacula",
+ "tag_list": ["backup", "bacula"],
+ "releases": [{"version": "0.0.1"}, {"version": "0.0.2"}],
+ "full_name": "puppetlabs/bacula",
+ "version": "0.0.2",
+ "project_url": "http://github.com/puppetlabs/puppetlabs-bacula",
+ "desc": "bacula"
+ }
+ ]
+ EOF
+ end
+
+ it "should return a list of matches from the forge" do
+ forge.search('bacula').should == PSON.load(response_body)
+ end
+ end
+
+ context "when the connection to the forge fails" do
+ let(:response) { stub(:body => '[]', :code => '404') }
+
+ it "should raise an error" do
+ lambda { forge.search('bacula') }.should raise_error RuntimeError
+ end
+ end
+ end
+
+ describe "the behavior of the get_release_package method" do
+
+ let(:response) do
+ response = mock()
+ response.stubs(:body).returns('{"file": "/system/releases/p/puppetlabs/puppetlabs-apache-0.0.3.tar.gz", "version": "0.0.3"}')
+ response
+ end
+
+ context "when source is not filesystem or repository" do
+ it "should raise an error" do
+ params = { :source => 'foo' }
+ lambda { forge.get_release_package(params) }.should
+ raise_error(ArgumentError, "Could not determine installation source")
+ end
+ end
+
+ context "when the source is a repository" do
+ let(:params) do
+ {
+ :source => :repository,
+ :author => 'fakeauthor',
+ :modname => 'fakemodule',
+ :version => '0.0.1'
+ }
+ end
+
+ it "should require author" do
+ params.delete(:author)
+ lambda { forge.get_release_package(params) }.should
+ raise_error(ArgumentError, ":author and :modename required")
+ end
+
+ it "should require modname" do
+ params.delete(:modname)
+ lambda { forge.get_release_package(params) }.should
+ raise_error(ArgumentError, ":author and :modename required")
+ end
+
+ it "should download the release package" do
+ forge.get_release_package(params).should == "/tmp/foo"
+ end
+ end
+
+ context "when the source is a filesystem" do
+ it "should require filename" do
+ params = { :source => :filesystem }
+ lambda { forge.get_release_package(params) }.should
+ raise_error(ArgumentError, ":filename required")
+ end
+ end
+ end
+
+ describe "the behavior of the get_releases method" do
+ let(:response) do
+ response = mock()
+ response.stubs(:body).returns('{"releases": [{"version": "0.0.1"}, {"version": "0.0.2"}, {"version": "0.0.3"}]}')
+ response
+ end
+
+ it "should return a list of module releases" do
+ forge.get_releases('fakeauthor', 'fakemodule').should == ["0.0.1", "0.0.2", "0.0.3"]
+ end
+ end
+end
diff --git a/spec/unit/module_tool/repository_spec.rb b/spec/unit/module_tool/repository_spec.rb
deleted file mode 100644
index 69be166..0000000
--- a/spec/unit/module_tool/repository_spec.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-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 '#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.make_http_request(nil)
- end
- end
- context "when given an :authenticate option" do
- it "should authenticate" do
- @repository.expects(:authenticate)
- @repository.make_http_request(nil, :authenticate => true)
- end
- end
- end
-
- describe '#authenticate' do
- it "should set basic auth on the request" do
- authenticated_request = stub
- authenticated_request.expects(:basic_auth)
- @repository.expects(:prompt).twice
- @repository.authenticate(authenticated_request)
- end
- end
-
- describe '#retrieve' do
- before do
- @uri = URI.parse('http://some.url.com')
- end
-
- it "should access the cache" do
- @repository.cache.expects(:retrieve).with(@uri)
- @repository.retrieve(@uri)
- end
- end
- end
-end
diff --git a/spec/unit/module_tool_spec.rb b/spec/unit/module_tool_spec.rb
index 15ca6c7..86d421e 100644
--- a/spec/unit/module_tool_spec.rb
+++ b/spec/unit/module_tool_spec.rb
@@ -2,37 +2,4 @@
require 'puppet/module_tool'
describe Puppet::Module::Tool do
- describe 'http_proxy support' do
- before :each do
- ENV["http_proxy"] = nil
- end
-
- after :each do
- ENV["http_proxy"] = nil
- end
-
- it "should support environment variable for port and host" do
- ENV["http_proxy"] = "http://test.com:8011"
- described_class.http_proxy_host.should == "test.com"
- described_class.http_proxy_port.should == 8011
- end
-
- it "should support puppet configuration for port and host" do
- ENV["http_proxy"] = nil
- Puppet.settings.stubs(:[]).with(:http_proxy_host).returns('test.com')
- Puppet.settings.stubs(:[]).with(:http_proxy_port).returns(7456)
-
- described_class.http_proxy_port.should == 7456
- described_class.http_proxy_host.should == "test.com"
- end
-
- it "should use environment variable before puppet settings" do
- ENV["http_proxy"] = "http://test1.com:8011"
- Puppet.settings.stubs(:[]).with(:http_proxy_host).returns('test2.com')
- Puppet.settings.stubs(:[]).with(:http_proxy_port).returns(7456)
-
- described_class.http_proxy_host.should == "test1.com"
- described_class.http_proxy_port.should == 8011
- end
- end
end
-- 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.
