mirror of
https://github.com/wahyd4/gitlabhq.git
synced 2026-08-20 01:56:06 +10:00
Implement Container Registry API client
This commit is contained in:
@@ -5,22 +5,24 @@ class Projects::ImagesController < Projects::ApplicationController
|
||||
layout 'project'
|
||||
|
||||
def index
|
||||
@tags = registry.tags
|
||||
@tags = image_repository.tags
|
||||
end
|
||||
|
||||
def destroy
|
||||
# registry.destroy_tag(tag['fsLayers'].first['blobSum'])
|
||||
registry.destroy_tag(registry.tag_digest(params[:id]))
|
||||
redirect_to namespace_project_images_path(project.namespace, project)
|
||||
if tag.delete
|
||||
redirect_to namespace_project_images_path(project.namespace, project)
|
||||
else
|
||||
redirect_to namespace_project_images_path(project.namespace, project), alert: 'Failed to remove tag'
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def registry
|
||||
@registry ||= project.registry
|
||||
def image_repository
|
||||
@image_repository ||= project.image_repository
|
||||
end
|
||||
|
||||
def tag
|
||||
@tag ||= registry.tag(params[:id])
|
||||
@tag ||= image_repository[params[:id]]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -370,8 +370,9 @@ class Project < ActiveRecord::Base
|
||||
@repository ||= Repository.new(path_with_namespace, self)
|
||||
end
|
||||
|
||||
def registry
|
||||
@registry ||= Registry.new(path_with_namespace, self)
|
||||
def image_repository
|
||||
@registry ||= ImageRegistry::Registry.new(Gitlab.config.registry.api_url)
|
||||
@image_repository ||= ImageRegistry::Repository.new(@registry, path_with_namespace)
|
||||
end
|
||||
|
||||
def registry_repository_url
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
require 'net/http'
|
||||
|
||||
class Registry
|
||||
attr_accessor :path_with_namespace, :project
|
||||
|
||||
def initialize(path_with_namespace, project)
|
||||
@path_with_namespace = path_with_namespace
|
||||
@project = project
|
||||
end
|
||||
|
||||
def tags
|
||||
@tags ||= client.tags(path_with_namespace)
|
||||
end
|
||||
|
||||
def tag(reference)
|
||||
return @tag[reference] if defined?(@tag[reference])
|
||||
@tag ||= {}
|
||||
@tag[reference] ||= client.tag(path_with_namespace, reference)
|
||||
end
|
||||
|
||||
def tag_digest(reference)
|
||||
return @tag_digest[reference] if defined?(@tag_digest[reference])
|
||||
@tag_digest ||= {}
|
||||
@tag_digest[reference] ||= client.tag_digest(path_with_namespace, reference)
|
||||
end
|
||||
|
||||
def destroy_tag(reference)
|
||||
client.delete_tag(path_with_namespace, reference)
|
||||
end
|
||||
|
||||
def blob_size(blob)
|
||||
return @blob_size[blob] if defined?(@blob_size[blob])
|
||||
@blob_size ||= {}
|
||||
@blob_size[blob] ||= client.blob_size(path_with_namespace, blob)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def client
|
||||
@client ||= RegistryClient.new(Gitlab.config.registry.api_url)
|
||||
end
|
||||
end
|
||||
@@ -17,32 +17,26 @@
|
||||
%thead
|
||||
%tr
|
||||
%th Name
|
||||
%th Layers
|
||||
%th Revision
|
||||
%th Size
|
||||
%th Created
|
||||
%th Docker
|
||||
%th
|
||||
|
||||
- @tags.sort.each do |tag|
|
||||
- details = @registry.tag(tag)
|
||||
- layer = details['history'].first
|
||||
- if layer && layer['v1Compatibility']
|
||||
- layer_data = JSON.parse(layer['v1Compatibility'])
|
||||
- @tags.each do |tag|
|
||||
%tr
|
||||
%td
|
||||
= link_to namespace_project_image_path(@project.namespace, @project, tag) do
|
||||
#{details['name']}:#{details['tag']}
|
||||
= link_to namespace_project_image_path(@project.namespace, @project, tag.name) do
|
||||
#{tag.repository.name}:#{tag.name}
|
||||
%td
|
||||
= details['fsLayers'].length
|
||||
- if layer = tag.layers.first
|
||||
\##{layer.short_revision}
|
||||
%td
|
||||
= number_to_human_size(details['fsLayers'].inject(0) { |sum, d| sum + @registry.blob_size(d['blobSum']) }.bytes)
|
||||
= pluralize(tag.layers.size, "layer")
|
||||
|
||||
= number_to_human_size(tag.total_size)
|
||||
%td
|
||||
- if layer_data
|
||||
= time_ago_in_words(DateTime.rfc3339(layer_data['created']))
|
||||
%td
|
||||
- if layer_data
|
||||
= layer_data['docker_version']
|
||||
= time_ago_in_words(tag.created_at)
|
||||
%td.content
|
||||
.controls.hidden-xs.pull-right
|
||||
= link_to namespace_project_image_path(@project.namespace, @project, tag), class: 'btn btn-remove has-tooltip', title: "Remove", data: { confirm: "Are you sure?" }, method: :delete do
|
||||
= link_to namespace_project_image_path(@project.namespace, @project, tag.name), class: 'btn btn-remove has-tooltip', title: "Remove", data: { confirm: "Are you sure?" }, method: :delete do
|
||||
= icon("trash cred")
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
module ImageRegistry
|
||||
class Blob
|
||||
attr_reader :repository, :config
|
||||
|
||||
def initialize(repository, config)
|
||||
@repository = repository
|
||||
@config = config || {}
|
||||
end
|
||||
|
||||
def valid?
|
||||
digest.present?
|
||||
end
|
||||
|
||||
def digest
|
||||
config['digest']
|
||||
end
|
||||
|
||||
def type
|
||||
config['mediaType']
|
||||
end
|
||||
|
||||
def size
|
||||
config['size']
|
||||
end
|
||||
|
||||
def revision
|
||||
digest.split(':')[1]
|
||||
end
|
||||
|
||||
def short_revision
|
||||
revision[0..8]
|
||||
end
|
||||
|
||||
def client
|
||||
@client ||= repository.client
|
||||
end
|
||||
|
||||
def delete
|
||||
client.delete_blob(repository.name, digest)
|
||||
end
|
||||
|
||||
def data
|
||||
return @data if defined?(@data)
|
||||
@data ||= client.blob(repository.name, digest, type)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,64 @@
|
||||
require 'faraday'
|
||||
require 'faraday_middleware'
|
||||
|
||||
module ImageRegistry
|
||||
class Client
|
||||
attr_accessor :uri
|
||||
|
||||
MANIFEST_VERSION = 'application/vnd.docker.distribution.manifest.v2+json'
|
||||
|
||||
def initialize(base_uri, options = {})
|
||||
@base_uri = base_uri
|
||||
@faraday = Faraday.new(@base_uri) do |builder|
|
||||
builder.request :json
|
||||
builder.headers['Accept'] = MANIFEST_VERSION
|
||||
|
||||
builder.response :json, :content_type => /\bjson$/
|
||||
builder.response :json, :content_type => 'application/vnd.docker.distribution.manifest.v1+prettyjws'
|
||||
|
||||
if options[:user] && options[:password]
|
||||
builder.request(:basic_auth, options[:user].to_s, options[:password].to_s)
|
||||
elsif options[:token]
|
||||
builder.request(:authentication, :Bearer, options[:token].to_s)
|
||||
end
|
||||
|
||||
builder.adapter :net_http
|
||||
end
|
||||
end
|
||||
|
||||
def repository_tags(name)
|
||||
@faraday.get("/v2/#{name}/tags/list").body
|
||||
end
|
||||
|
||||
def repository_manifest(name, reference)
|
||||
@faraday.get("/v2/#{name}/manifests/#{reference}").body
|
||||
end
|
||||
|
||||
def put_repository_manifest(name, reference, manifest)
|
||||
@faraday.put("/v2/#{name}/manifests/#{reference}", manifest, { "Content-Type" => MANIFEST_VERSION }).success?
|
||||
end
|
||||
|
||||
def repository_mount_blob(name, digest, from)
|
||||
@faraday.post("/v2/#{name}/blobls/uploads/?mount=#{digest}&from=#{from}").status == 201
|
||||
end
|
||||
|
||||
def repository_tag_digest(name, reference)
|
||||
response = @faraday.head("/v2/#{name}/manifests/#{reference}")
|
||||
response.headers['docker-content-digest'] if response.success?
|
||||
end
|
||||
|
||||
def delete_repository_tag(name, reference)
|
||||
@faraday.delete("/v2/#{name}/manifests/#{reference}").success?
|
||||
end
|
||||
|
||||
def blob(name, digest, type = nil)
|
||||
headers = {}
|
||||
headers['Accept'] = type if type
|
||||
@faraday.get("/v2/#{name}/blobs/#{digest}", nil, headers).body
|
||||
end
|
||||
|
||||
def delete_blob(name, digest)
|
||||
@faraday.delete("/v2/#{name}/blobs/#{digest}").success?
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,15 @@
|
||||
module ImageRegistry
|
||||
class Config
|
||||
attr_reader :tag, :blob, :data
|
||||
|
||||
def initialize(tag, blob)
|
||||
@tag, @blob = tag, blob
|
||||
@data = JSON.parse(blob.data)
|
||||
end
|
||||
|
||||
def [](key)
|
||||
return unless data
|
||||
data[key]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,14 @@
|
||||
module ImageRegistry
|
||||
class Registry
|
||||
attr_reader :uri, :client
|
||||
|
||||
def initialize(uri, options = {})
|
||||
@uri = URI.parse(uri)
|
||||
@client = ImageRegistry::Client.new(uri, options)
|
||||
end
|
||||
|
||||
def [](name)
|
||||
ImageRegistry::Repository.new(self, name)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,38 @@
|
||||
module ImageRegistry
|
||||
class Repository
|
||||
attr_reader :registry, :name
|
||||
|
||||
def initialize(registry, name)
|
||||
@registry, @name = registry, name
|
||||
end
|
||||
|
||||
def client
|
||||
@client ||= registry.client
|
||||
end
|
||||
|
||||
def [](tag)
|
||||
ImageRegistry::Tag.new(self, tag)
|
||||
end
|
||||
|
||||
def manifest
|
||||
return @manifest if defined?(@manifest)
|
||||
@manifest = client.repository_tags(name)
|
||||
end
|
||||
|
||||
def valid?
|
||||
manifest.present?
|
||||
end
|
||||
|
||||
def tags
|
||||
return @tags if defined?(@tags)
|
||||
return unless manifest && manifest['tags']
|
||||
@tags = manifest['tags'].map do |tag|
|
||||
ImageRegistry::Tag.new(self, tag)
|
||||
end
|
||||
end
|
||||
|
||||
def delete
|
||||
tags.each(:delete)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,62 @@
|
||||
module ImageRegistry
|
||||
class Tag
|
||||
attr_reader :repository, :name
|
||||
|
||||
def initialize(repository, name)
|
||||
@repository, @name = repository, name
|
||||
end
|
||||
|
||||
def valid?
|
||||
manifest.present?
|
||||
end
|
||||
|
||||
def manifest
|
||||
return @manifest if defined?(@manifest)
|
||||
@manifest = client.repository_manifest(repository.name, name)
|
||||
end
|
||||
|
||||
def [](key)
|
||||
return unless manifest
|
||||
manifest[key]
|
||||
end
|
||||
|
||||
def digest
|
||||
return @digest if defined?(@digest)
|
||||
@digest = client.repository_tag_digest(repository.name, name)
|
||||
end
|
||||
|
||||
def config
|
||||
return @config if defined?(@config)
|
||||
return unless manifest && manifest['config']
|
||||
blob = ImageRegistry::Blob.new(repository, manifest['config'])
|
||||
@config = ImageRegistry::Config.new(self, blob)
|
||||
end
|
||||
|
||||
def created_at
|
||||
return unless config
|
||||
@created_at ||= DateTime.rfc3339(config['created'])
|
||||
end
|
||||
|
||||
def layers
|
||||
return @layers if defined?(@layers)
|
||||
return unless manifest
|
||||
@layers = manifest['layers'].map do |layer|
|
||||
ImageRegistry::Blob.new(repository, layer)
|
||||
end
|
||||
end
|
||||
|
||||
def total_size
|
||||
return unless layers
|
||||
layers.map(&:size).sum
|
||||
end
|
||||
|
||||
def delete
|
||||
return unless digest
|
||||
client.delete_repository_tag(repository.name, digest)
|
||||
end
|
||||
|
||||
def client
|
||||
@client ||= repository.client
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,38 +0,0 @@
|
||||
require 'HTTParty'
|
||||
|
||||
class RegistryClient
|
||||
attr_accessor :uri
|
||||
|
||||
def initialize(uri)
|
||||
@uri = uri
|
||||
end
|
||||
|
||||
def tags(name)
|
||||
response = HTTParty.get("#{uri}/v2/#{name}/tags/list")
|
||||
response.parsed_response['tags']
|
||||
end
|
||||
|
||||
def tag(name, reference)
|
||||
response = HTTParty.get("#{uri}/v2/#{name}/manifests/#{reference}")
|
||||
JSON.parse(response)
|
||||
end
|
||||
|
||||
def tag_digest(name, reference)
|
||||
response = HTTParty.head("#{uri}/v2/#{name}/manifests/#{reference}")
|
||||
response.headers['docker-content-digest'].split(':')
|
||||
end
|
||||
|
||||
def delete_tag(name, reference)
|
||||
response = HTTParty.delete("#{uri}/v2/#{name}/manifests/#{reference}")
|
||||
response.parsed_response
|
||||
end
|
||||
|
||||
def blob_size(name, digest)
|
||||
response = HTTParty.head("#{uri}/v2/#{name}/blobs/#{digest}")
|
||||
response.headers.content_length
|
||||
end
|
||||
|
||||
def delete_blob(name, digest)
|
||||
HTTParty.delete("#{uri}/v2/#{name}/blobs/#{digest}")
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user