lib/berkshelf/locations/chef_api_location.rb in berkshelf-1.1.6 vs lib/berkshelf/locations/chef_api_location.rb in berkshelf-1.2.0.rc1

- old
+ new

@@ -1,9 +1,13 @@ module Berkshelf - # @author Jamie Winsor <jamie@vialstudios.com> + # @author Jamie Winsor <reset@riotgames.com> class ChefAPILocation class << self + def finalize + conn.terminate if conn.alive? + end + # @param [String] node_name # # @return [Boolean] def validate_node_name(node_name) node_name.is_a?(String) && !node_name.empty? @@ -73,17 +77,22 @@ # # @option options [String, Symbol] :chef_api # a URL to a Chef API. Alternatively the symbol :config can be provided # which will instantiate this location with the values found in your # Berkshelf configuration. - # @option options [String] :node_name + # @option options [String] :node_name (Berkshelf::Config.instance.chef.node_name) # the name of the client to use to communicate with the Chef API. - # Default: Chef::Config[:node_name] - # @option options [String] :client_key + # @option options [String] :client_key (Berkshelf::Config.instance.chef.client_key) # the filepath to the authentication key for the client - # Default: Chef::Config[:client_key] + # @option options [Boolean] :verify_ssl (Berkshelf::Config.instance.chef.ssl.verify) def initialize(name, version_constraint, options = {}) + options = options.reverse_merge( + client_key: Berkshelf::Config.instance.chef.client_key, + node_name: Berkshelf::Config.instance.chef.node_name, + verify_ssl: Berkshelf::Config.instance.ssl.verify + ) + @name = name @version_constraint = version_constraint @downloaded_status = false if options[:chef_api] == :knife @@ -95,83 +104,73 @@ if options[:chef_api] == :config unless Berkshelf::Config.instance.chef.node_name.present? && Berkshelf::Config.instance.chef.client_key.present? && Berkshelf::Config.instance.chef.chef_server_url.present? - raise ConfigurationError, "A Berkshelf configuration is required with a 'chef.client_key', 'chef.chef_server_Url', and 'chef.node_name' setting to install or upload cookbooks using 'chef_api :config'." + + msg = "A Berkshelf configuration is required with a 'chef.client_key', 'chef.chef_server_Url'," + msg << " and 'chef.node_name' setting to install or upload cookbooks using 'chef_api :config'." + + raise Berkshelf::ConfigurationError, msg end @node_name = Berkshelf::Config.instance.chef.node_name @client_key = Berkshelf::Config.instance.chef.client_key @uri = Berkshelf::Config.instance.chef.chef_server_url else @node_name = options[:node_name] @client_key = options[:client_key] @uri = options[:chef_api] end - @rest = Chef::REST.new(uri, node_name, client_key) + @conn = Ridley.new( + server_url: uri, + client_name: node_name, + client_key: client_key, + ssl: { + verify: options[:verify_ssl] + } + ) + + ObjectSpace.define_finalizer(self, self.class.method(:finalize).to_proc) end # @param [#to_s] destination # # @return [Berkshelf::CachedCookbook] def download(destination) - version, uri = target_version - scratch = download_files(download_manifest(uri)) + berks_path = File.join(destination, "#{name}-#{target_cookbook.version}") + + temp_path = target_cookbook.download + FileUtils.mv(temp_path, berks_path) - cb_path = File.join(destination, "#{name}-#{version}") - FileUtils.mv(scratch, cb_path) - - cached = CachedCookbook.from_store_path(cb_path) + cached = CachedCookbook.from_store_path(berks_path) validate_cached(cached) set_downloaded_status(true) cached end - # Returns a hash representing the cookbook versions on at a Chef API for location's cookbook. - # The keys are version strings and the values are URLs to download the cookbook version. + # Returns a Ridley::CookbookResource representing the cookbook that should be downloaded + # for this location # - # @example - # { - # "0.101.2" => "https://api.opscode.com/organizations/vialstudios/cookbooks/nginx/0.101.2", - # "0.101.5" => "https://api.opscode.com/organizations/vialstudios/cookbooks/nginx/0.101.5" - # } - # - # @return [Hash] - def versions - {}.tap do |versions| - rest.get_rest("cookbooks/#{name}").each do |name, data| - data["versions"].each do |version_info| - versions[version_info["version"]] = version_info["url"] - end - end - end - rescue Net::HTTPServerException => e - if e.response.code == "404" - raise CookbookNotFound, "Cookbook '#{name}' not found at chef_api: '#{uri}'" + # @return [Ridley::CookbookResource] + def target_cookbook + return @target_cookbook unless @target_cookbook.nil? + + @target_cookbook = if version_constraint + conn.cookbook.satisfy(name, version_constraint) else - raise + conn.cookbook.latest_version(name) end - end - # Returns an array where the first element is a string representing the latest version of - # the Cookbook and the second element is the download URL for that version. - # - # @example - # [ "0.101.2" => "https://api.opscode.com/organizations/vialstudios/cookbooks/nginx/0.101.2" ] - # - # @return [Array] - def latest_version - graph = Solve::Graph.new - versions.each do |version, url| - graph.artifacts(name, version) + if @target_cookbook.nil? + msg = "Cookbook '#{name}' found at #{self}" + msg << " that would satisfy constraint (#{version_constraint}" if version_constraint + raise CookbookNotFound, msg end - version = Solve.it!(graph, [name])[name] - - [ version, versions[version] ] + @target_cookbook end def to_hash super.merge(value: self.uri) end @@ -180,69 +179,13 @@ "#{self.class.location_key}: '#{uri}'" end private - attr_reader :rest + # @return [Ridley::Client] + attr_reader :conn - # Retrieve a cookbooks manifest from the given URL - # - # @param [String] uri - # - # @return [Hash] - def download_manifest(uri) - Chef::CookbookVersion.json_create(rest.get_rest(uri)).manifest - end - - # Returns an array containing the version and download URL for the cookbook version that - # should be downloaded for this location. - # - # @example - # [ "0.101.2" => "https://api.opscode.com/organizations/vialstudios/cookbooks/nginx/0.101.2" ] - # - # @return [Array] - def target_version - if version_constraint - solution = self.class.solve_for_constraint(version_constraint, versions) - - unless solution - raise NoSolution, "No cookbook version of '#{name}' found at #{self} that would satisfy constraint (#{version_constraint})." - end - - solution - else - latest_version - end - end - - # Download all of the files in the given manifest to the given destination. If no destination - # is provided a temporary directory will be created and the files will be downloaded to there. - # - # @note - # the manifest Hash is the same manifest that you get by sending the manifest message to - # an instance of Chef::CookbookVersion. - # - # @param [Hash] manifest - # @param [String] destination - # - # @return [String] - # the path to the directory containing the files - def download_files(manifest, destination = Dir.mktmpdir) - Chef::CookbookVersion::COOKBOOK_SEGMENTS.each do |segment| - next unless manifest.has_key?(segment) - manifest[segment].each do |segment_file| - dest = File.join(destination, segment_file['path'].gsub('/', File::SEPARATOR)) - FileUtils.mkdir_p(File.dirname(dest)) - rest.sign_on_redirect = false - tempfile = rest.get_rest(segment_file['url'], true) - FileUtils.mv(tempfile.path, dest) - end - end - - destination - end - # Validates the options hash given to the constructor. # # @param [Hash] options # # @raise [InvalidChefAPILocation] if any of the options are missing or their values do not @@ -254,10 +197,13 @@ missing_options = [:node_name, :client_key] - options.keys unless missing_options.empty? missing_options.collect! { |opt| "'#{opt}'" } - raise InvalidChefAPILocation, "Source '#{name}' is a 'chef_api' location with a URL for it's value but is missing options: #{missing_options.join(', ')}." + msg = "Source '#{name}' is a 'chef_api' location with a URL for it's value" + msg << " but is missing options: #{missing_options.join(', ')}." + + raise Berkshelf::InvalidChefAPILocation, msg end self.class.validate_node_name!(options[:node_name]) self.class.validate_client_key!(options[:client_key]) self.class.validate_uri!(options[:chef_api])