lib/berkshelf/source.rb
require_relative "api-client"
require_relative "chef_repo_universe"
require_relative "ssl_policies"
require "openssl" unless defined?(OpenSSL)
module Berkshelf
class Source
include Comparable
attr_accessor :type
attr_accessor :uri_string
attr_accessor :options
# @param [Berkshelf::Berksfile] berksfile
# @param [String, Berkshelf::SourceURI] source
def initialize(berksfile, source, **options)
@options = { timeout: api_timeout, open_timeout: [(api_timeout / 10), 3].max, ssl: {} }
@options.update(options)
case source
when String
# source "https://supermarket.chef.io/"
@type = :supermarket
@uri_string = source
when :chef_server
# source :chef_server
@type = :chef_server
@uri_string = options[:url] || Berkshelf::Config.instance.chef.chef_server_url
when Hash
# source type: uri, option: value, option: value
source = source.dup
@type, @uri_string = source.shift
@options.update(source)
end
# Default options for some source types.
case @type
when :chef_server
@options[:client_name] ||= Berkshelf::Config.instance.chef.node_name
@options[:client_key] ||= Berkshelf::Config.instance.chef.client_key
when :artifactory
@options[:api_key] ||= Berkshelf::Config.instance.chef.artifactory_api_key || ENV["ARTIFACTORY_API_KEY"]
when :chef_repo
@options[:path] = uri_string
# If given a relative path, expand it against the Berksfile's folder.
@options[:path] = File.expand_path(@options[:path], File.dirname(berksfile ? berksfile.filepath : Dir.pwd))
# Lie because this won't actually parse as a URI.
@uri_string = "file://#{@options[:path]}"
end
# Set some default SSL options.
Berkshelf::Config.instance.ssl.each do |key, value|
@options[:ssl][key.to_sym] = value unless @options[:ssl].include?(key.to_sym)
end
@options[:ssl][:cert_store] = ssl_policy.store if ssl_policy.store
@universe = nil
end
def ssl_policy
@ssl_policy ||= SSLPolicy.new
end
def api_client
@api_client ||= case type
when :chef_server
APIClient.chef_server(server_url: uri.to_s, **options)
when :artifactory
# Don't accidentally mutate the options.
client_options = options.dup
api_key = client_options.delete(:api_key)
APIClient.new(uri, headers: { "X-Jfrog-Art-Api" => api_key }, **client_options)
when :chef_repo
ChefRepoUniverse.new(uri_string, **options)
else
APIClient.new(uri, **options)
end
end
def uri
@uri ||= SourceURI.parse(uri_string)
end
# Forcefully obtain the universe from the API endpoint and assign it to {#universe}. This
# will reload the value of {#universe} even if it has been loaded before.
#
# @return [Array<APIClient::RemoteCookbook>]
def build_universe
@universe = api_client.universe
rescue => ex
@universe = []
raise ex
end
# Return the universe from the API endpoint.
#
# This is lazily loaded so the universe will be retrieved from the API endpoint on the first
# call and cached for future calls. Send the {#build_universe} message if you want to reload
# the cached universe.
#
# @return [Array<APIClient::RemoteCookbook>]
def universe
@universe || build_universe
end
# @param [String] name
# @param [String] version
#
# @return [APIClient::RemoteCookbook]
def cookbook(name, version)
universe.find { |cookbook| cookbook.name == name && cookbook.version == version }
end
# The list of remote cookbooks that match the given query.
#
# @param [String] name
#
# @return [Array<APIClient::RemoteCookbook]
def search(name)
universe
.select { |cookbook| cookbook.name =~ Regexp.new(name) }
.group_by(&:name)
.collect { |_, versions| versions.max_by { |v| Semverse::Version.new(v.version) } }
end
# Determine if this source is a "default" source, as defined in the
# {Berksfile}.
#
# @return [true, false]
# true if this a default source, false otherwise
def default?
@default_ ||= uri.host == URI.parse(Berksfile::DEFAULT_API_URL).host
end
# @param [String] name
#
# @return [APIClient::RemoteCookbook]
def latest(name)
versions(name).max
end
# @param [String] name
#
# @return [Array<APIClient::RemoteCookbook>]
def versions(name)
universe.select { |cookbook| cookbook.name == name }
end
def to_s
case type
when :supermarket
uri.to_s
when :chef_repo
options[:path]
else
"#{type}: #{uri}"
end
end
def inspect
"#<#{self.class.name} #{type}: #{uri.to_s.inspect}, #{options.map { |k, v| "#{k}: #{v.inspect}" }.join(", ")}>"
end
def hash
[type, uri_string, options].hash
end
def ==(other)
return false unless other.is_a?(self.class)
type == other.type && uri == other.uri
end
private
def api_timeout
Berkshelf::Config.instance.api.timeout.to_i
end
end
end