module Ridley # @author Jamie Winsor class Connection class << self def sync(options, &block) new(options).sync(&block) end # @raise [ArgumentError] # # @return [Boolean] def validate_options(options) missing = (REQUIRED_OPTIONS - options.keys) unless missing.empty? missing.collect! { |opt| "'#{opt}'" } raise ArgumentError, "Missing required option(s): #{missing.join(', ')}" end end # A hash of default options to be used in the Connection initializer # # @return [Hash] def default_options { thread_count: DEFAULT_THREAD_COUNT, ssh: Hash.new } end end extend Forwardable include Ridley::DSL attr_reader :client_name attr_reader :client_key attr_reader :organization attr_reader :ssh attr_reader :validator_client attr_reader :validator_path attr_reader :encrypted_data_bag_secret_path attr_accessor :thread_count def_delegator :conn, :build_url def_delegator :conn, :scheme def_delegator :conn, :host def_delegator :conn, :port def_delegator :conn, :path_prefix def_delegator :conn, :url_prefix= def_delegator :conn, :url_prefix def_delegator :conn, :get def_delegator :conn, :put def_delegator :conn, :post def_delegator :conn, :delete def_delegator :conn, :head def_delegator :conn, :in_parallel REQUIRED_OPTIONS = [ :server_url, :client_name, :client_key ].freeze DEFAULT_THREAD_COUNT = 8 # @option options [String] :server_url # URL to the Chef API # @option options [String] :client_name # name of the client used to authenticate with the Chef API # @option options [String] :client_key # filepath to the client's private key used to authenticate with # the Chef API # @option options [String] :organization # the Organization to connect to. This is only used if you are connecting to # private Chef or hosted Chef # @option options [String] :validator_client # (default: nil) # @option options [String] :validator_path # (default: nil) # @option options [String] :encrypted_data_bag_secret_path # (default: nil) # @option options [Integer] :thread_count # @option options [Hash] :ssh # authentication credentials for bootstrapping or connecting to nodes (default: Hash.new) # @option options [Hash] :params # URI query unencoded key/value pairs # @option options [Hash] :headers # unencoded HTTP header key/value pairs # @option options [Hash] :request # request options # @option options [Hash] :ssl # SSL options # @option options [URI, String, Hash] :proxy # URI, String, or Hash of HTTP proxy options def initialize(options = {}) options = self.class.default_options.merge(options) self.class.validate_options(options) @client_name = options[:client_name] @client_key = File.expand_path(options[:client_key]) @organization = options[:organization] @thread_count = options[:thread_count] @ssh = options[:ssh] @validator_client = options[:validator_client] @validator_path = options[:validator_path] @encrypted_data_bag_secret_path = options[:encrypted_data_bag_secret_path] unless @client_key.present? && File.exist?(@client_key) raise Errors::ClientKeyFileNotFound, "client key not found at: '#{@client_key}'" end faraday_options = options.slice(:params, :headers, :request, :ssl, :proxy) uri_hash = Addressable::URI.parse(options[:server_url]).to_hash.slice(:scheme, :host, :port) unless uri_hash[:port] uri_hash[:port] = (uri_hash[:scheme] == "https" ? 443 : 80) end if org_match = options[:server_url].match(/.*\/organizations\/(.*)/) @organization ||= org_match[1] end unless organization.nil? uri_hash[:path] = "/organizations/#{organization}" end server_uri = Addressable::URI.new(uri_hash) @conn = Faraday.new(server_uri, faraday_options) do |c| c.request :chef_auth, client_name, client_key c.response :chef_response c.response :json c.adapter Faraday.default_adapter end end def sync(&block) unless block raise Errors::InternalError, "A block must be given to synchronously process requests." end evaluate(&block) end # @return [Symbol] def api_type organization.nil? ? :foss : :hosted end # @return [Boolean] def hosted? api_type == :hosted end # @return [Boolean] def foss? api_type == :foss end def server_url self.url_prefix.to_s end # The encrypted data bag secret for this connection. # # @raise [Ridley::Errors::EncryptedDataBagSecretNotFound] # # @return [String, nil] def encrypted_data_bag_secret return nil if encrypted_data_bag_secret_path.nil? IO.read(encrypted_data_bag_secret_path).chomp rescue Errno::ENOENT => e raise Errors::EncryptedDataBagSecretNotFound, "Encrypted data bag secret provided but not found at '#{encrypted_data_bag_secret_path}'" end private attr_reader :conn def evaluate(&block) @self_before_instance_eval = eval("self", block.binding) instance_eval(&block) end def method_missing(method, *args, &block) if block_given? @self_before_instance_eval ||= eval("self", block.binding) end if @self_before_instance_eval.nil? super end @self_before_instance_eval.send(method, *args, &block) end end end