Class: DockerRegistry2::Registry

Inherits:
Object
  • Object
show all
Defined in:
lib/plankton/monkey_patches.rb

Overview

Monkey patch the DockerRegistry2::Registry issues away.

Things that are broken by default:

  • pull

Things that are not ideal:

  • Tons of round trips while auth probing (again, and again, and again..)

  • Tons of round trips to always get a “fresh” JWT token

  • Performance

See: docs.docker.com/registry/spec/api See: github.com/gitlabhq/gitlabhq/tree/v10.0.0/lib/container_registry

Instance Method Summary collapse

Constructor Details

#initialize(uri, options = {}) ⇒ Registry

Returns a new instance of Registry.

Parameters:

  • base_uri (#to_s)

    Docker registry base URI

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

    Client options

Options Hash (options):

  • :user (#to_s)

    User name for basic authentication

  • :password (#to_s)

    Password for basic authentication



27
28
29
30
# File 'lib/plankton/monkey_patches.rb', line 27

def initialize(uri, options = {})
  @verbose = options.key?(:verbose) ? options[:verbose] : false
  orig_initialize(uri, options)
end

Instance Method Details

#authenticate_bearer(header) ⇒ Object

Speed up processing, by memorizing the JWT token for the given header.



34
35
36
37
# File 'lib/plankton/monkey_patches.rb', line 34

def authenticate_bearer(header)
  @token ||= {}
  @token[header] ||= orig_authenticate_bearer(header)
end

#blob(repo, digest, file) ⇒ Object

Download a blob to a opened IO handle.



77
78
79
# File 'lib/plankton/monkey_patches.rb', line 77

def blob(repo, digest, file)
  doreq('get', "/v2/#{repo}/blobs/#{digest}", file)
end

#do_basic_req(type, url, stream = nil) ⇒ Object

Save the auth type as basic, when the very first (ping) request was successful.



41
42
43
44
45
# File 'lib/plankton/monkey_patches.rb', line 41

def do_basic_req(type, url, stream = nil)
  res = orig_do_basic_req(type, url, stream)
  @auth ||= :basic
  res
end

#do_bearer_req(type, url, header, stream = nil) ⇒ Object

Save the auth type as bearer, when the very first (ping) request was successful.



49
50
51
52
53
54
# File 'lib/plankton/monkey_patches.rb', line 49

def do_bearer_req(type, url, header, stream = nil)
  res = orig_do_bearer_req(type, url, header, stream)
  @auth ||= :bearer
  @header = header
  res
end

#doreq(type, url, stream = nil) ⇒ Object

# Speed up the system by memorize the probed auth type.



57
58
59
60
61
62
63
64
# File 'lib/plankton/monkey_patches.rb', line 57

def doreq(type, url, stream = nil)
  puts "[#{type.upcase}] #{@base_uri}#{url}" if @verbose
  return orig_do_basic_req(type, url, stream) if @auth == :basic
  return orig_do_bearer_req(type, url, @header, stream) if @auth == :bearer
  orig_doreq(type, url, stream)
rescue DockerRegistry2::RegistryAuthenticationException
  orig_doreq(type, url, stream)
end

#manifest(repo:, tag:) ⇒ Object

Do it the same way as the original, but return a OpenStruct because its easy to work with.



68
69
70
71
72
73
74
# File 'lib/plankton/monkey_patches.rb', line 68

def manifest(repo:, tag:)
  res = doget("/v2/#{repo}/manifests/#{tag}")
  digest = res.headers[:docker_content_digest]
  res = RecursiveOpenStruct.new(JSON.parse(res), recurse_over_arrays: true)
  res.digest = digest
  res
end

#orig_authenticate_bearerObject



18
# File 'lib/plankton/monkey_patches.rb', line 18

alias_method :orig_authenticate_bearer, :authenticate_bearer

#orig_do_basic_reqObject



19
# File 'lib/plankton/monkey_patches.rb', line 19

alias_method :orig_do_basic_req, :do_basic_req

#orig_do_bearer_reqObject



20
# File 'lib/plankton/monkey_patches.rb', line 20

alias_method :orig_do_bearer_req, :do_bearer_req

#orig_doreqObject



21
# File 'lib/plankton/monkey_patches.rb', line 21

alias_method :orig_doreq, :doreq

#orig_initializeObject

All these methods are useful, but needs some improvements from outer space.



16
# File 'lib/plankton/monkey_patches.rb', line 16

alias_method :orig_initialize, :initialize

#orig_rmtagObject



17
# File 'lib/plankton/monkey_patches.rb', line 17

alias_method :orig_rmtag, :rmtag

#pull(repo:, tag:, dir:) ⇒ Object

Pull all layers of a given tag.



82
83
84
85
86
87
88
89
90
91
92
# File 'lib/plankton/monkey_patches.rb', line 82

def pull(repo:, tag:, dir:)
  # make sure the directory exists
  FileUtils::mkdir_p(dir)
  # pull each of the layers
  manifest(repo: repo, tag: tag).layers.each do |layer|
    # make sure the layer does not exist first
    unless File.file? "#{dir}/#{layer.digest}"
      blob(repo, layer.digest, File.new("#{dir}/#{layer.digest}", 'w+'))
    end
  end
end

#rmtag(repo:, tag: nil, digest: nil) ⇒ Object

Sometimes it is handy do delete a tag by its name, sometimes it is handy to delete a tag by its digest. The digest variant is faster due no lookup have to be done.



128
129
130
131
132
# File 'lib/plankton/monkey_patches.rb', line 128

def rmtag(repo:, tag: nil, digest: nil)
  raise 'No tag or digest was given' if tag.nil? && digest.nil?
  return orig_rmtag(repo, tag) unless tag.nil?
  dodelete("/v2/#{repo}/manifests/#{digest}").code
end

#tag(repo:, tag:) ⇒ Object

Fetch details about a tag.



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/plankton/monkey_patches.rb', line 95

def tag(repo:, tag:)
  # Create new string IO handle
  config_input = StringIO.new
  # Download the manifest for the tag
  manifest = manifest(repo: repo, tag: tag)
  # Download the config blob for the tag
  blob(repo, manifest.config.digest, config_input)
  # Parse the JSON input
  config = RecursiveOpenStruct.new(JSON.parse(config_input.string))
  created = DateTime.parse(config.created)
  layer_size = manifest.layers.reduce(0) { |sum, l| sum + l.size }
  # Pass back all the detailed information
  OpenStruct.new(tag: tag,
                 digest: manifest.digest,
                 created_at: created,
                 layer_size: layer_size,
                 config: config,
                 manifest: manifest)
end

#tags(repo:, details: false, limit: 20) ⇒ Object

Fetch all tags of a repository, with the possibility to fetch all their details as well.



117
118
119
120
121
122
123
# File 'lib/plankton/monkey_patches.rb', line 117

def tags(repo:, details: false, limit: 20)
  unless details
    res = JSON.parse(doget("/v2/#{repo}/tags/list?n=#{limit}"))
    return RecursiveOpenStruct.new(res).tags || []
  end
  tags(repo: repo).map { |tag| tag(repo: repo, tag: tag) }
end