class Artifactory::Client

Attributes

basic_auth[R]
headers[R]
http[R]
uri[R]

Public Class Methods

new(endpoint:, username: nil, password: nil, api_key: nil, ssl_verify: true) click to toggle source

Initialize an Artifactory client instance

@param endpoint [String] Artifactory REST API endpoint @param username [String] Username for HTTP basic authentication @param password [String] Password for HTTP basic authentication @param api_key [String] API key @param ssl_verify [Boolean] Enable/Disable SSL verification

# File lib/artifactory/client.rb, line 19
def initialize(endpoint:, username: nil, password: nil, api_key: nil, ssl_verify: true)
  basic_auth = {}
  uri = URI.parse(endpoint)
  http = Net::HTTP.new(uri.host, uri.port)

  if (username and api_key) or (username.nil? and api_key.nil?)
    raise RuntimeError, "Either HTTP basic or API key are allowed as authentication methods"
  end

  headers = {
    'Content-type' => 'application/json',
    'Accept' => 'application/json',
  }

  if username
    basic_auth = {'username' => username, 'password' => password}
  else
    headers['X-JFrog-Art-Api'] = api_key
  end

  if uri.scheme == 'https'
    http.use_ssl = true
    http.verify_mode = OpenSSL::SSL::VERIFY_NONE unless ssl_verify
  end

  @uri = uri
  @http = http
  @basic_auth = basic_auth
  @headers = headers
end

Public Instance Methods

docker_images(repo_key:) click to toggle source

Lists all Docker repositories hosted in under an Artifactory Docker repository

@param repo_key [String] Repository key @param recurse [Boolean] Recursively retrieve image tags @return [Hash, Array<String>] List of docker images

# File lib/artifactory/client.rb, line 83
def docker_images(repo_key:)
  api_get("/docker/#{repo_key}/v2/_catalog")['repositories']
end
docker_manifest(repo_key:, image_name:, image_tag:) click to toggle source

Retrieve a docker image tag manifest

@param repo_key [String] Repository key @param image_name [String] Docker image name @param image_tag [String] Docker image tag @return [Hash] Docker manifest describing the tag

# File lib/artifactory/client.rb, line 104
def docker_manifest(repo_key:, image_name:, image_tag:)
  api_get("/docker/#{repo_key}/v2/#{image_name}/manifests/#{image_tag}")
end
docker_tags(repo_key:, image_name:) click to toggle source

Retrieve all tags for a docker image

@param repo_key [String] Repository key @param image_name [String] Docker image name @return [Array<String>] List of tags

# File lib/artifactory/client.rb, line 93
def docker_tags(repo_key:, image_name:)
  api_get("/docker/#{repo_key}/v2/#{image_name}/tags/list")['tags']
end
file_delete(repo_key:, path:) click to toggle source

Deletes a file or a folder from the specified destination

@param repo_key [String] Repository key @param path [String] Path of the file to delete

# File lib/artifactory/client.rb, line 186
def file_delete(repo_key:, path:)
  api_delete(File.join(repo_key, path))
end
file_info(repo_key:, path:) click to toggle source

Get file information like last modification time, creation time etc.

@param repo_key [String] Repository key @param path [String] Path of the file to look up @return [Hash] File information

# File lib/artifactory/client.rb, line 140
def file_info(repo_key:, path:)
  ret = {}

  api_get(File.join("/storage", repo_key, path).chomp('/')).each do |k, v|
    case k
      when "created", "lastModified", "lastUpdated"
        ret[k] = Time.parse(v)

      else
        ret[k] = v
    end
  end

  ret
end
file_list(repo_key:, folder_path: '/', deep: false, depth: 0, list_folders: false, md_timestamps: false, include_root_path: false) click to toggle source

Get a flat (the default) or deep listing of the files and folders (not included by default) within a folder

@param repo_key [String] Repository key @param image_name [String] Docker image name @param image_tag [String] Docker image tag @return [Hash] Docker manifest describing the tag

