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
- ACCEPT_ENCODING =
'Accept-Encoding'.freeze
- GZIP =
'gzip'.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.
- #debug(&block) ⇒ Object
-
#delete(path, body = nil, params = {}, headers = {}, auth = true) ⇒ Hash
Delete request.
- #error(&block) ⇒ Object
-
#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, gzip: 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
32 33 34 35 36 37 38 39 40 41 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 |
# File 'lib/kontena/client.rb', line 32 def initialize(api_url, token = nil, = {}) require 'json' require 'excon' require 'uri' require 'base64' require 'socket' require 'openssl' require 'uri' require 'time' require 'kontena/errors' require 'kontena/cli/version' require 'kontena/cli/config' @api_url, @token, @options = api_url, token, uri = URI.parse(@api_url) @host = uri.host @logger = Kontena.logger @options[:default_headers] ||= {} excon_opts = { omit_default_port: true, connect_timeout: ENV["EXCON_CONNECT_TIMEOUT"] ? ENV["EXCON_CONNECT_TIMEOUT"].to_i : 10, 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 : 10, ssl_verify_peer: ignore_ssl_errors? ? false : true, middlewares: Excon.defaults[:middlewares] + [Excon::Middleware::Decompress] } if Kontena.debug? require 'kontena/debug_instrumentor' excon_opts[:instrumentor] = Kontena::DebugInstrumentor end excon_opts[:ssl_ca_file] = @options[:ssl_cert_path] excon_opts[:ssl_verify_peer_host] = @options[:ssl_subject_cn] 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 end @api_url = api_url @path_prefix = [:prefix] || '/v1/' end |
Instance Attribute Details
#api_url ⇒ Object (readonly)
Returns the value of attribute api_url.
24 25 26 |
# File 'lib/kontena/client.rb', line 24 def api_url @api_url end |
#default_headers ⇒ Object
Returns the value of attribute default_headers.
17 18 19 |
# File 'lib/kontena/client.rb', line 17 def default_headers @default_headers end |
#host ⇒ Object (readonly)
Returns the value of attribute host.
25 26 27 |
# File 'lib/kontena/client.rb', line 25 def host @host end |
#http_client ⇒ Object (readonly)
Returns the value of attribute http_client.
19 20 21 |
# File 'lib/kontena/client.rb', line 19 def http_client @http_client end |
#last_response ⇒ Object (readonly)
Returns the value of attribute last_response.
20 21 22 |
# File 'lib/kontena/client.rb', line 20 def last_response @last_response end |
#logger ⇒ Object (readonly)
Returns the value of attribute logger.
23 24 25 |
# File 'lib/kontena/client.rb', line 23 def logger @logger end |
#options ⇒ Object (readonly)
Returns the value of attribute options.
21 22 23 |
# File 'lib/kontena/client.rb', line 21 def @options end |
#path_prefix ⇒ Object
Returns the value of attribute path_prefix.
18 19 20 |
# File 'lib/kontena/client.rb', line 18 def path_prefix @path_prefix end |
#token ⇒ Object (readonly)
Returns the value of attribute token.
22 23 24 |
# File 'lib/kontena/client.rb', line 22 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 143 |
# 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']) debug { "Requesting user info from #{final_path}" } request(path: final_path) true rescue => ex error { "Authentication verification exception" } error { ex } 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
182 183 184 |
# File 'lib/kontena/client.rb', line 182 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
189 190 191 |
# File 'lib/kontena/client.rb', line 189 def client_secret ENV['KONTENA_CLIENT_SECRET'] || CLIENT_SECRET end |
#debug(&block) ⇒ Object
90 91 92 |
# File 'lib/kontena/client.rb', line 90 def debug(&block) logger.debug("CLIENT", &block) end |
#delete(path, body = nil, params = {}, headers = {}, auth = true) ⇒ Hash
Delete request
243 244 245 |
# File 'lib/kontena/client.rb', line 243 def delete(path, body = nil, params = {}, headers = {}, auth = true) request(http_method: :delete, path: path, body: body, query: params, headers: headers, auth: auth) end |
#error(&block) ⇒ Object
94 95 96 |
# File 'lib/kontena/client.rb', line 94 def error(&block) logger.error("CLIENT", &block) end |
#exchange_code(code) ⇒ Object
Calls the code exchange endpoint in token’s config to exchange an authorization_code to a access_token
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/kontena/client.rb', line 147 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
199 200 201 |
# File 'lib/kontena/client.rb', line 199 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
253 254 255 |
# File 'lib/kontena/client.rb', line 253 def get_stream(path, response_block, params = nil, headers = {}, auth = true) request(path: path, query: params, headers: headers, response_block: response_block, auth: auth, gzip: false) end |
#patch(path, obj, params = {}, headers = {}, auth = true) ⇒ Hash
Patch request
232 233 234 |
# File 'lib/kontena/client.rb', line 232 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
210 211 212 |
# File 'lib/kontena/client.rb', line 210 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
221 222 223 |
# File 'lib/kontena/client.rb', line 221 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
356 357 358 359 360 361 362 363 |
# File 'lib/kontena/client.rb', line 356 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.
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 422 423 424 425 426 427 428 429 |
# File 'lib/kontena/client.rb', line 387 def refresh_token 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 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'] 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 debug { "Got null or bad response to refresh request: #{last_response.inspect}" } false end rescue => ex error { "Access token refresh exception" } error { ex } false end |
#request(http_method: :get, path: '/', body: nil, query: {}, headers: {}, response_block: nil, expects: [200, 201, 204], host: nil, port: nil, auth: true, gzip: 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.
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 346 347 348 349 350 351 |
# File 'lib/kontena/client.rb', line 285 def request(http_method: :get, path:'/', body: nil, query: {}, headers: {}, response_block: nil, expects: [200, 201, 204], host: nil, port: nil, auth: true, gzip: true) retried ||= false if auth && token_expired? raise Excon::Error::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: auth, gzip: gzip) 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::Error::Unauthorized if token 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::Error::HTTPStatus => error if error.response.headers['Content-Encoding'] == 'gzip' error.response.body = Zlib::GzipReader.new(StringIO.new(error.response.body)).read end 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 ‘/’
171 172 173 174 175 176 177 |
# File 'lib/kontena/client.rb', line 171 def server_version request(auth: false, expects: 200)['version'] rescue => ex error { "Server version exception" } error { ex } nil end |
#token_account ⇒ Object
Accessor to token’s account settings
366 367 368 369 370 371 372 373 374 375 376 377 378 379 |
# File 'lib/kontena/client.rb', line 366 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 error { "Access token refresh exception" } error { ex } false end |
#token_expired? ⇒ Boolean
257 258 259 260 261 262 263 264 265 266 |
# File 'lib/kontena/client.rb', line 257 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 |