Class: RegistryApiClient

Inherits:
Object
  • Object
show all
Defined in:
lib/docker_cake/registry_api_client.rb

Defined Under Namespace

Modules: HTTP Classes: InvalidMethod, JsonError, PubSub, ReauthenticatedException, RegistryAuthenticationException, RegistryAuthorizationException, RegistrySSLException, RegistryUnknownException, UnknownRegistryException, Waiter

Constant Summary collapse

DEFAULT_REGISTRY =
"https://registry.hub.docker.com"
DEFAULT_MANIFEST =
"application/vnd.docker.distribution.manifest.v2+json"
FAT_MANIFEST =
"application/vnd.docker.distribution.manifest.list.v2+json"

Instance Method Summary collapse

Constructor Details

#initialize(url: DEFAULT_REGISTRY, user: nil, password: nil) ⇒ RegistryApiClient

Returns a new instance of RegistryApiClient.

Parameters:

  • base_uri (#to_s)

    Docker registry base URI

  • options (Hash)

    Client options



82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/docker_cake/registry_api_client.rb', line 82

def initialize(url: DEFAULT_REGISTRY, user: nil, password: nil)
  url = url || DEFAULT_REGISTRY
  @url = url
  uri = URI.parse(url)
  @base_uri = "#{uri.scheme}://#{uri.host}:#{uri.port}"
  @user = user
  @password = password
  @manifest_format = "application/vnd.docker.distribution.manifest.v2+json"
  #@manifest_format = "application/vnd.docker.distribution.manifest.list.v2+json"
  #@manifest_format = "application/vnd.docker.container.image.v1+json"
  # make a ping connection
  #ping
end

Instance Method Details

#blob_size(repo, blobSum) ⇒ Object

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



231
232
233
234
# File 'lib/docker_cake/registry_api_client.rb', line 231

def blob_size(repo, blobSum)
  response = http_head("/v2/#{repo}/blobs/#{blobSum}")
  Integer(response.headers[:content_length], 10)
end

#http_delete(url) ⇒ Object



100
101
102
# File 'lib/docker_cake/registry_api_client.rb', line 100

def http_delete(url)
  http_req("delete", url)
end

#http_get(url, manifest: nil, auth: nil, auth_header: nil) ⇒ Object



96
97
98
# File 'lib/docker_cake/registry_api_client.rb', line 96

def http_get(url, manifest: nil, auth: nil, auth_header: nil)
  http_req("get", url, manifest: manifest, auth: auth, auth_header: auth_header)
end

#http_head(url) ⇒ Object



104
105
106
# File 'lib/docker_cake/registry_api_client.rb', line 104

def http_head(url)
  http_req("head", url)
end

#in_parallel(procs = {}) ⇒ Object



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
# File 'lib/docker_cake/registry_api_client.rb', line 196

def in_parallel(procs = {})
  threads = []
  result = {}
  errors = []
  procs.each do |key, fun|
    # handle array
    if fun == nil && key.is_a?(Proc)
      fun = key
      key = result.size
    end
    result[key] = nil
    threads << Thread.new do
      begin
        result[key] = fun.call
      rescue => error
        puts "#{error.class}: #{error.message}"
        puts error.backtrace
        errors << error
      end
    end
  end

  threads.each do |t|
    t.alive? && t.join
  end

  if errors.size > 0
    raise errors.first
  end

  result
end

#manifest(repo, tag, manifest: nil) ⇒ Object

combines small output and fat output to get layer names and sizes



155
156
157
158
159
160
# File 'lib/docker_cake/registry_api_client.rb', line 155

def manifest(repo, tag, manifest: nil)
  if @url == DEFAULT_REGISTRY
    auth_header = %{Bearer realm="https://auth.docker.io/token",service="registry.docker.io",scope="repository:#{repo}:pull"}
  end
  JSON.parse(http_get("/v2/#{repo}/manifests/#{tag}", manifest: manifest, auth: :bearer, auth_header: auth_header))
end

#manifest_layers(repo, tag) ⇒ Object



162
163
164
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
# File 'lib/docker_cake/registry_api_client.rb', line 162

def manifest_layers(repo, tag)
  basic_response = nil
  fat_response = nil

  resp = in_parallel(
    basic: lambda { manifest(repo, tag) },
    fat:   lambda { manifest(repo, tag, manifest: FAT_MANIFEST) }
  )

  unless resp[:basic]['layers']
    puts "Strange response"
    p resp[:basic]
  end

  basic = resp[:basic]['layers'] || []
  fat_response = resp[:fat]

  result = []

  fat_response['history'].each_with_index do |info, index|
    result[index] = JSON.parse(info['v1Compatibility'])
    result[index]['blobSum'] = fat_response['fsLayers'][index]['blobSum']
    result[index]['size'] = basic.detect do |layer|
      layer['digest'] == result[index]['blobSum']
    end
    result[index]['size'] = result[index]['size']['size'] if result[index]['size']
  end

  # require 'irb'
  # binding.irb

  result.reverse
end

#manifest_sum(manifest) ⇒ Object



236
237
238
239
240
241
242
# File 'lib/docker_cake/registry_api_client.rb', line 236

def manifest_sum(manifest)
  size = 0
  manifest["layers"].each do |layer|
    size += layer["size"]
  end
  size
end

#pingObject



108
109
110
# File 'lib/docker_cake/registry_api_client.rb', line 108

def ping
  response = http_get('/v2/')
end

#search(query = '') ⇒ Object



112
113
114
115
116
117
118
119
120
121
# File 'lib/docker_cake/registry_api_client.rb', line 112

def search(query = '')
  response = http_get "/v2/_catalog"
  # parse the response
  repos = JSON.parse(response)["repositories"]
  if query.strip.length > 0
    re = Regexp.new query
    repos = repos.find_all {|e| re =~ e }
  end
  return repos
end

#tags(repo, withHashes = false) ⇒ Object



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/docker_cake/registry_api_client.rb', line 123

def tags(repo, withHashes = false)
  response = http_get("/v2/#{repo}/tags/list")
  # parse the response
  resp = begin
    JSON.parse(response)
  rescue JSON::ParserError => e
    raise JsonError.new(e, response)
  end
  # do we include the hashes?
  if withHashes then
    useGet = false
    resp["hashes"] = {}
    resp["tags"].each {|tag|
      if useGet then
        head = http_get "/v2/#{repo}/manifests/#{tag}"
      else
        begin
          head = http_head("/v2/#{repo}/manifests/#{tag}")
        rescue InvalidMethod
          # in case we are in a registry pre-2.3.0, which did not support manifest HEAD
          useGet = true
          head = http_get("/v2/#{repo}/manifests/#{tag}")
        end
      end
      resp["hashes"][tag] = head.headers[:docker_content_digest]
    }
  end

  return resp
end