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.

Reply via email to