# File lib/artifactory/client.rb, line 115
def file_list(repo_key:, folder_path: '/', deep: false, depth: 0, list_folders: false, md_timestamps: false, include_root_path: false)
  path = ["/storage", repo_key, folder_path].join('/').chomp('/')
  params = ['list']
  params << "deep=#{deep ? 1 : 0}"
  params << "depth=#{depth}" if depth > 0
  params << "listFolders=#{list_folders ? 1 : 0}"
  params << "mdTimestamps=#{md_timestamps ? 1 : 0}"
  params << "includeRootPath=#{include_root_path ? 1 : 0}"

  files = {}
  api_get([path, params.join('&')].join('?'))['files'].each do |file|
    name = file['uri']
    files[name] = file.tap { |h| h.delete('uri') }
    files[name]['lastModified'] = Time.parse(files[name]['lastModified'])
  end

  files
end
file_stat(repo_key:, path:) click to toggle source

Get file statistics like the number of times an item was downloaded, last download date and last downloader.

@param repo_key [String] Repository key @param path [String] Path of the file to look up @return [Hash] File statistics

# File lib/artifactory/client.rb, line 162
def file_stat(repo_key:, path:)
  ret = {}

  p = File.join("/storage", repo_key, path).chomp('/')
  params = ['stats']

  api_get([p, params.join('&')].join('?')).tap { |h| h.delete('uri') }.each do |k, v|
    case k
      when "lastDownloaded", "remoteLastDownloaded"
        ret[k] = Time.at(v/1000) if v > 0

      else
        ret[k] = v
    end
  end

  ret
end
get_repo(key:) click to toggle source

Retrieves the current configuration of a repository. Supported by local, remote and virtual repositories.

@param key [String] Repository key @return [Hash] Repository information

# File lib/artifactory/client.rb, line 55
def get_repo(key:)
  api_get("/repositories/#{key}").tap { |h| h.delete('key') }
end
repos(type: nil, recurse: false) click to toggle source

Returns a list of minimal repository details (unless recurse is enabled) for all repositories of the specified type.

@param type [nil, local, remote, virtual] Optionally filter by repository type @param recurse [Boolean] Recursively retrieve repos configuration @return [Hash] List of repositories

# File lib/artifactory/client.rb, line 65
def repos(type: nil, recurse: false)
  ret = {}
  params = []
  params << "type=#{type}" if type

  api_get(["/repositories", params.join('&')].join('?')).each do |repo|
    ret[repo['key']] = recurse ? self.get_repo(key: repo['key']) : repo.tap { |h| h.delete('key') }
  end

  ret
end
search_creation(repo_key:, from_date: nil, to_date: Time.now) click to toggle source

Get all artifacts created in date range

@param repo_key [String, Array<String>] Repository key(s) @param from_date [Time] Return artifacts that have not been used since the given date @param to_date [Time] Return artifacts that have been created before the given date @return [Hash] Artifacts matching search criteria

# File lib/artifactory/client.rb, line 285
def search_creation(repo_key:, from_date: nil, to_date: Time.now)
  ret = []

  path = File.join("/search", "creation")
  params = []
  params << "from=#{(from_date.to_f.round(3) * 1000).to_i}" if from_date
  params << "to=#{(to_date.to_f.round(3) * 1000).to_i}"
  params << "repos=#{repo_key.is_a?(Array) ? repo_key.join(',') : repo_key}"

  api_get([path, params.join('&')].join('?'))['results'].each do |result|
    full_path = result['uri'].scan(/\/storage\/(.+?)(\/.*)/).flatten

    file = {
      "repo_key" => full_path[0],
      "path" => full_path[1]
    }

    result.each do |k, v|
      case k
        when "created"
          file[k] = Time.parse(v)

        else
          file[k] = v
      end
    end

    ret << file
  end

  ret
end
search_dates(repo_key:, from_date: nil, to_date: Time.now, date_fields:) click to toggle source

Get all artifacts with specified dates within the given range

@param repo_key [String, Array<String>] Repository key(s) @param from_date [Time] Return artifacts that have not been used since the given date @param to_date [Time] Return artifacts that have been created before the given date @param date_fields [created, lastModified, lastDownloaded] Date fields that specify which fields the from_date and to_date values should be applied to @return [Hash] Artifacts matching search criteria

