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.



50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/artifactory/client.rb', line 50

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



16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/artifactory/client.rb', line 16

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)


276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
# File 'lib/artifactory/client.rb', line 276

def build_uri(verb, path, params = {})
  # Add any query string parameters
  if [: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)


301
302
303
# File 'lib/artifactory/client.rb', line 301

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)


250
251
252
253
254
255
256
# File 'lib/artifactory/client.rb', line 250

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:



140
141
142
# File 'lib/artifactory/client.rb', line 140

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:



349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
# File 'lib/artifactory/client.rb', line 349

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 = {}) ⇒ String, Hash

Make a HTTP GET request

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

Returns:

  • (String, Hash)

    the response body

Raises:



83
84
85
# File 'lib/artifactory/client.rb', line 83

def get(path, params = {}, headers = {})
  request(:get, path, params, headers)
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:



126
127
128
# File 'lib/artifactory/client.rb', line 126

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:



98
99
100
# File 'lib/artifactory/client.rb', line 98

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:



112
113
114
# File 'lib/artifactory/client.rb', line 112

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

#request(verb, path, data = {}, headers = {}) ⇒ 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.

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

Returns:

  • (String, Hash)

    the response body

Raises:



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
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
# File 'lib/artifactory/client.rb', line 165

def request(verb, path, data = {}, headers = {})
  # 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)
  end

  # Setup PATCH/POST/PUT
  if [: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|
    response = http.request(request)

    case response
    when Net::HTTPRedirection
      redirect = URI.parse(response['location'])
      request(verb, redirect, params, headers)
    when Net::HTTPSuccess
      success(response)
    else
      error(response)
    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)


68
69
70
# File 'lib/artifactory/client.rb', line 68

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



332
333
334
335
336
337
338
# File 'lib/artifactory/client.rb', line 332

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



315
316
317
318
319
# File 'lib/artifactory/client.rb', line 315

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