Class: Kontena::Client
- Inherits:
-
Object
- Object
- Kontena::Client
- Defined in:
- lib/kontena/client.rb
Direct Known Subclasses
Constant Summary collapse
- CLIENT_ID =
ENV['KONTENA_CLIENT_ID'] || '15faec8a7a9b4f1e8b7daebb1307f1d8'.freeze
- CLIENT_SECRET =
ENV['KONTENA_CLIENT_SECRET'] || 'fb8942ae00da4c7b8d5a1898effc742f'.freeze
- CONTENT_URLENCODED =
'application/x-www-form-urlencoded'.freeze
- CONTENT_JSON =
'application/json'.freeze
- JSON_REGEX =
/application\/(.+?\+)?json/.freeze
- CONTENT_TYPE =
'Content-Type'.freeze
- X_KONTENA_VERSION =
'X-Kontena-Version'.freeze
- ACCEPT =
'Accept'.freeze
- AUTHORIZATION =
'Authorization'.freeze
Instance Attribute Summary collapse
-
#api_url ⇒ Object
readonly
Returns the value of attribute api_url.
-
#default_headers ⇒ Object
Returns the value of attribute default_headers.
-
#host ⇒ Object
readonly
Returns the value of attribute host.
-
#http_client ⇒ Object
readonly
Returns the value of attribute http_client.
-
#last_response ⇒ Object
readonly
Returns the value of attribute last_response.
-
#logger ⇒ Object
readonly
Returns the value of attribute logger.
-
#options ⇒ Object
readonly
Returns the value of attribute options.
-
#path_prefix ⇒ Object
Returns the value of attribute path_prefix.
-
#token ⇒ Object
readonly
Returns the value of attribute token.
Instance Method Summary collapse
-
#authentication_ok?(token_verify_path) ⇒ Boolean
Requests path supplied as argument and returns true if the request was a success.
-
#basic_auth_header(user = nil, pass = nil) ⇒ Hash
Generates a header hash for HTTP basic authentication.
-
#bearer_authorization_header ⇒ Hash
Generates a bearer token authentication header hash if a token object is available.
-
#client_id ⇒ String
OAuth2 client_id from ENV KONTENA_CLIENT_ID or client CLIENT_ID constant.
-
#client_secret ⇒ String
OAuth2 client_secret from ENV KONTENA_CLIENT_SECRET or client CLIENT_SECRET constant.
-
#delete(path, body = nil, params = {}, headers = {}, auth = true) ⇒ Hash
Delete request.
-
#exchange_code(code) ⇒ Object
Calls the code exchange endpoint in token's config to exchange an authorization_code to a access_token.
-
#get(path, params = nil, headers = {}, auth = true) ⇒ Hash
Get request.
-
#get_stream(path, response_block, params = nil, headers = {}, auth = true) ⇒ Object
Get stream request.
-
#initialize(api_url, token = nil, options = {}) ⇒ Client
constructor
Initialize api client.
-
#patch(path, obj, params = {}, headers = {}, auth = true) ⇒ Hash
Patch request.
-
#post(path, obj, params = {}, headers = {}, auth = true) ⇒ Hash
Post request.
-
#put(path, obj, params = {}, headers = {}, auth = true) ⇒ Hash
Put request.
-
#refresh_request_params ⇒ Hash
Build a token refresh request param hash.
-
#refresh_token ⇒ Boolean
Perform refresh token request to auth provider.
-
#request(http_method: :get, path: '/', body: nil, query: {}, headers: {}, response_block: nil, expects: [200, 201, 204], host: nil, port: nil, auth: true) ⇒ Hash, String
Perform a HTTP request.
-
#server_version ⇒ String
Return server version from a Kontena master by requesting '/'.
-
#token_account ⇒ Object
Accessor to token's account settings.
- #token_expired? ⇒ Boolean
Constructor Details
#initialize(api_url, token = nil, options = {}) ⇒ Client
Initialize api client
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/kontena/client.rb', line 42 def initialize(api_url, token = nil, = {}) @api_url, @token, @options = api_url, token, uri = URI.parse(@api_url) @host = uri.host @logger = Logger.new(ENV["DEBUG"] ? $stderr : $stdout) @logger.level = ENV["DEBUG"].nil? ? Logger::INFO : Logger::DEBUG @logger.progname = 'CLIENT' @options[:default_headers] ||= {} excon_opts = { omit_default_port: true, connect_timeout: ENV["EXCON_CONNECT_TIMEOUT"] ? ENV["EXCON_CONNECT_TIMEOUT"].to_i : 5, read_timeout: ENV["EXCON_READ_TIMEOUT"] ? ENV["EXCON_READ_TIMEOUT"].to_i : 30, write_timeout: ENV["EXCON_WRITE_TIMEOUT"] ? ENV["EXCON_WRITE_TIMEOUT"].to_i : 5, ssl_verify_peer: ignore_ssl_errors? ? false : true } if ENV["DEBUG"] require 'kontena/debug_instrumentor' excon_opts[:instrumentor] = Kontena::DebugInstrumentor end cert_file = File.join(Dir.home, "/.kontena/certs/#{uri.host}.pem") if File.exist?(cert_file) && File.readable?(cert_file) excon_opts[:ssl_ca_file] = cert_file key = OpenSSL::X509::Certificate.new(File.read(cert_file)) if key.issuer.to_s == "/C=FI/O=Test/OU=Test/CN=Test" logger.debug "Key looks like a self-signed cert made by Kontena CLI, setting verify_peer_host to 'Test'" excon_opts[:ssl_verify_peer_host] = 'Test' end end logger.debug "Excon opts: #{excon_opts.inspect}" @http_client = Excon.new(api_url, excon_opts) @default_headers = { ACCEPT => CONTENT_JSON, CONTENT_TYPE => CONTENT_JSON, 'User-Agent' => "kontena-cli/#{Kontena::Cli::VERSION}" }.merge([:default_headers]) if token if token.kind_of?(String) @token = { 'access_token' => token } else @token = token end @default_headers.merge!('Authorization' => "Bearer #{@token['access_token']}") end @api_url = api_url @path_prefix = [:prefix] || '/v1/' end |
Instance Attribute Details
#api_url ⇒ Object (readonly)
Returns the value of attribute api_url.
34 35 36 |
# File 'lib/kontena/client.rb', line 34 def api_url @api_url end |
#default_headers ⇒ Object
Returns the value of attribute default_headers.
27 28 29 |
# File 'lib/kontena/client.rb', line 27 def default_headers @default_headers end |
#host ⇒ Object (readonly)
Returns the value of attribute host.
35 36 37 |
# File 'lib/kontena/client.rb', line 35 def host @host end |
#http_client ⇒ Object (readonly)
Returns the value of attribute http_client.
29 30 31 |
# File 'lib/kontena/client.rb', line 29 def http_client @http_client end |
#last_response ⇒ Object (readonly)
Returns the value of attribute last_response.
30 31 32 |
# File 'lib/kontena/client.rb', line 30 def last_response @last_response end |
#logger ⇒ Object (readonly)
Returns the value of attribute logger.
33 34 35 |
# File 'lib/kontena/client.rb', line 33 def logger @logger end |
#options ⇒ Object (readonly)
Returns the value of attribute options.
31 32 33 |
# File 'lib/kontena/client.rb', line 31 def @options end |
#path_prefix ⇒ Object
Returns the value of attribute path_prefix.
28 29 30 |
# File 'lib/kontena/client.rb', line 28 def path_prefix @path_prefix end |
#token ⇒ Object (readonly)
Returns the value of attribute token.
32 33 34 |
# File 'lib/kontena/client.rb', line 32 def token @token end |
Instance Method Details
#authentication_ok?(token_verify_path) ⇒ Boolean
Requests path supplied as argument and returns true if the request was a success. For checking if the current authentication is valid.
130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'lib/kontena/client.rb', line 130 def authentication_ok?(token_verify_path) return false unless token return false unless token['access_token'] return false unless token_verify_path final_path = token_verify_path.gsub(/\:access\_token/, token['access_token']) logger.debug "Requesting user info from #{final_path}" request(path: final_path) true rescue => ex logger.debug "Authentication verification exception: #{ex.class.name} : #{ex.}\n#{ex.backtrace.join("\n ")}" false end |
#basic_auth_header(user = nil, pass = nil) ⇒ Hash
Generates a header hash for HTTP basic authentication. Defaults to using client_id and client_secret as user/pass
104 105 106 107 108 109 110 111 |
# File 'lib/kontena/client.rb', line 104 def basic_auth_header(user = nil, pass = nil) user ||= client_id pass ||= client_secret { AUTHORIZATION => "Basic #{Base64.encode64([user, pass].join(':')).gsub(/[\r\n]/, '')}" } end |
#bearer_authorization_header ⇒ Hash
Generates a bearer token authentication header hash if a token object is available. Otherwise returns an empty hash.
117 118 119 120 121 122 123 |
# File 'lib/kontena/client.rb', line 117 def if token && token['access_token'] {AUTHORIZATION => "Bearer #{token['access_token']}"} else {} end end |
#client_id ⇒ String
OAuth2 client_id from ENV KONTENA_CLIENT_ID or client CLIENT_ID constant
180 181 182 |
# File 'lib/kontena/client.rb', line 180 def client_id ENV['KONTENA_CLIENT_ID'] || CLIENT_ID end |
#client_secret ⇒ String
OAuth2 client_secret from ENV KONTENA_CLIENT_SECRET or client CLIENT_SECRET constant
187 188 189 |
# File 'lib/kontena/client.rb', line 187 def client_secret ENV['KONTENA_CLIENT_SECRET'] || CLIENT_SECRET end |
#delete(path, body = nil, params = {}, headers = {}, auth = true) ⇒ Hash
Delete request
241 242 243 |
# File 'lib/kontena/client.rb', line 241 def delete(path, body = nil, params = {}, headers = {}, auth = true) request(http_method: :delete, path: path, body: body, query: params, headers: headers, auth: auth) end |
#exchange_code(code) ⇒ Object
Calls the code exchange endpoint in token's config to exchange an authorization_code to a access_token
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/kontena/client.rb', line 146 def exchange_code(code) return nil unless token_account return nil unless token_account['token_endpoint'] response = request( http_method: token_account['token_method'].downcase.to_sym, path: token_account['token_endpoint'], headers: { CONTENT_TYPE => token_account['token_post_content_type'] }, body: { 'grant_type' => 'authorization_code', 'code' => code, 'client_id' => Kontena::Client::CLIENT_ID, 'client_secret' => Kontena::Client::CLIENT_SECRET }, expects: [200,201], auth: false ) response['expires_at'] ||= in_to_at(response['expires_in']) response end |
#get(path, params = nil, headers = {}, auth = true) ⇒ Hash
Get request
197 198 199 |
# File 'lib/kontena/client.rb', line 197 def get(path, params = nil, headers = {}, auth = true) request(path: path, query: params, headers: headers, auth: auth) end |
#get_stream(path, response_block, params = nil, headers = {}, auth = true) ⇒ Object
Get stream request
251 252 253 |
# File 'lib/kontena/client.rb', line 251 def get_stream(path, response_block, params = nil, headers = {}, auth = true) request(path: path, query: params, headers: headers, response_block: response_block, auth: auth) end |
#patch(path, obj, params = {}, headers = {}, auth = true) ⇒ Hash
Patch request
230 231 232 |
# File 'lib/kontena/client.rb', line 230 def patch(path, obj, params = {}, headers = {}, auth = true) request(http_method: :patch, path: path, body: obj, query: params, headers: headers, auth: auth) end |
#post(path, obj, params = {}, headers = {}, auth = true) ⇒ Hash
Post request
208 209 210 |
# File 'lib/kontena/client.rb', line 208 def post(path, obj, params = {}, headers = {}, auth = true) request(http_method: :post, path: path, body: obj, query: params, headers: headers, auth: auth) end |
#put(path, obj, params = {}, headers = {}, auth = true) ⇒ Hash
Put request
219 220 221 |
# File 'lib/kontena/client.rb', line 219 def put(path, obj, params = {}, headers = {}, auth = true) request(http_method: :put, path: path, body: obj, query: params, headers: headers, auth: auth) end |
#refresh_request_params ⇒ Hash
Build a token refresh request param hash
350 351 352 353 354 355 356 357 |
# File 'lib/kontena/client.rb', line 350 def refresh_request_params { refresh_token: token['refresh_token'], grant_type: 'refresh_token', client_id: client_id, client_secret: client_secret } end |
#refresh_token ⇒ Boolean
Perform refresh token request to auth provider. Updates the client's Token object and writes changes to configuration.
380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 |
# File 'lib/kontena/client.rb', line 380 def refresh_token logger.debug "Performing token refresh" return false if token.nil? return false if token['refresh_token'].nil? uri = URI.parse(token_account['token_endpoint']) endpoint_data = { path: uri.path } endpoint_data[:host] = uri.host if uri.host endpoint_data[:port] = uri.port if uri.port logger.debug "Token refresh endpoint: #{endpoint_data.inspect}" return false unless endpoint_data[:path] response = request( { http_method: token_account['token_method'].downcase.to_sym, body: refresh_request_params, headers: { CONTENT_TYPE => token_account['token_post_content_type'] }.merge( token_account['code_requires_basic_auth'] ? basic_auth_header : {} ), expects: [200, 201, 400, 401, 403], auth: false }.merge(endpoint_data) ) if response && response['access_token'] logger.debug "Got response to refresh request" token['access_token'] = response['access_token'] token['refresh_token'] = response['refresh_token'] token['expires_at'] = in_to_at(response['expires_in']) token.config.write if token.respond_to?(:config) true else logger.debug "Got null or bad response to refresh request: #{last_response.inspect}" false end rescue => ex logger.debug "Access token refresh exception: #{ex.class.name} : #{ex.}\n#{ex.backtrace.join("\n ")}" false end |
#request(http_method: :get, path: '/', body: nil, query: {}, headers: {}, response_block: nil, expects: [200, 201, 204], host: nil, port: nil, auth: true) ⇒ Hash, String
Perform a HTTP request. Will try to refresh the access token and retry if it's expired or if the server responds with HTTP 401.
Automatically parses a JSON response into a hash.
After the request has been performed, the response can be inspected using client.last_response.
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 |
# File 'lib/kontena/client.rb', line 283 def request(http_method: :get, path:'/', body: nil, query: {}, headers: {}, response_block: nil, expects: [200, 201, 204], host: nil, port: nil, auth: true) retried ||= false if auth && token_expired? raise Excon::Errors::Unauthorized, "Token expired or not valid, you need to login again, use: kontena #{token_is_for_master? ? "master" : "cloud"} login" end request_headers = request_headers(headers, auth) if body.nil? body_content = '' request_headers.delete(CONTENT_TYPE) else body_content = encode_body(body, request_headers[CONTENT_TYPE]) request_headers.merge!('Content-Length' => body_content.bytesize) end uri = URI.parse(path) = {} if uri.host [:host] = uri.host [:port] = uri.port [:scheme] = uri.scheme path = uri.request_uri else [:host] = host if host [:port] = port if port end = { method: http_method, expects: Array(expects), path: path_with_prefix(path), headers: request_headers, body: body_content, query: query }.merge() .merge!(response_block: response_block) if response_block # Store the response into client.last_response @last_response = http_client.request() parse_response(@last_response) rescue Excon::Errors::Unauthorized if token logger.debug 'Server reports access token expired' if retried || !token || !token['refresh_token'] raise Kontena::Errors::StandardError.new(401, 'The access token has expired and needs to be refreshed') end retried = true retry if refresh_token end raise Kontena::Errors::StandardError.new(401, 'Unauthorized') rescue Excon::Errors::HTTPStatusError => error logger.debug "Request #{error.request[:method].upcase} #{error.request[:path]}: #{error.response.status} #{error.response.reason_phrase}: #{error.response.body}" handle_error_response(error.response) end |
#server_version ⇒ String
Return server version from a Kontena master by requesting '/'
170 171 172 173 174 175 |
# File 'lib/kontena/client.rb', line 170 def server_version request(auth: false, expects: 200)['version'] rescue => ex logger.debug "Server version exception: #{ex.class.name} : #{ex.}\n#{ex.backtrace.join("\n ")}" nil end |
#token_account ⇒ Object
Accessor to token's account settings
360 361 362 363 364 365 366 367 368 369 370 371 372 |
# File 'lib/kontena/client.rb', line 360 def token_account return {} unless token if token.respond_to?(:account) token.account elsif token.kind_of?(Hash) && token['account'].kind_of?(String) config.find_account(token['account']) else {} end rescue => ex logger.debug "Access token refresh exception: #{ex.class.name} : #{ex.}\n#{ex.backtrace.join("\n ")}" false end |
#token_expired? ⇒ Boolean
255 256 257 258 259 260 261 262 263 264 |
# File 'lib/kontena/client.rb', line 255 def token_expired? return false unless token if token.respond_to?(:expired?) token.expired? elsif token['expires_at'].to_i > 0 token['expires_at'].to_i < Time.now.utc.to_i else false end end |