diff --git a/app/assets/stylesheets/pages/container_registry.scss b/app/assets/stylesheets/pages/container_registry.scss new file mode 100644 index 00000000000..7d68eae3c97 --- /dev/null +++ b/app/assets/stylesheets/pages/container_registry.scss @@ -0,0 +1,16 @@ +/** + * Container Registry + */ + +.container-image { + border-bottom: 1px solid #f0f0f0; +} + +.container-image-head { + padding: 0px 16px; + line-height: 4; +} + +.table.tags { + margin-bottom: 0px; +} diff --git a/app/controllers/projects/container_registry_controller.rb b/app/controllers/projects/container_registry_controller.rb index d1f46497207..54bcb5f504a 100644 --- a/app/controllers/projects/container_registry_controller.rb +++ b/app/controllers/projects/container_registry_controller.rb @@ -5,17 +5,22 @@ class Projects::ContainerRegistryController < Projects::ApplicationController layout 'project' def index - @tags = container_registry_repository.tags + @images = project.container_images end def destroy url = namespace_project_container_registry_index_path(project.namespace, project) - if tag.delete - redirect_to url + if tag + delete_tag(url) else - redirect_to url, alert: 'Failed to remove tag' + if image.destroy + redirect_to url + else + redirect_to url, alert: 'Failed to remove image' + end end + end private @@ -24,11 +29,20 @@ class Projects::ContainerRegistryController < Projects::ApplicationController render_404 unless Gitlab.config.registry.enabled end - def container_registry_repository - @container_registry_repository ||= project.container_registry_repository + def delete_tag(url) + if tag.delete + image.destroy if image.tags.empty? + redirect_to url + else + redirect_to url, alert: 'Failed to remove tag' + end + end + + def image + @image ||= project.container_images.find_by(id: params[:id]) end def tag - @tag ||= container_registry_repository.tag(params[:id]) + @tag ||= image.tag(params[:tag]) if params[:tag].present? end end diff --git a/app/models/container_image.rb b/app/models/container_image.rb index dcc4a7af629..7721c53a6fc 100644 --- a/app/models/container_image.rb +++ b/app/models/container_image.rb @@ -1,23 +1,28 @@ class ContainerImage < ActiveRecord::Base - belongs_to :container_images_repository + belongs_to :project - delegate :registry, :registry_path_with_namespace, :client, to: :container_images_repository + delegate :container_registry, :container_registry_allowed_paths, + :container_registry_path_with_namespace, to: :project + + delegate :client, to: :container_registry validates :manifest, presence: true + before_destroy :delete_tags + before_validation :update_token, on: :create def update_token - paths = container_images_repository.allowed_paths << name_with_namespace + paths = container_registry_allowed_paths << name_with_namespace token = Auth::ContainerRegistryAuthenticationService.full_access_token(paths) client.update_token(token) end def path - [registry.path, name_with_namespace].compact.join('/') + [container_registry.path, name_with_namespace].compact.join('/') end def name_with_namespace - [registry_path_with_namespace, name].compact.join('/') + [container_registry_path_with_namespace, name].compact.join('/') end def tag(tag) @@ -44,7 +49,10 @@ class ContainerImage < ActiveRecord::Base def delete_tags return unless tags - tags.all?(&:delete) + digests = tags.map {|tag| tag.digest }.to_set + digests.all? do |digest| + client.delete_repository_tag(name_with_namespace, digest) + end end def self.split_namespace(full_path) diff --git a/app/models/container_images_repository.rb b/app/models/container_images_repository.rb deleted file mode 100644 index 99e94d2a6d0..00000000000 --- a/app/models/container_images_repository.rb +++ /dev/null @@ -1,26 +0,0 @@ -class ContainerImagesRepository < ActiveRecord::Base - - belongs_to :project - - has_many :container_images, dependent: :destroy - - delegate :client, to: :registry - - def registry_path_with_namespace - project.path_with_namespace.downcase - end - - def allowed_paths - @allowed_paths ||= [registry_path_with_namespace] + - container_images.map { |i| i.name_with_namespace } - end - - def registry - @registry ||= begin - token = Auth::ContainerRegistryAuthenticationService.full_access_token(allowed_paths) - url = Gitlab.config.registry.api_url - host_port = Gitlab.config.registry.host_port - ContainerRegistry::Registry.new(url, token: token, path: host_port) - end - end -end diff --git a/app/models/project.rb b/app/models/project.rb index 703e24eb79a..afaf2095a4c 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -157,7 +157,7 @@ class Project < ActiveRecord::Base has_one :import_data, dependent: :destroy, class_name: "ProjectImportData" has_one :project_feature, dependent: :destroy has_one :statistics, class_name: 'ProjectStatistics', dependent: :delete - has_one :container_images_repository, dependent: :destroy + has_many :container_images, dependent: :destroy has_many :commit_statuses, dependent: :destroy, foreign_key: :gl_project_id has_many :pipelines, dependent: :destroy, class_name: 'Ci::Pipeline', foreign_key: :gl_project_id @@ -405,15 +405,19 @@ class Project < ActiveRecord::Base path_with_namespace.downcase end - def container_registry_repository + def container_registry_allowed_paths + @container_registry_allowed_paths ||= [container_registry_path_with_namespace] + + container_images.map { |i| i.name_with_namespace } + end + + def container_registry return unless Gitlab.config.registry.enabled - @container_registry_repository ||= begin - token = Auth::ContainerRegistryAuthenticationService.full_access_token(container_registry_path_with_namespace) + @container_registry ||= begin + token = Auth::ContainerRegistryAuthenticationService.full_access_token(container_registry_allowed_paths) url = Gitlab.config.registry.api_url host_port = Gitlab.config.registry.host_port - registry = ContainerRegistry::Registry.new(url, token: token, path: host_port) - registry.repository(container_registry_path_with_namespace) + ContainerRegistry::Registry.new(url, token: token, path: host_port) end end @@ -424,9 +428,9 @@ class Project < ActiveRecord::Base end def has_container_registry_tags? - return unless container_registry_repository + return unless container_images - container_registry_repository.tags.any? + container_images.first.tags.any? end def commit(ref = 'HEAD') diff --git a/app/services/container_images/destroy_service.rb b/app/services/container_images/destroy_service.rb new file mode 100644 index 00000000000..bc5b53fd055 --- /dev/null +++ b/app/services/container_images/destroy_service.rb @@ -0,0 +1,32 @@ +module ContainerImages + class DestroyService < BaseService + + class DestroyError < StandardError; end + + def execute(container_image) + @container_image = container_image + + return false unless can?(current_user, :remove_project, project) + + ContainerImage.transaction do + container_image.destroy! + + unless remove_container_image_tags + raise_error('Failed to remove container image tags. Please try again or contact administrator') + end + end + + true + end + + private + + def raise_error(message) + raise DestroyError.new(message) + end + + def remove_container_image_tags + container_image.delete_tags + end + end +end diff --git a/app/services/container_images_repositories/container_images/create_service.rb b/app/services/container_images_repositories/container_images/create_service.rb deleted file mode 100644 index 0c2c69d5183..00000000000 --- a/app/services/container_images_repositories/container_images/create_service.rb +++ /dev/null @@ -1,16 +0,0 @@ -module ContainerImagesRepositories - module ContainerImages - class CreateService < BaseService - def execute - @container_image = container_images_repository.container_images.create(params) - @container_image if @container_image.valid? - end - - private - - def container_images_repository - @container_images_repository ||= project.container_images_repository - end - end - end -end diff --git a/app/services/container_images_repositories/container_images/destroy_service.rb b/app/services/container_images_repositories/container_images/destroy_service.rb deleted file mode 100644 index 91b8cfeea47..00000000000 --- a/app/services/container_images_repositories/container_images/destroy_service.rb +++ /dev/null @@ -1,11 +0,0 @@ -module ContainerImagesRepositories - module ContainerImages - class DestroyService < BaseService - def execute(container_image) - return false unless container_image - - container_image.destroy - end - end - end -end diff --git a/app/services/container_images_repositories/container_images/push_service.rb b/app/services/container_images_repositories/container_images/push_service.rb deleted file mode 100644 index 2731cf1d52e..00000000000 --- a/app/services/container_images_repositories/container_images/push_service.rb +++ /dev/null @@ -1,26 +0,0 @@ -module ContainerImagesRepositories - module ContainerImages - class PushService < BaseService - def execute(container_image_name, event) - find_or_create_container_image(container_image_name).valid? - end - - private - - def find_or_create_container_image(container_image_name) - options = {name: container_image_name} - container_images.find_by(options) || - ::ContainerImagesRepositories::ContainerImages::CreateService.new(project, - current_user, options).execute - end - - def container_images_repository - @container_images_repository ||= project.container_images_repository - end - - def container_images - @container_images ||= container_images_repository.container_images - end - end - end -end diff --git a/app/services/container_images_repositories/create_service.rb b/app/services/container_images_repositories/create_service.rb deleted file mode 100644 index 7e9dd3abe5f..00000000000 --- a/app/services/container_images_repositories/create_service.rb +++ /dev/null @@ -1,7 +0,0 @@ -module ContainerImagesRepositories - class CreateService < BaseService - def execute - project.container_images_repository || ::ContainerImagesRepository.create(project: project) - end - end -end diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb index 9716a1780a9..ba410b79e8c 100644 --- a/app/services/projects/destroy_service.rb +++ b/app/services/projects/destroy_service.rb @@ -31,10 +31,6 @@ module Projects project.team.truncate project.destroy! - unless remove_registry_tags - raise_error('Failed to remove project container 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 @@ -68,12 +64,6 @@ module Projects end end - def remove_registry_tags - return true unless Gitlab.config.registry.enabled - - project.container_registry_repository.delete_tags - end - def raise_error(message) raise DestroyError.new(message) end diff --git a/app/views/projects/container_registry/_image.html.haml b/app/views/projects/container_registry/_image.html.haml new file mode 100644 index 00000000000..b1d62e34a97 --- /dev/null +++ b/app/views/projects/container_registry/_image.html.haml @@ -0,0 +1,35 @@ +- expanded = false +.container-image.js-toggle-container + .container-image-head + = link_to "#", class: "js-toggle-button" do + - if expanded + = icon("chevron-up") + - else + = icon("chevron-down") + + = escape_once(image.name) + = clipboard_button(clipboard_text: "docker pull #{image.path}") + .controls.hidden-xs.pull-right + = link_to namespace_project_container_registry_path(@project.namespace, @project, image.id), class: 'btn btn-remove has-tooltip', title: "Remove", data: { confirm: "Are you sure?" }, method: :delete do + = icon("trash cred") + + + .container-image-tags.js-toggle-content{ class: ("hide" unless expanded) } + - if image.tags.blank? + %li + .nothing-here-block No tags in Container Registry for this container image. + + - else + .table-holder + %table.table.tags + %thead + %tr + %th Name + %th Image ID + %th Size + %th Created + - if can?(current_user, :update_container_image, @project) + %th + + - image.tags.each do |tag| + = render 'tag', tag: tag diff --git a/app/views/projects/container_registry/_tag.html.haml b/app/views/projects/container_registry/_tag.html.haml index 10822b6184c..00345ec26de 100644 --- a/app/views/projects/container_registry/_tag.html.haml +++ b/app/views/projects/container_registry/_tag.html.haml @@ -25,5 +25,5 @@ - 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 + = link_to namespace_project_container_registry_path(@project.namespace, @project, { id: tag.repository.id, tag: 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 993da27310f..f074ce6be6d 100644 --- a/app/views/projects/container_registry/index.html.haml +++ b/app/views/projects/container_registry/index.html.haml @@ -19,21 +19,9 @@ %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. + - if @images.blank? + .nothing-here-block No container images in Container Registry for this project. - else - .table-holder - %table.table.tags - %thead - %tr - %th Name - %th Image ID - %th Size - %th Created - - if can?(current_user, :update_container_image, @project) - %th - - - @tags.each do |tag| - = render 'tag', tag: tag + - @images.each do |image| + = render 'image', image: image diff --git a/db/migrate/20161029153736_create_container_images_repository.rb b/db/migrate/20161029153736_create_container_images_repository.rb deleted file mode 100644 index d93180b1674..00000000000 --- a/db/migrate/20161029153736_create_container_images_repository.rb +++ /dev/null @@ -1,31 +0,0 @@ -# See http://doc.gitlab.com/ce/development/migration_style_guide.html -# for more information on how to write migrations for GitLab. - -class CreateContainerImagesRepository < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - - # Set this constant to true if this migration requires downtime. - DOWNTIME = false - - # When a migration requires downtime you **must** uncomment the following - # constant and define a short and easy to understand explanation as to why the - # migration requires downtime. - # DOWNTIME_REASON = '' - - # When using the methods "add_concurrent_index" or "add_column_with_default" - # you must disable the use of transactions as these methods can not run in an - # existing transaction. When using "add_concurrent_index" make sure that this - # method is the _only_ method called in the migration, any other changes - # should go in a separate migration. This ensures that upon failure _only_ the - # index creation fails and can be retried or reverted easily. - # - # To disable transactions uncomment the following line and remove these - # comments: - # disable_ddl_transaction! - - def change - create_table :container_images_repositories do |t| - t.integer :project_id, null: false - end - end -end diff --git a/db/migrate/20161031013926_create_container_image.rb b/db/migrate/20161031013926_create_container_image.rb index 94feae280a6..85c0913b8f3 100644 --- a/db/migrate/20161031013926_create_container_image.rb +++ b/db/migrate/20161031013926_create_container_image.rb @@ -25,7 +25,7 @@ class CreateContainerImage < ActiveRecord::Migration def change create_table :container_images do |t| - t.integer :container_images_repository_id + t.integer :project_id t.string :name end end diff --git a/lib/api/registry_events.rb b/lib/api/registry_events.rb index c0473051424..dc7279d2b75 100644 --- a/lib/api/registry_events.rb +++ b/lib/api/registry_events.rb @@ -41,9 +41,19 @@ module API if event['action'] == 'push' and !!event['target']['tag'] namespace, container_image_name = ContainerImage::split_namespace(repository) - ::ContainerImagesRepositories::ContainerImages::PushService.new( - Project::find_with_namespace(namespace), current_user - ).execute(container_image_name, event) + project = Project::find_with_namespace(namespace) + + if project + container_image = project.container_images.find_or_create_by(name: container_image_name) + + if container_image.valid? + puts('Valid!') + else + render_api_error!({ error: "Failed to create container image!" }, 400) + end + else + not_found!('Project') + end end end end