Commit 6cd5b7db authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent b3e0658c
......@@ -350,7 +350,9 @@ quick-rspec geo pg-10 ee:
- $CI_COMMIT_REF_NAME =~ /(^geo[\/-].*|.*-geo$)/
rspec quarantine pg ee:
extends: rspec quarantine pg
extends:
- rspec quarantine pg
- .only-ee
script:
- export NO_KNAPSACK=1 CACHE_CLASSES=true
- scripts/gitaly-test-spawn
......
......@@ -22,7 +22,6 @@ import Board from 'ee_else_ce/boards/components/board';
import BoardSidebar from 'ee_else_ce/boards/components/board_sidebar';
import initNewListDropdown from 'ee_else_ce/boards/components/new_list_dropdown';
import BoardAddIssuesModal from '~/boards/components/modal/index.vue';
import '~/vue_shared/vue_resource_interceptor';
import {
NavigationType,
convertObjectPropsToCamelCase,
......
import Vue from 'vue';
import '../vue_shared/vue_resource_interceptor';
if (process.env.NODE_ENV !== 'production') {
Vue.config.productionTip = false;
......
......@@ -2,7 +2,6 @@
import Vue from 'vue';
import Flash from '../../flash';
import '../../vue_shared/vue_resource_interceptor';
import { __ } from '~/locale';
window.gl = window.gl || {};
......
......@@ -2,7 +2,6 @@ import Vue from 'vue';
import { initSidebarTracking } from 'ee_else_ce/event_tracking/issue_sidebar';
import issuableApp from './components/app.vue';
import { parseIssuableData } from './utils/parse_data';
import '../vue_shared/vue_resource_interceptor';
export default function initIssueableApp() {
return new Vue({
......
......@@ -8,19 +8,19 @@ axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
// Maintain a global counter for active requests
// see: spec/support/wait_for_requests.rb
axios.interceptors.request.use(config => {
window.activeVueResources = window.activeVueResources || 0;
window.activeVueResources += 1;
window.pendingRequests = window.pendingRequests || 0;
window.pendingRequests += 1;
return config;
});
// Remove the global counter
axios.interceptors.response.use(
response => {
window.activeVueResources -= 1;
window.pendingRequests -= 1;
return response;
},
err => {
window.activeVueResources -= 1;
window.pendingRequests -= 1;
return Promise.reject(err);
},
);
......
......@@ -27,8 +27,7 @@ export default {
/**
pageInfo will come from the headers of the API call
in the `.then` clause of the VueResource API call
there should be a function that contructs the pageInfo for this component
there should be a function that constructs the pageInfo for this component
This is an example:
......
import Vue from 'vue';
import VueResource from 'vue-resource';
import csrf from '../lib/utils/csrf';
Vue.use(VueResource);
// Maintain a global counter for active requests
// see: spec/support/wait_for_requests.rb
Vue.http.interceptors.push((request, next) => {
window.activeVueResources = window.activeVueResources || 0;
window.activeVueResources += 1;
next(() => {
window.activeVueResources -= 1;
});
});
// Inject CSRF token and parse headers.
// New Vue Resource version uses Headers, we are expecting a plain object to render pagination
// and polling.
Vue.http.interceptors.push((request, next) => {
request.headers.set(csrf.headerKey, csrf.token);
next(response => {
// Headers object has a `forEach` property that iterates through all values.
const headers = {};
response.headers.forEach((value, key) => {
headers[key] = value;
});
// eslint-disable-next-line no-param-reassign
response.headers = headers;
});
});
......@@ -141,7 +141,7 @@
}
.sidebar-top-level-items > li > a {
min-height: 44px;
min-height: 45px;
}
.fly-out-top-item {
......
......@@ -19,14 +19,12 @@ module Projects
end
def destroy
if tag.delete
respond_to do |format|
format.json { head :no_content }
end
else
respond_to do |format|
format.json { head :bad_request }
end
result = Projects::ContainerRepository::DeleteTagsService
.new(image.project, current_user, tags: [params[:id]])
.execute(image)
respond_to do |format|
format.json { head(result[:status] == :success ? :ok : bad_request) }
end
end
......@@ -42,21 +40,12 @@ module Projects
return
end
@tags = tag_names.map { |tag_name| image.tag(tag_name) }
unless @tags.all? { |tag| tag.valid_name? }
head :bad_request
return
end
success_count = 0
@tags.each do |tag|
if tag.delete
success_count += 1
end
end
result = Projects::ContainerRepository::DeleteTagsService
.new(image.project, current_user, tags: tag_names)
.execute(image)
respond_to do |format|
format.json { head(success_count == @tags.size ? :no_content : :bad_request) }
format.json { head(result[:status] == :success ? :no_content : :bad_request) }
end
end
......@@ -70,10 +59,6 @@ module Projects
@image ||= project.container_repositories
.find(params[:repository_id])
end
def tag
@tag ||= image.tag(params[:id])
end
end
end
end
......@@ -40,7 +40,7 @@ module Projects
return unless tags.count == other_tags.count
# delete all tags
tags.map(&:delete)
tags.map(&:unsafe_delete)
end
def group_by_digest(tags)
......
# frozen_string_literal: true
module Projects
module ContainerRepository
class DeleteTagsService < BaseService
def execute(container_repository)
return error('access denied') unless can?(current_user, :destroy_container_image, project)
tag_names = params[:tags]
return error('not tags specified') if tag_names.blank?
if can_use?
smart_delete(container_repository, tag_names)
else
unsafe_delete(container_repository, tag_names)
end
end
private
def unsafe_delete(container_repository, tag_names)
deleted_tags = tag_names.select do |tag_name|
container_repository.tag(tag_name).unsafe_delete
end
return error('could not delete tags') if deleted_tags.empty?
success(deleted: deleted_tags)
end
# Replace a tag on the registry with a dummy tag.
# This is a hack as the registry doesn't support deleting individual
# tags. This code effectively pushes a dummy image and assigns the tag to it.
# This way when the tag is deleted only the dummy image is affected.
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/21405 for a discussion
def smart_delete(container_repository, tag_names)
# generates the blobs for the dummy image
dummy_manifest = container_repository.client.generate_empty_manifest(container_repository.path)
# update the manifests of the tags with the new dummy image
tag_digests = tag_names.map do |name|
container_repository.client.put_tag(container_repository.path, name, dummy_manifest)
end
# make sure the digests are the same (it should always be)
tag_digests.uniq!
# rubocop: disable CodeReuse/ActiveRecord
Gitlab::Sentry.track_exception(ArgumentError.new('multiple tag digests')) if tag_digests.many?
# deletes the dummy image
# all created tag digests are the same since they all have the same dummy image.
# a single delete is sufficient to remove all tags with it
if container_repository.client.delete_repository_tag(container_repository.path, tag_digests.first)
success(deleted: tag_names)
else
error('could not delete tags')
end
end
def can_use?
Feature.enabled?(:container_registry_smart_delete, project, default_enabled: true)
end
end
end
end
---
title: 'Adds the ability to delete single tags from the docker registry. Fix the issue that caused all related tags and image to be deleted at the same time.'
merge_request: 16886
author:
type: fixed
---
title: Stop sidebar icons from jumping when expanded & collapsed
merge_request: 16971
author:
type: fixed
......@@ -252,8 +252,8 @@ This action does not delete blobs. In order to delete them and recycle disk spac
[run the garbage collection](https://docs.gitlab.com/omnibus/maintenance/README.html#removing-unused-layers-not-referenced-by-manifests).
NOTE: **Note:**
Due to a [Docker Distribution deficiency](https://gitlab.com/gitlab-org/gitlab-foss/issues/21405),
it doesn't remove tags whose manifest is shared by multiple tags.
Since GitLab 12.4, individual tags are deleted.
For more details, see the [discussion](https://gitlab.com/gitlab-org/gitlab/issues/15737).
Examples:
......
......@@ -253,13 +253,13 @@ table_display_block: true
---
```
## Emphasis
### Emphasis
- Use double asterisks (`**`) to mark a word or text in bold (`**bold**`).
- Use underscore (`_`) for text in italics (`_italic_`).
- Use greater than (`>`) for blockquotes.
## Punctuation
### Punctuation
Check the general punctuation rules for the GitLab documentation on the table below.
Check specific punctuation rules for [lists](#lists) below.
......@@ -274,6 +274,20 @@ Check specific punctuation rules for [lists](#lists) below.
| Always add a space before and after dashes when using it in a sentence (for replacing a comma, for example). | _You should try this - or not._ |
| Always use lowercase after a colon. | _Related Issues: a way to create a relationship between issues._ |
### Placeholder text
Often in examples, a writer will provide a command or configuration that is complete apart from
a value specific to the reader.
In these cases, use [`<` and `>`](https://en.wikipedia.org/wiki/Usage_message#Pattern) to call out
where a reader must replace text with their own value.
For example:
```sh
cp <your_source_directory> <your_destination_directory>
```
## Lists
- Always start list items with a capital letter, unless they are parameters or commands
......
......@@ -106,9 +106,15 @@ module API
authorize_destroy_container_image!
validate_tag!
tag.delete
status :ok
result = ::Projects::ContainerRepository::DeleteTagsService
.new(repository.project, current_user, tags: [declared_params[:tag_name]])
.execute(repository)
if result[:status] == :success
status :ok
else
status :bad_request
end
end
end
......
......@@ -2,6 +2,7 @@
require 'faraday'
require 'faraday_middleware'
require 'digest'
module ContainerRegistry
class Client
......@@ -9,6 +10,8 @@ module ContainerRegistry
DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE = 'application/vnd.docker.distribution.manifest.v2+json'
OCI_MANIFEST_V1_TYPE = 'application/vnd.oci.image.manifest.v1+json'
CONTAINER_IMAGE_V1_TYPE = 'application/vnd.docker.container.image.v1+json'
ACCEPTED_TYPES = [DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE, OCI_MANIFEST_V1_TYPE].freeze
# Taken from: FaradayMiddleware::FollowRedirects
......@@ -36,6 +39,45 @@ module ContainerRegistry
faraday.delete("/v2/#{name}/manifests/#{reference}").success?
end
def upload_raw_blob(path, blob)
digest = "sha256:#{Digest::SHA256.hexdigest(blob)}"
if upload_blob(path, blob, digest).success?
[blob, digest]
end
end
def upload_blob(name, content, digest)
upload = faraday.post("/v2/#{name}/blobs/uploads/")
return unless upload.success?
location = URI(upload.headers['location'])
faraday.put("#{location.path}?#{location.query}") do |req|
req.params['digest'] = digest
req.headers['Content-Type'] = 'application/octet-stream'
req.body = content
end
end
def generate_empty_manifest(path)
image = {
config: {}
}
image, image_digest = upload_raw_blob(path, JSON.pretty_generate(image))
return unless image
{
schemaVersion: 2,
mediaType: DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE,
config: {
mediaType: CONTAINER_IMAGE_V1_TYPE,
size: image.size,
digest: image_digest
}
}
end
def blob(name, digest, type = nil)
type ||= 'application/octet-stream'
response_body faraday_blob.get("/v2/#{name}/blobs/#{digest}", nil, 'Accept' => type), allow_redirect: true
......@@ -45,6 +87,15 @@ module ContainerRegistry
faraday.delete("/v2/#{name}/blobs/#{digest}").success?
end
def put_tag(name, reference, manifest)
response = faraday.put("/v2/#{name}/manifests/#{reference}") do |req|
req.headers['Content-Type'] = DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE
req.body = JSON.pretty_generate(manifest)
end
response.headers['docker-content-digest'] if response.success?
end
private
def initialize_connection(conn, options)
......
......@@ -98,6 +98,10 @@ module ContainerRegistry
end
end
def put(digests)
repository.client.put_tag(repository.path, name, digests)
end
# rubocop: disable CodeReuse/ActiveRecord
def total_size
return unless layers
......@@ -106,7 +110,10 @@ module ContainerRegistry
end
# rubocop: enable CodeReuse/ActiveRecord
def delete
# Deletes the image associated with this tag
# Note this will delete the image and all tags associated with it.
# Consider using DeleteTagsService instead.
def unsafe_delete
return unless digest
client.delete_repository_tag(repository.path, digest)
......
This diff is collapsed.
desc 'Checks if the branch would apply cleanly to EE'
task ee_compat_check: :environment do
Rake::Task['gitlab:dev:ee_compat_check'].invoke
end
namespace :gitlab do
namespace :dev do
desc 'Checks if the branch would apply cleanly to EE'
task :ee_compat_check, [:branch] => :environment do |_, args|
opts =
if ENV['CI']
{
ce_project_url: ENV['CI_PROJECT_URL'],
branch: ENV['CI_COMMIT_REF_NAME'],
job_id: ENV['CI_JOB_ID']
}
else
unless args[:branch]
puts "Must specify a branch as an argument".color(:red)
exit 1
end
args
end
if File.basename(Rails.root) == 'gitlab'
puts "Skipping EE projects"
exit 0
elsif Gitlab::EeCompatCheck.new(opts || {}).check
exit 0
else
exit 1
end
end
end
end
......@@ -28,14 +28,23 @@ unless Rails.env.production?
task :all do
status = 0
%w[
tasks = %w[
config_lint
lint:haml
scss_lint
gettext:lint
gettext:updated_check
lint:static_verification
].each do |task|
]
if Gitlab.ee?
# This task will fail on CE installations (e.g. gitlab-org/gitlab-foss)
# since it will detect strings in the locale files that do not exist in
# the source files. To work around this we will only enable this task on
# EE installations.
tasks << 'gettext:updated_check'
end
tasks.each do |task|
pid = Process.fork do
puts "*** Running rake task: #{task} ***"
......
This diff is collapsed.
......@@ -37,10 +37,6 @@ tasks = [
%w[scripts/lint-rugged]
]
if Gitlab.ee?
tasks.unshift(%w[ruby -rbundler/setup scripts/ee_specific_check/ee_specific_check.rb])
end
static_analysis = Gitlab::Popen::Runner.new
static_analysis.run(tasks) do |cmd, &run|
......
......@@ -10,6 +10,8 @@ describe Projects::Registry::TagsController do
create(:container_repository, name: 'image', project: project)
end
let(:service) { double('service') }
before do
sign_in(user)
stub_container_registry_config(enabled: true)
......@@ -84,17 +86,17 @@ describe Projects::Registry::TagsController do
context 'when there is matching tag present' do
before do
stub_container_registry_tags(repository: repository.path, tags: %w[rc1 test.])
stub_container_registry_tags(repository: repository.path, tags: %w[rc1], with_manifest: true)
end
it 'makes it possible to delete regular tag' do
expect_any_instance_of(ContainerRegistry::Tag).to receive(:delete)
expect_delete_tags(%w[rc1])
destroy_tag('rc1')
end
it 'makes it possible to delete a tag that ends with a dot' do
expect_any_instance_of(ContainerRegistry::Tag).to receive(:delete)
expect_delete_tags(%w[test.])
destroy_tag('test.')
end
......@@ -125,11 +127,12 @@ describe Projects::Registry::TagsController do
stub_container_registry_tags(repository: repository.path, tags: %w[rc1 test.])
end
let(:tags) { %w[tc1 test.] }
it 'makes it possible to delete tags in bulk' do
allow_any_instance_of(ContainerRegistry::Tag).to receive(:delete) { |*args| ContainerRegistry::Tag.delete(*args) }
expect(ContainerRegistry::Tag).to receive(:delete).exactly(2).times
expect_delete_tags(tags)
bulk_destroy_tags(['rc1', 'test.'])
bulk_destroy_tags(tags)
end
end
end
......@@ -146,4 +149,9 @@ describe Projects::Registry::TagsController do
format: :json
end
end
def expect_delete_tags(tags, status = :success)
expect(service).to receive(:execute).with(repository) { { status: status } }
expect(Projects::ContainerRepository::DeleteTagsService).to receive(:new).with(repository.project, user, tags: tags) { service }
end
end
......@@ -53,7 +53,9 @@ describe 'Container Registry', :js do
find('.js-toggle-repo').click
wait_for_requests
expect_any_instance_of(ContainerRegistry::Tag).to receive(:delete).and_return(true)
service = double('service')
expect(service).to receive(:execute).with(container_repository) { { status: :success } }
expect(Projects::ContainerRepository::DeleteTagsService).to receive(:new).with(container_repository.project, user, tags: ['latest']) { service }
click_on(class: 'js-delete-registry-row', visible: false)
expect(find('.modal .modal-title')).to have_content 'Remove image'
......
......@@ -470,9 +470,6 @@ describe 'Issues' do
expect(page).to have_content 'None'
end
# wait_for_requests does not work with vue-resource at the moment
sleep 1
expect(issue.reload.assignees).to be_empty
end
......
// eslint-disable-next-line import/prefer-default-export
export const headersInterceptor = (request, next) => {
next(response => {
const headers = {};
response.headers.forEach((value, key) => {
headers[key] = value;
});
// eslint-disable-next-line no-param-reassign
response.headers = headers;
});
};
// eslint-disable-next-line import/prefer-default-export
export const headersInterceptor = (request, next) => {
next(response => {
const headers = {};
response.headers.forEach((value, key) => {
headers[key] = value;
});
// eslint-disable-next-line no-param-reassign
response.headers = headers;
});
};
......@@ -7,7 +7,6 @@ import 'core-js/features/set-immediate';
import 'vendor/jasmine-jquery';
import '~/commons';
import Vue from 'vue';
import VueResource from 'vue-resource';
import Translate from '~/vue_shared/translate';
import jasmineDiff from 'jasmine-diff';
import { config as testUtilsConfig } from '@vue/test-utils';
......@@ -46,7 +45,6 @@ Vue.config.errorHandler = function(err) {
fail(err);
};
Vue.use(VueResource);
Vue.use(Translate);
// enable test fixtures
......@@ -102,13 +100,6 @@ afterEach(__rewire_reset_all__); // eslint-disable-line
// to run our unit tests.
beforeEach(done => done());
const builtinVueHttpInterceptors = Vue.http.interceptors.slice();
beforeEach(() => {
// restore interceptors so we have no remaining ones from previous tests
Vue.http.interceptors = builtinVueHttpInterceptors.slice();
});
let longRunningTestTimeoutHandle;
beforeEach(done => {
......
......@@ -73,4 +73,69 @@ describe ContainerRegistry::Client do
expect(response).to eq('Successfully redirected')
end
end
def stub_upload(path, content, digest, status = 200)
stub_request(:post, "http://container-registry/v2/#{path}/blobs/uploads/")
.to_return(status: status, body: "", headers: { 'location' => 'http://container-registry/next_upload?id=someid' })
stub_request(:put, "http://container-registry/next_upload?digest=#{digest}&id=someid")
.with(body: content)
.to_return(status: status, body: "", headers: {})
end
describe '#upload_blob' do
subject { client.upload_blob('path', 'content', 'sha256:123') }
context 'with successful uploads' do
it 'starts the upload and posts the blob' do
stub_upload('path', 'content', 'sha256:123')
expect(subject).to be_success
end
end
context 'with a failed upload' do
before do
stub_upload('path', 'content', 'sha256:123', 400)
end
it 'returns nil' do
expect(subject).to be nil
end
end
end
describe '#generate_empty_manifest' do
subject { client.generate_empty_manifest('path') }
let(:result_manifest) do
{
schemaVersion: 2,
mediaType: 'application/vnd.docker.distribution.manifest.v2+json',
config: {
mediaType: 'application/vnd.docker.container.image.v1+json',
size: 21,
digest: 'sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3'
}
}
end
it 'uploads a random image and returns the manifest' do
stub_upload('path', "{\n \"config\": {\n }\n}", 'sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3')
expect(subject).to eq(result_manifest)
end
end
describe '#put_tag' do
subject { client.put_tag('path', 'tagA', { foo: :bar }) }
it 'uploads the manifest and returns the digest' do
stub_request(:put, "http://container-registry/v2/path/manifests/tagA")
.with(body: "{\n \"foo\": \"bar\"\n}")
.to_return(status: 200, body: "", headers: { 'docker-content-digest' => 'sha256:123' })
expect(subject).to eq 'sha256:123'
end
end
end
......@@ -179,7 +179,7 @@ describe ContainerRegistry::Tag do
end
end
describe '#delete' do
describe '#unsafe_delete' do
before do
stub_request(:delete, 'http://registry.gitlab/v2/group/test/manifests/sha256:digest')
.with(headers: headers)
......@@ -187,7 +187,7 @@ describe ContainerRegistry::Tag do
end
it 'correctly deletes the tag' do
expect(tag.delete).to be_truthy
expect(tag.unsafe_delete).to be_truthy
end
end
end
......
......@@ -150,7 +150,7 @@ describe API::ProjectContainerRepositories do
expect(response).to have_gitlab_http_status(:accepted)
end
context 'called multiple times in one hour' do
context 'called multiple times in one hour', :clean_gitlab_redis_shared_state do
it 'returns 400 with an error message' do
stub_exclusive_lease_taken(lease_key, timeout: 1.hour)
subject
......@@ -202,6 +202,8 @@ describe API::ProjectContainerRepositories do
end
describe 'DELETE /projects/:id/registry/repositories/:repository_id/tags/:tag_name' do
let(:service) { double('service') }
subject { delete api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags/rootA", api_user) }
it_behaves_like 'rejected container repository access', :reporter, :forbidden
......@@ -210,18 +212,34 @@ describe API::ProjectContainerRepositories do
context 'for developer' do
let(:api_user) { developer }
before do
stub_container_registry_tags(repository: root_repository.path, tags: %w(rootA), with_manifest: true)
context 'when there are multiple tags' do
before do
stub_container_registry_tags(repository: root_repository.path, tags: %w(rootA rootB), with_manifest: true)
end
it 'properly removes tag' do
expect(service).to receive(:execute).with(root_repository) { { status: :success } }
expect(Projects::ContainerRepository::DeleteTagsService).to receive(:new).with(root_repository.project, api_user, tags: %w[rootA]) { service }
subject
expect(response).to have_gitlab_http_status(:ok)
end
end
it 'properly removes tag' do
expect_any_instance_of(ContainerRegistry::Client)
.to receive(:delete_repository_tag).with(root_repository.path,
'sha256:4c8e63ca4cb663ce6c688cb06f1c372b088dac5b6d7ad7d49cd620d85cf72a15')
context 'when there\'s only one tag' do
before do
stub_container_registry_tags(repository: root_repository.path, tags: %w(rootA), with_manifest: true)
end
subject
it 'properly removes tag' do
expect(service).to receive(:execute).with(root_repository) { { status: :success } }
expect(Projects::ContainerRepository::DeleteTagsService).to receive(:new).with(root_repository.project, api_user, tags: %w[rootA]) { service }
expect(response).to have_gitlab_http_status(:ok)
subject
expect(response).to have_gitlab_http_status(:ok)
end
end
end
end
......
# frozen_string_literal: true
require 'spec_helper'
describe Projects::ContainerRepository::DeleteTagsService do
set(:user) { create(:user) }
set(:project) { create(:project, :private) }
set(:repository) { create(:container_repository, :root, project: project) }
let(:params) { { tags: tags } }
let(:service) { described_class.new(project, user, params) }
before do
stub_container_registry_config(enabled: true,
api_url: 'http://registry.gitlab',
host_port: 'registry.gitlab')
stub_container_registry_tags(
repository: repository.path,
tags: %w(latest A Ba Bb C D E))
stub_tag_digest('latest', 'sha256:configA')
stub_tag_digest('A', 'sha256:configA')
stub_tag_digest('Ba', 'sha256:configB')
end
describe '#execute' do
let(:tags) { %w[A] }
subject { service.execute(repository) }
context 'without permissions' do
it { is_expected.to include(status: :error) }
end
context 'with permissions' do
before do
project.add_developer(user)
end
context 'when no params are specified' do
let(:params) { {} }
it 'does not remove anything' do
expect_any_instance_of(ContainerRegistry::Client).not_to receive(:delete_repository_tag)
is_expected.to include(status: :error)
end
end
context 'with empty tags' do
let(:tags) { [] }
it 'does not remove anything' do
expect_any_instance_of(ContainerRegistry::Client).not_to receive(:delete_repository_tag)
is_expected.to include(status: :error)
end
end
context 'with dummy tags disabled' do
let(:tags) { %w[A Ba] }
before do
stub_feature_flags(container_registry_smart_delete: false)
end
it 'deletes tags one by one' do
expect_delete_tag('sha256:configA')
expect_delete_tag('sha256:configB')
is_expected.to include(status: :success)
end
end
context 'with dummy tags enabled' do
let(:tags) { %w[A Ba] }
it 'deletes the tags using a dummy image' do
stub_upload("{\n \"config\": {\n }\n}", 'sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3')
stub_request(:put, "http://registry.gitlab/v2/#{repository.path}/manifests/A")
.to_return(status: 200, body: "", headers: { 'docker-content-digest' => 'sha256:dummy' })
stub_request(:put, "http://registry.gitlab/v2/#{repository.path}/manifests/Ba")
.to_return(status: 200, body: "", headers: { 'docker-content-digest' => 'sha256:dummy' })
expect_delete_tag('sha256:dummy')
is_expected.to include(status: :success)
end
end
end
end
private
def stub_tag_digest(tag, digest)
stub_request(:head, "http://registry.gitlab/v2/#{repository.path}/manifests/#{tag}")
.to_return(status: 200, body: "", headers: { 'docker-content-digest' => digest })
end
def stub_digest_config(digest, created_at)
allow_any_instance_of(ContainerRegistry::Client)
.to receive(:blob)
.with(repository.path, digest, nil) do
{ 'created' => created_at.to_datetime.rfc3339 }.to_json if created_at
end
end
def stub_upload(content, digest)
expect_any_instance_of(ContainerRegistry::Client)
.to receive(:upload_blob)
.with(repository.path, content, digest) { double(success?: true ) }
end
def expect_delete_tag(digest)
expect_any_instance_of(ContainerRegistry::Client)
.to receive(:delete_repository_tag)
.with(repository.path, digest) { true }
end
end
......@@ -49,11 +49,11 @@ module WaitForRequests
return true unless javascript_test?
finished_all_ajax_requests? &&
finished_all_vue_resource_requests?
finished_all_axios_requests?
end
def finished_all_vue_resource_requests?
Capybara.page.evaluate_script('window.activeVueResources || 0').zero?
def finished_all_axios_requests?
Capybara.page.evaluate_script('window.pendingRequests || 0').zero?
end
def finished_all_ajax_requests?
......
......@@ -1131,7 +1131,6 @@ vue-apollo,3.0.0-beta.25,ISC
vue-functional-data-merge,2.0.6,MIT
vue-hot-reload-api,2.3.0,MIT
vue-loader,15.4.2,MIT
vue-resource,1.5.0,MIT
vue-router,3.0.1,MIT
vue-style-loader,4.1.0,MIT
vue-template-compiler,2.5.17,MIT
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment