Commit 9243b6d0 authored by Kamil Trzcinski's avatar Kamil Trzcinski

Implement Container Registry API client

parent b0ddbaa0
......@@ -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]))
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
......
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")
&nbsp;
= 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")
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
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
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
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
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
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
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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment