Class: ContainerRegistry::Client

Inherits:
Object
  • Object
show all
Includes:
Gitlab::Utils::StrongMemoize
Defined in:
lib/container_registry/client.rb

Constant Summary collapse

DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE =
'application/vnd.docker.distribution.manifest.v2+json'
OCI_MANIFEST_V1_TYPE =
'application/vnd.oci.image.manifest.v1+json'
CONTAINER_IMAGE_V1_TYPE =
'application/vnd.docker.container.image.v1+json'
REGISTRY_VERSION_HEADER =
'gitlab-container-registry-version'
REGISTRY_FEATURES_HEADER =
'gitlab-container-registry-features'
ACCEPTED_TYPES =
[DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE, OCI_MANIFEST_V1_TYPE].freeze
REDIRECT_CODES =

Taken from: FaradayMiddleware::FollowRedirects

Set.new [301, 302, 303, 307]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Gitlab::Utils::StrongMemoize

#clear_memoization, #strong_memoize, #strong_memoized?

Constructor Details

#initialize(base_uri, options = {}) ⇒ Client

Returns a new instance of Client.


35
36
37
38
# File 'lib/container_registry/client.rb', line 35

def initialize(base_uri, options = {})
  @base_uri = base_uri
  @options = options
end

Instance Attribute Details

#uriObject

Returns the value of attribute uri


11
12
13
# File 'lib/container_registry/client.rb', line 11

def uri
  @uri
end

Class Method Details

.supports_tag_delete?Boolean

Returns:

  • (Boolean)

24
25
26
27
28
29
30
31
32
33
# File 'lib/container_registry/client.rb', line 24

def self.supports_tag_delete?
  registry_config = Gitlab.config.registry
  return false unless registry_config.enabled && registry_config.api_url.present?

  return true if ::Gitlab.com?

  token = Auth::ContainerRegistryAuthenticationService.access_token([], [])
  client = new(registry_config.api_url, token: token)
  client.supports_tag_delete?
end

Instance Method Details

#blob(name, digest, type = nil) ⇒ Object


128
129
130
131
# File 'lib/container_registry/client.rb', line 128

def blob(name, digest, type = nil)
  type ||= 'application/octet-stream'
  response_body faraday_blob.get("/v2/#{name}/blobs/#{digest}", nil, 'Accept' => type), allow_redirect: true
end

#delete_blob(name, digest) ⇒ Object


133
134
135
# File 'lib/container_registry/client.rb', line 133

def delete_blob(name, digest)
  delete_if_exists("/v2/#{name}/blobs/#{digest}")
end

#delete_repository_tag_by_digest(name, reference) ⇒ Object


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

def delete_repository_tag_by_digest(name, reference)
  delete_if_exists("/v2/#{name}/manifests/#{reference}")
end

#delete_repository_tag_by_name(name, reference) ⇒ Object


72
73
74
# File 'lib/container_registry/client.rb', line 72

def delete_repository_tag_by_name(name, reference)
  delete_if_exists("/v2/#{name}/tags/reference/#{reference}")
end

#generate_empty_manifest(path) ⇒ Object


110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/container_registry/client.rb', line 110

def generate_empty_manifest(path)
  image = {
    config: {}
  }
  image, image_digest = upload_raw_blob(path, Gitlab::Json.pretty_generate(image))
  return unless image

  {
    schemaVersion: 2,
    mediaType: DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE,
    config: {
      mediaType: CONTAINER_IMAGE_V1_TYPE,
      size: image.size,
      digest: image_digest
    }
  }
end

#put_tag(name, reference, manifest) ⇒ Object


137
138
139
140
141
142
143
144
# File 'lib/container_registry/client.rb', line 137

def put_tag(name, reference, manifest)
  response = faraday.put("/v2/#{name}/manifests/#{reference}") do |req|
    req.headers['Content-Type'] = DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE
    req.body = Gitlab::Json.pretty_generate(manifest)
  end

  response.headers['docker-content-digest'] if response.success?
end

#registry_infoObject


40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/container_registry/client.rb', line 40

def registry_info
  response = faraday.get("/v2/")

  return {} unless response&.success?

  version = response.headers[REGISTRY_VERSION_HEADER]
  features = response.headers.fetch(REGISTRY_FEATURES_HEADER, '')

  {
    version: version,
    features: features.split(',').map(&:strip),
    vendor: version ? 'gitlab' : 'other'
  }
end

#repository_manifest(name, reference) ⇒ Object


59
60
61
# File 'lib/container_registry/client.rb', line 59

def repository_manifest(name, reference)
  response_body faraday.get("/v2/#{name}/manifests/#{reference}")
end

#repository_tag_digest(name, reference) ⇒ Object


63
64
65
66
# File 'lib/container_registry/client.rb', line 63

def repository_tag_digest(name, reference)
  response = faraday.head("/v2/#{name}/manifests/#{reference}")
  response.headers['docker-content-digest'] if response.success?
end

#repository_tags(name) ⇒ Object


55
56
57
# File 'lib/container_registry/client.rb', line 55

def repository_tags(name)
  response_body faraday.get("/v2/#{name}/tags/list")
end

#supports_tag_delete?Boolean

Check if the registry supports tag deletion. This is only supported by the GitLab registry fork. The fastest and safest way to check this is to send an OPTIONS request to /v2/<name>/tags/reference/<tag>, using a random repository name and tag (the registry won't check if they exist). Registries that support tag deletion will reply with a 200 OK and include the DELETE method in the Allow header. Others reply with an 404 Not Found.

Returns:

  • (Boolean)

82
83
84
85
86
87
# File 'lib/container_registry/client.rb', line 82

def supports_tag_delete?
  strong_memoize(:supports_tag_delete) do
    response = faraday.run_request(:options, '/v2/name/tags/reference/tag', '', {})
    response.success? && response.headers['allow']&.include?('DELETE')
  end
end

#upload_blob(name, content, digest) ⇒ Object


97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/container_registry/client.rb', line 97

def upload_blob(name, content, digest)
  upload = faraday.post("/v2/#{name}/blobs/uploads/")
  return upload unless upload.success?

  location = URI(upload.headers['location'])

  faraday.put("#{location.path}?#{location.query}") do |req|
    req.params['digest'] = digest
    req.headers['Content-Type'] = 'application/octet-stream'
    req.body = content
  end
end

#upload_raw_blob(path, blob) ⇒ Object


89
90
91
92
93
94
95
# File 'lib/container_registry/client.rb', line 89

def upload_raw_blob(path, blob)
  digest = "sha256:#{Digest::SHA256.hexdigest(blob)}"

  if upload_blob(path, blob, digest).success?
    [blob, digest]
  end
end