class DockerRegistry2::Registry

Public Class Methods

new(uri, options = {}) click to toggle source

@param [#to_s] base_uri Docker registry base URI @param [Hash] options Client options @option options [#to_s] :user User name for basic authentication @option options [#to_s] :password Password for basic authentication @option options [#to_s] :open_timeout Time to wait for a connection with a registry.

It is ignored if http_options[:open_timeout] is also specified.

@option options [#to_s] :read_timeout Time to wait for data from a registry.

It is ignored if http_options[:read_timeout] is also specified.

@option options [Hash] :http_options Extra options for RestClient::Request.execute.

# File lib/registry/registry.rb, line 15
def initialize(uri, options = {})
  @uri = URI.parse(uri)
  @base_uri = "#{@uri.scheme}://#{@uri.host}:#{@uri.port}"
  @user = options[:user]
  @password = options[:password]
  @http_options = options[:http_options] || {}
  @http_options[:open_timeout] ||= options[:open_timeout] || 2
  @http_options[:read_timeout] ||= options[:read_timeout] || 5
end

Public Instance Methods

_pull_v1(repo, manifest, dir) click to toggle source
# File lib/registry/registry.rb, line 174
def _pull_v1(repo, manifest, dir)
  # make sure the directory exists
  FileUtils.mkdir_p dir
  return false unless manifest['schemaVersion'] == 1
  # pull each of the layers
  manifest['fsLayers'].each do |layer|
    # define path of file to save layer in
    layer_file = "#{dir}/#{layer['blobSum']}"
    # skip layer if we already got it
    next if File.file? layer_file
    # download layer
    # puts "getting layer (v1) #{layer['blobSum']}"
    blob(repo, layer['blobSum'], layer_file)
    # return layer file
    layer_file
  end
end
_pull_v2(repo, manifest, dir) click to toggle source
# File lib/registry/registry.rb, line 157
def _pull_v2(repo, manifest, dir)
  # make sure the directory exists
  FileUtils.mkdir_p dir
  return false unless manifest['schemaVersion'] == 2
  # pull each of the layers
  manifest['layers'].each do |layer|
    # define path of file to save layer in
    layer_file = "#{dir}/#{layer['digest']}"
    # skip layer if we already got it
    next if File.file? layer_file
    # download layer
    # puts "getting layer (v2) #{layer['digest']}"
    blob(repo, layer['digest'], layer_file)
    layer_file
  end
end
blob(repo, digest, outpath=nil) click to toggle source
# File lib/registry/registry.rb, line 114
def blob(repo, digest, outpath=nil)
  blob_url = "/v2/#{repo}/blobs/#{digest}"
  if outpath.nil? 
    response = doget(blob_url)
    DockerRegistry2::Blob.new(response.headers, response.body)
  else
    File.open(outpath, 'w') do |fd|
      doreq('get', blob_url, fd)
    end 

    outpath
  end
end
blob_size(repo,blobSum) click to toggle source

gets the size of a particular blob, given the repo and the content-addressable hash usually unneeded, since manifest includes it

# File lib/registry/registry.rb, line 210
def blob_size(repo,blobSum)
  response = dohead "/v2/#{repo}/blobs/#{blobSum}"
  Integer(response.headers[:content_length],10)
end
copy(repo,tag,newregistry,newrepo,newtag) click to toggle source
# File lib/registry/registry.rb, line 205
def copy(repo,tag,newregistry,newrepo,newtag)
end
digest(repo, tag) click to toggle source
# File lib/registry/registry.rb, line 128
def digest(repo, tag)
  tag_path = "/v2/#{repo}/manifests/#{tag}"
  dohead(tag_path).headers[:docker_content_digest]
rescue DockerRegistry2::InvalidMethod
  # Pre-2.3.0 registries didn't support manifest HEAD requests
  doget(tag_path).headers[:docker_content_digest]
end
dodelete(url) click to toggle source
# File lib/registry/registry.rb, line 33
def dodelete(url)
  return doreq "delete", url
end
doget(url) click to toggle source
# File lib/registry/registry.rb, line 25
def doget(url)
  return doreq "get", url
end
dohead(url) click to toggle source
# File lib/registry/registry.rb, line 37
def dohead(url)
  return doreq "head", url
end
doput(url,payload=nil) click to toggle source
# File lib/registry/registry.rb, line 29
def doput(url,payload=nil)
  return doreq "put", url, nil, payload
end
last(header) click to toggle source
# File lib/registry/registry.rb, line 215
def last(header)
  last=''
  parts = header.split(',')
  links = Hash.new

  # Parse each part into a named link
  parts.each do |part, index|
    section = part.split(';')
    url = section[0][/<(.*)>/,1]
    name = section[1][/rel="(.*)"/,1].to_sym
    links[name] = url
  end

  if links[:next]
    query=URI(links[:next]).query
    last=URI::decode_www_form(query).to_h["last"]
  end
  last
end
manifest(repo,tag) click to toggle source
# File lib/registry/registry.rb, line 104
def manifest(repo,tag)
  # first get the manifest
  response = doget "/v2/#{repo}/manifests/#{tag}"
  parsed = JSON.parse response.body
  manifest = DockerRegistry2::Manifest[parsed]
  manifest.body = response.body
  manifest.headers = response.headers
  manifest
end
manifest_sum(manifest) click to toggle source
# File lib/registry/registry.rb, line 235
def manifest_sum(manifest)
  size = 0
  manifest["layers"].each { |layer|
    size += layer["size"]
  }
  size
end
ping() click to toggle source
# File lib/registry/registry.rb, line 41
def ping
  doget '/v2/'
end
pull(repo, tag, dir) click to toggle source
# File lib/registry/registry.rb, line 143
def pull(repo, tag, dir)
  # make sure the directory exists
  FileUtils.mkdir_p dir
  # get the manifest
  m = manifest repo, tag
  # puts "pulling #{repo}:#{tag} into #{dir}"
  # manifest can contain multiple manifests one for each API version
  downloaded_layers = []
  downloaded_layers += _pull_v2(repo, m, dir) if m['schemaVersion'] == 2
  downloaded_layers += _pull_v1(repo, m, dir) if m['schemaVersion'] == 1
  # return downloaded_layers
  downloaded_layers
end
push(manifest,dir) click to toggle source
# File lib/registry/registry.rb, line 192
def push(manifest,dir)
end
rmtag(image, tag) click to toggle source
# File lib/registry/registry.rb, line 136
def rmtag(image, tag)
  # TODO: Need full response back. Rewrite other manifests() calls without JSON?
  reference = doget("/v2/#{image}/manifests/#{tag}").headers[:docker_content_digest]

  return dodelete("/v2/#{image}/manifests/#{reference}").code
end
tag(repo,tag,newrepo,newtag) click to toggle source
# File lib/registry/registry.rb, line 195
def tag(repo,tag,newrepo,newtag)
  manifest = manifest(repo, tag)

  if manifest['schemaVersion'] == 2
    doput "/v2/#{newrepo}/manifests/#{newtag}", manifest.to_json
  else
    raise DockerRegistry2::RegistryVersionException
  end
end
tags(repo,count=nil,last="",withHashes = false, auto_paginate: false) click to toggle source
# File lib/registry/registry.rb, line 56
def tags(repo,count=nil,last="",withHashes = false, auto_paginate: false)
  #create query params
  params = []
  params.push(["last",last]) if last && last != ""
  params.push(["n",count]) unless count.nil?

  query_vars = ""
  query_vars = "?#{URI.encode_www_form(params)}" if params.length > 0

  response = doget "/v2/#{repo}/tags/list#{query_vars}"
  # parse the response
  resp = JSON.parse response
  # parse out next page link if necessary
  resp["last"] = last(response.headers[:link]) if response.headers[:link]

  # do we include the hashes?
  if withHashes
    useGet = false
    resp["hashes"] = {}
    resp["tags"].each do |tag|
      if useGet
        head = doget "/v2/#{repo}/manifests/#{tag}"
      else
        begin
          head = dohead "/v2/#{repo}/manifests/#{tag}"
        rescue DockerRegistry2::InvalidMethod
          # in case we are in a registry pre-2.3.0, which did not support manifest HEAD
          useGet = true
          head = doget "/v2/#{repo}/manifests/#{tag}"
        end
      end
      resp["hashes"][tag] = head.headers[:docker_content_digest]
    end
  end

  return resp unless auto_paginate

  while (last_tag = resp.delete("last"))
    additional_tags = tags(repo, count, last_tag, withHashes)
    resp["last"] = additional_tags["last"]
    resp["tags"] += additional_tags["tags"]
    resp["tags"] = resp["tags"].uniq
    resp["hashes"].merge!(additional_tags["hashes"]) if withHashes
  end

  resp
end

Private Instance Methods

authenticate_bearer(header) click to toggle source
# File lib/registry/registry.rb, line 333
def authenticate_bearer(header)
  # get the parts we need
  target = split_auth_header(header)
  # did we have a username and password?
  if defined? @user and @user.to_s.strip.length != 0
    target[:params][:account] = @user
  end
  # authenticate against the realm
  uri = URI.parse(target[:realm])
  begin
    response = RestClient::Request.execute(@http_options.merge(
      method: :get,
      url: uri.to_s, headers: {params: target[:params]},
      user: @user,
      password: @password,
    ))
  rescue RestClient::Unauthorized, RestClient::Forbidden
    # bad authentication
    raise DockerRegistry2::RegistryAuthenticationException
  rescue RestClient::NotFound => error
    raise DockerRegistry2::NotFound, error
  end
  # now save the web token
  result = JSON.parse(response)
  return result["token"] || result["access_token"]
end
do_basic_req(type, url, stream=nil, payload=nil) click to toggle source
# File lib/registry/registry.rb, line 277
def do_basic_req(type, url, stream=nil, payload=nil)
  begin
    block = stream.nil? ? nil : proc { |response|
      response.read_body do |chunk|
        stream.write chunk
      end
    }
    response = RestClient::Request.execute(@http_options.merge(
      method: type,
      url: @base_uri+url,
      user: @user,
      password: @password,
      headers: headers(payload: payload),
      block_response: block,
      payload: payload
    ))
  rescue SocketError
    raise DockerRegistry2::RegistryUnknownException
  rescue RestClient::Unauthorized
    raise DockerRegistry2::RegistryAuthenticationException
  rescue RestClient::MethodNotAllowed
    raise DockerRegistry2::InvalidMethod
  rescue RestClient::NotFound => error
    raise DockerRegistry2::NotFound, error
  end
  return response
end
do_bearer_req(type, url, header, stream=false, payload=nil) click to toggle source
# File lib/registry/registry.rb, line 305
def do_bearer_req(type, url, header, stream=false, payload=nil)
  token = authenticate_bearer(header)
  begin
    block = stream.nil? ? nil : proc { |response|
      response.read_body do |chunk|
        stream.write chunk
      end
    }
    response = RestClient::Request.execute(@http_options.merge(
      method: type,
      url: @base_uri+url,
      headers: headers(payload: payload, bearer_token: token),
      block_response: block,
      payload: payload
    ))
  rescue SocketError
    raise DockerRegistry2::RegistryUnknownException
  rescue RestClient::Unauthorized
    raise DockerRegistry2::RegistryAuthenticationException
  rescue RestClient::MethodNotAllowed
    raise DockerRegistry2::InvalidMethod
  rescue RestClient::NotFound => error
    raise DockerRegistry2::NotFound, error
  end

  return response
end
doreq(type,url,stream=nil,payload=nil) click to toggle source
# File lib/registry/registry.rb, line 244
def doreq(type,url,stream=nil,payload=nil)
  begin
    block = stream.nil? ? nil : proc { |response|
      response.read_body do |chunk|
        stream.write chunk
      end
    }
    response = RestClient::Request.execute(@http_options.merge(
      method: type,
      url: @base_uri+url,
      headers: headers(payload: payload),
      block_response: block,
      payload: payload
    ))
  rescue SocketError
    raise DockerRegistry2::RegistryUnknownException
  rescue RestClient::NotFound => error
    raise DockerRegistry2::NotFound, error
  rescue RestClient::Unauthorized => e
    header = e.response.headers[:www_authenticate]
    method = header.downcase.split(' ')[0]
    case method
    when 'basic'
      response = do_basic_req(type, url, stream, payload)
    when 'bearer'
      response = do_bearer_req(type, url, header, stream, payload)
    else
      raise DockerRegistry2::RegistryUnknownException
    end
  end
  return response
end
headers(payload: nil, bearer_token: nil) click to toggle source
# File lib/registry/registry.rb, line 374
def headers(payload: nil, bearer_token: nil)
  headers={}
  headers['Authorization']="Bearer #{bearer_token}" unless bearer_token.nil?
  headers['Accept']='application/vnd.docker.distribution.manifest.v2+json, application/json' if payload.nil?
  headers['Content-Type']='application/vnd.docker.distribution.manifest.v2+json' unless payload.nil?

  headers
end
split_auth_header(header = '') click to toggle source
# File lib/registry/registry.rb, line 360
def split_auth_header(header = '')
  h = Hash.new
  h = {params: {}}
  header.scan(/([\w]+)\=\"([^"]+)\"/) do |entry|
    case entry[0]
    when 'realm'
      h[:realm] = entry[1]
    else
      h[:params][entry[0]] = entry[1]
    end
  end
  h
end