From b0ddbaa07cd780b0ed86aa4e3c24744c6426b1e1 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 18 Apr 2016 08:14:40 -0400 Subject: Added docker registry view --- app/controllers/projects/images_controller.rb | 26 ++++++++++++ app/helpers/gitlab_routing_helper.rb | 4 ++ app/helpers/projects_helper.rb | 4 ++ app/models/project.rb | 4 ++ app/models/registry.rb | 42 ++++++++++++++++++++ app/views/layouts/nav/_project.html.haml | 7 ++++ app/views/projects/images/_header_title.html.haml | 1 + app/views/projects/images/index.html.haml | 48 +++++++++++++++++++++++ config/initializers/mime_types.rb | 7 ++++ config/routes.rb | 2 + lib/gitlab/regex.rb | 4 ++ lib/registry_client.rb | 38 ++++++++++++++++++ 12 files changed, 187 insertions(+) create mode 100644 app/controllers/projects/images_controller.rb create mode 100644 app/models/registry.rb create mode 100644 app/views/projects/images/_header_title.html.haml create mode 100644 app/views/projects/images/index.html.haml create mode 100644 lib/registry_client.rb diff --git a/app/controllers/projects/images_controller.rb b/app/controllers/projects/images_controller.rb new file mode 100644 index 00000000000..5b10746aa0d --- /dev/null +++ b/app/controllers/projects/images_controller.rb @@ -0,0 +1,26 @@ +class Projects::ImagesController < Projects::ApplicationController + before_action :authorize_read_image! + before_action :authorize_update_image!, only: [:destroy] + before_action :tag, except: [:index] + layout 'project' + + def index + @tags = registry.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) + end + + private + + def registry + @registry ||= project.registry + end + + def tag + @tag ||= registry.tag(params[:id]) + end +end diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index f07eff3fb57..66cb41cc496 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -33,6 +33,10 @@ module GitlabRoutingHelper namespace_project_builds_path(project.namespace, project, *args) end + def project_images_path(project, *args) + namespace_project_images_path(project.namespace, project, *args) + end + def activity_project_path(project, *args) activity_namespace_project_path(project.namespace, project, *args) end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 3d5e61d2c18..6d1e630a097 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -152,6 +152,10 @@ module ProjectsHelper nav_tabs << :builds end + if can?(current_user, :read_image, project) + nav_tabs << :images + end + if can?(current_user, :admin_project, project) nav_tabs << :settings end diff --git a/app/models/project.rb b/app/models/project.rb index 76265a59ea7..496f9f3e347 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -370,6 +370,10 @@ class Project < ActiveRecord::Base @repository ||= Repository.new(path_with_namespace, self) end + def registry + @registry ||= Registry.new(path_with_namespace, self) + end + def registry_repository_url "#{Gitlab.config.registry.host_with_port}/#{path_with_namespace}" if images_enabled? && Gitlab.config.registry.enabled end diff --git a/app/models/registry.rb b/app/models/registry.rb new file mode 100644 index 00000000000..b4ef60a016f --- /dev/null +++ b/app/models/registry.rb @@ -0,0 +1,42 @@ +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 diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 479bde33719..2577afefa95 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -46,6 +46,13 @@ Builds %span.count.builds_counter= number_with_delimiter(@project.builds.running_or_pending.count(:all)) + - if project_nav_tab? :images + = nav_link(controller: %w(images)) do + = link_to project_images_path(@project), title: 'Images', class: 'shortcuts-images' do + = icon('image fw') + %span + Images + - if project_nav_tab? :graphs = nav_link(controller: %w(graphs)) do = link_to namespace_project_graph_path(@project.namespace, @project, current_ref), title: 'Graphs', class: 'shortcuts-graphs' do diff --git a/app/views/projects/images/_header_title.html.haml b/app/views/projects/images/_header_title.html.haml new file mode 100644 index 00000000000..648aeeef2dc --- /dev/null +++ b/app/views/projects/images/_header_title.html.haml @@ -0,0 +1 @@ +- header_title project_title(@project, "Images", project_images_path(@project)) diff --git a/app/views/projects/images/index.html.haml b/app/views/projects/images/index.html.haml new file mode 100644 index 00000000000..338f3e5662c --- /dev/null +++ b/app/views/projects/images/index.html.haml @@ -0,0 +1,48 @@ +- page_title "Images" += render "header_title" + +.top-area + .nav-controls + +.gray-content-block + A list of Docker Images for this project + +%ul.content-list + - if @tags.blank? + %li + .nothing-here-block No images to show + - else + .table-holder + %table.table.builds + %thead + %tr + %th Name + %th Layers + %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']) + %tr + %td + = link_to namespace_project_image_path(@project.namespace, @project, tag) do + #{details['name']}:#{details['tag']} + %td + = details['fsLayers'].length + %td + = number_to_human_size(details['fsLayers'].inject(0) { |sum, d| sum + @registry.blob_size(d['blobSum']) }.bytes) + %td + - if layer_data + = time_ago_in_words(DateTime.rfc3339(layer_data['created'])) + %td + - if layer_data + = layer_data['docker_version'] + %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 + = icon("trash cred") diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb index ca58ae92d1b..71e3c9d7db4 100644 --- a/config/initializers/mime_types.rb +++ b/config/initializers/mime_types.rb @@ -8,3 +8,10 @@ Mime::Type.register_alias "text/plain", :diff Mime::Type.register_alias "text/plain", :patch Mime::Type.register_alias 'text/html', :markdown Mime::Type.register_alias 'text/html', :md +#Mime::Type.unregister :json +Mime::Type.register_alias 'application/vnd.docker.distribution.manifest.v1+prettyjws', :json +#Mime::Type.register 'application/json', :json, %w( text/plain text/x-json application/jsonrequest ) + +ActionDispatch::ParamsParser::DEFAULT_PARSERS[Mime::Type.lookup('application/vnd.docker.distribution.manifest.v1+prettyjws')]=lambda do |body| + JSON.parse(body) +end diff --git a/config/routes.rb b/config/routes.rb index 5b48819dd9d..0280898accd 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -690,6 +690,8 @@ Rails.application.routes.draw do end end + resources :images, only: [:index, :destroy], constraints: { id: Gitlab::Regex.image_reference_regex } + resources :milestones, constraints: { id: /\d+/ } do member do put :sort_issues diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index ace906a6f59..9b8f416ddfa 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -96,5 +96,9 @@ module Gitlab (? Date: Wed, 4 May 2016 14:22:54 +0200 Subject: Implement Container Registry API client --- app/controllers/projects/images_controller.rb | 16 ++++--- app/models/project.rb | 5 ++- app/models/registry.rb | 42 ------------------ app/views/projects/images/index.html.haml | 28 +++++------- lib/image_registry/blob.rb | 47 ++++++++++++++++++++ lib/image_registry/client.rb | 64 +++++++++++++++++++++++++++ lib/image_registry/config.rb | 15 +++++++ lib/image_registry/registry.rb | 14 ++++++ lib/image_registry/repository.rb | 38 ++++++++++++++++ lib/image_registry/tag.rb | 62 ++++++++++++++++++++++++++ lib/registry_client.rb | 38 ---------------- 11 files changed, 263 insertions(+), 106 deletions(-) delete mode 100644 app/models/registry.rb create mode 100644 lib/image_registry/blob.rb create mode 100644 lib/image_registry/client.rb create mode 100644 lib/image_registry/config.rb create mode 100644 lib/image_registry/registry.rb create mode 100644 lib/image_registry/repository.rb create mode 100644 lib/image_registry/tag.rb delete mode 100644 lib/registry_client.rb diff --git a/app/controllers/projects/images_controller.rb b/app/controllers/projects/images_controller.rb index 5b10746aa0d..cf3bdd42cf4 100644 --- a/app/controllers/projects/images_controller.rb +++ b/app/controllers/projects/images_controller.rb @@ -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 diff --git a/app/models/project.rb b/app/models/project.rb index 496f9f3e347..b905ebbfcaa 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -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 diff --git a/app/models/registry.rb b/app/models/registry.rb deleted file mode 100644 index b4ef60a016f..00000000000 --- a/app/models/registry.rb +++ /dev/null @@ -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 diff --git a/app/views/projects/images/index.html.haml b/app/views/projects/images/index.html.haml index 338f3e5662c..0987c7a39eb 100644 --- a/app/views/projects/images/index.html.haml +++ b/app/views/projects/images/index.html.haml @@ -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") diff --git a/lib/image_registry/blob.rb b/lib/image_registry/blob.rb new file mode 100644 index 00000000000..1aeeba7a686 --- /dev/null +++ b/lib/image_registry/blob.rb @@ -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 diff --git a/lib/image_registry/client.rb b/lib/image_registry/client.rb new file mode 100644 index 00000000000..b2e43ce4aeb --- /dev/null +++ b/lib/image_registry/client.rb @@ -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 diff --git a/lib/image_registry/config.rb b/lib/image_registry/config.rb new file mode 100644 index 00000000000..1c2abec1bfa --- /dev/null +++ b/lib/image_registry/config.rb @@ -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 diff --git a/lib/image_registry/registry.rb b/lib/image_registry/registry.rb new file mode 100644 index 00000000000..d8de8e392e9 --- /dev/null +++ b/lib/image_registry/registry.rb @@ -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 diff --git a/lib/image_registry/repository.rb b/lib/image_registry/repository.rb new file mode 100644 index 00000000000..f4f4ba65afc --- /dev/null +++ b/lib/image_registry/repository.rb @@ -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 diff --git a/lib/image_registry/tag.rb b/lib/image_registry/tag.rb new file mode 100644 index 00000000000..2bf0b8e345f --- /dev/null +++ b/lib/image_registry/tag.rb @@ -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 diff --git a/lib/registry_client.rb b/lib/registry_client.rb deleted file mode 100644 index 87518a7b39c..00000000000 --- a/lib/registry_client.rb +++ /dev/null @@ -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 -- cgit v1.2.1 From d85b88962b603d46672ed6ebd01955ca7560fcc6 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 4 May 2016 14:23:43 +0200 Subject: Remove unused mime_types --- config/initializers/mime_types.rb | 7 ------- 1 file changed, 7 deletions(-) diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb index 71e3c9d7db4..ca58ae92d1b 100644 --- a/config/initializers/mime_types.rb +++ b/config/initializers/mime_types.rb @@ -8,10 +8,3 @@ Mime::Type.register_alias "text/plain", :diff Mime::Type.register_alias "text/plain", :patch Mime::Type.register_alias 'text/html', :markdown Mime::Type.register_alias 'text/html', :md -#Mime::Type.unregister :json -Mime::Type.register_alias 'application/vnd.docker.distribution.manifest.v1+prettyjws', :json -#Mime::Type.register 'application/json', :json, %w( text/plain text/x-json application/jsonrequest ) - -ActionDispatch::ParamsParser::DEFAULT_PARSERS[Mime::Type.lookup('application/vnd.docker.distribution.manifest.v1+prettyjws')]=lambda do |body| - JSON.parse(body) -end -- cgit v1.2.1 From 7168493e8a25836dc7eedf25ec3241afd0d501b8 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 4 May 2016 14:35:18 +0200 Subject: Remove container registry on project removal --- app/services/projects/destroy_service.rb | 8 ++++++++ lib/image_registry/repository.rb | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb index df5054f08d7..70bfc1fd533 100644 --- a/app/services/projects/destroy_service.rb +++ b/app/services/projects/destroy_service.rb @@ -28,6 +28,10 @@ module Projects Project.transaction do project.destroy! + unless remove_registry_tags + raise_error('Failed to remove project image registry. Please try again or contact administrator') + end + unless remove_repository(repo_path) raise_error('Failed to remove project repository. Please try again or contact administrator') end @@ -61,6 +65,10 @@ module Projects end end + def remove_registry_tags + project.image_registry.delete_tags + end + def raise_error(message) raise DestroyError.new(message) end diff --git a/lib/image_registry/repository.rb b/lib/image_registry/repository.rb index f4f4ba65afc..c45fa2911e7 100644 --- a/lib/image_registry/repository.rb +++ b/lib/image_registry/repository.rb @@ -31,7 +31,8 @@ module ImageRegistry end end - def delete + def delete_tags + return unless tags tags.each(:delete) end end -- cgit v1.2.1 From e1c8663a3e7ad1f77a8476888331c376cc35eda5 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 4 May 2016 15:15:16 +0200 Subject: Allow to copy all manifests from one container repository to another --- lib/image_registry/blob.rb | 4 ++++ lib/image_registry/client.rb | 2 +- lib/image_registry/repository.rb | 16 ++++++++++++++++ lib/image_registry/tag.rb | 21 +++++++++++++++++---- 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/lib/image_registry/blob.rb b/lib/image_registry/blob.rb index 1aeeba7a686..43665149e23 100644 --- a/lib/image_registry/blob.rb +++ b/lib/image_registry/blob.rb @@ -43,5 +43,9 @@ module ImageRegistry return @data if defined?(@data) @data ||= client.blob(repository.name, digest, type) end + + def mount_to(to_repository) + client.repository_mount_blob(to_repository.name, digest, repository.name) + end end end diff --git a/lib/image_registry/client.rb b/lib/image_registry/client.rb index b2e43ce4aeb..84375ce8029 100644 --- a/lib/image_registry/client.rb +++ b/lib/image_registry/client.rb @@ -39,7 +39,7 @@ module ImageRegistry end def repository_mount_blob(name, digest, from) - @faraday.post("/v2/#{name}/blobls/uploads/?mount=#{digest}&from=#{from}").status == 201 + @faraday.post("/v2/#{name}/blobs/uploads/?mount=#{digest}&from=#{from}").status == 201 end def repository_tag_digest(name, reference) diff --git a/lib/image_registry/repository.rb b/lib/image_registry/repository.rb index c45fa2911e7..763d8669555 100644 --- a/lib/image_registry/repository.rb +++ b/lib/image_registry/repository.rb @@ -29,11 +29,27 @@ module ImageRegistry @tags = manifest['tags'].map do |tag| ImageRegistry::Tag.new(self, tag) end + @tags ||= [] end def delete_tags return unless tags tags.each(:delete) end + + def mount_blob(blob) + return unless blob + client.repository_mount_blob(name, blob.digest, blob.repository.name) + end + + def mount_manifest(tag, manifest) + client.put_repository_manifest(name, tag, manifest) + end + + def copy_to(other_repository) + tags.all? do |tag| + tag.copy_to(other_repository) + end + end end end diff --git a/lib/image_registry/tag.rb b/lib/image_registry/tag.rb index 2bf0b8e345f..76946a6ce5b 100644 --- a/lib/image_registry/tag.rb +++ b/lib/image_registry/tag.rb @@ -25,11 +25,15 @@ module ImageRegistry @digest = client.repository_tag_digest(repository.name, name) end - def config - return @config if defined?(@config) + def config_blob + return @config_blob if defined?(@config_blob) return unless manifest && manifest['config'] - blob = ImageRegistry::Blob.new(repository, manifest['config']) - @config = ImageRegistry::Config.new(self, blob) + @config_blob = ImageRegistry::Blob.new(repository, manifest['config']) + end + + def config + return unless config_blob + @config ||= ImageRegistry::Config.new(self, config_blob) end def created_at @@ -55,6 +59,15 @@ module ImageRegistry client.delete_repository_tag(repository.name, digest) end + def copy_to(repository) + return unless manifest + layers.each do |blob| + repository.mount_blob(blob) + end + repository.mount_blob(config_blob) + repository.mount_manifest(name, manifest.to_json) + end + def client @client ||= repository.client end -- cgit v1.2.1 From de008127eb9a7a14b06b2e4a3d3d1822ad6a54d7 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 4 May 2016 16:16:54 +0200 Subject: Fix bearer token support --- lib/image_registry/client.rb | 2 +- lib/image_registry/repository.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/image_registry/client.rb b/lib/image_registry/client.rb index 84375ce8029..a4432059097 100644 --- a/lib/image_registry/client.rb +++ b/lib/image_registry/client.rb @@ -19,7 +19,7 @@ module ImageRegistry 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) + builder.request(:authorization, :bearer, options[:token].to_s) end builder.adapter :net_http diff --git a/lib/image_registry/repository.rb b/lib/image_registry/repository.rb index 763d8669555..43e8e7720db 100644 --- a/lib/image_registry/repository.rb +++ b/lib/image_registry/repository.rb @@ -25,7 +25,7 @@ module ImageRegistry def tags return @tags if defined?(@tags) - return unless manifest && manifest['tags'] + return [] unless manifest && manifest['tags'] @tags = manifest['tags'].map do |tag| ImageRegistry::Tag.new(self, tag) end -- cgit v1.2.1 From 7731bb59c8d43cfa7e47c945d7aed05e5e3932c1 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 4 May 2016 16:17:08 +0200 Subject: Use bearer token to access registry --- app/models/project.rb | 3 ++- app/services/jwt/docker_authentication_service.rb | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/app/models/project.rb b/app/models/project.rb index b905ebbfcaa..c50ea45d3eb 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -371,7 +371,8 @@ class Project < ActiveRecord::Base end def image_repository - @registry ||= ImageRegistry::Registry.new(Gitlab.config.registry.api_url) + @registry_token ||= Jwt::DockerAuthenticationService.full_access_token(path_with_namespace) + @registry ||= ImageRegistry::Registry.new(Gitlab.config.registry.api_url, token: @registry_token) @image_repository ||= ImageRegistry::Repository.new(@registry, path_with_namespace) end diff --git a/app/services/jwt/docker_authentication_service.rb b/app/services/jwt/docker_authentication_service.rb index ce28085e5d6..16d77193a1e 100644 --- a/app/services/jwt/docker_authentication_service.rb +++ b/app/services/jwt/docker_authentication_service.rb @@ -8,6 +8,17 @@ module Jwt { token: token.encoded } end + def self.full_access_token(*names) + registry = Gitlab.config.registry + token = ::Jwt::RSAToken.new(registry.key) + token.issuer = registry.issuer + token.audience = 'docker' + token[:access] = names.map do |name| + { type: 'repository', name: name, actions: %w(pull push) } + end + token.encoded + end + private def token -- cgit v1.2.1 From 2afae7eac97d24d51eb949b9faa676314f06cdd6 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 4 May 2016 16:17:35 +0200 Subject: Use Container Images instead of Images --- app/views/projects/images/_header_title.html.haml | 2 +- app/views/projects/images/index.html.haml | 30 ++++++++++++++++------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/app/views/projects/images/_header_title.html.haml b/app/views/projects/images/_header_title.html.haml index 648aeeef2dc..f583e7fcfef 100644 --- a/app/views/projects/images/_header_title.html.haml +++ b/app/views/projects/images/_header_title.html.haml @@ -1 +1 @@ -- header_title project_title(@project, "Images", project_images_path(@project)) +- header_title project_title(@project, "Container Images", project_images_path(@project)) diff --git a/app/views/projects/images/index.html.haml b/app/views/projects/images/index.html.haml index 0987c7a39eb..3732698c088 100644 --- a/app/views/projects/images/index.html.haml +++ b/app/views/projects/images/index.html.haml @@ -1,11 +1,23 @@ -- page_title "Images" +- page_title "Container Images" = render "header_title" -.top-area - .nav-controls +.light.prepend-top-default + %p + A 'container image' is a snapshot of a container. + You can host your 'container images' with GitLab. + %br + To start using container images hosted on GitLab you first need to login: + %pre + %code + docker login #{Gitlab.config.registry.host_port} + %br + Then you are free to create and upload a container images with build and push commands: + %pre + docker build -t #{Gitlab.config.registry.host_port}/#{@project.path_with_namespace} . + %br + docker push #{Gitlab.config.registry.host_port}/#{@project.path_with_namespace} -.gray-content-block - A list of Docker Images for this project +%hr %ul.content-list - if @tags.blank? @@ -25,15 +37,15 @@ - @tags.each do |tag| %tr %td - = link_to namespace_project_image_path(@project.namespace, @project, tag.name) do - #{tag.repository.name}:#{tag.name} + #{tag.repository.name}:#{tag.name} + = clipboard_button(clipboard_text: "docker pull #{Gitlab.config.registry.host_port}/#{tag.repository.name}:#{tag.name}") %td - if layer = tag.layers.first \##{layer.short_revision} %td - = pluralize(tag.layers.size, "layer") -   = number_to_human_size(tag.total_size) + · + = pluralize(tag.layers.size, "layer") %td = time_ago_in_words(tag.created_at) %td.content -- cgit v1.2.1 From 9e619d3813764566e5f4c0208e5f2c7365351808 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 4 May 2016 16:28:01 +0200 Subject: Use Container Images --- app/views/layouts/nav/_project.html.haml | 4 ++-- app/views/projects/images/index.html.haml | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 2577afefa95..bef350adf34 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -49,9 +49,9 @@ - if project_nav_tab? :images = nav_link(controller: %w(images)) do = link_to project_images_path(@project), title: 'Images', class: 'shortcuts-images' do - = icon('image fw') + = icon('hdd-o fw') %span - Images + Container Images - if project_nav_tab? :graphs = nav_link(controller: %w(graphs)) do diff --git a/app/views/projects/images/index.html.haml b/app/views/projects/images/index.html.haml index 3732698c088..08f67345b4a 100644 --- a/app/views/projects/images/index.html.haml +++ b/app/views/projects/images/index.html.haml @@ -29,7 +29,7 @@ %thead %tr %th Name - %th Revision + %th Digest %th Size %th Created %th @@ -41,7 +41,10 @@ = clipboard_button(clipboard_text: "docker pull #{Gitlab.config.registry.host_port}/#{tag.repository.name}:#{tag.name}") %td - if layer = tag.layers.first - \##{layer.short_revision} + %span.has-tooltip(title="#{layer.revision}") + = layer.short_revision + - else + \- %td = number_to_human_size(tag.total_size) · -- cgit v1.2.1 From 5196f8e993491e8e9dea6e36a7c5c3b8d95a5491 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sun, 8 May 2016 22:50:30 +0200 Subject: WIP --- .../projects/container_registry_controller.rb | 31 ++++++++++++ app/controllers/projects/images_controller.rb | 28 ----------- app/helpers/gitlab_routing_helper.rb | 4 +- app/models/project.rb | 4 +- app/views/layouts/nav/_project.html.haml | 6 +-- .../container_registry/_header_title.html.haml | 1 + .../projects/container_registry/index.html.haml | 57 ++++++++++++++++++++++ app/views/projects/images/_header_title.html.haml | 1 - app/views/projects/images/index.html.haml | 57 ---------------------- config/routes.rb | 2 +- lib/gitlab/regex.rb | 2 +- 11 files changed, 98 insertions(+), 95 deletions(-) create mode 100644 app/controllers/projects/container_registry_controller.rb delete mode 100644 app/controllers/projects/images_controller.rb create mode 100644 app/views/projects/container_registry/_header_title.html.haml create mode 100644 app/views/projects/container_registry/index.html.haml delete mode 100644 app/views/projects/images/_header_title.html.haml delete mode 100644 app/views/projects/images/index.html.haml diff --git a/app/controllers/projects/container_registry_controller.rb b/app/controllers/projects/container_registry_controller.rb new file mode 100644 index 00000000000..ffd455e6476 --- /dev/null +++ b/app/controllers/projects/container_registry_controller.rb @@ -0,0 +1,31 @@ +class Projects::ContainerRegistryController < Projects::ApplicationController + before_action :authorize_read_image! + before_action :authorize_update_image!, only: [:destroy] + before_action :tag, except: [:index] + layout 'project' + + def index + @tags = container_registry.tags + + other_repository = container_registry.registry["gitlab/gitlab-test3"] + container_registry.copy_to(other_repository) + end + + def destroy + if tag.delete + redirect_to namespace_project_container_registry_index_path(project.namespace, project) + else + redirect_to namespace_project_container_registry_index_path(project.namespace, project), alert: 'Failed to remove tag' + end + end + + private + + def container_registry + @container_registry ||= project.container_registry + end + + def tag + @tag ||= container_registry[params[:id]] + end +end diff --git a/app/controllers/projects/images_controller.rb b/app/controllers/projects/images_controller.rb deleted file mode 100644 index cf3bdd42cf4..00000000000 --- a/app/controllers/projects/images_controller.rb +++ /dev/null @@ -1,28 +0,0 @@ -class Projects::ImagesController < Projects::ApplicationController - before_action :authorize_read_image! - before_action :authorize_update_image!, only: [:destroy] - before_action :tag, except: [:index] - layout 'project' - - def index - @tags = image_repository.tags - end - - def destroy - 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 image_repository - @image_repository ||= project.image_repository - end - - def tag - @tag ||= image_repository[params[:id]] - end -end diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index 66cb41cc496..dd9536b1460 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -33,8 +33,8 @@ module GitlabRoutingHelper namespace_project_builds_path(project.namespace, project, *args) end - def project_images_path(project, *args) - namespace_project_images_path(project.namespace, project, *args) + def project_container_registry_path(project, *args) + namespace_project_container_registry_index_url(project.namespace, project, *args) end def activity_project_path(project, *args) diff --git a/app/models/project.rb b/app/models/project.rb index c50ea45d3eb..0e32be5a536 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -370,10 +370,10 @@ class Project < ActiveRecord::Base @repository ||= Repository.new(path_with_namespace, self) end - def image_repository + def container_registry @registry_token ||= Jwt::DockerAuthenticationService.full_access_token(path_with_namespace) @registry ||= ImageRegistry::Registry.new(Gitlab.config.registry.api_url, token: @registry_token) - @image_repository ||= ImageRegistry::Repository.new(@registry, path_with_namespace) + @container_registry ||= ImageRegistry::Repository.new(@registry, path_with_namespace) end def registry_repository_url diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index bef350adf34..8448599c1cc 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -47,11 +47,11 @@ %span.count.builds_counter= number_with_delimiter(@project.builds.running_or_pending.count(:all)) - if project_nav_tab? :images - = nav_link(controller: %w(images)) do - = link_to project_images_path(@project), title: 'Images', class: 'shortcuts-images' do + = nav_link(controller: %w(container_registry)) do + = link_to project_container_registry_path(@project), title: 'Container Registry', class: 'shortcuts-images' do = icon('hdd-o fw') %span - Container Images + Container Registry - if project_nav_tab? :graphs = nav_link(controller: %w(graphs)) do diff --git a/app/views/projects/container_registry/_header_title.html.haml b/app/views/projects/container_registry/_header_title.html.haml new file mode 100644 index 00000000000..f1863c52a3e --- /dev/null +++ b/app/views/projects/container_registry/_header_title.html.haml @@ -0,0 +1 @@ +- header_title project_title(@project, "Container Registry", project_container_registry_path(@project)) diff --git a/app/views/projects/container_registry/index.html.haml b/app/views/projects/container_registry/index.html.haml new file mode 100644 index 00000000000..1ac3a62f54e --- /dev/null +++ b/app/views/projects/container_registry/index.html.haml @@ -0,0 +1,57 @@ +- page_title "Container Registry" += render "header_title" + +.light.prepend-top-default + %p + A 'container image' is a snapshot of a container. + You can host your 'container images' with GitLab. + %br + To start using container images hosted on GitLab you first need to login: + %pre + %code + docker login #{Gitlab.config.registry.host_port} + %br + Then you are free to create and upload a container images with build and push commands: + %pre + docker build -t #{Gitlab.config.registry.host_port}/#{@project.path_with_namespace} . + %br + docker push #{Gitlab.config.registry.host_port}/#{@project.path_with_namespace} + +%hr + +%ul.content-list + - if @tags.blank? + %li + .nothing-here-block No images to show + - else + .table-holder + %table.table.builds + %thead + %tr + %th Name + %th Digest + %th Size + %th Created + %th + + - @tags.each do |tag| + %tr + %td + #{tag.repository.name}:#{tag.name} + = clipboard_button(clipboard_text: "docker pull #{Gitlab.config.registry.host_port}/#{tag.repository.name}:#{tag.name}") + %td + - if layer = tag.layers.first + %span.has-tooltip(title="#{layer.revision}") + = layer.short_revision + - else + \- + %td + = number_to_human_size(tag.total_size) + · + = pluralize(tag.layers.size, "layer") + %td + = time_ago_in_words(tag.created_at) + %td.content + .controls.hidden-xs.pull-right + = link_to namespace_project_container_registry_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") diff --git a/app/views/projects/images/_header_title.html.haml b/app/views/projects/images/_header_title.html.haml deleted file mode 100644 index f583e7fcfef..00000000000 --- a/app/views/projects/images/_header_title.html.haml +++ /dev/null @@ -1 +0,0 @@ -- header_title project_title(@project, "Container Images", project_images_path(@project)) diff --git a/app/views/projects/images/index.html.haml b/app/views/projects/images/index.html.haml deleted file mode 100644 index 08f67345b4a..00000000000 --- a/app/views/projects/images/index.html.haml +++ /dev/null @@ -1,57 +0,0 @@ -- page_title "Container Images" -= render "header_title" - -.light.prepend-top-default - %p - A 'container image' is a snapshot of a container. - You can host your 'container images' with GitLab. - %br - To start using container images hosted on GitLab you first need to login: - %pre - %code - docker login #{Gitlab.config.registry.host_port} - %br - Then you are free to create and upload a container images with build and push commands: - %pre - docker build -t #{Gitlab.config.registry.host_port}/#{@project.path_with_namespace} . - %br - docker push #{Gitlab.config.registry.host_port}/#{@project.path_with_namespace} - -%hr - -%ul.content-list - - if @tags.blank? - %li - .nothing-here-block No images to show - - else - .table-holder - %table.table.builds - %thead - %tr - %th Name - %th Digest - %th Size - %th Created - %th - - - @tags.each do |tag| - %tr - %td - #{tag.repository.name}:#{tag.name} - = clipboard_button(clipboard_text: "docker pull #{Gitlab.config.registry.host_port}/#{tag.repository.name}:#{tag.name}") - %td - - if layer = tag.layers.first - %span.has-tooltip(title="#{layer.revision}") - = layer.short_revision - - else - \- - %td - = number_to_human_size(tag.total_size) - · - = pluralize(tag.layers.size, "layer") - %td - = time_ago_in_words(tag.created_at) - %td.content - .controls.hidden-xs.pull-right - = 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") diff --git a/config/routes.rb b/config/routes.rb index 0280898accd..ce75329597b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -690,7 +690,7 @@ Rails.application.routes.draw do end end - resources :images, only: [:index, :destroy], constraints: { id: Gitlab::Regex.image_reference_regex } + resources :container_registry, only: [:index, :destroy], constraints: { id: Gitlab::Regex.container_registry_reference_regex } resources :milestones, constraints: { id: /\d+/ } do member do diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index 9b8f416ddfa..1cbd6d945a0 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -97,7 +97,7 @@ module Gitlab }x.freeze end - def image_reference_regex + def container_registry_reference_regex git_reference_regex end end -- cgit v1.2.1 From 08396be619eee2e71c2f5f7aa27eea6f5ddf10ff Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 9 May 2016 22:14:46 +0300 Subject: Rename ImageRegistry to ContainerRegistry --- .../projects/container_registry_controller.rb | 9 +-- app/models/project.rb | 8 ++- lib/container_registry/blob.rb | 51 +++++++++++++++ lib/container_registry/client.rb | 64 ++++++++++++++++++ lib/container_registry/config.rb | 15 +++++ lib/container_registry/registry.rb | 14 ++++ lib/container_registry/repository.rb | 55 ++++++++++++++++ lib/container_registry/tag.rb | 75 ++++++++++++++++++++++ lib/image_registry/blob.rb | 51 --------------- lib/image_registry/client.rb | 64 ------------------ lib/image_registry/config.rb | 15 ----- lib/image_registry/registry.rb | 14 ---- lib/image_registry/repository.rb | 55 ---------------- lib/image_registry/tag.rb | 75 ---------------------- 14 files changed, 282 insertions(+), 283 deletions(-) create mode 100644 lib/container_registry/blob.rb create mode 100644 lib/container_registry/client.rb create mode 100644 lib/container_registry/config.rb create mode 100644 lib/container_registry/registry.rb create mode 100644 lib/container_registry/repository.rb create mode 100644 lib/container_registry/tag.rb delete mode 100644 lib/image_registry/blob.rb delete mode 100644 lib/image_registry/client.rb delete mode 100644 lib/image_registry/config.rb delete mode 100644 lib/image_registry/registry.rb delete mode 100644 lib/image_registry/repository.rb delete mode 100644 lib/image_registry/tag.rb diff --git a/app/controllers/projects/container_registry_controller.rb b/app/controllers/projects/container_registry_controller.rb index ffd455e6476..94f7580f0ea 100644 --- a/app/controllers/projects/container_registry_controller.rb +++ b/app/controllers/projects/container_registry_controller.rb @@ -5,10 +5,7 @@ class Projects::ContainerRegistryController < Projects::ApplicationController layout 'project' def index - @tags = container_registry.tags - - other_repository = container_registry.registry["gitlab/gitlab-test3"] - container_registry.copy_to(other_repository) + @tags = container_registry_repository.tags end def destroy @@ -21,8 +18,8 @@ class Projects::ContainerRegistryController < Projects::ApplicationController private - def container_registry - @container_registry ||= project.container_registry + def container_registry_repository + @container_registry_repository ||= project.container_registry_repository end def tag diff --git a/app/models/project.rb b/app/models/project.rb index f81d9a52628..d384c58ca36 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -376,9 +376,11 @@ class Project < ActiveRecord::Base end def container_registry - @registry_token ||= Jwt::DockerAuthenticationService.full_access_token(path_with_namespace) - @registry ||= ImageRegistry::Registry.new(Gitlab.config.registry.api_url, token: @registry_token) - @container_registry ||= ImageRegistry::Repository.new(@registry, path_with_namespace) + @container_registry_repository ||= begin + token = Jwt::ContainerRegistryAuthenticationService.full_access_token(path_with_namespace) + registry = ContainerRegistry::Registry.new(Gitlab.config.registry.api_url, token: token) + registry[path_with_namespace] + end end def container_registry_url diff --git a/lib/container_registry/blob.rb b/lib/container_registry/blob.rb new file mode 100644 index 00000000000..e0d9923f217 --- /dev/null +++ b/lib/container_registry/blob.rb @@ -0,0 +1,51 @@ +module ContainerRegistry + 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 + + def mount_to(to_repository) + client.repository_mount_blob(to_repository.name, digest, repository.name) + end + end +end diff --git a/lib/container_registry/client.rb b/lib/container_registry/client.rb new file mode 100644 index 00000000000..b823428344b --- /dev/null +++ b/lib/container_registry/client.rb @@ -0,0 +1,64 @@ +require 'faraday' +require 'faraday_middleware' + +module ContainerRegistry + 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(:authorization, :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}/blobs/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 diff --git a/lib/container_registry/config.rb b/lib/container_registry/config.rb new file mode 100644 index 00000000000..626b36cbaa9 --- /dev/null +++ b/lib/container_registry/config.rb @@ -0,0 +1,15 @@ +module ContainerRegistry + 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 diff --git a/lib/container_registry/registry.rb b/lib/container_registry/registry.rb new file mode 100644 index 00000000000..f866862db22 --- /dev/null +++ b/lib/container_registry/registry.rb @@ -0,0 +1,14 @@ +module ContainerRegistry + class Registry + attr_reader :uri, :client + + def initialize(uri, options = {}) + @uri = URI.parse(uri) + @client = ContainerRegistry::Client.new(uri, options) + end + + def [](name) + ContainerRegistry::Repository.new(self, name) + end + end +end diff --git a/lib/container_registry/repository.rb b/lib/container_registry/repository.rb new file mode 100644 index 00000000000..c930878d265 --- /dev/null +++ b/lib/container_registry/repository.rb @@ -0,0 +1,55 @@ +module ContainerRegistry + class Repository + attr_reader :registry, :name + + def initialize(registry, name) + @registry, @name = registry, name + end + + def client + @client ||= registry.client + end + + def [](tag) + ContainerRegistry::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| + ContainerRegistry::Tag.new(self, tag) + end + @tags ||= [] + end + + def delete_tags + return unless tags + tags.each(:delete) + end + + def mount_blob(blob) + return unless blob + client.repository_mount_blob(name, blob.digest, blob.repository.name) + end + + def mount_manifest(tag, manifest) + client.put_repository_manifest(name, tag, manifest) + end + + def copy_to(other_repository) + tags.all? do |tag| + tag.copy_to(other_repository) + end + end + end +end diff --git a/lib/container_registry/tag.rb b/lib/container_registry/tag.rb new file mode 100644 index 00000000000..324778bdf2a --- /dev/null +++ b/lib/container_registry/tag.rb @@ -0,0 +1,75 @@ +module ContainerRegistry + 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_blob + return @config_blob if defined?(@config_blob) + return unless manifest && manifest['config'] + @config_blob = ContainerRegistry::Blob.new(repository, manifest['config']) + end + + def config + return unless config_blob + @config ||= ContainerRegistry::Config.new(self, config_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| + ContainerRegistry::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 copy_to(repository) + return unless manifest + layers.each do |blob| + repository.mount_blob(blob) + end + repository.mount_blob(config_blob) + repository.mount_manifest(name, manifest.to_json) + end + + def client + @client ||= repository.client + end + end +end diff --git a/lib/image_registry/blob.rb b/lib/image_registry/blob.rb deleted file mode 100644 index 43665149e23..00000000000 --- a/lib/image_registry/blob.rb +++ /dev/null @@ -1,51 +0,0 @@ -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 - - def mount_to(to_repository) - client.repository_mount_blob(to_repository.name, digest, repository.name) - end - end -end diff --git a/lib/image_registry/client.rb b/lib/image_registry/client.rb deleted file mode 100644 index a4432059097..00000000000 --- a/lib/image_registry/client.rb +++ /dev/null @@ -1,64 +0,0 @@ -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(:authorization, :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}/blobs/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 diff --git a/lib/image_registry/config.rb b/lib/image_registry/config.rb deleted file mode 100644 index 1c2abec1bfa..00000000000 --- a/lib/image_registry/config.rb +++ /dev/null @@ -1,15 +0,0 @@ -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 diff --git a/lib/image_registry/registry.rb b/lib/image_registry/registry.rb deleted file mode 100644 index d8de8e392e9..00000000000 --- a/lib/image_registry/registry.rb +++ /dev/null @@ -1,14 +0,0 @@ -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 diff --git a/lib/image_registry/repository.rb b/lib/image_registry/repository.rb deleted file mode 100644 index 43e8e7720db..00000000000 --- a/lib/image_registry/repository.rb +++ /dev/null @@ -1,55 +0,0 @@ -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 - @tags ||= [] - end - - def delete_tags - return unless tags - tags.each(:delete) - end - - def mount_blob(blob) - return unless blob - client.repository_mount_blob(name, blob.digest, blob.repository.name) - end - - def mount_manifest(tag, manifest) - client.put_repository_manifest(name, tag, manifest) - end - - def copy_to(other_repository) - tags.all? do |tag| - tag.copy_to(other_repository) - end - end - end -end diff --git a/lib/image_registry/tag.rb b/lib/image_registry/tag.rb deleted file mode 100644 index 76946a6ce5b..00000000000 --- a/lib/image_registry/tag.rb +++ /dev/null @@ -1,75 +0,0 @@ -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_blob - return @config_blob if defined?(@config_blob) - return unless manifest && manifest['config'] - @config_blob = ImageRegistry::Blob.new(repository, manifest['config']) - end - - def config - return unless config_blob - @config ||= ImageRegistry::Config.new(self, config_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 copy_to(repository) - return unless manifest - layers.each do |blob| - repository.mount_blob(blob) - end - repository.mount_blob(config_blob) - repository.mount_manifest(name, manifest.to_json) - end - - def client - @client ||= repository.client - end - end -end -- cgit v1.2.1 From d7b91fb596e50411c0adb5aa8fb17341087f3f57 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 9 May 2016 22:27:06 +0300 Subject: Simplify Container Registry view implementation --- app/models/project.rb | 10 +++--- .../projects/container_registry/index.html.haml | 41 +++++++++++----------- lib/container_registry/blob.rb | 4 +++ lib/container_registry/registry.rb | 3 +- lib/container_registry/repository.rb | 4 +++ lib/container_registry/tag.rb | 4 +++ 6 files changed, 41 insertions(+), 25 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index d384c58ca36..36ce472f3a3 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -375,17 +375,19 @@ class Project < ActiveRecord::Base @repository ||= Repository.new(path_with_namespace, self) end - def container_registry + def container_registry_repository @container_registry_repository ||= begin token = Jwt::ContainerRegistryAuthenticationService.full_access_token(path_with_namespace) - registry = ContainerRegistry::Registry.new(Gitlab.config.registry.api_url, token: token) + url = Gitlab.config.registry.api_url + host_port = Gitlab.config.registry.host_port + registry = ContainerRegistry::Registry.new(url, token: token, path: host_port) registry[path_with_namespace] end end - def container_registry_url + def container_registry_repository_url if container_registry_enabled? && Gitlab.config.registry.enabled - "#{Gitlab.config.registry.host_with_port}/#{path_with_namespace}" + "#{Gitlab.config.registry.host_port}/#{path_with_namespace}" end end diff --git a/app/views/projects/container_registry/index.html.haml b/app/views/projects/container_registry/index.html.haml index 1ac3a62f54e..5b7dd27ace6 100644 --- a/app/views/projects/container_registry/index.html.haml +++ b/app/views/projects/container_registry/index.html.haml @@ -1,35 +1,36 @@ - page_title "Container Registry" = render "header_title" -.light.prepend-top-default - %p - A 'container image' is a snapshot of a container. - You can host your 'container images' with GitLab. - %br - To start using container images hosted on GitLab you first need to login: - %pre - %code - docker login #{Gitlab.config.registry.host_port} - %br - Then you are free to create and upload a container images with build and push commands: - %pre - docker build -t #{Gitlab.config.registry.host_port}/#{@project.path_with_namespace} . - %br - docker push #{Gitlab.config.registry.host_port}/#{@project.path_with_namespace} - %hr %ul.content-list - if @tags.blank? %li - .nothing-here-block No images to show + .nothing-here-block No images in Container Registry for this project. + + .light.prepend-top-default + %p + A 'container image' is a snapshot of a container. + You can host your container images with GitLab. + %br + To start using container images hosted on GitLab you first need to login: + %pre + %code + docker login #{Gitlab.config.registry.host_port} + %br + Then you are free to create and upload a container images with build and push commands: + %pre + docker build -t #{escape_once(@project.container_registry_repository_url)} . + %br + docker push #{escape_once(@project.container_registry_repository_url)} + - else .table-holder %table.table.builds %thead %tr %th Name - %th Digest + %th Image ID %th Size %th Created %th @@ -37,8 +38,8 @@ - @tags.each do |tag| %tr %td - #{tag.repository.name}:#{tag.name} - = clipboard_button(clipboard_text: "docker pull #{Gitlab.config.registry.host_port}/#{tag.repository.name}:#{tag.name}") + = escape_once(tag.name) + = clipboard_button(clipboard_text: "docker pull #{tag.path}") %td - if layer = tag.layers.first %span.has-tooltip(title="#{layer.revision}") diff --git a/lib/container_registry/blob.rb b/lib/container_registry/blob.rb index e0d9923f217..16e3f853418 100644 --- a/lib/container_registry/blob.rb +++ b/lib/container_registry/blob.rb @@ -11,6 +11,10 @@ module ContainerRegistry digest.present? end + def path + "#{repository.path}@#{digest}" + end + def digest config['digest'] end diff --git a/lib/container_registry/registry.rb b/lib/container_registry/registry.rb index f866862db22..a86ddb9326a 100644 --- a/lib/container_registry/registry.rb +++ b/lib/container_registry/registry.rb @@ -1,8 +1,9 @@ module ContainerRegistry class Registry - attr_reader :uri, :client + attr_reader :uri, :client, :path def initialize(uri, options = {}) + @path = uri || options[:path] @uri = URI.parse(uri) @client = ContainerRegistry::Client.new(uri, options) end diff --git a/lib/container_registry/repository.rb b/lib/container_registry/repository.rb index c930878d265..f01330f3648 100644 --- a/lib/container_registry/repository.rb +++ b/lib/container_registry/repository.rb @@ -10,6 +10,10 @@ module ContainerRegistry @client ||= registry.client end + def path + [registry.path, name].compact.join('/') + end + def [](tag) ContainerRegistry::Tag.new(self, tag) end diff --git a/lib/container_registry/tag.rb b/lib/container_registry/tag.rb index 324778bdf2a..14cee8be889 100644 --- a/lib/container_registry/tag.rb +++ b/lib/container_registry/tag.rb @@ -15,6 +15,10 @@ module ContainerRegistry @manifest = client.repository_manifest(repository.name, name) end + def path + "#{repository.path}:#{name}" + end + def [](key) return unless manifest manifest[key] -- cgit v1.2.1 From 60869580023da553613ee18bcdbc8682ebdde2cf Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 9 May 2016 22:34:10 +0300 Subject: Rename image to container_registry --- app/controllers/projects/container_registry_controller.rb | 6 +++--- app/helpers/projects_helper.rb | 4 ++-- app/views/layouts/nav/_project.html.haml | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/controllers/projects/container_registry_controller.rb b/app/controllers/projects/container_registry_controller.rb index 94f7580f0ea..82c621b29e4 100644 --- a/app/controllers/projects/container_registry_controller.rb +++ b/app/controllers/projects/container_registry_controller.rb @@ -1,6 +1,6 @@ class Projects::ContainerRegistryController < Projects::ApplicationController - before_action :authorize_read_image! - before_action :authorize_update_image!, only: [:destroy] + before_action :authorize_read_container_registry! + before_action :authorize_update_container_registry!, only: [:destroy] before_action :tag, except: [:index] layout 'project' @@ -23,6 +23,6 @@ class Projects::ContainerRegistryController < Projects::ApplicationController end def tag - @tag ||= container_registry[params[:id]] + @tag ||= container_registry_repository[params[:id]] end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index a76b5e22600..664fdb6d745 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -152,8 +152,8 @@ module ProjectsHelper nav_tabs << :builds end - if can?(current_user, :read_image, project) - nav_tabs << :images + if can?(current_user, :read_container_registry, project) + nav_tabs << :container_registry end if can?(current_user, :admin_project, project) diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 8448599c1cc..d3d715aad3b 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -46,9 +46,9 @@ Builds %span.count.builds_counter= number_with_delimiter(@project.builds.running_or_pending.count(:all)) - - if project_nav_tab? :images + - if project_nav_tab? :container_registry = nav_link(controller: %w(container_registry)) do - = link_to project_container_registry_path(@project), title: 'Container Registry', class: 'shortcuts-images' do + = link_to project_container_registry_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do = icon('hdd-o fw') %span Container Registry -- cgit v1.2.1 From b5043d5d33ed2a213889dfe52c819addd0d847ef Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 9 May 2016 22:34:24 +0300 Subject: Fix review comments --- doc/permissions/permissions.md | 1 + lib/container_registry/blob.rb | 1 - lib/container_registry/client.rb | 33 +++++++++++++++++++-------------- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md index 6be5ea0b486..30f6c75e1cf 100644 --- a/doc/permissions/permissions.md +++ b/doc/permissions/permissions.md @@ -39,6 +39,7 @@ documentation](../workflow/add-user/add-user.md). | Cancel and retry builds | | | ✓ | ✓ | ✓ | | Create or update commit status | | | ✓ | ✓ | ✓ | | Update a container registry | | | ✓ | ✓ | ✓ | +| Remove a container registry images | | | ✓ | ✓ | ✓ | | Create new milestones | | | | ✓ | ✓ | | Add new team members | | | | ✓ | ✓ | | Push to protected branches | | | | ✓ | ✓ | diff --git a/lib/container_registry/blob.rb b/lib/container_registry/blob.rb index 16e3f853418..d59792a383e 100644 --- a/lib/container_registry/blob.rb +++ b/lib/container_registry/blob.rb @@ -44,7 +44,6 @@ module ContainerRegistry end def data - return @data if defined?(@data) @data ||= client.blob(repository.name, digest, type) end diff --git a/lib/container_registry/client.rb b/lib/container_registry/client.rb index b823428344b..41d9cb46ae9 100644 --- a/lib/container_registry/client.rb +++ b/lib/container_registry/client.rb @@ -9,20 +9,8 @@ module ContainerRegistry 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(:authorization, :bearer, options[:token].to_s) - end - - builder.adapter :net_http + @faraday = Faraday.new(@base_uri) do |conn| + initialize_connection(conn) end end @@ -60,5 +48,22 @@ module ContainerRegistry def delete_blob(name, digest) @faraday.delete("/v2/#{name}/blobs/#{digest}").success? end + + private + + def initialize_connection(conn) + conn.request :json + conn.headers['Accept'] = MANIFEST_VERSION + + conn.response :json, :content_type => /\bjson$/ + + if options[:user] && options[:password] + conn.request(:basic_auth, options[:user].to_s, options[:password].to_s) + elsif options[:token] + conn.request(:authorization, :bearer, options[:token].to_s) + end + + conn.adapter :net_http + end end end -- cgit v1.2.1 From d5d8e76bd79cd9d61c66539a5069104cf46be2bd Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 9 May 2016 22:41:48 +0300 Subject: Block renaming project or repository if it has container registry tags --- app/models/namespace.rb | 8 ++++++++ app/models/project.rb | 11 +++++++++++ app/services/projects/transfer_service.rb | 5 +++++ 3 files changed, 24 insertions(+) diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 741e912171d..0f61cee7888 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -127,6 +127,10 @@ class Namespace < ActiveRecord::Base # Ensure old directory exists before moving it gitlab_shell.add_namespace(path_was) + if any_project_has_container_registry_tags? + raise Exception.new('namespace cannot be moved, because at least one project has tags in container registry') + end + if gitlab_shell.mv_namespace(path_was, path) Gitlab::UploadsTransfer.new.rename_namespace(path_was, path) @@ -148,6 +152,10 @@ class Namespace < ActiveRecord::Base end end + def any_project_has_container_registry_tags? + projects.any?(:has_container_registry_tags?) + end + def send_update_instructions projects.each do |project| project.send_move_instructions("#{path_was}/#{project.path}") diff --git a/app/models/project.rb b/app/models/project.rb index 36ce472f3a3..d3ae7803bea 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -391,6 +391,12 @@ class Project < ActiveRecord::Base end end + def has_container_registry_tags? + if container_registry_enabled? && Gitlab.config.registry.enabled + container_registry_repository.tags.any? + end + end + def commit(id = 'HEAD') repository.commit(id) end @@ -806,6 +812,11 @@ class Project < ActiveRecord::Base expire_caches_before_rename(old_path_with_namespace) + if has_container_registry_tags? + # we currently doesn't support renaming repository if it contains tags in container registry + raise Exception.new('repository cannot be renamed, due to tags in container registry') + end + if gitlab_shell.mv_repository(old_path_with_namespace, new_path_with_namespace) # If repository moved successfully we need to send update instructions to users. # However we cannot allow rollback since we moved repository diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index 111b3ec05ea..0d72286dec7 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -34,6 +34,11 @@ module Projects raise TransferError.new("Project with same path in target namespace already exists") end + if project.has_container_registry_tags? + # we currently doesn't support renaming repository if it contains tags in container registry + raise TransferError.new('Repository cannot be renamed, due to tags in container registry') + end + project.expire_caches_before_rename(old_path) # Apply new namespace id and visibility level -- cgit v1.2.1 From d05f0030a3de42ab3ec6d8c8be290b74698bb929 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 9 May 2016 23:32:18 +0300 Subject: Added Docker Registry View tests --- app/controllers/jwt_controller.rb | 2 +- .../container_registry_authentication_service.rb | 6 ++- app/services/projects/destroy_service.rb | 2 +- .../projects/container_registry/_tag.html.haml | 20 ++++++++++ .../projects/container_registry/index.html.haml | 23 +----------- lib/container_registry/client.rb | 4 +- spec/features/container_registry_spec.rb | 43 ++++++++++++++++++++++ spec/fixtures/container_registry/config_blob.json | 1 + spec/fixtures/container_registry/tag_manifest.json | 1 + spec/support/stub_gitlab_calls.rb | 12 ++++++ 10 files changed, 87 insertions(+), 27 deletions(-) create mode 100644 app/views/projects/container_registry/_tag.html.haml create mode 100644 spec/features/container_registry_spec.rb create mode 100644 spec/fixtures/container_registry/config_blob.json create mode 100644 spec/fixtures/container_registry/tag_manifest.json diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb index 0048a1a31ea..07a842970b8 100644 --- a/app/controllers/jwt_controller.rb +++ b/app/controllers/jwt_controller.rb @@ -3,7 +3,7 @@ class JwtController < ApplicationController skip_before_action :verify_authenticity_token SERVICES = { - 'container_registry' => Jwt::ContainerRegistryAuthenticationService, + Jwt::ContainerRegistryAuthenticationService::AUDIENCE => Jwt::ContainerRegistryAuthenticationService, } def auth diff --git a/app/services/jwt/container_registry_authentication_service.rb b/app/services/jwt/container_registry_authentication_service.rb index 88af4f8361b..2edee1f0ab0 100644 --- a/app/services/jwt/container_registry_authentication_service.rb +++ b/app/services/jwt/container_registry_authentication_service.rb @@ -1,5 +1,7 @@ module Jwt class ContainerRegistryAuthenticationService < BaseService + AUDIENCE = 'container_registry' + def execute if params[:offline_token] return error('forbidden', 403) unless current_user @@ -14,7 +16,7 @@ module Jwt registry = Gitlab.config.registry token = ::Jwt::RSAToken.new(registry.key) token.issuer = registry.issuer - token.audience = 'docker' + token.audience = AUDIENCE token[:access] = names.map do |name| { type: 'repository', name: name, actions: %w(pull push) } end @@ -26,7 +28,7 @@ module Jwt def authorized_token(access) token = ::Jwt::RSAToken.new(registry.key) token.issuer = registry.issuer - token.audience = params[:service] + token.audience = AUDIENCE token.subject = current_user.try(:username) token[:access] = access token diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb index a8b31f95c4c..8e2e46346ca 100644 --- a/app/services/projects/destroy_service.rb +++ b/app/services/projects/destroy_service.rb @@ -27,7 +27,7 @@ module Projects project.destroy! unless remove_registry_tags - raise_error('Failed to remove project image registry. Please try again or contact administrator') + raise_error('Failed to remove project container registry. Please try again or contact administrator') end unless remove_repository(repo_path) diff --git a/app/views/projects/container_registry/_tag.html.haml b/app/views/projects/container_registry/_tag.html.haml new file mode 100644 index 00000000000..f97988d20cf --- /dev/null +++ b/app/views/projects/container_registry/_tag.html.haml @@ -0,0 +1,20 @@ +%tr.tag + %td + = escape_once(tag.name) + = clipboard_button(clipboard_text: "docker pull #{tag.path}") + %td + - if layer = tag.layers.first + %span.has-tooltip(title="#{layer.revision}") + = layer.short_revision + - else + \- + %td + = number_to_human_size(tag.total_size) + · + = pluralize(tag.layers.size, "layer") + %td + = time_ago_in_words(tag.created_at) + %td.content + .controls.hidden-xs.pull-right + = link_to namespace_project_container_registry_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") diff --git a/app/views/projects/container_registry/index.html.haml b/app/views/projects/container_registry/index.html.haml index 5b7dd27ace6..701f1a86b56 100644 --- a/app/views/projects/container_registry/index.html.haml +++ b/app/views/projects/container_registry/index.html.haml @@ -26,7 +26,7 @@ - else .table-holder - %table.table.builds + %table.table.tags %thead %tr %th Name @@ -36,23 +36,4 @@ %th - @tags.each do |tag| - %tr - %td - = escape_once(tag.name) - = clipboard_button(clipboard_text: "docker pull #{tag.path}") - %td - - if layer = tag.layers.first - %span.has-tooltip(title="#{layer.revision}") - = layer.short_revision - - else - \- - %td - = number_to_human_size(tag.total_size) - · - = pluralize(tag.layers.size, "layer") - %td - = time_ago_in_words(tag.created_at) - %td.content - .controls.hidden-xs.pull-right - = link_to namespace_project_container_registry_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") + = render 'tag', tag: tag \ No newline at end of file diff --git a/lib/container_registry/client.rb b/lib/container_registry/client.rb index 41d9cb46ae9..0bfb6baffd4 100644 --- a/lib/container_registry/client.rb +++ b/lib/container_registry/client.rb @@ -10,7 +10,7 @@ module ContainerRegistry def initialize(base_uri, options = {}) @base_uri = base_uri @faraday = Faraday.new(@base_uri) do |conn| - initialize_connection(conn) + initialize_connection(conn, options) end end @@ -51,7 +51,7 @@ module ContainerRegistry private - def initialize_connection(conn) + def initialize_connection(conn, options) conn.request :json conn.headers['Accept'] = MANIFEST_VERSION diff --git a/spec/features/container_registry_spec.rb b/spec/features/container_registry_spec.rb new file mode 100644 index 00000000000..7bef7a2ee81 --- /dev/null +++ b/spec/features/container_registry_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' + +describe "Container Registry" do + let(:project) { create(:empty_project) } + let(:repository) { project.container_registry_repository } + let(:tag_name) { 'latest' } + let(:tags) { [tag_name] } + + before do + end + + before do + login_as(:user) + project.team << [@user, :developer] + stub_container_registry(*tags) + end + + describe 'GET /:project/container_registry' do + before do + visit namespace_project_container_registry_index_path(project.namespace, project) + end + + context 'when no tags' do + let(:tags) { [] } + it { expect(page).to have_content('No images in Container Registry for this project') } + end + + context 'when there are tags' do + it { expect(page).to have_content(tag_name)} + end + end + + describe 'DELETE /:project/container_registry/tag' do + before do + visit namespace_project_container_registry_index_path(project.namespace, project) + end + + it do + expect_any_instance_of(::ContainerRegistry::Tag).to receive(:delete).and_return(true) + click_on 'Remove' + end + end +end \ No newline at end of file diff --git a/spec/fixtures/container_registry/config_blob.json b/spec/fixtures/container_registry/config_blob.json new file mode 100644 index 00000000000..1028c994a24 --- /dev/null +++ b/spec/fixtures/container_registry/config_blob.json @@ -0,0 +1 @@ +{"architecture":"amd64","config":{"Hostname":"b14cd8298755","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":null,"Cmd":null,"Image":"","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"container":"b14cd82987550b01af9a666a2f4c996280a6152e66873134fae5a0f223dc5976","container_config":{"Hostname":"b14cd8298755","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":null,"Cmd":["/bin/sh","-c","#(nop) ADD file:033ab063740d9ff4dcfb1c69eccf25f91d88729f57cd5a73050e014e3e094aa0 in /"],"Image":"","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"created":"2016-04-01T20:53:00.160300546Z","docker_version":"1.9.1","history":[{"created":"2016-04-01T20:53:00.160300546Z","created_by":"/bin/sh -c #(nop) ADD file:033ab063740d9ff4dcfb1c69eccf25f91d88729f57cd5a73050e014e3e094aa0 in /"}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:c56b7dabbc7aa730eeab07668bdcbd7e3d40855047ca9a0cc1bfed23a2486111"]}} diff --git a/spec/fixtures/container_registry/tag_manifest.json b/spec/fixtures/container_registry/tag_manifest.json new file mode 100644 index 00000000000..1b6008e2872 --- /dev/null +++ b/spec/fixtures/container_registry/tag_manifest.json @@ -0,0 +1 @@ +{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/octet-stream","size":1145,"digest":"sha256:d7a513a663c1a6dcdba9ed832ca53c02ac2af0c333322cd6ca92936d1d9917ac"},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","size":2319870,"digest":"sha256:420890c9e918b6668faaedd9000e220190f2493b0693ee563ebd7b4cc754a57d"}]} diff --git a/spec/support/stub_gitlab_calls.rb b/spec/support/stub_gitlab_calls.rb index eec2e681117..6b3a4010063 100644 --- a/spec/support/stub_gitlab_calls.rb +++ b/spec/support/stub_gitlab_calls.rb @@ -25,6 +25,18 @@ module StubGitlabCalls allow_any_instance_of(Project).to receive(:builds_enabled?).and_return(false) end + def stub_container_registry(*tags) + allow_any_instance_of(ContainerRegistry::Client).to receive(:repository_tags).and_return( + { "tags" => tags } + ) + allow_any_instance_of(ContainerRegistry::Client).to receive(:repository_manifest).and_return( + JSON.load(File.read(Rails.root + 'spec/fixtures/container_registry/tag_manifest.json')) + ) + allow_any_instance_of(ContainerRegistry::Client).to receive(:blob).and_return( + File.read(Rails.root + 'spec/fixtures/container_registry/config_blob.json') + ) + end + private def gitlab_url -- cgit v1.2.1 From 5c2f2fd2890b7efd7a63f9a371b2f795f2e9fa43 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 12 May 2016 13:03:04 -0500 Subject: Fix CI tests --- app/controllers/projects/container_registry_controller.rb | 1 - app/helpers/gitlab_routing_helper.rb | 2 +- app/models/ability.rb | 2 +- app/models/namespace.rb | 2 +- app/models/project.rb | 4 ++-- .../jwt/container_registry_authentication_service.rb | 2 +- app/services/projects/destroy_service.rb | 4 +++- app/services/projects/transfer_service.rb | 2 +- app/views/projects/container_registry/_tag.html.haml | 9 +++++---- app/views/projects/container_registry/index.html.haml | 5 +++-- lib/container_registry/client.rb | 2 +- lib/container_registry/repository.rb | 4 +++- spec/features/container_registry_spec.rb | 12 +++++++++--- 13 files changed, 31 insertions(+), 20 deletions(-) diff --git a/app/controllers/projects/container_registry_controller.rb b/app/controllers/projects/container_registry_controller.rb index 82c621b29e4..c470789a5bb 100644 --- a/app/controllers/projects/container_registry_controller.rb +++ b/app/controllers/projects/container_registry_controller.rb @@ -1,7 +1,6 @@ class Projects::ContainerRegistryController < Projects::ApplicationController before_action :authorize_read_container_registry! before_action :authorize_update_container_registry!, only: [:destroy] - before_action :tag, except: [:index] layout 'project' def index diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index dd9536b1460..2ce2d4e694f 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -34,7 +34,7 @@ module GitlabRoutingHelper end def project_container_registry_path(project, *args) - namespace_project_container_registry_index_url(project.namespace, project, *args) + namespace_project_container_registry_index_path(project.namespace, project, *args) end def activity_project_path(project, *args) diff --git a/app/models/ability.rb b/app/models/ability.rb index 59d5195f5b9..2465c1f424c 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -291,7 +291,7 @@ class Ability rules += named_abilities('build') end - unless project.container_registry_enabled + unless project.container_registry_enabled && Gitlab.config.registry.enabled rules += named_abilities('container_registry') end diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 0f61cee7888..62ef6de5db1 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -128,7 +128,7 @@ class Namespace < ActiveRecord::Base gitlab_shell.add_namespace(path_was) if any_project_has_container_registry_tags? - raise Exception.new('namespace cannot be moved, because at least one project has tags in container registry') + raise Exception.new('Namespace cannot be moved, because at least one project has tags in container registry') end if gitlab_shell.mv_namespace(path_was, path) diff --git a/app/models/project.rb b/app/models/project.rb index d3ae7803bea..e5ace7d755b 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -377,7 +377,7 @@ class Project < ActiveRecord::Base def container_registry_repository @container_registry_repository ||= begin - token = Jwt::ContainerRegistryAuthenticationService.full_access_token(path_with_namespace) + token = JWT::ContainerRegistryAuthenticationService.full_access_token(path_with_namespace) url = Gitlab.config.registry.api_url host_port = Gitlab.config.registry.host_port registry = ContainerRegistry::Registry.new(url, token: token, path: host_port) @@ -814,7 +814,7 @@ class Project < ActiveRecord::Base if has_container_registry_tags? # we currently doesn't support renaming repository if it contains tags in container registry - raise Exception.new('repository cannot be renamed, due to tags in container registry') + raise Exception.new('Project cannot be renamed, because tags are present in its container registry') end if gitlab_shell.mv_repository(old_path_with_namespace, new_path_with_namespace) diff --git a/app/services/jwt/container_registry_authentication_service.rb b/app/services/jwt/container_registry_authentication_service.rb index bc7e663caa6..91bad347278 100644 --- a/app/services/jwt/container_registry_authentication_service.rb +++ b/app/services/jwt/container_registry_authentication_service.rb @@ -14,7 +14,7 @@ module JWT def self.full_access_token(*names) registry = Gitlab.config.registry - token = ::Jwt::RSAToken.new(registry.key) + token = ::JWT::RSAToken.new(registry.key) token.issuer = registry.issuer token.audience = AUDIENCE token[:access] = names.map do |name| diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb index 8e2e46346ca..0ff2bc3cb81 100644 --- a/app/services/projects/destroy_service.rb +++ b/app/services/projects/destroy_service.rb @@ -64,7 +64,9 @@ module Projects end def remove_registry_tags - project.image_registry.delete_tags + return unless Gitlab.config.registry.enabled + + project.container_registry_repository.delete_tags end def raise_error(message) diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index 0d72286dec7..03b57dea51e 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -36,7 +36,7 @@ module Projects if project.has_container_registry_tags? # we currently doesn't support renaming repository if it contains tags in container registry - raise TransferError.new('Repository cannot be renamed, due to tags in container registry') + raise TransferError.new('Project cannot be transferred, because tags are present in its container registry') end project.expire_caches_before_rename(old_path) diff --git a/app/views/projects/container_registry/_tag.html.haml b/app/views/projects/container_registry/_tag.html.haml index f97988d20cf..bf816d109b6 100644 --- a/app/views/projects/container_registry/_tag.html.haml +++ b/app/views/projects/container_registry/_tag.html.haml @@ -14,7 +14,8 @@ = pluralize(tag.layers.size, "layer") %td = time_ago_in_words(tag.created_at) - %td.content - .controls.hidden-xs.pull-right - = link_to namespace_project_container_registry_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") + - if can?(current_user, :update_container_registry, @project) + %td.content + .controls.hidden-xs.pull-right + = link_to namespace_project_container_registry_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") diff --git a/app/views/projects/container_registry/index.html.haml b/app/views/projects/container_registry/index.html.haml index 701f1a86b56..ea7c4faaaec 100644 --- a/app/views/projects/container_registry/index.html.haml +++ b/app/views/projects/container_registry/index.html.haml @@ -18,7 +18,7 @@ %code docker login #{Gitlab.config.registry.host_port} %br - Then you are free to create and upload a container images with build and push commands: + Then you are free to create and upload a container image with build and push commands: %pre docker build -t #{escape_once(@project.container_registry_repository_url)} . %br @@ -33,7 +33,8 @@ %th Image ID %th Size %th Created - %th + - if can?(current_user, :update_container_registry, @project) + %th - @tags.each do |tag| = render 'tag', tag: tag \ No newline at end of file diff --git a/lib/container_registry/client.rb b/lib/container_registry/client.rb index 0bfb6baffd4..c250a4b6946 100644 --- a/lib/container_registry/client.rb +++ b/lib/container_registry/client.rb @@ -55,7 +55,7 @@ module ContainerRegistry conn.request :json conn.headers['Accept'] = MANIFEST_VERSION - conn.response :json, :content_type => /\bjson$/ + conn.response :json, content_type: /\bjson$/ if options[:user] && options[:password] conn.request(:basic_auth, options[:user].to_s, options[:password].to_s) diff --git a/lib/container_registry/repository.rb b/lib/container_registry/repository.rb index f01330f3648..b30cb527b60 100644 --- a/lib/container_registry/repository.rb +++ b/lib/container_registry/repository.rb @@ -30,19 +30,21 @@ module ContainerRegistry def tags return @tags if defined?(@tags) return [] unless manifest && manifest['tags'] + @tags = manifest['tags'].map do |tag| ContainerRegistry::Tag.new(self, tag) end - @tags ||= [] end def delete_tags return unless tags + tags.each(:delete) end def mount_blob(blob) return unless blob + client.repository_mount_blob(name, blob.digest, blob.repository.name) end diff --git a/spec/features/container_registry_spec.rb b/spec/features/container_registry_spec.rb index 7bef7a2ee81..6c4d675fd6a 100644 --- a/spec/features/container_registry_spec.rb +++ b/spec/features/container_registry_spec.rb @@ -5,14 +5,18 @@ describe "Container Registry" do let(:repository) { project.container_registry_repository } let(:tag_name) { 'latest' } let(:tags) { [tag_name] } - - before do + let(:registry_settings) do + { + enabled: true + } end before do login_as(:user) project.team << [@user, :developer] stub_container_registry(*tags) + allow(Gitlab.config.registry).to receive_messages(registry_settings) + allow(JWT::ContainerRegistryAuthenticationService).to receive(:full_access_token).and_return('token') end describe 'GET /:project/container_registry' do @@ -22,6 +26,7 @@ describe "Container Registry" do context 'when no tags' do let(:tags) { [] } + it { expect(page).to have_content('No images in Container Registry for this project') } end @@ -37,7 +42,8 @@ describe "Container Registry" do it do expect_any_instance_of(::ContainerRegistry::Tag).to receive(:delete).and_return(true) + click_on 'Remove' end end -end \ No newline at end of file +end -- cgit v1.2.1 From 575a73c896a113e6e9b76adb582cb23025cf0032 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 13 May 2016 08:45:57 -0500 Subject: Fix specs --- app/models/namespace.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 62ef6de5db1..cadd4ab044e 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -153,7 +153,7 @@ class Namespace < ActiveRecord::Base end def any_project_has_container_registry_tags? - projects.any?(:has_container_registry_tags?) + projects.any?(&:has_container_registry_tags?) end def send_update_instructions -- cgit v1.2.1 From 9e318bd99deb90a93130cd4ef79e54f18555d4dc Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 13 May 2016 12:20:23 -0500 Subject: Fix container registry permissions --- app/models/ability.rb | 1 + .../jwt/container_registry_authentication_service.rb | 6 +++++- app/services/projects/destroy_service.rb | 2 +- .../jwt/container_registry_authentication_service_spec.rb | 14 ++++++++++++++ 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index 2465c1f424c..09dea54689e 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -61,6 +61,7 @@ class Ability :read_merge_request, :read_note, :read_commit_status, + :read_container_registry, :download_code ] diff --git a/app/services/jwt/container_registry_authentication_service.rb b/app/services/jwt/container_registry_authentication_service.rb index 91bad347278..b60cd3c57e5 100644 --- a/app/services/jwt/container_registry_authentication_service.rb +++ b/app/services/jwt/container_registry_authentication_service.rb @@ -3,6 +3,8 @@ module JWT AUDIENCE = 'container_registry' def execute + return error('not found', 404) unless registry.enabled + if params[:offline_token] return error('forbidden', 403) unless current_user end @@ -65,9 +67,11 @@ module JWT end def can_access?(requested_project, requested_action) + return false unless requested_project.container_registry_enabled? + case requested_action when 'pull' - requested_project.public? || requested_project == project || can?(current_user, :read_container_registry, requested_project) + requested_project == project || can?(current_user, :read_container_registry, requested_project) when 'push' requested_project == project || can?(current_user, :create_container_registry, requested_project) else diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb index 0ff2bc3cb81..d3920ac8baa 100644 --- a/app/services/projects/destroy_service.rb +++ b/app/services/projects/destroy_service.rb @@ -64,7 +64,7 @@ module Projects end def remove_registry_tags - return unless Gitlab.config.registry.enabled + return true unless Gitlab.config.registry.enabled project.container_registry_repository.delete_tags end diff --git a/spec/services/jwt/container_registry_authentication_service_spec.rb b/spec/services/jwt/container_registry_authentication_service_spec.rb index 1873ea2639b..672a7579dd3 100644 --- a/spec/services/jwt/container_registry_authentication_service_spec.rb +++ b/spec/services/jwt/container_registry_authentication_service_spec.rb @@ -7,6 +7,7 @@ describe JWT::ContainerRegistryAuthenticationService, services: true do let(:rsa_key) { OpenSSL::PKey::RSA.generate(512) } let(:registry_settings) do { + enabled: true, issuer: 'rspec', key: nil } @@ -146,7 +147,20 @@ describe JWT::ContainerRegistryAuthenticationService, services: true do it_behaves_like 'a forbidden' end end + end + + context 'for project without container registry' do + let(:project) { create(:empty_project, :public, container_registry_enabled: false) } + + before { project.update(container_registry_enabled: false) } + context 'disallow when pulling' do + let(:current_params) do + { scope: "repository:#{project.path_with_namespace}:pull" } + end + + it_behaves_like 'a forbidden' + end end end -- cgit v1.2.1 From ee725db8e0f5d95c14031bd939a414e068497bb4 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sat, 14 May 2016 14:16:44 -0500 Subject: Add TODO --- app/controllers/jwt_controller.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb index e0e0a98a6c1..0edf084e9e4 100644 --- a/app/controllers/jwt_controller.rb +++ b/app/controllers/jwt_controller.rb @@ -42,6 +42,9 @@ class JwtController < ApplicationController end def authenticate_user(login, password) + # TODO: this is a copy and paste from grack_auth, + # it should be refactored in the future + user = Gitlab::Auth.new.find(login, password) # If the user authenticated successfully, we reset the auth failure count -- cgit v1.2.1 From 284dc3285a4ec2a626e90b16ca3d372eed82d349 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sat, 14 May 2016 14:22:10 -0500 Subject: Fix abilities --- app/models/ability.rb | 2 +- app/models/project.rb | 10 +++++----- app/views/projects/container_registry/index.html.haml | 2 +- spec/requests/ci/api/runners_spec.rb | 1 - 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index 09dea54689e..74321240468 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -292,7 +292,7 @@ class Ability rules += named_abilities('build') end - unless project.container_registry_enabled && Gitlab.config.registry.enabled + unless project.container_registry_enabled rules += named_abilities('container_registry') end diff --git a/app/models/project.rb b/app/models/project.rb index 0206ccc8d37..ed7719ed31c 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -332,20 +332,20 @@ class Project < ActiveRecord::Base @container_registry_repository ||= begin token = Auth::ContainerRegistryAuthenticationService.full_access_token(path_with_namespace) url = Gitlab.config.registry.api_url - host_port = Gitlab.config.registry.host_port - registry = ContainerRegistry::Registry.new(url, token: token, path: host_port) + host = Gitlab.config.registry.host + registry = ContainerRegistry::Registry.new(url, token: token, path: host) registry[path_with_namespace] end end def container_registry_repository_url - if container_registry_enabled? && Gitlab.config.registry.enabled - "#{Gitlab.config.registry.host_port}/#{path_with_namespace}" + if Gitlab.config.registry.enabled + "#{Gitlab.config.registry.host}/#{path_with_namespace}" end end def has_container_registry_tags? - if container_registry_enabled? && Gitlab.config.registry.enabled + if Gitlab.config.registry.enabled container_registry_repository.tags.any? end end diff --git a/app/views/projects/container_registry/index.html.haml b/app/views/projects/container_registry/index.html.haml index ea7c4faaaec..990253719bf 100644 --- a/app/views/projects/container_registry/index.html.haml +++ b/app/views/projects/container_registry/index.html.haml @@ -16,7 +16,7 @@ To start using container images hosted on GitLab you first need to login: %pre %code - docker login #{Gitlab.config.registry.host_port} + docker login #{Gitlab.config.registry.host} %br Then you are free to create and upload a container image with build and push commands: %pre diff --git a/spec/requests/ci/api/runners_spec.rb b/spec/requests/ci/api/runners_spec.rb index db8189ffb79..43f9fe89c8e 100644 --- a/spec/requests/ci/api/runners_spec.rb +++ b/spec/requests/ci/api/runners_spec.rb @@ -7,7 +7,6 @@ describe Ci::API::API do let(:registration_token) { 'abcdefg123456' } before do - stub_gitlab_calls stub_application_setting(runners_registration_token: registration_token) end -- cgit v1.2.1 From d40bd7419f9ca0f7caedef05209b86e7431dd882 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sat, 14 May 2016 14:22:45 -0500 Subject: Fix authentication service --- app/services/auth/container_registry_authentication_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb index 0323a42b697..100f7cbae28 100644 --- a/app/services/auth/container_registry_authentication_service.rb +++ b/app/services/auth/container_registry_authentication_service.rb @@ -54,7 +54,7 @@ module Auth def can_access?(requested_project, requested_action) case requested_action when 'pull' - requested_project.public? || requested_project == project || can?(current_user, :read_container_registry, requested_project) + requested_project == project || can?(current_user, :read_container_registry, requested_project) when 'push' requested_project == project || can?(current_user, :create_container_registry, requested_project) else -- cgit v1.2.1 From c275c91373103c10dfe5e21afd6fc54a7a4dfdbe Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sat, 14 May 2016 18:26:26 -0500 Subject: Change abilities from container_registry to container_image --- app/controllers/projects/container_registry_controller.rb | 4 ++-- app/helpers/projects_helper.rb | 2 +- app/views/projects/container_registry/_tag.html.haml | 2 +- app/views/projects/container_registry/index.html.haml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/controllers/projects/container_registry_controller.rb b/app/controllers/projects/container_registry_controller.rb index c470789a5bb..e48f205d216 100644 --- a/app/controllers/projects/container_registry_controller.rb +++ b/app/controllers/projects/container_registry_controller.rb @@ -1,6 +1,6 @@ class Projects::ContainerRegistryController < Projects::ApplicationController - before_action :authorize_read_container_registry! - before_action :authorize_update_container_registry!, only: [:destroy] + before_action :authorize_read_container_image! + before_action :authorize_update_container_image!, only: [:destroy] layout 'project' def index diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 7113e28924b..466929443d6 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -152,7 +152,7 @@ module ProjectsHelper nav_tabs << :builds end - if can?(current_user, :read_container_registry, project) + if can?(current_user, :read_container_image, project) nav_tabs << :container_registry end diff --git a/app/views/projects/container_registry/_tag.html.haml b/app/views/projects/container_registry/_tag.html.haml index bf816d109b6..10eabc6cd6f 100644 --- a/app/views/projects/container_registry/_tag.html.haml +++ b/app/views/projects/container_registry/_tag.html.haml @@ -14,7 +14,7 @@ = pluralize(tag.layers.size, "layer") %td = time_ago_in_words(tag.created_at) - - if can?(current_user, :update_container_registry, @project) + - if can?(current_user, :update_container_image, @project) %td.content .controls.hidden-xs.pull-right = link_to namespace_project_container_registry_path(@project.namespace, @project, tag.name), class: 'btn btn-remove has-tooltip', title: "Remove", data: { confirm: "Are you sure?" }, method: :delete do diff --git a/app/views/projects/container_registry/index.html.haml b/app/views/projects/container_registry/index.html.haml index 990253719bf..f3b2881ce09 100644 --- a/app/views/projects/container_registry/index.html.haml +++ b/app/views/projects/container_registry/index.html.haml @@ -33,7 +33,7 @@ %th Image ID %th Size %th Created - - if can?(current_user, :update_container_registry, @project) + - if can?(current_user, :update_container_image, @project) %th - @tags.each do |tag| -- cgit v1.2.1 From b575b2f1ef10c44a59151567aa1aa390f4a94ab1 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sun, 15 May 2016 08:47:48 -0500 Subject: Fix Container Service full access token --- app/services/auth/container_registry_authentication_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb index 69ad634c368..9bfc1085fb9 100644 --- a/app/services/auth/container_registry_authentication_service.rb +++ b/app/services/auth/container_registry_authentication_service.rb @@ -16,7 +16,7 @@ module Auth def self.full_access_token(*names) registry = Gitlab.config.registry - token = ::JWT::RSAToken.new(registry.key) + token = JSONWebToken::RSAToken.new(registry.key) token.issuer = registry.issuer token.audience = AUDIENCE token[:access] = names.map do |name| -- cgit v1.2.1 From 938d5b6fe4bb3d7d6d85188fc5fd1aac77803577 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sun, 15 May 2016 10:46:54 -0500 Subject: Fix http status codes for container registry authentication service --- .../auth/container_registry_authentication_service.rb | 4 ++-- .../container_registry_authentication_service_spec.rb | 17 ++++++----------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb index a0cda4adc56..169e0387e85 100644 --- a/app/services/auth/container_registry_authentication_service.rb +++ b/app/services/auth/container_registry_authentication_service.rb @@ -6,9 +6,9 @@ module Auth return error('not found', 404) unless registry.enabled if params[:offline_token] - return error('forbidden', 403) unless current_user + return error('forbidden', 401) unless current_user else - return error('forbidden', 401) unless scope + return error('forbidden', 403) unless scope end { token: authorized_token(scope).encoded } diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb index 409d08e49f1..143d992b6e4 100644 --- a/spec/services/auth/container_registry_authentication_service_spec.rb +++ b/spec/services/auth/container_registry_authentication_service_spec.rb @@ -57,11 +57,6 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do end end - shared_examples 'a unauthorized' do - it { is_expected.to include(http_status: 401) } - it { is_expected.to_not include(:token) } - end - shared_examples 'a forbidden' do it { is_expected.to include(http_status: 403) } it { is_expected.to_not include(:token) } @@ -116,7 +111,7 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do { scope: "repository:#{project.path_with_namespace}:pull,push" } end - it_behaves_like 'a unauthorized' + it_behaves_like 'a forbidden' end end @@ -154,7 +149,7 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do context 'disallow for private' do let(:project) { create(:empty_project, :private) } - it_behaves_like 'a unauthorized' + it_behaves_like 'a forbidden' end end @@ -165,7 +160,7 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do context 'disallow for all' do let(:project) { create(:empty_project, :public) } - it_behaves_like 'a unauthorized' + it_behaves_like 'a forbidden' end end end @@ -199,7 +194,7 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do { scope: 'invalid:aa:bb' } end - it_behaves_like 'a unauthorized' + it_behaves_like 'a forbidden' end context 'for private project' do @@ -209,7 +204,7 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do { scope: "repository:#{project.path_with_namespace}:pull" } end - it_behaves_like 'a unauthorized' + it_behaves_like 'a forbidden' end context 'for public project' do @@ -228,7 +223,7 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do { scope: "repository:#{project.path_with_namespace}:push" } end - it_behaves_like 'a unauthorized' + it_behaves_like 'a forbidden' end end end -- cgit v1.2.1 From 623102d4556c42fd386e37724e638156dabca277 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 16 May 2016 09:48:39 -0500 Subject: Use registry.port to construct the registry host --- app/models/project.rb | 6 +++--- app/views/projects/container_registry/index.html.haml | 2 +- config/initializers/1_settings.rb | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index ed7719ed31c..a3eb7d83e49 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -332,15 +332,15 @@ class Project < ActiveRecord::Base @container_registry_repository ||= begin token = Auth::ContainerRegistryAuthenticationService.full_access_token(path_with_namespace) url = Gitlab.config.registry.api_url - host = Gitlab.config.registry.host - registry = ContainerRegistry::Registry.new(url, token: token, path: host) + host_port = Gitlab.config.registry.host_port + registry = ContainerRegistry::Registry.new(url, token: token, path: host_port) registry[path_with_namespace] end end def container_registry_repository_url if Gitlab.config.registry.enabled - "#{Gitlab.config.registry.host}/#{path_with_namespace}" + "#{Gitlab.config.registry.host_port}/#{path_with_namespace}" end end diff --git a/app/views/projects/container_registry/index.html.haml b/app/views/projects/container_registry/index.html.haml index f3b2881ce09..6a1e46b14b9 100644 --- a/app/views/projects/container_registry/index.html.haml +++ b/app/views/projects/container_registry/index.html.haml @@ -16,7 +16,7 @@ To start using container images hosted on GitLab you first need to login: %pre %code - docker login #{Gitlab.config.registry.host} + docker login #{Gitlab.config.registry.host_port} %br Then you are free to create and upload a container image with build and push commands: %pre diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index d1fcb053bee..20f920ea854 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -249,9 +249,11 @@ Settings.artifacts['max_size'] ||= 100 # in megabytes Settings['registry'] ||= Settingslogic.new({}) Settings.registry['enabled'] ||= false Settings.registry['host'] ||= "example.com" +Settings.registry['port'] ||= nil Settings.registry['api_url'] ||= "http://localhost:5000/" Settings.registry['key'] ||= nil Settings.registry['issuer'] ||= nil +Settings.registry['host_port'] ||= [Settings.registry['host'], Settings.registry['port']].join(':') # # Git LFS -- cgit v1.2.1 From 5b3ac8c83f19bfa8b897f307c056c521fd1d052a Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 16 May 2016 09:45:18 -0500 Subject: Bring back port to registry configuration --- config/gitlab.yml.example | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 2e383bc90fa..d935121d88b 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -178,7 +178,8 @@ production: &base registry: # enabled: true - # host: localhost + # host: registry.example.com + # port: 5000 # api_url: http://localhost:5000/ # key: config/registry.key # issuer: omnibus-certificate -- cgit v1.2.1 From e60bbdf9d9609fd0624be61dcf8a3a0432040ad1 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 16 May 2016 09:49:55 -0500 Subject: Fix path to tag --- lib/container_registry/registry.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/container_registry/registry.rb b/lib/container_registry/registry.rb index a86ddb9326a..d3b117eeaca 100644 --- a/lib/container_registry/registry.rb +++ b/lib/container_registry/registry.rb @@ -3,7 +3,7 @@ module ContainerRegistry attr_reader :uri, :client, :path def initialize(uri, options = {}) - @path = uri || options[:path] + @path = options[:path] || uri @uri = URI.parse(uri) @client = ContainerRegistry::Client.new(uri, options) end -- cgit v1.2.1 From a26ec8a10660ca6ffcf49abac6ddad57fd99f80d Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 16 May 2016 09:51:15 -0500 Subject: Compact host and port --- config/initializers/1_settings.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 20f920ea854..129fd9b79f2 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -253,7 +253,7 @@ Settings.registry['port'] ||= nil Settings.registry['api_url'] ||= "http://localhost:5000/" Settings.registry['key'] ||= nil Settings.registry['issuer'] ||= nil -Settings.registry['host_port'] ||= [Settings.registry['host'], Settings.registry['port']].join(':') +Settings.registry['host_port'] ||= [Settings.registry['host'], Settings.registry['port']].compact.join(':') # # Git LFS -- cgit v1.2.1 From e3e392965ee16f79a80b0f8517ff8c9e445bb907 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 16 May 2016 16:39:23 -0500 Subject: Update permission doc --- doc/permissions/permissions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md index 30f6c75e1cf..b76ce31cbad 100644 --- a/doc/permissions/permissions.md +++ b/doc/permissions/permissions.md @@ -39,7 +39,7 @@ documentation](../workflow/add-user/add-user.md). | Cancel and retry builds | | | ✓ | ✓ | ✓ | | Create or update commit status | | | ✓ | ✓ | ✓ | | Update a container registry | | | ✓ | ✓ | ✓ | -| Remove a container registry images | | | ✓ | ✓ | ✓ | +| Remove a container registry image | | | ✓ | ✓ | ✓ | | Create new milestones | | | | ✓ | ✓ | | Add new team members | | | | ✓ | ✓ | | Push to protected branches | | | | ✓ | ✓ | -- cgit v1.2.1 From 23d1c454108d50bbb1795b0a5855ee7ae9024e3c Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 16 May 2016 17:12:45 -0500 Subject: Fix 401 message --- app/services/auth/container_registry_authentication_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb index 169e0387e85..3144e96ba31 100644 --- a/app/services/auth/container_registry_authentication_service.rb +++ b/app/services/auth/container_registry_authentication_service.rb @@ -6,7 +6,7 @@ module Auth return error('not found', 404) unless registry.enabled if params[:offline_token] - return error('forbidden', 401) unless current_user + return error('unauthorized', 401) unless current_user else return error('forbidden', 403) unless scope end -- cgit v1.2.1 From 89644edc4e2a9447cc31d4f966c163316dff95fa Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 16 May 2016 17:15:38 -0500 Subject: Bring back path to registry settings --- config/gitlab.yml.example | 1 + config/initializers/1_settings.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index d935121d88b..0cef2794f4e 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -183,6 +183,7 @@ production: &base # api_url: http://localhost:5000/ # key: config/registry.key # issuer: omnibus-certificate + # path: shared/registry # # 2. GitLab CI settings diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 129fd9b79f2..796b8178c5e 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -254,6 +254,7 @@ Settings.registry['api_url'] ||= "http://localhost:5000/" Settings.registry['key'] ||= nil Settings.registry['issuer'] ||= nil Settings.registry['host_port'] ||= [Settings.registry['host'], Settings.registry['port']].compact.join(':') +Settings.registry['path'] = File.expand_path(Settings.artifacts['path'] || File.join(Settings.shared['path'], "registry"), Rails.root) # # Git LFS -- cgit v1.2.1 From 143cd58c398b693db1b9d02f7267db39a8acb87c Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 16 May 2016 17:17:57 -0500 Subject: Added backup of container registry --- lib/backup/manager.rb | 2 +- lib/backup/registry.rb | 13 +++++++++++++ lib/tasks/gitlab/backup.rake | 21 +++++++++++++++++++++ spec/tasks/gitlab/backup_rake_spec.rb | 14 +++++++++----- 4 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 lib/backup/registry.rb diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb index 4962f5e53ce..7d0608f09da 100644 --- a/lib/backup/manager.rb +++ b/lib/backup/manager.rb @@ -157,7 +157,7 @@ module Backup end def archives_to_backup - %w{uploads builds artifacts lfs}.map{ |name| (name + ".tar.gz") unless skipped?(name) }.compact + %w{uploads builds artifacts lfs registry}.map{ |name| (name + ".tar.gz") unless skipped?(name) }.compact end def folders_to_backup diff --git a/lib/backup/registry.rb b/lib/backup/registry.rb new file mode 100644 index 00000000000..67fe0231087 --- /dev/null +++ b/lib/backup/registry.rb @@ -0,0 +1,13 @@ +require 'backup/files' + +module Backup + class Registry < Files + def initialize + super('registry', Settings.registry.path) + end + + def create_files_dir + Dir.mkdir(app_files_dir, 0700) + end + end +end diff --git a/lib/tasks/gitlab/backup.rake b/lib/tasks/gitlab/backup.rake index 402bb338f27..d97d974ec20 100644 --- a/lib/tasks/gitlab/backup.rake +++ b/lib/tasks/gitlab/backup.rake @@ -14,6 +14,7 @@ namespace :gitlab do Rake::Task["gitlab:backup:builds:create"].invoke Rake::Task["gitlab:backup:artifacts:create"].invoke Rake::Task["gitlab:backup:lfs:create"].invoke + Rake::Task["gitlab:backup:registry:create"].invoke backup = Backup::Manager.new backup.pack @@ -54,6 +55,7 @@ namespace :gitlab do Rake::Task['gitlab:backup:builds:restore'].invoke unless backup.skipped?('builds') Rake::Task['gitlab:backup:artifacts:restore'].invoke unless backup.skipped?('artifacts') Rake::Task['gitlab:backup:lfs:restore'].invoke unless backup.skipped?('lfs') + Rake::Task['gitlab:backup:registry:restore'].invoke unless backup.skipped?('registry') Rake::Task['gitlab:shell:setup'].invoke backup.cleanup @@ -173,6 +175,25 @@ namespace :gitlab do end end + namespace :registry do + task create: :environment do + $progress.puts "Dumping container registry images ... ".blue + + if ENV["SKIP"] && ENV["SKIP"].include?("registry") + $progress.puts "[SKIPPED]".cyan + else + Backup::Registry.new.dump + $progress.puts "done".green + end + end + + task restore: :environment do + $progress.puts "Restoring container registry images ... ".blue + Backup::Registry.new.restore + $progress.puts "done".green + end + end + def configure_cron_mode if ENV['CRON'] # We need an object we can say 'puts' and 'print' to; let's use a diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb index 05fc4c4554f..8aeb013eec6 100644 --- a/spec/tasks/gitlab/backup_rake_spec.rb +++ b/spec/tasks/gitlab/backup_rake_spec.rb @@ -21,7 +21,7 @@ describe 'gitlab:app namespace rake task' do end def reenable_backup_sub_tasks - %w{db repo uploads builds artifacts lfs}.each do |subtask| + %w{db repo uploads builds artifacts lfs registry}.each do |subtask| Rake::Task["gitlab:backup:#{subtask}:create"].reenable end end @@ -65,6 +65,7 @@ describe 'gitlab:app namespace rake task' do expect(Rake::Task['gitlab:backup:uploads:restore']).to receive(:invoke) expect(Rake::Task['gitlab:backup:artifacts:restore']).to receive(:invoke) expect(Rake::Task['gitlab:backup:lfs:restore']).to receive(:invoke) + expect(Rake::Task['gitlab:backup:registry:restore']).to receive(:invoke) expect(Rake::Task['gitlab:shell:setup']).to receive(:invoke) expect { run_rake_task('gitlab:backup:restore') }.not_to raise_error end @@ -122,7 +123,7 @@ describe 'gitlab:app namespace rake task' do it 'should set correct permissions on the tar contents' do tar_contents, exit_status = Gitlab::Popen.popen( - %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz lfs.tar.gz} + %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz lfs.tar.gz registry.tar.gz} ) expect(exit_status).to eq(0) expect(tar_contents).to match('db/') @@ -131,12 +132,13 @@ describe 'gitlab:app namespace rake task' do expect(tar_contents).to match('builds.tar.gz') expect(tar_contents).to match('artifacts.tar.gz') expect(tar_contents).to match('lfs.tar.gz') - expect(tar_contents).not_to match(/^.{4,9}[rwx].* (database.sql.gz|uploads.tar.gz|repositories|builds.tar.gz|artifacts.tar.gz)\/$/) + expect(tar_contents).to match('registry.tar.gz') + expect(tar_contents).not_to match(/^.{4,9}[rwx].* (database.sql.gz|uploads.tar.gz|repositories|builds.tar.gz|artifacts.tar.gz|registry.tar.gz)\/$/) end it 'should delete temp directories' do temp_dirs = Dir.glob( - File.join(Gitlab.config.backup.path, '{db,repositories,uploads,builds,artifacts,lfs}') + File.join(Gitlab.config.backup.path, '{db,repositories,uploads,builds,artifacts,lfs,registry}') ) expect(temp_dirs).to be_empty @@ -172,7 +174,7 @@ describe 'gitlab:app namespace rake task' do it "does not contain skipped item" do tar_contents, _exit_status = Gitlab::Popen.popen( - %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz lfs.tar.gz} + %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz lfs.tar.gz registry.tar.gz} ) expect(tar_contents).to match('db/') @@ -180,6 +182,7 @@ describe 'gitlab:app namespace rake task' do expect(tar_contents).to match('builds.tar.gz') expect(tar_contents).to match('artifacts.tar.gz') expect(tar_contents).to match('lfs.tar.gz') + expect(tar_contents).to match('registry.tar.gz') expect(tar_contents).not_to match('repositories/') end @@ -195,6 +198,7 @@ describe 'gitlab:app namespace rake task' do expect(Rake::Task['gitlab:backup:builds:restore']).to receive :invoke expect(Rake::Task['gitlab:backup:artifacts:restore']).to receive :invoke expect(Rake::Task['gitlab:backup:lfs:restore']).to receive :invoke + expect(Rake::Task['gitlab:backup:registry:restore']).to receive :invoke expect(Rake::Task['gitlab:shell:setup']).to receive :invoke expect { run_rake_task('gitlab:backup:restore') }.not_to raise_error end -- cgit v1.2.1 From 7aee713420d5533452613480082660f0647f5598 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 16 May 2016 17:29:14 -0500 Subject: Improve design of the feature --- app/views/projects/container_registry/_tag.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/container_registry/_tag.html.haml b/app/views/projects/container_registry/_tag.html.haml index 10eabc6cd6f..4e9f936539b 100644 --- a/app/views/projects/container_registry/_tag.html.haml +++ b/app/views/projects/container_registry/_tag.html.haml @@ -4,7 +4,7 @@ = clipboard_button(clipboard_text: "docker pull #{tag.path}") %td - if layer = tag.layers.first - %span.has-tooltip(title="#{layer.revision}") + %span.has-tooltip{ title: "#{layer.revision}" } = layer.short_revision - else \- -- cgit v1.2.1 From 154270a3d4f42f1751502112d0e9cd6b5983032b Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 16 May 2016 17:37:28 -0500 Subject: Test container related specs --- spec/models/project_spec.rb | 75 ++++++++++++++++++++++++++++++++++++ spec/requests/ci/api/runners_spec.rb | 1 + 2 files changed, 76 insertions(+) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index f6e5b132643..6de75af08e4 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -772,4 +772,79 @@ describe Project, models: true do expect(project.protected_branch?('foo')).to eq(false) end end + + describe '#container_registry_repository' do + let(:project) { create(:empty_project) } + + subject { project.container_registry_repository } + + it { is_expected.to_not be_nil } + end + + describe '#container_registry_repository_url' do + let(:project) { create(:empty_project) } + + subject { project.container_registry_repository_url } + + before { allow(Gitlab.config.registry).to receive_messages(registry_settings) } + + context 'for enabled registry' do + let(:registry_settings) do + { + enabled: true, + host_port: 'example.com', + } + end + + it { is_expected.to_not be_nil } + end + + context 'for disabled registry' do + let(:registry_settings) do + { + enabled: false + } + end + + it { is_expected.to be_nil } + end + end + + describe '#has_container_registry_tags?' do + let(:project) { create(:empty_project) } + + subject { project.has_container_registry_tags? } + + before { allow(Gitlab.config.registry).to receive_messages(registry_settings) } + + context 'for enabled registry' do + let(:registry_settings) do + { + enabled: true + } + end + + context 'with tags' do + before { stub_container_registry('test', 'test2') } + + it { is_expected.to be_truthy } + end + + context 'when no tags' do + before { stub_container_registry } + + it { is_expected.to be_falsey } + end + end + + context 'for disabled registry' do + let(:registry_settings) do + { + enabled: false + } + end + + it { is_expected.to be_falsey } + end + end end diff --git a/spec/requests/ci/api/runners_spec.rb b/spec/requests/ci/api/runners_spec.rb index 43f9fe89c8e..db8189ffb79 100644 --- a/spec/requests/ci/api/runners_spec.rb +++ b/spec/requests/ci/api/runners_spec.rb @@ -7,6 +7,7 @@ describe Ci::API::API do let(:registration_token) { 'abcdefg123456' } before do + stub_gitlab_calls stub_application_setting(runners_registration_token: registration_token) end -- cgit v1.2.1 From 72a71e9d17c75b11e82623cd9edd22ba70c9ba4f Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 16 May 2016 17:38:58 -0500 Subject: Show container registry item only when container registry is enabled --- app/helpers/projects_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 466929443d6..b6ba66bf3e5 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -152,7 +152,7 @@ module ProjectsHelper nav_tabs << :builds end - if can?(current_user, :read_container_image, project) + if project.container_registry_repository_url.present? && can?(current_user, :read_container_image, project) nav_tabs << :container_registry end -- cgit v1.2.1 From 91c4002a0c2f32944ec669cc159c4b1c9176866f Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 16 May 2016 18:03:55 -0500 Subject: Improve test coverage --- lib/container_registry/repository.rb | 2 +- spec/features/container_registry_spec.rb | 2 +- spec/models/namespace_spec.rb | 14 ++++++++++++ spec/models/project_spec.rb | 19 ++++++++++++---- spec/services/projects/destroy_service_spec.rb | 29 +++++++++++++++++++++++++ spec/services/projects/transfer_service_spec.rb | 11 ++++++++++ spec/support/stub_gitlab_calls.rb | 6 ++++- 7 files changed, 76 insertions(+), 7 deletions(-) diff --git a/lib/container_registry/repository.rb b/lib/container_registry/repository.rb index b30cb527b60..07cdb78264e 100644 --- a/lib/container_registry/repository.rb +++ b/lib/container_registry/repository.rb @@ -39,7 +39,7 @@ module ContainerRegistry def delete_tags return unless tags - tags.each(:delete) + tags.all?(&:delete) end def mount_blob(blob) diff --git a/spec/features/container_registry_spec.rb b/spec/features/container_registry_spec.rb index be5910e4abb..271ef883d13 100644 --- a/spec/features/container_registry_spec.rb +++ b/spec/features/container_registry_spec.rb @@ -14,7 +14,7 @@ describe "Container Registry" do before do login_as(:user) project.team << [@user, :developer] - stub_container_registry(*tags) + stub_container_registry_tags(*tags) allow(Gitlab.config.registry).to receive_messages(registry_settings) allow(Auth::ContainerRegistryAuthenticationService).to receive(:full_access_token).and_return('token') end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 4074f966299..4e68ac5e63a 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -70,6 +70,20 @@ describe Namespace, models: true do allow(@namespace).to receive(:path).and_return(new_path) expect(@namespace.move_dir).to be_truthy end + + context "when any project has container tags" do + before do + stub_container_registry_config(enabled: true) + stub_container_registry_tags('tag') + + create(:empty_project, namespace: @namespace) + + allow(@namespace).to receive(:path_was).and_return(@namespace.path) + allow(@namespace).to receive(:path).and_return('new_path') + end + + it { expect { @namespace.move_dir }.to raise_error('Namespace cannot be moved, because at least one project has tags in container registry') } + end end describe :rm_dir do diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 6de75af08e4..262f4122220 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -634,11 +634,11 @@ describe Project, models: true do # Project#gitlab_shell returns a new instance of Gitlab::Shell on every # call. This makes testing a bit easier. allow(project).to receive(:gitlab_shell).and_return(gitlab_shell) - end - it 'renames a repository' do allow(project).to receive(:previous_changes).and_return('path' => ['foo']) + end + it 'renames a repository' do ns = project.namespace_dir expect(gitlab_shell).to receive(:mv_repository). @@ -663,6 +663,17 @@ describe Project, models: true do project.rename_repo end + + context 'container registry with tags' do + before do + stub_container_registry_config(enabled: true) + stub_container_registry_tags('tag') + end + + subject { project.rename_repo } + + it { expect{subject}.to raise_error(Exception) } + end end describe '#expire_caches_before_rename' do @@ -825,13 +836,13 @@ describe Project, models: true do end context 'with tags' do - before { stub_container_registry('test', 'test2') } + before { stub_container_registry_tags('test', 'test2') } it { is_expected.to be_truthy } end context 'when no tags' do - before { stub_container_registry } + before { stub_container_registry_tags } it { is_expected.to be_falsey } end diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb index a5cb6f382e4..45b78ccf136 100644 --- a/spec/services/projects/destroy_service_spec.rb +++ b/spec/services/projects/destroy_service_spec.rb @@ -28,6 +28,35 @@ describe Projects::DestroyService, services: true do it { expect(Dir.exist?(remove_path)).to be_truthy } end + context 'container registry' do + let(:registry_settings) do + { + enabled: true + } + end + + before do + allow(Gitlab.config.registry).to receive_messages(registry_settings) + stub_container_registry_tags('tag') + end + + context 'tags deletion succeeds' do + it do + expect_any_instance_of(ContainerRegistry::Tag).to receive(:delete).and_return(true) + + destroy_project(project, user, {}) + end + end + + context 'tags deletion fails' do + before { expect_any_instance_of(ContainerRegistry::Tag).to receive(:delete).and_return(false) } + + subject { destroy_project(project, user, {}) } + + it { expect{subject}.to raise_error(Projects::DestroyService::DestroyError) } + end + end + def destroy_project(project, user, params) Projects::DestroyService.new(project, user, params).execute end diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index 06017317339..d5aa115a074 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -26,6 +26,17 @@ describe Projects::TransferService, services: true do it { expect(project.namespace).to eq(user.namespace) } end + context 'disallow transfering of project with tags' do + before do + stub_container_registry_config(enabled: true) + stub_container_registry_tags('tag') + end + + subject { transfer_project(project, user, group) } + + it { is_expected.to be_falsey } + end + context 'namespace -> not allowed namespace' do before do @result = transfer_project(project, user, group) diff --git a/spec/support/stub_gitlab_calls.rb b/spec/support/stub_gitlab_calls.rb index 2c31cbe3faf..36e234c2e9c 100644 --- a/spec/support/stub_gitlab_calls.rb +++ b/spec/support/stub_gitlab_calls.rb @@ -25,7 +25,11 @@ module StubGitlabCalls allow_any_instance_of(Project).to receive(:builds_enabled?).and_return(false) end - def stub_container_registry(*tags) + def stub_container_registry_config(registry_settings) + allow(Gitlab.config.registry).to receive_messages(registry_settings) + end + + def stub_container_registry_tags(*tags) allow_any_instance_of(ContainerRegistry::Client).to receive(:repository_tags).and_return( { "tags" => tags } ) -- cgit v1.2.1 From 8572a6b2d469009f01533b1003e73952bd2b6850 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 16 May 2016 18:07:37 -0500 Subject: Always show documentation how to access images --- .../projects/container_registry/index.html.haml | 32 +++++++++++----------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/app/views/projects/container_registry/index.html.haml b/app/views/projects/container_registry/index.html.haml index 6a1e46b14b9..40957993b22 100644 --- a/app/views/projects/container_registry/index.html.haml +++ b/app/views/projects/container_registry/index.html.haml @@ -4,26 +4,26 @@ %hr %ul.content-list + .light.prepend-top-default + %p + A 'container image' is a snapshot of a container. + You can host your container images with GitLab. + %br + To start using container images hosted on GitLab you first need to login: + %pre + %code + docker login #{Gitlab.config.registry.host_port} + %br + Then you are free to create and upload a container image with build and push commands: + %pre + docker build -t #{escape_once(@project.container_registry_repository_url)} . + %br + docker push #{escape_once(@project.container_registry_repository_url)} + - if @tags.blank? %li .nothing-here-block No images in Container Registry for this project. - .light.prepend-top-default - %p - A 'container image' is a snapshot of a container. - You can host your container images with GitLab. - %br - To start using container images hosted on GitLab you first need to login: - %pre - %code - docker login #{Gitlab.config.registry.host_port} - %br - Then you are free to create and upload a container image with build and push commands: - %pre - docker build -t #{escape_once(@project.container_registry_repository_url)} . - %br - docker push #{escape_once(@project.container_registry_repository_url)} - - else .table-holder %table.table.tags -- cgit v1.2.1 From 04933fd572f60909e8dbd14bd9366e96dc40806e Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 16 May 2016 18:07:49 -0500 Subject: Use container registry config stub --- spec/features/container_registry_spec.rb | 7 +------ spec/models/project_spec.rb | 16 +++------------- .../container_registry_authentication_service_spec.rb | 9 +-------- spec/services/projects/destroy_service_spec.rb | 8 +------- 4 files changed, 6 insertions(+), 34 deletions(-) diff --git a/spec/features/container_registry_spec.rb b/spec/features/container_registry_spec.rb index 271ef883d13..53b4f027117 100644 --- a/spec/features/container_registry_spec.rb +++ b/spec/features/container_registry_spec.rb @@ -5,17 +5,12 @@ describe "Container Registry" do let(:repository) { project.container_registry_repository } let(:tag_name) { 'latest' } let(:tags) { [tag_name] } - let(:registry_settings) do - { - enabled: true - } - end before do login_as(:user) project.team << [@user, :developer] stub_container_registry_tags(*tags) - allow(Gitlab.config.registry).to receive_messages(registry_settings) + stub_container_registry_config(enabled: true) allow(Auth::ContainerRegistryAuthenticationService).to receive(:full_access_token).and_return('token') end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 262f4122220..e434d267896 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -797,7 +797,7 @@ describe Project, models: true do subject { project.container_registry_repository_url } - before { allow(Gitlab.config.registry).to receive_messages(registry_settings) } + before { stub_container_registry_config(**registry_settings) } context 'for enabled registry' do let(:registry_settings) do @@ -826,14 +826,8 @@ describe Project, models: true do subject { project.has_container_registry_tags? } - before { allow(Gitlab.config.registry).to receive_messages(registry_settings) } - context 'for enabled registry' do - let(:registry_settings) do - { - enabled: true - } - end + before { stub_container_registry_config(enabled: true) } context 'with tags' do before { stub_container_registry_tags('test', 'test2') } @@ -849,11 +843,7 @@ describe Project, models: true do end context 'for disabled registry' do - let(:registry_settings) do - { - enabled: false - } - end + before { stub_container_registry_config(enabled: false) } it { is_expected.to be_falsey } end diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb index 143d992b6e4..d90e2982c4f 100644 --- a/spec/services/auth/container_registry_authentication_service_spec.rb +++ b/spec/services/auth/container_registry_authentication_service_spec.rb @@ -5,19 +5,12 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do let(:current_user) { nil } let(:current_params) { {} } let(:rsa_key) { OpenSSL::PKey::RSA.generate(512) } - let(:registry_settings) do - { - enabled: true, - issuer: 'rspec', - key: nil - } - end let(:payload) { JWT.decode(subject[:token], rsa_key).first } subject { described_class.new(current_project, current_user, current_params).execute } before do - allow(Gitlab.config.registry).to receive_messages(registry_settings) + stub_container_registry_config(enabled: true, issuer: 'rspec', key: nil) allow_any_instance_of(JSONWebToken::RSAToken).to receive(:key).and_return(rsa_key) end diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb index 45b78ccf136..29341c5e57e 100644 --- a/spec/services/projects/destroy_service_spec.rb +++ b/spec/services/projects/destroy_service_spec.rb @@ -29,14 +29,8 @@ describe Projects::DestroyService, services: true do end context 'container registry' do - let(:registry_settings) do - { - enabled: true - } - end - before do - allow(Gitlab.config.registry).to receive_messages(registry_settings) + stub_container_registry_config(enabled: true) stub_container_registry_tags('tag') end -- cgit v1.2.1 From 0a6c3494b0326de4cdc35d6369f48810d8403f5a Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 16 May 2016 23:32:48 -0500 Subject: Fix rubocop offenses --- config/initializers/1_settings.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 796b8178c5e..124d63ce3ac 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -254,7 +254,7 @@ Settings.registry['api_url'] ||= "http://localhost:5000/" Settings.registry['key'] ||= nil Settings.registry['issuer'] ||= nil Settings.registry['host_port'] ||= [Settings.registry['host'], Settings.registry['port']].compact.join(':') -Settings.registry['path'] = File.expand_path(Settings.artifacts['path'] || File.join(Settings.shared['path'], "registry"), Rails.root) +Settings.registry['path'] = File.expand_path(Settings.registry['path'] || File.join(Settings.shared['path'], 'registry'), Rails.root) # # Git LFS -- cgit v1.2.1 From ac6992ba682de08b79e5ddde08dbf566827e2f07 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 16 May 2016 23:40:40 -0500 Subject: Fix specs --- spec/models/project_spec.rb | 2 ++ .../auth/container_registry_authentication_service_spec.rb | 9 +++++++-- spec/support/stub_gitlab_calls.rb | 1 + 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index e434d267896..60e1ec43f2b 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -787,6 +787,8 @@ describe Project, models: true do describe '#container_registry_repository' do let(:project) { create(:empty_project) } + before { stub_container_registry_config(enabled: true) } + subject { project.container_registry_repository } it { is_expected.to_not be_nil } diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb index d90e2982c4f..6c9f56a4fba 100644 --- a/spec/services/auth/container_registry_authentication_service_spec.rb +++ b/spec/services/auth/container_registry_authentication_service_spec.rb @@ -50,6 +50,11 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do end end + shared_examples 'an unauthorized' do + it { is_expected.to include(http_status: 401) } + it { is_expected.to_not include(:token) } + end + shared_examples 'a forbidden' do it { is_expected.to include(http_status: 403) } it { is_expected.to_not include(:token) } @@ -116,7 +121,7 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do { offline_token: true } end - it_behaves_like 'a forbidden' + it_behaves_like 'an unauthorized' end context 'allow to pull and push images' do @@ -179,7 +184,7 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do { offline_token: true } end - it_behaves_like 'a forbidden' + it_behaves_like 'an unauthorized' end context 'for invalid scope' do diff --git a/spec/support/stub_gitlab_calls.rb b/spec/support/stub_gitlab_calls.rb index 36e234c2e9c..f73416a3d0f 100644 --- a/spec/support/stub_gitlab_calls.rb +++ b/spec/support/stub_gitlab_calls.rb @@ -27,6 +27,7 @@ module StubGitlabCalls def stub_container_registry_config(registry_settings) allow(Gitlab.config.registry).to receive_messages(registry_settings) + allow(Auth::ContainerRegistryAuthenticationService).to receive(:full_access_token).and_return('token') end def stub_container_registry_tags(*tags) -- cgit v1.2.1 From a82109eee80bf703ad8e82de2410f490e5fc6d54 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 17 May 2016 09:41:47 -0500 Subject: Add .gitkeep --- lib/container_registry/config.rb | 1 + lib/container_registry/registry.rb | 10 ++++++++-- lib/container_registry/repository.rb | 1 + lib/container_registry/tag.rb | 10 ++++++++++ shared/registry/.gitkeep | 0 spec/lib/container_registry/registry_spec.rb | 28 ++++++++++++++++++++++++++++ 6 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 shared/registry/.gitkeep create mode 100644 spec/lib/container_registry/registry_spec.rb diff --git a/lib/container_registry/config.rb b/lib/container_registry/config.rb index 626b36cbaa9..589f9f4380a 100644 --- a/lib/container_registry/config.rb +++ b/lib/container_registry/config.rb @@ -9,6 +9,7 @@ module ContainerRegistry def [](key) return unless data + data[key] end end diff --git a/lib/container_registry/registry.rb b/lib/container_registry/registry.rb index d3b117eeaca..07490de94ba 100644 --- a/lib/container_registry/registry.rb +++ b/lib/container_registry/registry.rb @@ -3,13 +3,19 @@ module ContainerRegistry attr_reader :uri, :client, :path def initialize(uri, options = {}) - @path = options[:path] || uri - @uri = URI.parse(uri) + @uri = uri + @path = options[:path] || default_path @client = ContainerRegistry::Client.new(uri, options) end def [](name) ContainerRegistry::Repository.new(self, name) end + + private + + def default_path + @uri.sub(/^https?:\/\//, '') + end end end diff --git a/lib/container_registry/repository.rb b/lib/container_registry/repository.rb index 07cdb78264e..77825056138 100644 --- a/lib/container_registry/repository.rb +++ b/lib/container_registry/repository.rb @@ -20,6 +20,7 @@ module ContainerRegistry def manifest return @manifest if defined?(@manifest) + @manifest = client.repository_tags(name) end diff --git a/lib/container_registry/tag.rb b/lib/container_registry/tag.rb index 14cee8be889..f06806db6a8 100644 --- a/lib/container_registry/tag.rb +++ b/lib/container_registry/tag.rb @@ -12,6 +12,7 @@ module ContainerRegistry def manifest return @manifest if defined?(@manifest) + @manifest = client.repository_manifest(repository.name, name) end @@ -21,33 +22,39 @@ module ContainerRegistry 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_blob return @config_blob if defined?(@config_blob) return unless manifest && manifest['config'] + @config_blob = ContainerRegistry::Blob.new(repository, manifest['config']) end def config return unless config_blob + @config ||= ContainerRegistry::Config.new(self, config_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| ContainerRegistry::Blob.new(repository, layer) end @@ -55,16 +62,19 @@ module ContainerRegistry 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 copy_to(repository) return unless manifest + layers.each do |blob| repository.mount_blob(blob) end diff --git a/shared/registry/.gitkeep b/shared/registry/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/spec/lib/container_registry/registry_spec.rb b/spec/lib/container_registry/registry_spec.rb new file mode 100644 index 00000000000..f5f7c3c8bf2 --- /dev/null +++ b/spec/lib/container_registry/registry_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe ContainerRegistry::Registry do + let(:path) { nil } + let(:registry) { described_class.new('http://example.com', path: path) } + + subject { registry } + + it { is_expected.to respond_to(:client) } + it { is_expected.to respond_to(:uri) } + it { is_expected.to respond_to(:path) } + + it { expect(subject['test']).to_not be_nil } + + context '#path' do + subject { registry.path } + + context 'path from URL' do + it { is_expected.to eq('example.com') } + end + + context 'custom path' do + let(:path) { 'registry.example.com' } + + it { is_expected.to eq(path) } + end + end +end -- cgit v1.2.1 From 24145592e804ffbe58a38b15095808919337d545 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 17 May 2016 13:20:11 -0500 Subject: Provide full test coverage to lib/container_registry API implementation --- .../projects/container_registry_controller.rb | 2 +- app/models/project.rb | 2 +- lib/container_registry/blob.rb | 10 +-- lib/container_registry/client.rb | 8 -- lib/container_registry/registry.rb | 2 +- lib/container_registry/repository.rb | 28 ++----- lib/container_registry/tag.rb | 20 +---- spec/lib/container_registry/blob_spec.rb | 61 +++++++++++++++ spec/lib/container_registry/registry_spec.rb | 2 +- spec/lib/container_registry/repository_spec.rb | 65 ++++++++++++++++ spec/lib/container_registry/tag_spec.rb | 89 ++++++++++++++++++++++ 11 files changed, 232 insertions(+), 57 deletions(-) create mode 100644 spec/lib/container_registry/blob_spec.rb create mode 100644 spec/lib/container_registry/repository_spec.rb create mode 100644 spec/lib/container_registry/tag_spec.rb diff --git a/app/controllers/projects/container_registry_controller.rb b/app/controllers/projects/container_registry_controller.rb index e48f205d216..8ed5a8ff6fd 100644 --- a/app/controllers/projects/container_registry_controller.rb +++ b/app/controllers/projects/container_registry_controller.rb @@ -22,6 +22,6 @@ class Projects::ContainerRegistryController < Projects::ApplicationController end def tag - @tag ||= container_registry_repository[params[:id]] + @tag ||= container_registry_repository.tag(params[:id]) end end diff --git a/app/models/project.rb b/app/models/project.rb index 8e80a6b5c01..09a214da043 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -336,7 +336,7 @@ class Project < ActiveRecord::Base url = Gitlab.config.registry.api_url host_port = Gitlab.config.registry.host_port registry = ContainerRegistry::Registry.new(url, token: token, path: host_port) - registry[path_with_namespace] + registry.repository(path_with_namespace) end end diff --git a/lib/container_registry/blob.rb b/lib/container_registry/blob.rb index d59792a383e..4e20dc4f875 100644 --- a/lib/container_registry/blob.rb +++ b/lib/container_registry/blob.rb @@ -2,6 +2,8 @@ module ContainerRegistry class Blob attr_reader :repository, :config + delegate :registry, :client, to: :repository + def initialize(repository, config) @repository = repository @config = config || {} @@ -35,10 +37,6 @@ module ContainerRegistry revision[0..8] end - def client - @client ||= repository.client - end - def delete client.delete_blob(repository.name, digest) end @@ -46,9 +44,5 @@ module ContainerRegistry def data @data ||= client.blob(repository.name, digest, type) end - - def mount_to(to_repository) - client.repository_mount_blob(to_repository.name, digest, repository.name) - end end end diff --git a/lib/container_registry/client.rb b/lib/container_registry/client.rb index c250a4b6946..4d726692f45 100644 --- a/lib/container_registry/client.rb +++ b/lib/container_registry/client.rb @@ -22,14 +22,6 @@ module ContainerRegistry @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}/blobs/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? diff --git a/lib/container_registry/registry.rb b/lib/container_registry/registry.rb index 07490de94ba..0e634f6b6ef 100644 --- a/lib/container_registry/registry.rb +++ b/lib/container_registry/registry.rb @@ -8,7 +8,7 @@ module ContainerRegistry @client = ContainerRegistry::Client.new(uri, options) end - def [](name) + def repository(name) ContainerRegistry::Repository.new(self, name) end diff --git a/lib/container_registry/repository.rb b/lib/container_registry/repository.rb index 77825056138..0e4a7cb3cc9 100644 --- a/lib/container_registry/repository.rb +++ b/lib/container_registry/repository.rb @@ -2,19 +2,17 @@ module ContainerRegistry class Repository attr_reader :registry, :name + delegate :client, to: :registry + def initialize(registry, name) @registry, @name = registry, name end - def client - @client ||= registry.client - end - def path [registry.path, name].compact.join('/') end - def [](tag) + def tag(tag) ContainerRegistry::Tag.new(self, tag) end @@ -37,26 +35,14 @@ module ContainerRegistry end end + def blob(config) + ContainerRegistry::Blob.new(self, config) + end + def delete_tags return unless tags tags.all?(&:delete) end - - def mount_blob(blob) - return unless blob - - client.repository_mount_blob(name, blob.digest, blob.repository.name) - end - - def mount_manifest(tag, manifest) - client.put_repository_manifest(name, tag, manifest) - end - - def copy_to(other_repository) - tags.all? do |tag| - tag.copy_to(other_repository) - end - end end end diff --git a/lib/container_registry/tag.rb b/lib/container_registry/tag.rb index f06806db6a8..43f8d6dc8c2 100644 --- a/lib/container_registry/tag.rb +++ b/lib/container_registry/tag.rb @@ -2,6 +2,8 @@ module ContainerRegistry class Tag attr_reader :repository, :name + delegate :registry, :client, to: :repository + def initialize(repository, name) @repository, @name = repository, name end @@ -36,7 +38,7 @@ module ContainerRegistry return @config_blob if defined?(@config_blob) return unless manifest && manifest['config'] - @config_blob = ContainerRegistry::Blob.new(repository, manifest['config']) + @config_blob = repository.blob(manifest['config']) end def config @@ -56,7 +58,7 @@ module ContainerRegistry return unless manifest @layers = manifest['layers'].map do |layer| - ContainerRegistry::Blob.new(repository, layer) + repository.blob(layer) end end @@ -71,19 +73,5 @@ module ContainerRegistry client.delete_repository_tag(repository.name, digest) end - - def copy_to(repository) - return unless manifest - - layers.each do |blob| - repository.mount_blob(blob) - end - repository.mount_blob(config_blob) - repository.mount_manifest(name, manifest.to_json) - end - - def client - @client ||= repository.client - end end end diff --git a/spec/lib/container_registry/blob_spec.rb b/spec/lib/container_registry/blob_spec.rb new file mode 100644 index 00000000000..4d8cb787dde --- /dev/null +++ b/spec/lib/container_registry/blob_spec.rb @@ -0,0 +1,61 @@ +require 'spec_helper' + +describe ContainerRegistry::Blob do + let(:digest) { 'sha256:0123456789012345' } + let(:config) do + { + 'digest' => digest, + 'mediaType' => 'binary', + 'size' => 1000 + } + end + + let(:registry) { ContainerRegistry::Registry.new('http://example.com') } + let(:repository) { registry.repository('group/test') } + let(:blob) { repository.blob(config) } + + it { expect(blob).to respond_to(:repository) } + it { expect(blob).to delegate_method(:registry).to(:repository) } + it { expect(blob).to delegate_method(:client).to(:repository) } + + context '#path' do + subject { blob.path } + + it { is_expected.to eq('example.com/group/test@sha256:0123456789012345') } + end + + context '#digest' do + subject { blob.digest } + + it { is_expected.to eq(digest) } + end + + context '#type' do + subject { blob.type } + + it { is_expected.to eq('binary') } + end + + context '#revision' do + subject { blob.revision } + + it { is_expected.to eq('0123456789012345') } + end + + context '#short_revision' do + subject { blob.short_revision } + + it { is_expected.to eq('012345678') } + end + + context '#delete' do + before do + stub_request(:delete, 'http://example.com/v2/group/test/blobs/sha256:0123456789012345'). + to_return(status: 200) + end + + subject { blob.delete } + + it { is_expected.to be_truthy } + end +end diff --git a/spec/lib/container_registry/registry_spec.rb b/spec/lib/container_registry/registry_spec.rb index f5f7c3c8bf2..2638401ae6e 100644 --- a/spec/lib/container_registry/registry_spec.rb +++ b/spec/lib/container_registry/registry_spec.rb @@ -10,7 +10,7 @@ describe ContainerRegistry::Registry do it { is_expected.to respond_to(:uri) } it { is_expected.to respond_to(:path) } - it { expect(subject['test']).to_not be_nil } + it { expect(subject.repository('test')).to_not be_nil } context '#path' do subject { registry.path } diff --git a/spec/lib/container_registry/repository_spec.rb b/spec/lib/container_registry/repository_spec.rb new file mode 100644 index 00000000000..e6d66b11e4e --- /dev/null +++ b/spec/lib/container_registry/repository_spec.rb @@ -0,0 +1,65 @@ +require 'spec_helper' + +describe ContainerRegistry::Repository do + let(:registry) { ContainerRegistry::Registry.new('http://example.com') } + let(:repository) { registry.repository('group/test') } + + it { expect(repository).to respond_to(:registry) } + it { expect(repository).to delegate_method(:client).to(:registry) } + it { expect(repository.tag('test')).to_not be_nil } + + context '#path' do + subject { repository.path } + + it { is_expected.to eq('example.com/group/test') } + end + + context 'manifest processing' do + before do + stub_request(:get, 'http://example.com/v2/group/test/tags/list'). + with(headers: { 'Accept' => 'application/vnd.docker.distribution.manifest.v2+json' }). + to_return( + status: 200, + body: JSON.dump(tags: ['test']), + headers: { 'Content-Type' => 'application/vnd.docker.distribution.manifest.v2+json' }) + end + + context '#manifest' do + subject { repository.manifest } + + it { is_expected.to_not be_nil } + end + + context '#valid?' do + subject { repository.valid? } + + it { is_expected.to be_truthy } + end + + context '#tags' do + subject { repository.tags } + + it { is_expected.to_not be_empty } + end + end + + context '#delete_tags' do + let(:tag) { ContainerRegistry::Tag.new(repository, 'tag') } + + before { expect(repository).to receive(:tags).twice.and_return([tag]) } + + subject { repository.delete_tags } + + context 'succeeds' do + before { expect(tag).to receive(:delete).and_return(true) } + + it { is_expected.to be_truthy } + end + + context 'any fails' do + before { expect(tag).to receive(:delete).and_return(false) } + + it { is_expected.to be_falsey } + end + end +end diff --git a/spec/lib/container_registry/tag_spec.rb b/spec/lib/container_registry/tag_spec.rb new file mode 100644 index 00000000000..12cf91127ed --- /dev/null +++ b/spec/lib/container_registry/tag_spec.rb @@ -0,0 +1,89 @@ +require 'spec_helper' + +describe ContainerRegistry::Tag do + let(:registry) { ContainerRegistry::Registry.new('http://example.com') } + let(:repository) { registry.repository('group/test') } + let(:tag) { repository.tag('tag') } + let(:headers) { { 'Accept' => 'application/vnd.docker.distribution.manifest.v2+json' } } + + it { expect(tag).to respond_to(:repository) } + it { expect(tag).to delegate_method(:registry).to(:repository) } + it { expect(tag).to delegate_method(:client).to(:repository) } + + context '#path' do + subject { tag.path } + + it { is_expected.to eq('example.com/group/test:tag') } + end + + context 'manifest processing' do + before do + stub_request(:get, 'http://example.com/v2/group/test/manifests/tag'). + with(headers: headers). + to_return( + status: 200, + body: File.read(Rails.root + 'spec/fixtures/container_registry/tag_manifest.json'), + headers: { 'Content-Type' => 'application/vnd.docker.distribution.manifest.v2+json' }) + end + + context '#layers' do + subject { tag.layers } + + it { expect(subject.length).to eq(1) } + end + + context '#total_size' do + subject { tag.total_size } + + it { is_expected.to eq(2319870) } + end + + context 'config processing' do + before do + stub_request(:get, 'http://example.com/v2/group/test/blobs/sha256:d7a513a663c1a6dcdba9ed832ca53c02ac2af0c333322cd6ca92936d1d9917ac'). + with(headers: { 'Accept' => 'application/octet-stream' }). + to_return( + status: 200, + body: File.read(Rails.root + 'spec/fixtures/container_registry/config_blob.json')) + end + + context '#config' do + subject { tag.config } + + it { is_expected.to_not be_nil } + end + + context '#created_at' do + subject { tag.created_at } + + it { is_expected.to_not be_nil } + end + end + end + + context 'manifest digest' do + before do + stub_request(:head, 'http://example.com/v2/group/test/manifests/tag'). + with(headers: headers). + to_return(status: 200, headers: { 'Docker-Content-Digest' => 'sha256:digest' }) + end + + context '#digest' do + subject { tag.digest } + + it { is_expected.to eq('sha256:digest') } + end + + context '#delete' do + before do + stub_request(:delete, 'http://example.com/v2/group/test/manifests/sha256:digest'). + with(headers: headers). + to_return(status: 200) + end + + subject { tag.delete } + + it { is_expected.to be_truthy } + end + end +end -- cgit v1.2.1 From 2f1cb7ce0439de003e92c55a27c6a8db7f0f1803 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 18 May 2016 11:22:28 -0500 Subject: Improve code design --- app/controllers/projects/container_registry_controller.rb | 6 ++++-- app/models/project.rb | 8 +++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/controllers/projects/container_registry_controller.rb b/app/controllers/projects/container_registry_controller.rb index 8ed5a8ff6fd..3648f8894a6 100644 --- a/app/controllers/projects/container_registry_controller.rb +++ b/app/controllers/projects/container_registry_controller.rb @@ -8,10 +8,12 @@ class Projects::ContainerRegistryController < Projects::ApplicationController end def destroy + url = namespace_project_container_registry_index_path(project.namespace, project) + if tag.delete - redirect_to namespace_project_container_registry_index_path(project.namespace, project) + redirect_to url else - redirect_to namespace_project_container_registry_index_path(project.namespace, project), alert: 'Failed to remove tag' + redirect_to url, alert: 'Failed to remove tag' end end diff --git a/app/models/project.rb b/app/models/project.rb index 09a214da043..321a1932a20 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -331,6 +331,8 @@ class Project < ActiveRecord::Base end def container_registry_repository + return unless Gitlab.config.registry.enabled + @container_registry_repository ||= begin token = Auth::ContainerRegistryAuthenticationService.full_access_token(path_with_namespace) url = Gitlab.config.registry.api_url @@ -347,9 +349,9 @@ class Project < ActiveRecord::Base end def has_container_registry_tags? - if Gitlab.config.registry.enabled - container_registry_repository.tags.any? - end + return unless container_registry_repository + + container_registry_repository.tags.any? end def commit(id = 'HEAD') -- cgit v1.2.1 From 98a7486ba76eb7235174b4b6f74794c664df29da Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 18 May 2016 12:28:48 -0500 Subject: Verify if registry is enabled in registry --- app/controllers/projects/container_registry_controller.rb | 5 +++++ app/helpers/projects_helper.rb | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/controllers/projects/container_registry_controller.rb b/app/controllers/projects/container_registry_controller.rb index 3648f8894a6..d1f46497207 100644 --- a/app/controllers/projects/container_registry_controller.rb +++ b/app/controllers/projects/container_registry_controller.rb @@ -1,4 +1,5 @@ class Projects::ContainerRegistryController < Projects::ApplicationController + before_action :verify_registry_enabled before_action :authorize_read_container_image! before_action :authorize_update_container_image!, only: [:destroy] layout 'project' @@ -19,6 +20,10 @@ class Projects::ContainerRegistryController < Projects::ApplicationController private + def verify_registry_enabled + render_404 unless Gitlab.config.registry.enabled + end + def container_registry_repository @container_registry_repository ||= project.container_registry_repository end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index b6ba66bf3e5..a140bbc3c67 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -152,7 +152,7 @@ module ProjectsHelper nav_tabs << :builds end - if project.container_registry_repository_url.present? && can?(current_user, :read_container_image, project) + if Gitlab.config.registry.enabled && can?(current_user, :read_container_image, project) nav_tabs << :container_registry end -- cgit v1.2.1