lib/onering/api.rb in onering-client-0.0.41 vs lib/onering/api.rb in onering-client-0.0.42
- old
+ new
@@ -1,6 +1,6 @@
-require 'net/https'
+require 'rest_client'
require 'uri'
require 'multi_json'
require 'yaml'
require 'addressable/uri'
require 'deep_merge'
@@ -10,10 +10,11 @@
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
@@ -27,10 +28,14 @@
DEFAULT_CLIENT_PEM=[
"~/.onering/client.pem",
"/etc/onering/client.pem"
]
+ DEFAULT_VALIDATION_PEM=[
+ "/etc/onering/validation.pem"
+ ]
+
class<<self
def connect(options={})
# list all existing config files from least specific to most
@_configfiles = ([options[:config]] + DEFAULT_OPTIONS_FILE).compact.select{|i|
(File.exists?(File.expand_path(i)) rescue false)
@@ -49,25 +54,104 @@
@_uri = Addressable::URI.parse("#{options[:host]}/#{DEFAULT_PATH}")
else
@_uri = Addressable::URI.parse("#{@_config['url'] || DEFAULT_BASE}/#{@_config['apiroot'] || DEFAULT_PATH}")
end
- if @_uri
- @_pemfile = ([options[:pemfile], @_config['pemfile']]+DEFAULT_CLIENT_PEM).compact.select{|i|
- (File.exists?(File.expand_path(i)) rescue false)
- }.first
+ unless @_uri.nil?
+ begin
+ @_pemfile = ([options[:pemfile], @_config['pemfile']]+DEFAULT_CLIENT_PEM).compact.select{|i|
+ (File.exists?((File.expand_path(i) rescue i)) rescue nil)
+ }.compact.first
- if @_pemfile
- @_pem = File.read(File.expand_path(@_pemfile))
- @_http = Net::HTTP.new(@_uri.host, (@_uri.port || 443))
- @_http.open_timeout = 30
- @_http.read_timeout = 120
- @_http.use_ssl = true
- @_http.cert = OpenSSL::X509::Certificate.new(@_pem)
- @_http.key = OpenSSL::PKey::RSA.new(@_pem)
- @_http.verify_mode = OpenSSL::SSL::VERIFY_NONE
+ raise Errors::ClientPemNotFound if @_pemfile.nil?
+
+ @_pem = File.read((File.expand_path(@_pemfile) rescue @_pemfile))
+
+ @rest = RestClient::Resource.new("#{@_uri.scheme}://#{@_uri.host}:#{@_uri.port || 443}", {
+ :timeout => 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]))
+ File.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)
@@ -75,45 +159,40 @@
request = nil
uri = Addressable::URI.parse("#{@_uri.to_s}/#{endpoint}")
uri.query_values = options[:fields] if options[:fields]
- raise Errors::NotConnected unless @_http
+ raise Errors::NotConnected unless @rest
- case options[:method]
- when :post
- request = Net::HTTP::Post.new(uri.request_uri)
- request['Content-Type'] = 'application/json'
- request.body = MultiJson.dump(options[:data]) if options[:data]
+ begin
+ case options[:method]
+ when :post
+ data = (options[:data].nil? ? nil : MultiJson.dump(options[:data]))
- when :delete
- request = Net::HTTP::Delete.new(uri.request_uri)
+ response = @rest[uri.request_uri].post(data, {
+ :content_type => 'application/json'
+ })
- else
- request = Net::HTTP::Get.new(uri.request_uri)
+ when :delete
+ response = @rest[uri.request_uri].delete()
- end
+ when :head
+ response = @rest[uri.request_uri].head()
+ else
+ response = @rest[uri.request_uri].get()
+ end
- response = @_http.request(request)
+ rescue RestClient::Unauthorized => e
+ raise Errors::ClientError.new("You are not authorized to perform this request")
- if response.code.to_i >= 400
- rv = (MultiJson.load(response.body) rescue {}) unless response.body.empty?
+ rescue RestClient::Exception => e
+ raise Errors::ClientError.new("(HTTP #{e.http_code}) #{e.class.name}: #{e.message}")
+ end
- if rv['errors']
- msg = "#{rv['errors']['type']}: #{rv['errors']['message']}"
- end
-
- if response.code.to_i >= 500
- raise Errors::ServerError.new("HTTP #{response.code}: #{msg}")
- else
- raise Errors::ClientError.new("HTTP #{response.code}: #{msg}")
- end
- else
- if response['Content-Type'] == 'application/json'
- rv = (response.body.empty? ? nil : MultiJson.load(response.body))
- else
- rv = response.body
- end
+ begin
+ rv = (response.empty? ? nil : MultiJson.load(response))
+ rescue Exception
+ rv = response
end
rv
end