Class: Artifactory::Client

Inherits:
Object
  • Object
show all
Includes:
Configurable
Defined in:
lib/artifactory/client.rb

Overview

Client for the Artifactory API.

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Configurable

#configure, keys, #reset!

Constructor Details

#initialize(options = {}) ⇒ Artifactory::Client

Create a new Artifactory Client with the given options. Any options given take precedence over the default options.



69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/artifactory/client.rb', line 69

def initialize(options = {})
  # Use any options given, but fall back to the defaults set on the module
  Artifactory::Configurable.keys.each do |key|
    value = if options[key].nil?
              Artifactory.instance_variable_get(:"@#{key}")
            else
              options[key]
            end

    instance_variable_set(:"@#{key}", value)
  end
end

Class Method Details

.proxy(klass) ⇒ Object



33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/artifactory/client.rb', line 33

def proxy(klass)
  namespace = klass.name.split("::").last.downcase
  klass.singleton_methods(false).each do |name|
    define_method("#{namespace}_#{name}") do |*args|
      if args.last.is_a?(Hash)
        args.last[:client] = self
      else
        args << { client: self }
      end

      klass.send(name, *args)
    end
  end
end

Instance Method Details

#build_uri(verb, path, params = {}) ⇒ URI

Construct a URL from the given verb and path. If the request is a GET or DELETE request, the params are assumed to be query params are are converted as such using #to_query_string.

If the path is relative, it is merged with the Defaults.endpoint attribute. If the path is absolute, it is converted to a URI object and returned.

Parameters:

  • verb (Symbol)

    the lowercase HTTP verb (e.g. :get)

  • path (String)

    the absolute or relative HTTP path (url) to get

  • params (Hash) (defaults to: {})

    the list of params to build the URI with (for GET and DELETE requests)

Returns:

  • (URI)


323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
# File 'lib/artifactory/client.rb', line 323

def build_uri(verb, path, params = {})
  # Add any query string parameters
  if %i{delete get}.include?(verb)
    path = [path, to_query_string(params)].compact.join("?")
  end

  # Parse the URI
  uri = URI.parse(path)

  # Don't merge absolute URLs
  uri = URI.parse(File.join(endpoint, path)) unless uri.absolute?

  # Return the URI object
  uri
end

#class_for_request(verb) ⇒ Class

Helper method to get the corresponding Net::HTTP class from the given HTTP verb.