# File lib/artifactory/client.rb, line 238
def search_dates(repo_key:, from_date: nil, to_date: Time.now, date_fields:)
  ret = []

  valid_date_fields = ["created", "lastModified", "lastDownloaded"]

  date_fields.each do |date_field|
    raise ValueError, "Not a valid date field '#{date_field}'" unless valid_date_fields.include?(date_field)
  end

  path = File.join("/search", "dates")
  params = []
  params << "from=#{(from_date.to_f.round(3) * 1000).to_i}" if from_date
  params << "to=#{(to_date.to_f.round(3) * 1000).to_i}"
  params << "repos=#{repo_key.is_a?(Array) ? repo_key.join(',') : repo_key}"
  params << "dateFields=#{date_fields.join(',')}"

  api_get([path, params.join('&')].join('?'))['results'].each do |result|
    full_path = result['uri'].scan(/\/storage\/(.+?)(\/.*)/).flatten

    file = {
      "repo_key" => full_path[0],
      "path" => full_path[1]
    }

    result.each do |k, v|
      case k
        when *valid_date_fields
          file[k] = Time.parse(v)

        else
          file[k] = v
      end
    end

    ret << file
  end

  ret
end
search_pattern(repo_key:, pattern:) click to toggle source

Get all artifacts matching the given path pattern

@param repo_key [String] Repository key @param pattern [String] File pattern @return [Hash] Artifacts matching search pattern

# File lib/artifactory/client.rb, line 324
def search_pattern(repo_key:, pattern:)
  path = File.join("/search", "pattern")
  params = ["pattern=#{repo_key}:#{pattern}"]

  api_get([path, params].join('?'))['files']
end
search_usage(repo_key:, not_used_since:, created_before: nil) click to toggle source

Retrieve all artifacts not downloaded since the specified Java epoch in milliseconds

@param repo_key [String, Array<String>] Repository key(s) @param not_used_since [Time] Return artifacts that have not been used since the given date @param created_before [Time] Return artifacts that have been created before the given date @return [Hash] Artifacts matching search criteria

# File lib/artifactory/client.rb, line 197
def search_usage(repo_key:, not_used_since:, created_before: nil)
  ret = []

  path = File.join("/search", "usage")
  params = []
  params << "notUsedSince=#{(not_used_since.to_f.round(3) * 1000).to_i}"
  params << "createdBefore=#{(created_before.to_f.round(3) * 1000).to_i}" if created_before
  params << "repos=#{repo_key.is_a?(Array) ? repo_key.join(',') : repo_key}"

  api_get([path, params.join('&')].join('?'))['results'].each do |result|
    full_path = result['uri'].scan(/\/storage\/(.+?)(\/.*)/).flatten

    file = {
      "repo_key" => full_path[0],
      "path" => full_path[1]
    }

    result.each do |k, v|
      case k
        when "lastDownloaded", "remoteLastDownloaded"
          file[k] = Time.parse(v)

        else
          file[k] = v
      end
    end

    ret << file
  end

  ret
end

Private Instance Methods

api_delete(query) click to toggle source

Dispatch a DELETE request to the Artifactory API interface

@param query [String] HTTP request query

# File lib/artifactory/client.rb, line 363
def api_delete(query)
  begin
    req = Net::HTTP::Delete.new(File.join(self.uri.path, query), self.headers)
    req.basic_auth(self.basic_auth['username'], self.basic_auth['password']) if self.basic_auth
    resp = self.http.request(req)

    raise Exception, "Query returned a non successful HTTP code (Code: #{resp.code}, Error: #{resp.message})" unless resp.is_a?(Net::HTTPNoContent)
  rescue
    raise Exception, "Failed to execute DELETE request to Artifactory REST API (#{$!})"
  end
end
api_get(query) click to toggle source

Dispatch a GET request to the Artifactory API interface

@param query [String] HTTP request query @return Response from the server

# File lib/artifactory/client.rb, line 337
def api_get(query)
  begin
    req = Net::HTTP::Get.new(File.join(self.uri.path, 'api', query), self.headers)
    req.basic_auth(self.basic_auth['username'], self.basic_auth['password']) if self.basic_auth
    resp = self.http.request(req)

    if resp.is_a?(Net::HTTPOK)
      begin
        data = JSON.parse(resp.body)
      rescue JSON::ParserError
        raise Exception, "Failed to decode response message"
      end
    else
      raise Exception, "Query returned a non successful HTTP code (Code: #{resp.code}, Error: #{resp.message})"
    end
  rescue
    raise Exception, "Failed to execute GET request to Artifactory REST API (#{$!})"
  end

  data
end