Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
c7bdf25321
commit
829e846dd5
|
|
@ -1,3 +1,3 @@
|
|||
import Vue from 'vue';
|
||||
import createEventHub from '~/helpers/event_hub_factory';
|
||||
|
||||
export default new Vue();
|
||||
export default createEventHub();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
import { __ } from '~/locale';
|
||||
import FilteredSearchTokenKeys from './filtered_search_token_keys';
|
||||
|
||||
const tokenKeys = [
|
||||
{
|
||||
formattedKey: __('Status'),
|
||||
key: 'status',
|
||||
type: 'string',
|
||||
param: 'status',
|
||||
symbol: '',
|
||||
icon: 'messages',
|
||||
tag: 'status',
|
||||
},
|
||||
{
|
||||
formattedKey: __('Type'),
|
||||
key: 'type',
|
||||
type: 'string',
|
||||
param: 'type',
|
||||
symbol: '',
|
||||
icon: 'cube',
|
||||
tag: 'type',
|
||||
},
|
||||
];
|
||||
|
||||
const GroupRunnersFilteredSearchTokenKeys = new FilteredSearchTokenKeys(tokenKeys);
|
||||
|
||||
export default GroupRunnersFilteredSearchTokenKeys;
|
||||
|
|
@ -294,6 +294,14 @@ export default {
|
|||
data-qa-selector="alert_threshold_field"
|
||||
/>
|
||||
</gl-form-group>
|
||||
<gl-form-group
|
||||
v-if="glFeatures.alertRunbooks"
|
||||
:label="s__('PrometheusAlerts|Runbook')"
|
||||
label-for="alert-runbook"
|
||||
data-testid="alertRunbookField"
|
||||
>
|
||||
<gl-form-input id="alert-runbook" :disabled="formDisabled" type="text" />
|
||||
</gl-form-group>
|
||||
</div>
|
||||
<template #modal-ok>
|
||||
<gl-link
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
import Vue from 'vue';
|
||||
import createEventHub from '~/helpers/event_hub_factory';
|
||||
|
||||
export default new Vue();
|
||||
export default createEventHub();
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import initSettingsPanels from '~/settings_panels';
|
|||
import AjaxVariableList from '~/ci_variable_list/ajax_variable_list';
|
||||
import initVariableList from '~/ci_variable_list';
|
||||
import initFilteredSearch from '~/pages/search/init_filtered_search';
|
||||
import AdminRunnersFilteredSearchTokenKeys from '~/filtered_search/admin_runners_filtered_search_token_keys';
|
||||
import GroupRunnersFilteredSearchTokenKeys from '~/filtered_search/group_runners_filtered_search_token_keys';
|
||||
import { FILTERED_SEARCH } from '~/pages/constants';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
|
@ -11,7 +11,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
|
||||
initFilteredSearch({
|
||||
page: FILTERED_SEARCH.ADMIN_RUNNERS,
|
||||
filteredSearchTokenKeys: AdminRunnersFilteredSearchTokenKeys,
|
||||
filteredSearchTokenKeys: GroupRunnersFilteredSearchTokenKeys,
|
||||
anchor: FILTERED_SEARCH.GROUP_RUNNERS_ANCHOR,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -224,12 +224,6 @@
|
|||
font-size: $gl-font-size-large;
|
||||
}
|
||||
}
|
||||
|
||||
.notifications-btn {
|
||||
.fa-bell {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav > .project-buttons {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ module Projects
|
|||
before_action do
|
||||
push_frontend_feature_flag(:prometheus_computed_alerts)
|
||||
push_frontend_feature_flag(:disable_metric_dashboard_refresh_rate)
|
||||
push_frontend_feature_flag(:alert_runbooks)
|
||||
end
|
||||
|
||||
def show
|
||||
|
|
|
|||
|
|
@ -42,8 +42,8 @@ module Projects
|
|||
# Technical Debt: https://gitlab.com/gitlab-org/gitlab/issues/207267
|
||||
# name_regex to be removed when container_expiration_policies is updated
|
||||
# to have both regex columns
|
||||
regex_delete = Gitlab::UntrustedRegexp.new("\\A#{params['name_regex_delete'] || params['name_regex']}\\z")
|
||||
regex_retain = Gitlab::UntrustedRegexp.new("\\A#{params['name_regex_keep']}\\z")
|
||||
regex_delete = ::Gitlab::UntrustedRegexp.new("\\A#{params['name_regex_delete'] || params['name_regex']}\\z")
|
||||
regex_retain = ::Gitlab::UntrustedRegexp.new("\\A#{params['name_regex_keep']}\\z")
|
||||
|
||||
tags.select do |tag|
|
||||
# regex_retain will override any overlapping matches by regex_delete
|
||||
|
|
@ -81,11 +81,11 @@ module Projects
|
|||
def valid_regex?
|
||||
%w(name_regex_delete name_regex name_regex_keep).each do |param_name|
|
||||
regex = params[param_name]
|
||||
Gitlab::UntrustedRegexp.new(regex) unless regex.blank?
|
||||
::Gitlab::UntrustedRegexp.new(regex) unless regex.blank?
|
||||
end
|
||||
true
|
||||
rescue RegexpError => e
|
||||
Gitlab::ErrorTracking.log_exception(e, project_id: project.id)
|
||||
::Gitlab::ErrorTracking.log_exception(e, project_id: project.id)
|
||||
false
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,65 +6,35 @@ module Projects
|
|||
LOG_DATA_BASE = { service_class: self.to_s }.freeze
|
||||
|
||||
def execute(container_repository)
|
||||
@container_repository = 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?
|
||||
@tag_names = params[:tags]
|
||||
return error('not tags specified') if @tag_names.blank?
|
||||
|
||||
smart_delete(container_repository, tag_names)
|
||||
delete_tags
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Delete tags by name with a single DELETE request. This is only supported
|
||||
# by the GitLab Container Registry fork. See
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23325 for details.
|
||||
def fast_delete(container_repository, tag_names)
|
||||
deleted_tags = tag_names.select do |name|
|
||||
container_repository.delete_tag_by_name(name)
|
||||
end
|
||||
|
||||
deleted_tags.any? ? success(deleted: deleted_tags) : error('could not delete tags')
|
||||
def delete_tags
|
||||
delete_service.execute
|
||||
.tap(&method(:log_response))
|
||||
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.
|
||||
# This is used to preverse compatibility with third-party registries that
|
||||
# don't support fast delete.
|
||||
# See https://gitlab.com/gitlab-org/gitlab/issues/15737 for a discussion
|
||||
def slow_delete(container_repository, tag_names)
|
||||
# generates the blobs for the dummy image
|
||||
dummy_manifest = container_repository.client.generate_empty_manifest(container_repository.path)
|
||||
return error('could not generate manifest') if dummy_manifest.nil?
|
||||
|
||||
deleted_tags = replace_tag_manifests(container_repository, dummy_manifest, tag_names)
|
||||
|
||||
# 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 deleted_tags.any? && container_repository.delete_tag_by_digest(deleted_tags.each_value.first)
|
||||
success(deleted: deleted_tags.keys)
|
||||
else
|
||||
error('could not delete tags')
|
||||
end
|
||||
end
|
||||
|
||||
def smart_delete(container_repository, tag_names)
|
||||
def delete_service
|
||||
fast_delete_enabled = Feature.enabled?(:container_registry_fast_tag_delete, default_enabled: true)
|
||||
response = if fast_delete_enabled && container_repository.client.supports_tag_delete?
|
||||
fast_delete(container_repository, tag_names)
|
||||
else
|
||||
slow_delete(container_repository, tag_names)
|
||||
end
|
||||
|
||||
response.tap { |r| log_response(r, container_repository) }
|
||||
if fast_delete_enabled && @container_repository.client.supports_tag_delete?
|
||||
::Projects::ContainerRepository::Gitlab::DeleteTagsService.new(@container_repository, @tag_names)
|
||||
else
|
||||
::Projects::ContainerRepository::ThirdParty::DeleteTagsService.new(@container_repository, @tag_names)
|
||||
end
|
||||
end
|
||||
|
||||
def log_response(response, container_repository)
|
||||
def log_response(response)
|
||||
log_data = LOG_DATA_BASE.merge(
|
||||
container_repository_id: container_repository.id,
|
||||
container_repository_id: @container_repository.id,
|
||||
message: 'deleted tags'
|
||||
)
|
||||
|
||||
|
|
@ -76,26 +46,6 @@ module Projects
|
|||
log_error(log_data)
|
||||
end
|
||||
end
|
||||
|
||||
# update the manifests of the tags with the new dummy image
|
||||
def replace_tag_manifests(container_repository, dummy_manifest, tag_names)
|
||||
deleted_tags = {}
|
||||
|
||||
tag_names.each do |name|
|
||||
digest = container_repository.client.put_tag(container_repository.path, name, dummy_manifest)
|
||||
next unless digest
|
||||
|
||||
deleted_tags[name] = digest
|
||||
end
|
||||
|
||||
# make sure the digests are the same (it should always be)
|
||||
digests = deleted_tags.values.uniq
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(ArgumentError.new('multiple tag digests')) if digests.many?
|
||||
|
||||
deleted_tags
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Projects
|
||||
module ContainerRepository
|
||||
module Gitlab
|
||||
class DeleteTagsService
|
||||
include BaseServiceUtility
|
||||
|
||||
def initialize(container_repository, tag_names)
|
||||
@container_repository = container_repository
|
||||
@tag_names = tag_names
|
||||
end
|
||||
|
||||
# Delete tags by name with a single DELETE request. This is only supported
|
||||
# by the GitLab Container Registry fork. See
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23325 for details.
|
||||
def execute
|
||||
return success(deleted: []) if @tag_names.empty?
|
||||
|
||||
deleted_tags = @tag_names.select do |name|
|
||||
@container_repository.delete_tag_by_name(name)
|
||||
end
|
||||
|
||||
deleted_tags.any? ? success(deleted: deleted_tags) : error('could not delete tags')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
64
app/services/projects/container_repository/third_party/delete_tags_service.rb
vendored
Normal file
64
app/services/projects/container_repository/third_party/delete_tags_service.rb
vendored
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Projects
|
||||
module ContainerRepository
|
||||
module ThirdParty
|
||||
class DeleteTagsService
|
||||
include BaseServiceUtility
|
||||
|
||||
def initialize(container_repository, tag_names)
|
||||
@container_repository = container_repository
|
||||
@tag_names = tag_names
|
||||
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.
|
||||
# This is used to preverse compatibility with third-party registries that
|
||||
# don't support fast delete.
|
||||
# See https://gitlab.com/gitlab-org/gitlab/issues/15737 for a discussion
|
||||
def execute
|
||||
return success(deleted: []) if @tag_names.empty?
|
||||
|
||||
# generates the blobs for the dummy image
|
||||
dummy_manifest = @container_repository.client.generate_empty_manifest(@container_repository.path)
|
||||
return error('could not generate manifest') if dummy_manifest.nil?
|
||||
|
||||
deleted_tags = replace_tag_manifests(dummy_manifest)
|
||||
|
||||
# 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 deleted_tags.any? && @container_repository.delete_tag_by_digest(deleted_tags.each_value.first)
|
||||
success(deleted: deleted_tags.keys)
|
||||
else
|
||||
error('could not delete tags')
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# update the manifests of the tags with the new dummy image
|
||||
def replace_tag_manifests(dummy_manifest)
|
||||
deleted_tags = {}
|
||||
|
||||
@tag_names.each do |name|
|
||||
digest = @container_repository.client.put_tag(@container_repository.path, name, dummy_manifest)
|
||||
next unless digest
|
||||
|
||||
deleted_tags[name] = digest
|
||||
end
|
||||
|
||||
# make sure the digests are the same (it should always be)
|
||||
digests = deleted_tags.values.uniq
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(ArgumentError.new('multiple tag digests')) if digests.many?
|
||||
|
||||
deleted_tags
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -17,16 +17,16 @@
|
|||
.js-notification-toggle-btns
|
||||
%div{ class: ("btn-group" if notification_setting.custom?) }
|
||||
- if notification_setting.custom?
|
||||
%button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn.text-left#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => aria_label, data: { container: "body", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } }
|
||||
= icon("bell", class: "js-notification-loading")
|
||||
%button.dropdown-new.btn.btn-defaul.btn-icon.gl-button.has-tooltip.notifications-btn.text-left#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => aria_label, data: { container: "body", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } }
|
||||
= sprite_icon("notifications", size: 16, css_class: "js-notification-loading")
|
||||
= notification_title(notification_setting.level)
|
||||
%button.btn.dropdown-toggle.d-flex{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
|
||||
= icon('caret-down')
|
||||
.sr-only Toggle dropdown
|
||||
- else
|
||||
%button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => aria_label, data: { container: "body", toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
|
||||
%button.dropdown-new.btn.btn-default.btn-icon.gl-button.has-tooltip.notifications-btn#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => aria_label, data: { container: "body", toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
|
||||
.float-left
|
||||
= icon("bell", class: "js-notification-loading")
|
||||
= sprite_icon("notifications", size: 16, css_class: "js-notification-loading")
|
||||
= notification_title(notification_setting.level)
|
||||
.float-right
|
||||
= icon("caret-down")
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
- if project.archived
|
||||
%span.d-flex.badge.badge-warning
|
||||
%span.d-flex.badge-pill.gl-badge.badge-warning.gl-ml-3
|
||||
= _('archived')
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Replace fa-bell icons with GitLab SVG notifications icon
|
||||
merge_request: 37676
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix bug in group runners filtered search
|
||||
merge_request: 37626
|
||||
author: Arthur de Lapertosa Lisboa
|
||||
type: fixed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Use new badge style for 'archived' project badge
|
||||
merge_request: 38013
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -283,6 +283,7 @@ Settings.sentry['clientside_dsn'] ||= nil
|
|||
# Pages
|
||||
#
|
||||
Settings['pages'] ||= Settingslogic.new({})
|
||||
Settings['pages'] = ::Gitlab::Pages::Settings.new(Settings.pages) # For path access detection https://gitlab.com/gitlab-org/gitlab/-/issues/230702
|
||||
Settings.pages['enabled'] = false if Settings.pages['enabled'].nil?
|
||||
Settings.pages['access_control'] = false if Settings.pages['access_control'].nil?
|
||||
Settings.pages['path'] = Settings.absolute(Settings.pages['path'] || File.join(Settings.shared['path'], "pages"))
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
class Pages
|
||||
module Pages
|
||||
VERSION = File.read(Rails.root.join("GITLAB_PAGES_VERSION")).strip.freeze
|
||||
INTERNAL_API_REQUEST_HEADER = 'Gitlab-Pages-Api-Request'.freeze
|
||||
MAX_SIZE = 1.terabyte
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Pages
|
||||
class Settings < ::SimpleDelegator
|
||||
DiskAccessDenied = Class.new(StandardError)
|
||||
|
||||
def path
|
||||
if ::Gitlab::Runtime.web_server? && ENV['GITLAB_PAGES_DENY_DISK_ACCESS'] == '1'
|
||||
begin
|
||||
raise DiskAccessDenied
|
||||
rescue DiskAccessDenied => ex
|
||||
::Gitlab::ErrorTracking.track_exception(ex)
|
||||
end
|
||||
end
|
||||
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -19033,6 +19033,9 @@ msgstr ""
|
|||
msgid "PrometheusAlerts|Operator"
|
||||
msgstr ""
|
||||
|
||||
msgid "PrometheusAlerts|Runbook"
|
||||
msgstr ""
|
||||
|
||||
msgid "PrometheusAlerts|Select query"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -21153,9 +21156,6 @@ msgstr ""
|
|||
msgid "SecurityReports|No vulnerabilities found"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityReports|No vulnerabilities found for this group"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityReports|No vulnerabilities found for this pipeline"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -21258,9 +21258,6 @@ msgstr ""
|
|||
msgid "SecurityReports|Undo dismiss"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityReports|While it's rare to have no vulnerabilities for your group, it can happen. In any event, we ask that you double check your settings to make sure you've set up your dashboard correctly."
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityReports|While it's rare to have no vulnerabilities for your pipeline, it can happen. In any event, we ask that you double check your settings to make sure all security scanning jobs have passed successfully."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -8,9 +8,12 @@ module QA
|
|||
#
|
||||
module Members
|
||||
def add_member(user, access_level = AccessLevel::DEVELOPER)
|
||||
QA::Runtime::Logger.debug(%Q[Adding user #{user.username} to #{full_path} #{self.class.name}])
|
||||
Support::Retrier.retry_until do
|
||||
QA::Runtime::Logger.debug(%Q[Adding user #{user.username} to #{full_path} #{self.class.name}])
|
||||
|
||||
post Runtime::API::Request.new(api_client, api_members_path).url, { user_id: user.id, access_level: access_level }
|
||||
response = post Runtime::API::Request.new(api_client, api_members_path).url, { user_id: user.id, access_level: access_level }
|
||||
response.code == QA::Support::Api::HTTP_STATUS_CREATED
|
||||
end
|
||||
end
|
||||
|
||||
def remove_member(user)
|
||||
|
|
|
|||
|
|
@ -27,7 +27,10 @@ module QA
|
|||
# The number of selectors should be able to be reduced after
|
||||
# migration to the new spinner is complete.
|
||||
# https://gitlab.com/groups/gitlab-org/-/epics/956
|
||||
Capybara.page.has_no_css?('.gl-spinner, .fa-spinner, .spinner', wait: wait)
|
||||
# retry_on_exception added here due to `StaleElementReferenceError`. See: https://gitlab.com/gitlab-org/gitlab/-/issues/232485
|
||||
Support::Retrier.retry_on_exception do
|
||||
Capybara.page.has_no_css?('.gl-spinner, .fa-spinner, .spinner', wait: wait)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -274,6 +274,8 @@ module Trigger
|
|||
def create_remote_branch!
|
||||
Gitlab.create_branch(downstream_project_path, ref, 'master')
|
||||
puts "=> Remote branch '#{ref}' created"
|
||||
rescue Gitlab::Error::BadRequest
|
||||
puts "=> Remote branch '#{ref}' already exists!"
|
||||
end
|
||||
|
||||
def cancel_latest_pipeline!
|
||||
|
|
@ -292,8 +294,6 @@ module Trigger
|
|||
|
||||
# Cancel the pipeline
|
||||
Gitlab.cancel_pipeline(downstream_project_path, pipeline_id)
|
||||
rescue Gitlab::Error::BadRequest
|
||||
puts "=> Remote branch '#{ref}' already exists!"
|
||||
end
|
||||
|
||||
def display_success_message
|
||||
|
|
|
|||
|
|
@ -450,5 +450,19 @@ RSpec.describe 'Runners' do
|
|||
expect(all(:link, href: group_runner_path(group, runner)).length).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'filtered search' do
|
||||
it 'allows user to search by status and type', :js do
|
||||
visit group_settings_ci_cd_path(group)
|
||||
|
||||
find('.filtered-search').click
|
||||
|
||||
page.within('#js-dropdown-hint') do
|
||||
expect(page).to have_content('Status')
|
||||
expect(page).to have_content('Type')
|
||||
expect(page).not_to have_content('Tag')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ describe('AlertWidgetForm', () => {
|
|||
configuredAlert: metricId,
|
||||
};
|
||||
|
||||
function createComponent(props = {}) {
|
||||
function createComponent(props = {}, featureFlags = {}) {
|
||||
const propsData = {
|
||||
...defaultProps,
|
||||
...props,
|
||||
|
|
@ -37,6 +37,9 @@ describe('AlertWidgetForm', () => {
|
|||
|
||||
wrapper = shallowMount(AlertWidgetForm, {
|
||||
propsData,
|
||||
provide: {
|
||||
glFeatures: featureFlags,
|
||||
},
|
||||
stubs: {
|
||||
GlModal: ModalStub,
|
||||
},
|
||||
|
|
@ -46,6 +49,7 @@ describe('AlertWidgetForm', () => {
|
|||
const modal = () => wrapper.find(ModalStub);
|
||||
const modalTitle = () => modal().attributes('title');
|
||||
const submitButton = () => modal().find(GlLink);
|
||||
const alertRunbookField = () => wrapper.find('[data-testid="alertRunbookField"]');
|
||||
const submitButtonTrackingOpts = () =>
|
||||
JSON.parse(submitButton().attributes('data-tracking-options'));
|
||||
const e = {
|
||||
|
|
@ -217,4 +221,18 @@ describe('AlertWidgetForm', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('alert runbooks feature flag', () => {
|
||||
it('hides the runbook field when the flag is disabled', () => {
|
||||
createComponent(undefined, { alertRunbooks: false });
|
||||
|
||||
expect(alertRunbookField().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('shows the runbook field when the flag is enabled', () => {
|
||||
createComponent(undefined, { alertRunbooks: true });
|
||||
|
||||
expect(alertRunbookField().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do
|
|||
.to eq Gitlab::UntrustedRegexp.new('pattern')
|
||||
end
|
||||
|
||||
it 'is a eager scanner for regexp boundaries' do
|
||||
it 'is an eager scanner for regexp boundaries' do
|
||||
scanner = StringScanner.new('/some .* / pattern/')
|
||||
|
||||
token = described_class.scan(scanner)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Pages::Settings do
|
||||
describe '#path' do
|
||||
subject { described_class.new(settings).path }
|
||||
|
||||
let(:settings) { double(path: 'the path') }
|
||||
|
||||
it { is_expected.to eq('the path') }
|
||||
|
||||
it 'does not track calls' do
|
||||
expect(::Gitlab::ErrorTracking).not_to receive(:track_exception)
|
||||
|
||||
subject
|
||||
end
|
||||
|
||||
context 'when running under a web server' do
|
||||
before do
|
||||
allow(::Gitlab::Runtime).to receive(:web_server?).and_return(true)
|
||||
end
|
||||
|
||||
it { is_expected.to eq('the path') }
|
||||
|
||||
it 'does not track calls' do
|
||||
expect(::Gitlab::ErrorTracking).not_to receive(:track_exception)
|
||||
|
||||
subject
|
||||
end
|
||||
|
||||
context 'with the env var' do
|
||||
before do
|
||||
stub_env('GITLAB_PAGES_DENY_DISK_ACCESS', '1')
|
||||
end
|
||||
|
||||
it { is_expected.to eq('the path') }
|
||||
|
||||
it 'tracks a DiskAccessDenied exception' do
|
||||
expect(::Gitlab::ErrorTracking).to receive(:track_exception)
|
||||
.with(instance_of(described_class::DiskAccessDenied)).and_call_original
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -3,21 +3,15 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Projects::ContainerRepository::DeleteTagsService do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project, :private) }
|
||||
let_it_be(:repository) { create(:container_repository, :root, project: project) }
|
||||
include_context 'container repository delete tags service shared context'
|
||||
|
||||
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))
|
||||
let_it_be(:available_service_classes) do
|
||||
[
|
||||
::Projects::ContainerRepository::Gitlab::DeleteTagsService,
|
||||
::Projects::ContainerRepository::ThirdParty::DeleteTagsService
|
||||
]
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'logging a success response' do
|
||||
|
|
@ -45,8 +39,54 @@ RSpec.describe Projects::ContainerRepository::DeleteTagsService do
|
|||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'calling the correct delete tags service' do |expected_service_class|
|
||||
let(:service_response) { { status: :success, deleted: tags } }
|
||||
let(:excluded_service_class) { available_service_classes.excluding(expected_service_class).first }
|
||||
|
||||
before do
|
||||
service_double = double
|
||||
expect(expected_service_class).to receive(:new).with(repository, tags).and_return(service_double)
|
||||
expect(excluded_service_class).not_to receive(:new)
|
||||
expect(service_double).to receive(:execute).and_return(service_response)
|
||||
end
|
||||
|
||||
it { is_expected.to include(status: :success) }
|
||||
|
||||
it_behaves_like 'logging a success response'
|
||||
|
||||
context 'with an error service response' do
|
||||
let(:service_response) { { status: :error, message: 'could not delete tags' } }
|
||||
|
||||
it { is_expected.to include(status: :error) }
|
||||
|
||||
it_behaves_like 'logging an error response'
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'handling invalid params' do
|
||||
context 'with invalid params' do
|
||||
before do
|
||||
expect(::Projects::ContainerRepository::Gitlab::DeleteTagsService).not_to receive(:new)
|
||||
expect(::Projects::ContainerRepository::ThirdParty::DeleteTagsService).not_to receive(:new)
|
||||
expect_any_instance_of(ContainerRegistry::Client).not_to receive(:delete_repository_tag_by_name)
|
||||
end
|
||||
|
||||
context 'when no params are specified' do
|
||||
let_it_be(:params) { {} }
|
||||
|
||||
it { is_expected.to include(status: :error) }
|
||||
end
|
||||
|
||||
context 'with empty tags' do
|
||||
let_it_be(:tags) { [] }
|
||||
|
||||
it { is_expected.to include(status: :error) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#execute' do
|
||||
let(:tags) { %w[A] }
|
||||
let(:tags) { %w[A Ba] }
|
||||
|
||||
subject { service.execute(repository) }
|
||||
|
||||
|
|
@ -61,247 +101,58 @@ RSpec.describe Projects::ContainerRepository::DeleteTagsService do
|
|||
|
||||
context 'when the registry supports fast delete' do
|
||||
context 'and the feature is enabled' do
|
||||
let_it_be(:project) { create(:project, :private) }
|
||||
let_it_be(:repository) { create(:container_repository, :root, project: project) }
|
||||
|
||||
before do
|
||||
allow(repository.client).to receive(:supports_tag_delete?).and_return(true)
|
||||
end
|
||||
|
||||
context 'with tags to delete' do
|
||||
let_it_be(:tags) { %w[A Ba] }
|
||||
it_behaves_like 'calling the correct delete tags service', ::Projects::ContainerRepository::Gitlab::DeleteTagsService
|
||||
|
||||
it 'deletes the tags by name' do
|
||||
stub_delete_reference_request('A')
|
||||
stub_delete_reference_request('Ba')
|
||||
it_behaves_like 'handling invalid params'
|
||||
|
||||
expect_delete_tag_by_name('A')
|
||||
expect_delete_tag_by_name('Ba')
|
||||
|
||||
is_expected.to include(status: :success)
|
||||
context 'with the real service' do
|
||||
before do
|
||||
stub_delete_reference_requests(tags)
|
||||
expect_delete_tag_by_names(tags)
|
||||
end
|
||||
|
||||
it 'succeeds when tag delete returns 404' do
|
||||
stub_delete_reference_request('A')
|
||||
stub_delete_reference_request('Ba', 404)
|
||||
it { is_expected.to include(status: :success) }
|
||||
|
||||
is_expected.to include(status: :success)
|
||||
end
|
||||
|
||||
it_behaves_like 'logging a success response' do
|
||||
before do
|
||||
stub_delete_reference_request('A')
|
||||
stub_delete_reference_request('Ba')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with failures' do
|
||||
context 'when the delete request fails' do
|
||||
before do
|
||||
stub_delete_reference_request('A', 500)
|
||||
stub_delete_reference_request('Ba', 500)
|
||||
end
|
||||
|
||||
it { is_expected.to include(status: :error) }
|
||||
|
||||
it_behaves_like 'logging an error response'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no params are specified' do
|
||||
let_it_be(:params) { {} }
|
||||
|
||||
it 'does not remove anything' do
|
||||
expect_any_instance_of(ContainerRegistry::Client).not_to receive(:delete_repository_tag_by_name)
|
||||
|
||||
is_expected.to include(status: :error)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with empty tags' do
|
||||
let_it_be(:tags) { [] }
|
||||
|
||||
it 'does not remove anything' do
|
||||
expect_any_instance_of(ContainerRegistry::Client).not_to receive(:delete_repository_tag_by_name)
|
||||
|
||||
is_expected.to include(status: :error)
|
||||
end
|
||||
it_behaves_like 'logging a success response'
|
||||
end
|
||||
end
|
||||
|
||||
context 'and the feature is disabled' do
|
||||
let_it_be(:tags) { %w[A Ba] }
|
||||
|
||||
before do
|
||||
stub_feature_flags(container_registry_fast_tag_delete: false)
|
||||
stub_upload("{\n \"config\": {\n }\n}", 'sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3')
|
||||
stub_put_manifest_request('A')
|
||||
stub_put_manifest_request('Ba')
|
||||
end
|
||||
|
||||
it 'fallbacks to slow delete' do
|
||||
expect(service).not_to receive(:fast_delete)
|
||||
expect(service).to receive(:slow_delete).with(repository, tags).and_call_original
|
||||
it_behaves_like 'calling the correct delete tags service', ::Projects::ContainerRepository::ThirdParty::DeleteTagsService
|
||||
|
||||
expect_delete_tag_by_digest('sha256:dummy')
|
||||
it_behaves_like 'handling invalid params'
|
||||
|
||||
subject
|
||||
end
|
||||
|
||||
it_behaves_like 'logging a success response' do
|
||||
context 'with the real service' do
|
||||
before do
|
||||
allow(service).to receive(:slow_delete).and_call_original
|
||||
stub_upload('sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3')
|
||||
tags.each { |tag| stub_put_manifest_request(tag) }
|
||||
expect_delete_tag_by_digest('sha256:dummy')
|
||||
end
|
||||
|
||||
it { is_expected.to include(status: :success) }
|
||||
|
||||
it_behaves_like 'logging a success response'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the registry does not support fast delete' do
|
||||
let_it_be(:project) { create(:project, :private) }
|
||||
let_it_be(:repository) { create(:container_repository, :root, project: project) }
|
||||
|
||||
before do
|
||||
stub_tag_digest('latest', 'sha256:configA')
|
||||
stub_tag_digest('A', 'sha256:configA')
|
||||
stub_tag_digest('Ba', 'sha256:configB')
|
||||
|
||||
allow(repository.client).to receive(:supports_tag_delete?).and_return(false)
|
||||
end
|
||||
|
||||
context 'when no params are specified' do
|
||||
let_it_be(:params) { {} }
|
||||
it_behaves_like 'calling the correct delete tags service', ::Projects::ContainerRepository::ThirdParty::DeleteTagsService
|
||||
|
||||
it 'does not remove anything' do
|
||||
expect_any_instance_of(ContainerRegistry::Client).not_to receive(:delete_repository_tag_by_digest)
|
||||
|
||||
is_expected.to include(status: :error)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with empty tags' do
|
||||
let_it_be(:tags) { [] }
|
||||
|
||||
it 'does not remove anything' do
|
||||
expect_any_instance_of(ContainerRegistry::Client).not_to receive(:delete_repository_tag_by_digest)
|
||||
|
||||
is_expected.to include(status: :error)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with tags to delete' do
|
||||
let_it_be(:tags) { %w[A Ba] }
|
||||
|
||||
it 'deletes the tags using a dummy image' do
|
||||
stub_upload("{\n \"config\": {\n }\n}", 'sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3')
|
||||
|
||||
stub_put_manifest_request('A')
|
||||
stub_put_manifest_request('Ba')
|
||||
|
||||
expect_delete_tag_by_digest('sha256:dummy')
|
||||
|
||||
is_expected.to include(status: :success)
|
||||
end
|
||||
|
||||
it 'succeeds when tag delete returns 404' do
|
||||
stub_upload("{\n \"config\": {\n }\n}", 'sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3')
|
||||
|
||||
stub_put_manifest_request('A')
|
||||
stub_put_manifest_request('Ba')
|
||||
|
||||
stub_request(:delete, "http://registry.gitlab/v2/#{repository.path}/manifests/sha256:dummy")
|
||||
.to_return(status: 404, body: "", headers: {})
|
||||
|
||||
is_expected.to include(status: :success)
|
||||
end
|
||||
|
||||
it_behaves_like 'logging a success response' do
|
||||
before do
|
||||
stub_upload("{\n \"config\": {\n }\n}", 'sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3')
|
||||
stub_put_manifest_request('A')
|
||||
stub_put_manifest_request('Ba')
|
||||
expect_delete_tag_by_digest('sha256:dummy')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with failures' do
|
||||
context 'when the dummy manifest generation fails' do
|
||||
before do
|
||||
stub_upload("{\n \"config\": {\n }\n}", 'sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3', success: false)
|
||||
end
|
||||
|
||||
it { is_expected.to include(status: :error) }
|
||||
|
||||
it_behaves_like 'logging an error response', message: 'could not generate manifest'
|
||||
end
|
||||
|
||||
context 'when updating the tags fails' do
|
||||
before do
|
||||
stub_upload("{\n \"config\": {\n }\n}", 'sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3')
|
||||
|
||||
stub_put_manifest_request('A', 500)
|
||||
stub_put_manifest_request('Ba', 500)
|
||||
|
||||
stub_request(:delete, "http://registry.gitlab/v2/#{repository.path}/manifests/sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3")
|
||||
.to_return(status: 200, body: "", headers: {})
|
||||
end
|
||||
|
||||
it { is_expected.to include(status: :error) }
|
||||
it_behaves_like 'logging an error response'
|
||||
end
|
||||
end
|
||||
end
|
||||
it_behaves_like 'handling invalid params'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def stub_delete_reference_request(tag, status = 200)
|
||||
stub_request(:delete, "http://registry.gitlab/v2/#{repository.path}/tags/reference/#{tag}")
|
||||
.to_return(status: status, body: '')
|
||||
end
|
||||
|
||||
def stub_put_manifest_request(tag, status = 200, headers = { 'docker-content-digest' => 'sha256:dummy' })
|
||||
stub_request(:put, "http://registry.gitlab/v2/#{repository.path}/manifests/#{tag}")
|
||||
.to_return(status: status, body: '', headers: headers)
|
||||
end
|
||||
|
||||
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, success: true)
|
||||
expect_any_instance_of(ContainerRegistry::Client)
|
||||
.to receive(:upload_blob)
|
||||
.with(repository.path, content, digest) { double(success?: success ) }
|
||||
end
|
||||
|
||||
def expect_delete_tag_by_digest(digest)
|
||||
expect_any_instance_of(ContainerRegistry::Client)
|
||||
.to receive(:delete_repository_tag_by_digest)
|
||||
.with(repository.path, digest) { true }
|
||||
|
||||
expect_any_instance_of(ContainerRegistry::Client)
|
||||
.not_to receive(:delete_repository_tag_by_name)
|
||||
end
|
||||
|
||||
def expect_delete_tag_by_name(name)
|
||||
expect_any_instance_of(ContainerRegistry::Client)
|
||||
.to receive(:delete_repository_tag_by_name)
|
||||
.with(repository.path, name) { true }
|
||||
|
||||
expect_any_instance_of(ContainerRegistry::Client)
|
||||
.not_to receive(:delete_repository_tag_by_digest)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Projects::ContainerRepository::Gitlab::DeleteTagsService do
|
||||
include_context 'container repository delete tags service shared context'
|
||||
|
||||
let(:service) { described_class.new(repository, tags) }
|
||||
|
||||
describe '#execute' do
|
||||
let(:tags) { %w[A Ba] }
|
||||
|
||||
subject { service.execute }
|
||||
|
||||
context 'with tags to delete' do
|
||||
it 'deletes the tags by name' do
|
||||
stub_delete_reference_requests(tags)
|
||||
expect_delete_tag_by_names(tags)
|
||||
|
||||
is_expected.to eq(status: :success, deleted: tags)
|
||||
end
|
||||
|
||||
it 'succeeds when tag delete returns 404' do
|
||||
stub_delete_reference_requests('A' => 200, 'Ba' => 404)
|
||||
|
||||
is_expected.to eq(status: :success, deleted: tags)
|
||||
end
|
||||
|
||||
it 'succeeds when a tag delete returns 500' do
|
||||
stub_delete_reference_requests('A' => 200, 'Ba' => 500)
|
||||
|
||||
is_expected.to eq(status: :success, deleted: ['A'])
|
||||
end
|
||||
|
||||
context 'with failures' do
|
||||
context 'when the delete request fails' do
|
||||
before do
|
||||
stub_delete_reference_requests('A' => 500, 'Ba' => 500)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(status: :error, message: 'could not delete tags') }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with empty tags' do
|
||||
let_it_be(:tags) { [] }
|
||||
|
||||
it 'does not remove anything' do
|
||||
expect_any_instance_of(ContainerRegistry::Client).not_to receive(:delete_repository_tag_by_name)
|
||||
|
||||
is_expected.to eq(status: :success, deleted: [])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
89
spec/services/projects/container_repository/third_party/delete_tags_service_spec.rb
vendored
Normal file
89
spec/services/projects/container_repository/third_party/delete_tags_service_spec.rb
vendored
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Projects::ContainerRepository::ThirdParty::DeleteTagsService do
|
||||
include_context 'container repository delete tags service shared context'
|
||||
|
||||
let(:service) { described_class.new(repository, tags) }
|
||||
|
||||
describe '#execute' do
|
||||
let(:tags) { %w[A Ba] }
|
||||
|
||||
subject { service.execute }
|
||||
|
||||
context 'with tags to delete' do
|
||||
it 'deletes the tags by name' do
|
||||
stub_upload('sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3')
|
||||
|
||||
tags.each { |tag| stub_put_manifest_request(tag) }
|
||||
|
||||
expect_delete_tag_by_digest('sha256:dummy')
|
||||
|
||||
is_expected.to eq(status: :success, deleted: tags)
|
||||
end
|
||||
|
||||
it 'succeeds when tag delete returns 404' do
|
||||
stub_upload('sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3')
|
||||
|
||||
stub_put_manifest_request('A')
|
||||
stub_put_manifest_request('Ba')
|
||||
|
||||
stub_request(:delete, "http://registry.gitlab/v2/#{repository.path}/manifests/sha256:dummy")
|
||||
.to_return(status: 404, body: '', headers: {})
|
||||
|
||||
is_expected.to eq(status: :success, deleted: tags)
|
||||
end
|
||||
|
||||
context 'with failures' do
|
||||
context 'when the dummy manifest generation fails' do
|
||||
before do
|
||||
stub_upload('sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3', success: false)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(status: :error, message: 'could not generate manifest') }
|
||||
end
|
||||
|
||||
context 'when updating tags fails' do
|
||||
before do
|
||||
stub_upload('sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3')
|
||||
|
||||
stub_request(:delete, "http://registry.gitlab/v2/#{repository.path}/manifests/sha256:4435000728ee66e6a80e55637fc22725c256b61de344a2ecdeaac6bdb36e8bc3")
|
||||
.to_return(status: 200, body: '', headers: {})
|
||||
end
|
||||
|
||||
context 'all tag updates fail' do
|
||||
before do
|
||||
stub_put_manifest_request('A', 500, {})
|
||||
stub_put_manifest_request('Ba', 500, {})
|
||||
end
|
||||
|
||||
it { is_expected.to eq(status: :error, message: 'could not delete tags') }
|
||||
end
|
||||
|
||||
context 'a single tag update fails' do
|
||||
before do
|
||||
stub_put_manifest_request('A')
|
||||
stub_put_manifest_request('Ba', 500, {})
|
||||
|
||||
stub_request(:delete, "http://registry.gitlab/v2/#{repository.path}/manifests/sha256:dummy")
|
||||
.to_return(status: 404, body: '', headers: {})
|
||||
end
|
||||
|
||||
it { is_expected.to eq(status: :success, deleted: ['A']) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with empty tags' do
|
||||
let_it_be(:tags) { [] }
|
||||
|
||||
it 'does not remove anything' do
|
||||
expect_any_instance_of(ContainerRegistry::Client).not_to receive(:delete_repository_tag_by_name)
|
||||
|
||||
is_expected.to eq(status: :success, deleted: [])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_context 'container repository delete tags service shared context' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project, reload: true) { create(:project, :private) }
|
||||
let_it_be(:repository) { create(:container_repository, :root, project: project) }
|
||||
|
||||
let(:params) { { tags: tags } }
|
||||
|
||||
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))
|
||||
end
|
||||
|
||||
def stub_delete_reference_request(tag, status = 200)
|
||||
stub_request(:delete, "http://registry.gitlab/v2/#{repository.path}/tags/reference/#{tag}")
|
||||
.to_return(status: status, body: '')
|
||||
end
|
||||
|
||||
def stub_delete_reference_requests(tags)
|
||||
tags = Hash[Array.wrap(tags).map { |tag| [tag, 200] }] unless tags.is_a?(Hash)
|
||||
|
||||
tags.each do |tag, status|
|
||||
stub_request(:delete, "http://registry.gitlab/v2/#{repository.path}/tags/reference/#{tag}")
|
||||
.to_return(status: status, body: '')
|
||||
end
|
||||
end
|
||||
|
||||
def stub_put_manifest_request(tag, status = 200, headers = { 'docker-content-digest' => 'sha256:dummy' })
|
||||
stub_request(:put, "http://registry.gitlab/v2/#{repository.path}/manifests/#{tag}")
|
||||
.to_return(status: status, body: '', headers: headers)
|
||||
end
|
||||
|
||||
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(digest, success: true)
|
||||
content = "{\n \"config\": {\n }\n}"
|
||||
expect_any_instance_of(ContainerRegistry::Client)
|
||||
.to receive(:upload_blob)
|
||||
.with(repository.path, content, digest) { double(success?: success ) }
|
||||
end
|
||||
|
||||
def expect_delete_tag_by_digest(digest)
|
||||
expect_any_instance_of(ContainerRegistry::Client)
|
||||
.to receive(:delete_repository_tag_by_digest)
|
||||
.with(repository.path, digest) { true }
|
||||
|
||||
expect_any_instance_of(ContainerRegistry::Client)
|
||||
.not_to receive(:delete_repository_tag_by_name)
|
||||
end
|
||||
|
||||
def expect_delete_tag_by_names(names)
|
||||
Array.wrap(names).each do |name|
|
||||
expect_any_instance_of(ContainerRegistry::Client)
|
||||
.to receive(:delete_repository_tag_by_name)
|
||||
.with(repository.path, name) { true }
|
||||
|
||||
expect_any_instance_of(ContainerRegistry::Client)
|
||||
.not_to receive(:delete_repository_tag_by_digest)
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Reference in New Issue