Parameters:

  • verb (#to_s)

    the HTTP verb to create a class from

Returns:

  • (Class)


348
349
350
# File 'lib/artifactory/client.rb', line 348

def class_for_request(verb)
  Net::HTTP.const_get(verb.to_s.capitalize)
end

#default_headersHash

The list of default headers (such as Keep-Alive and User-Agent) for the client object.

Returns:

  • (Hash)


297
298
299
300
301
302
303
# File 'lib/artifactory/client.rb', line 297

def default_headers
  {
    "Connection" => "keep-alive",
    "Keep-Alive" => "30",
    "User-Agent" => user_agent,
  }
end

#delete(path, params = {}, headers = {}) ⇒ String, Hash

Make a HTTP DELETE request

Parameters:

  • path (String)

    the absolute or relative path from Defaults.endpoint to make the request against

  • params (Hash) (defaults to: {})

    the list of query params

  • headers (Hash) (defaults to: {})

    the list of headers to use

Returns:

  • (String, Hash)

    the response body

Raises:



164
165
166
# File 'lib/artifactory/client.rb', line 164

def delete(path, params = {}, headers = {})
  request(:delete, path, params, headers)
end

#error(response) ⇒ Object

Raise a response error, extracting as much information from the server’s response as possible.

Parameters:

  • response (HTTP::Message)

    the response object from the request

Raises:



396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
# File 'lib/artifactory/client.rb', line 396

def error(response)
  if (response.content_type || "").include?("json")
    # Attempt to parse the error as JSON
    begin
      json = JSON.parse(response.body)

      if json["errors"] && json["errors"].first
        raise Error::HTTPError.new(json["errors"].first)
      end
    rescue JSON::ParserError; end
  end

  raise Error::HTTPError.new(
    "status"  => response.code,
    "message" => response.body
  )
end

#get(path, params = {}, headers = {}) {|chunk| ... } ⇒ String, Hash

Make a HTTP GET request

If a block is provided the response body is yielded in chunks/fragments as it is read from the undelrying socket.

Parameters:

  • params (Hash) (defaults to: {})

    the list of query params

  • path (String)

    the absolute or relative path from Defaults.endpoint to make the request against

  • headers (Hash) (defaults to: {})

    the list of headers to use

Yields:

  • (chunk)

    Partial piece of response body

Returns:

  • (String, Hash)

    the response body

Raises:



107
108
109
# File 'lib/artifactory/client.rb', line 107

def get(path, params = {}, headers = {}, &block)
  request(:get, path, params, headers, &block)
end

#patch(path, data, headers = {}) ⇒ String, Hash

Make a HTTP PATCH request

Parameters:

  • path (String)

    the absolute or relative path from Defaults.endpoint to make the request against

  • data (String, #read)

    the body to use for the request

  • headers (Hash) (defaults to: {})

    the list of headers to use

Returns:

  • (String, Hash)

    the response body

Raises:



150
151
152
# File 'lib/artifactory/client.rb', line 150

def patch(path, data, headers = {})
  request(:patch, path, data, headers)
end

#post(path, data, headers = {}) ⇒ String, Hash

Make a HTTP POST request

Parameters:

  • data (String, #read)

    the body to use for the request

  • path (String)

    the absolute or relative path from Defaults.endpoint to make the request against

  • headers (Hash) (defaults to: {})

    the list of headers to use

Returns:

  • (String, Hash)

    the response body

Raises:



122
123
124
# File 'lib/artifactory/client.rb', line 122

def post(path, data, headers = {})
  request(:post, path, data, headers)
end

#put(path, data, headers = {}) ⇒ String, Hash

Make a HTTP PUT request

Parameters:

  • path (String)

    the absolute or relative path from Defaults.endpoint to make the request against

  • data (String, #read)

    the body to use for the request

  • headers (Hash) (defaults to: {})

    the list of headers to use

Returns:

  • (String, Hash)

    the response body

Raises:



136
137
138
# File 'lib/artifactory/client.rb', line 136

def put(path, data, headers = {})
  request(:put, path, data, headers)
end

#request(verb, path, data = {}, headers = {}) {|chunk| ... } ⇒ String, Hash

Make an HTTP request with the given verb, data, params, and headers. If the response has a return type of JSON, the JSON is automatically parsed and returned as a hash; otherwise it is returned as a string. If a block is provided the response body is yielded in chunks/fragments as it is read from the undelrying socket.

Parameters:

  • verb (Symbol)

    the lowercase symbol of the HTTP verb (e.g. :get, :delete)

  • path (String)

    the absolute or relative path from Defaults.endpoint to make the request against

  • data (#read, Hash, nil) (defaults to: {})

    the data to use (varies based on the verb)

  • headers (Hash) (defaults to: {})

    the list of headers to use

Yields:

  • (chunk)

    Partial piece of response body

Returns:

  • (String, Hash)

    the response body

Raises:



193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/artifactory/client.rb', line 193

def request(verb, path, data = {}, headers = {}, &block)
  # Build the URI and request object from the given information
  uri = build_uri(verb, path, data)
  request = class_for_request(verb).new(uri.request_uri)

  # Add headers
  default_headers.merge(headers).each do |key, value|
    request.add_field(key, value)
  end

  # Add basic authentication
  if username && password
    request.basic_auth(username, password)
  elsif api_key
    request.add_field("X-JFrog-Art-Api", api_key)
  end

  # Setup PATCH/POST/PUT
  if %i{patch post put}.include?(verb)
    if data.respond_to?(:read)
      request.content_length = data.size
      request.body_stream = data
    elsif data.is_a?(Hash)
      request.form_data = data
    else
      request.body = data
    end
  end

  # Create the HTTP connection object - since the proxy information defaults
  # to +nil+, we can just pass it to the initializer method instead of doing
  # crazy strange conditionals.
  connection = Net::HTTP.new(uri.host, uri.port,
    proxy_address, proxy_port, proxy_username, proxy_password)

  # The artifacts being uploaded might be large, so there’s a good chance
  # we'll need to bump this higher than the `Net::HTTP` default of 60
  # seconds.
  connection.read_timeout = read_timeout

  # Apply SSL, if applicable
  if uri.scheme == "https"
    require "net/https" unless defined?(Net::HTTPS)

    # Turn on SSL
    connection.use_ssl = true

    # Custom pem files, no problem!
    if ssl_pem_file
      pem = File.read(ssl_pem_file)
      connection.cert = OpenSSL::X509::Certificate.new(pem)
      connection.key = OpenSSL::PKey::RSA.new(pem)
      connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
    end

    # Naughty, naughty, naughty! Don't blame when when someone hops in
    # and executes a MITM attack!
    unless ssl_verify
      connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
    end
  end

  # Create a connection using the block form, which will ensure the socket
  # is properly closed in the event of an error.
  connection.start do |http|

    if block_given?
      http.request(request) do |response|
        case response
        when Net::HTTPRedirection
          redirect = response["location"]
          request(verb, redirect, data, headers, &block)
        when Net::HTTPSuccess
          response.read_body do |chunk|
            yield chunk
          end
        else
          error(response)
        end
      end
    else
      response = http.request(request)

      case response
      when Net::HTTPRedirection
        redirect = response["location"]
        request(verb, redirect, data, headers)
      when Net::HTTPSuccess
        success(response)
      else
        error(response)
      end
    end
  end
rescue SocketError, Errno::ECONNREFUSED, EOFError
  raise Error::ConnectionError.new(endpoint)
end

#same_options?(opts) ⇒ Boolean

Determine if the given options are the same as ours.

Returns:

  • (Boolean)


87
88
89
# File 'lib/artifactory/client.rb', line 87

def same_options?(opts)
  opts.hash == options.hash
end

#success(response) ⇒ String, Hash

Parse the response object and manipulate the result based on the given Content-Type header. For now, this method only parses JSON, but it could be expanded in the future to accept other content types.

Parameters:

  • response (HTTP::Message)

    the response object from the request

Returns:

  • (String, Hash)

    the parsed response, as an object



379
380
381
382
383
384
385
# File 'lib/artifactory/client.rb', line 379

def success(response)
  if (response.content_type || "").include?("json")
    JSON.parse(response.body || "{}")
  else
    response.body || ""
  end
end

#to_query_string(hash) ⇒ String?

Convert the given hash to a list of query string parameters. Each key and value in the hash is URI-escaped for safety.

Parameters:

  • hash (Hash)

    the hash to create the query string from

Returns:

  • (String, nil)

    the query string as a string, or nil if there are no params



362
363
364
365
366
# File 'lib/artifactory/client.rb', line 362

def to_query_string(hash)
  hash.map do |key, value|
    "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"
  end.join("&")[/.+/]
end