require 'rest_client' require 'uri' require 'multi_json' require 'yaml' require 'addressable/uri' require 'deep_merge' module Onering module API module Errors class NotConnected < Exception; end class ClientError < Exception; end class ServerError < Exception; end class ConnectionTimeout < Exception; end class ClientPemNotFound < Exception; end end class Base include Onering::Util DEFAULT_BASE="https://onering" DEFAULT_PATH="/api" DEFAULT_OPTIONS_FILE=[ "~/.onering/cli.yml", "/etc/onering/cli.yml" ] DEFAULT_CLIENT_PEM=[ "~/.onering/client.pem", "/etc/onering/client.pem" ] DEFAULT_VALIDATION_PEM=[ "/etc/onering/validation.pem" ] class< 120, :open_timeout => 30, :ssl_client_cert => OpenSSL::X509::Certificate.new(@_pem), :ssl_client_key => OpenSSL::PKey::RSA.new(@_pem), :verify_peer => OpenSSL::SSL::VERIFY_PEER }) rescue Errors::ClientPemNotFound # client PEM not present, attempt autoregistration STDERR.puts("Onering client.pem not found, attempting automatic registration...") @_validation = ([options[:validationfile], @_config['validationfile']]+DEFAULT_VALIDATION_PEM).compact.select{|i| (File.exists?((File.expand_path(i) rescue i)) rescue nil) }.compact.first if @_validation.nil? raise Errors::ClientError.new("Cannot automatically register client, cannot find validation.pem") end @_validation = File.read(@_validation) @rest = RestClient::Resource.new("#{@_uri.scheme}://#{@_uri.host}:#{@_uri.port || 443}", { :timeout => 120, :open_timeout => 30, :ssl_client_cert => OpenSSL::X509::Certificate.new(@_validation), :ssl_client_key => OpenSSL::PKey::RSA.new(@_validation), :verify_peer => OpenSSL::SSL::VERIFY_PEER }) clients = [{ :path => "/etc/onering", :name => (@_config['id'] || File.read("/etc/hardware.id")).strip.chomp, :keyname => 'system', :autodelete => true },{ :path => "~/.onering", :name => ENV['USER'], :keyname => 'cli', :autodelete => false }] # attempt to autoregister clients from least specific to most (machine account then user account) clients.each do |client| # determine if we can create this client client[:path] = (File.expand_path(client[:path]) rescue client[:path]) next unless File.writable?(File.dirname(client[:path])) Dir.mkdir(client[:path]) unless File.directory?(client[:path]) next unless File.writable?(client[:path]) begin response = @rest["/api/users/#{client[:name]}/keys/#{client[:keyname]}"].get({ :params => { :cert => 'pem', :autodelete => client[:autodelete] } }) rescue RestClient::Forbidden STDERR.puts("Cannot re-download key '#{client[:keyname]}' for client #{client[:name]}. Please remove the client key from Onering and try again.") next rescue RestClient::Exception => e raise Errors::ClientError.new("HTTP #{e.http_code}: #{e.message}") end File.open("#{client[:path]}/client.pem", "w") do |file| file.puts(response.to_str) STDERR.puts("Successfully registered client key #{client[:name]}:#{client[:keyname]}, key is at #{file.path}") break end end if clients.select{|i| p = "#{i[:path]}/client.pem"; File.exists?((File.expand_path(p) rescue p)) }.empty? raise Errors::ClientError.new("Unable to register a Onering client.") end retry end else raise Errors::ClientError.new("Could not parse API URL.") end end def request(endpoint, options={}) options = @_config.merge(options) options[:method] = (options[:method].to_s.downcase.to_sym rescue nil) request = nil uri = Addressable::URI.parse("#{@_uri.to_s}/#{endpoint}") uri.query_values = options[:fields] if options[:fields] raise Errors::NotConnected unless @rest begin case options[:method] when :post data = (options[:data].nil? ? nil : MultiJson.dump(options[:data])) response = @rest[uri.request_uri].post(data, { :content_type => 'application/json' }) when :delete response = @rest[uri.request_uri].delete() when :head response = @rest[uri.request_uri].head() else response = @rest[uri.request_uri].get() end rescue RestClient::Unauthorized => e raise Errors::ClientError.new("You are not authorized to perform this request") rescue RestClient::Exception => e raise Errors::ClientError.new("(HTTP #{e.http_code}) #{e.class.name}: #{e.message}") end begin rv = (response.empty? ? nil : MultiJson.load(response)) rescue Exception rv = response end rv end def make_filter(filter) filter = filter.collect{|k,v| "#{k}/#{v}" } if filter.is_a?(Hash) filter = filter.collect{|i| i.sub(':','/') }.join("/") if filter.is_a?(Array) filter end def echo(obj) if obj.is_a?(Array) obj.each do |i| puts i end end end end end end end