Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-07-14 18:08:31 +00:00
parent b689f37135
commit 06ac12d53c
93 changed files with 658 additions and 369 deletions

View File

@ -2443,17 +2443,6 @@ Gitlab/FeatureAvailableUsage:
# WIP See https://gitlab.com/gitlab-org/gitlab/-/issues/327490
Style/RegexpLiteralMixedPreserve:
Exclude:
- 'app/controllers/projects/repositories_controller.rb'
- 'app/helpers/ci/variables_helper.rb'
- 'app/models/alert_management/alert.rb'
- 'app/models/application_setting.rb'
- 'app/models/blob_viewer/go_mod.rb'
- 'app/models/concerns/ci/maskable.rb'
- 'app/models/operations/feature_flag.rb'
- 'app/models/packages/go/module.rb'
- 'app/services/packages/conan/search_service.rb'
- 'app/services/projects/update_remote_mirror_service.rb'
- 'config/initializers/rspec_profiling.rb'
- 'ee/app/models/status_page/project_setting.rb'
- 'ee/app/presenters/vulnerability_presenter.rb'
- 'ee/lib/api/geo_nodes.rb'

View File

@ -1 +1 @@
40511f7a14ded77c826809d054d740a66e1c106f
9cde7b7d1ecc68f5ca3b68df5ad32e6b0bc9d661

View File

@ -20,3 +20,9 @@ export const I18N_USER_ACTIONS = {
ban: s__('AdminUsers|Ban user'),
unban: s__('AdminUsers|Unban user'),
};
export const CONFIRM_DELETE_BUTTON_SELECTOR = '.js-delete-user-modal-button';
export const MODAL_TEXTS_CONTAINER_SELECTOR = '#js-modal-texts';
export const MODAL_MANAGER_SELECTOR = '#js-delete-user-modal';

View File

@ -2,7 +2,14 @@ import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import csrf from '~/lib/utils/csrf';
import AdminUsersApp from './components/app.vue';
import ModalManager from './components/modals/user_modal_manager.vue';
import {
CONFIRM_DELETE_BUTTON_SELECTOR,
MODAL_TEXTS_CONTAINER_SELECTOR,
MODAL_MANAGER_SELECTOR,
} from './constants';
Vue.use(VueApollo);
@ -29,3 +36,45 @@ export const initAdminUsersApp = (el = document.querySelector('#js-admin-users-a
}),
});
};
export const initDeleteUserModals = () => {
const modalsMountElement = document.querySelector(MODAL_TEXTS_CONTAINER_SELECTOR);
if (!modalsMountElement) {
return;
}
const modalConfiguration = Array.from(modalsMountElement.children).reduce((accumulator, node) => {
const { modal, ...config } = node.dataset;
return {
...accumulator,
[modal]: {
title: node.dataset.title,
...config,
content: node.innerHTML,
},
};
}, {});
// eslint-disable-next-line no-new
new Vue({
el: MODAL_MANAGER_SELECTOR,
functional: true,
methods: {
show(...args) {
this.$refs.manager.show(...args);
},
},
render(h) {
return h(ModalManager, {
ref: 'manager',
props: {
selector: CONFIRM_DELETE_BUTTON_SELECTOR,
modalConfiguration,
csrfToken: csrf.token,
},
});
},
});
};

View File

@ -134,7 +134,7 @@ export default {
labelFilterParam: {
type: String,
required: false,
default: null,
default: undefined,
},
isManualOrdering: {
type: Boolean,

View File

@ -1,3 +1,5 @@
import { initExpiresAtField } from '~/access_tokens';
import initConfirmModal from '~/confirm_modal';
initExpiresAtField();
initConfirmModal();

View File

@ -1,64 +1,6 @@
import Vue from 'vue';
import { initAdminUsersApp } from '~/admin/users';
import { initAdminUsersApp, initDeleteUserModals } from '~/admin/users';
import initConfirmModal from '~/confirm_modal';
import csrf from '~/lib/utils/csrf';
import Translate from '~/vue_shared/translate';
import ModalManager from './components/user_modal_manager.vue';
const CONFIRM_DELETE_BUTTON_SELECTOR = '.js-delete-user-modal-button';
const MODAL_TEXTS_CONTAINER_SELECTOR = '#js-modal-texts';
const MODAL_MANAGER_SELECTOR = '#js-delete-user-modal';
function loadModalsConfigurationFromHtml(modalsElement) {
const modalsConfiguration = {};
if (!modalsElement) {
/* eslint-disable-next-line @gitlab/require-i18n-strings */
throw new Error('Modals content element not found!');
}
Array.from(modalsElement.children).forEach((node) => {
const { modal, ...config } = node.dataset;
modalsConfiguration[modal] = {
title: node.dataset.title,
...config,
content: node.innerHTML,
};
});
return modalsConfiguration;
}
document.addEventListener('DOMContentLoaded', () => {
Vue.use(Translate);
initAdminUsersApp();
const modalConfiguration = loadModalsConfigurationFromHtml(
document.querySelector(MODAL_TEXTS_CONTAINER_SELECTOR),
);
// eslint-disable-next-line no-new
new Vue({
el: MODAL_MANAGER_SELECTOR,
functional: true,
methods: {
show(...args) {
this.$refs.manager.show(...args);
},
},
render(h) {
return h(ModalManager, {
ref: 'manager',
props: {
selector: CONFIRM_DELETE_BUTTON_SELECTOR,
modalConfiguration,
csrfToken: csrf.token,
},
});
},
});
initConfirmModal();
});
initAdminUsersApp();
initDeleteUserModals();
initConfirmModal();

View File

@ -70,7 +70,7 @@ export default {
);
},
showParentRow() {
return !this.isLoading && ['', '/'].indexOf(this.path) === -1;
return ['', '/'].indexOf(this.path) === -1;
},
},
methods: {

View File

@ -32,7 +32,12 @@ export const fetchProjects = ({ commit, state }, search) => {
if (groupId) {
// TODO (https://gitlab.com/gitlab-org/gitlab/-/issues/323331): For errors `createFlash` is called twice; in `callback` and in `Api.groupProjects`
Api.groupProjects(groupId, search, { order_by: 'similarity' }, callback);
Api.groupProjects(
groupId,
search,
{ order_by: 'similarity', with_shared: false, include_subgroups: true },
callback,
);
} else {
// The .catch() is due to the API method not handling a rejection properly
Api.projects(search, { order_by: 'id' }, callback).catch(() => {

View File

@ -117,7 +117,7 @@ class Projects::RepositoriesController < Projects::ApplicationController
# from Redis.
def extract_ref_and_filename(id)
path = id.strip
data = path.match(/(.*)\/(.*)/)
data = path.match(%r{(.*)/(.*)})
if data
[data[1], data[2]]

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true
class Projects::ServiceHookLogsController < Projects::HookLogsController
extend Gitlab::Utils::Override
before_action :integration, only: [:show, :retry]
def retry
@ -10,11 +12,12 @@ class Projects::ServiceHookLogsController < Projects::HookLogsController
private
def hook
@hook ||= integration.service_hook
end
def integration
@integration ||= @project.find_or_initialize_integration(params[:service_id])
end
override :hook
def hook
@hook ||= integration.service_hook || not_found
end
end

View File

@ -48,7 +48,7 @@ module Ci
end
def ci_variable_maskable_regex
Ci::Maskable::REGEX.inspect.sub('\\A', '^').sub('\\z', '$').sub(/^\//, '').sub(/\/[a-z]*$/, '').gsub('\/', '/')
Ci::Maskable::REGEX.inspect.sub('\\A', '^').sub('\\z', '$').sub(%r{^/}, '').sub(%r{/[a-z]*$}, '').gsub('\/', '/')
end
end
end

View File

@ -49,7 +49,7 @@ module Routing
def gitlab_raw_snippet_blob_url(snippet, path, ref = nil, **options)
params = {
snippet_id: snippet,
ref: ref || snippet.repository.root_ref,
ref: ref || snippet.default_branch,
path: path
}

View File

@ -210,7 +210,7 @@ module AlertManagement
end
def self.link_reference_pattern
@link_reference_pattern ||= super("alert_management", /(?<alert>\d+)\/details(\#)?/)
@link_reference_pattern ||= super("alert_management", %r{(?<alert>\d+)/details(\#)?})
end
def self.reference_valid?(reference)

View File

@ -293,7 +293,7 @@ class ApplicationSetting < ApplicationRecord
validates :user_default_internal_regex, js_regex: true, allow_nil: true
validates :personal_access_token_prefix,
format: { with: /\A[a-zA-Z0-9_+=\/@:.-]+\z/,
format: { with: %r{\A[a-zA-Z0-9_+=/@:.-]+\z},
message: _("can contain only letters of the Base64 alphabet (RFC4648) with the addition of '@', ':' and '.'") },
length: { maximum: 20, message: _('is too long (maximum is %{count} characters)') },
allow_blank: true
@ -590,7 +590,7 @@ class ApplicationSetting < ApplicationRecord
end
def sourcegraph_url_is_com?
!!(sourcegraph_url =~ /\Ahttps:\/\/(www\.)?sourcegraph\.com/)
!!(sourcegraph_url =~ %r{\Ahttps://(www\.)?sourcegraph\.com})
end
def instance_review_permitted?

View File

@ -5,13 +5,13 @@ module BlobViewer
include ServerSide
include Gitlab::Utils::StrongMemoize
MODULE_REGEX = /
MODULE_REGEX = %r{
\A (?# beginning of file)
module\s+ (?# module directive)
(?<name>.*?) (?# module name)
\s*(?:\/\/.*)? (?# comment)
\s*(?://.*)? (?# comment)
(?:\n|\z) (?# newline or end of file)
/x.freeze
}x.freeze
self.file_types = %i(go_mod go_sum)

View File

@ -11,7 +11,7 @@ module Ci
# * Minimal length of 8 characters
# * Characters must be from the Base64 alphabet (RFC4648) with the addition of '@', ':', '.', and '~'
# * Absolutely no fun is allowed
REGEX = /\A[a-zA-Z0-9_+=\/@:.~-]{8,}\z/.freeze
REGEX = %r{\A[a-zA-Z0-9_+=/@:.~-]{8,}\z}.freeze
included do
validates :masked, inclusion: { in: [true, false] }

View File

@ -0,0 +1,36 @@
# frozen_string_literal: true
module Integrations
module HasWebHook
extend ActiveSupport::Concern
included do
after_save :update_web_hook!, if: :activated?
end
# Return the URL to be used for the webhook.
def hook_url
raise NotImplementedError
end
# Return whether the webhook should use SSL verification.
def hook_ssl_verification
true
end
# Create or update the webhook, raising an exception if it cannot be saved.
def update_web_hook!
hook = service_hook || build_service_hook
hook.url = hook_url if hook.url != hook_url # avoid reencryption
hook.enable_ssl_verification = hook_ssl_verification
hook.save! if hook.changed?
hook
end
# Execute the webhook, creating it if necessary.
def execute_web_hook!(*args)
update_web_hook!
service_hook.execute(*args)
end
end
end

View File

@ -18,14 +18,8 @@ module Integrations
attr_accessor :response
after_save :compose_service_hook, if: :activated?
before_update :reset_password
def compose_service_hook
hook = service_hook || build_service_hook
hook.save
end
def reset_password
if bamboo_url_changed? && !password_touched?
self.password = nil

View File

@ -4,7 +4,9 @@ require "addressable/uri"
module Integrations
class Buildkite < BaseCi
include HasWebHook
include ReactiveService
extend Gitlab::Utils::Override
ENDPOINT = "https://buildkite.com"
@ -13,8 +15,6 @@ module Integrations
validates :project_url, presence: true, public_url: true, if: :activated?
validates :token, presence: true, if: :activated?
after_save :compose_service_hook, if: :activated?
def self.supported_events
%w(push merge_request tag_push)
end
@ -35,21 +35,15 @@ module Integrations
self.properties.delete('enable_ssl_verification') # Remove unused key
end
def webhook_url
override :hook_url
def hook_url
"#{buildkite_endpoint('webhook')}/deliver/#{webhook_token}"
end
def compose_service_hook
hook = service_hook || build_service_hook
hook.url = webhook_url
hook.enable_ssl_verification = true
hook.save
end
def execute(data)
return unless supported_events.include?(data[:object_kind])
service_hook.execute(data)
execute_web_hook!(data)
end
def commit_status(sha, ref)

View File

@ -2,6 +2,9 @@
module Integrations
class Datadog < Integration
include HasWebHook
extend Gitlab::Utils::Override
DEFAULT_DOMAIN = 'datadoghq.com'
URL_TEMPLATE = 'https://webhooks-http-intake.logs.%{datadog_domain}/api/v2/webhook'
URL_TEMPLATE_API_KEYS = 'https://app.%{datadog_domain}/account/settings#api'
@ -21,8 +24,6 @@ module Integrations
validates :api_url, presence: true, unless: -> (obj) { obj.datadog_site.present? }
end
after_save :compose_service_hook, if: :activated?
def initialize_properties
super
@ -98,12 +99,7 @@ module Integrations
]
end
def compose_service_hook
hook = service_hook || build_service_hook
hook.url = hook_url
hook.save
end
override :hook_url
def hook_url
url = api_url.presence || sprintf(URL_TEMPLATE, datadog_domain: datadog_domain)
url = URI.parse(url)
@ -127,7 +123,7 @@ module Integrations
object_kind = 'job' if object_kind == 'build'
return unless supported_events.include?(object_kind)
service_hook.execute(data, "#{object_kind} hook")
execute_web_hook!(data, "#{object_kind} hook")
end
def test(data)

View File

@ -2,8 +2,10 @@
module Integrations
class DroneCi < BaseCi
include HasWebHook
include ReactiveService
include ServicePushDataValidations
extend Gitlab::Utils::Override
prop_accessor :drone_url, :token
boolean_accessor :enable_ssl_verification
@ -11,24 +13,16 @@ module Integrations
validates :drone_url, presence: true, public_url: true, if: :activated?
validates :token, presence: true, if: :activated?
after_save :compose_service_hook, if: :activated?
def compose_service_hook
hook = service_hook || build_service_hook
# If using a service template, project may not be available
hook.url = [drone_url, "/hook", "?owner=#{project.namespace.full_path}", "&name=#{project.path}", "&access_token=#{token}"].join if project
hook.enable_ssl_verification = !!enable_ssl_verification
hook.save
end
def execute(data)
return unless project
case data[:object_kind]
when 'push'
service_hook.execute(data) if push_valid?(data)
execute_web_hook!(data) if push_valid?(data)
when 'merge_request'
service_hook.execute(data) if merge_request_valid?(data)
execute_web_hook!(data) if merge_request_valid?(data)
when 'tag_push'
service_hook.execute(data) if tag_push_valid?(data)
execute_web_hook!(data) if tag_push_valid?(data)
end
end
@ -105,5 +99,21 @@ module Integrations
{ type: 'checkbox', name: 'enable_ssl_verification', title: "Enable SSL verification" }
]
end
override :hook_url
def hook_url
[drone_url, "/hook", "?owner=#{project.namespace.full_path}", "&name=#{project.path}", "&access_token=#{token}"].join
end
override :hook_ssl_verification
def hook_ssl_verification
!!enable_ssl_verification
end
override :update_web_hook!
def update_web_hook!
# If using a service template, project may not be available
super if project
end
end
end

View File

@ -2,7 +2,9 @@
module Integrations
class Jenkins < BaseCi
include HasWebHook
include ActionView::Helpers::UrlHelper
extend Gitlab::Utils::Override
prop_accessor :jenkins_url, :project_name, :username, :password
@ -16,8 +18,6 @@ module Integrations
default_value_for :merge_requests_events, false
default_value_for :tag_push_events, false
after_save :compose_service_hook, if: :activated?
def reset_password
# don't reset the password if a new one is provided
if (jenkins_url_changed? || username.blank?) && !password_touched?
@ -25,16 +25,10 @@ module Integrations
end
end
def compose_service_hook
hook = service_hook || build_service_hook
hook.url = hook_url
hook.save
end
def execute(data)
return unless supported_events.include?(data[:object_kind])
service_hook.execute(data, "#{data[:object_kind]}_hook")
execute_web_hook!(data, "#{data[:object_kind]}_hook")
end
def test(data)
@ -48,6 +42,7 @@ module Integrations
{ success: true, result: result[:message] }
end
override :hook_url
def hook_url
url = URI.parse(jenkins_url)
url.path = File.join(url.path || '/', "project/#{project_name}")

View File

@ -2,6 +2,9 @@
module Integrations
class Packagist < Integration
include HasWebHook
extend Gitlab::Utils::Override
prop_accessor :username, :token, :server
validates :username, presence: true, if: :activated?
@ -10,8 +13,6 @@ module Integrations
default_value_for :push_events, true
default_value_for :tag_push_events, true
after_save :compose_service_hook, if: :activated?
def title
'Packagist'
end
@ -39,7 +40,7 @@ module Integrations
def execute(data)
return unless supported_events.include?(data[:object_kind])
service_hook.execute(data)
execute_web_hook!(data)
end
def test(data)
@ -53,12 +54,7 @@ module Integrations
{ success: true, result: result[:message] }
end
def compose_service_hook
hook = service_hook || build_service_hook
hook.url = hook_url
hook.save
end
override :hook_url
def hook_url
base_url = server.presence || 'https://packagist.org'
"#{base_url}/api/update-package?username=#{username}&apiToken=#{token}"

View File

@ -18,7 +18,6 @@ module Integrations
attr_accessor :response
after_save :compose_service_hook, if: :activated?
before_update :reset_password
class << self
@ -31,11 +30,6 @@ module Integrations
end
end
def compose_service_hook
hook = service_hook || build_service_hook
hook.save
end
def reset_password
if teamcity_url_changed? && !password_touched?
self.password = nil

View File

@ -80,7 +80,7 @@ module Operations
end
def link_reference_pattern
@link_reference_pattern ||= super("feature_flags", /(?<feature_flag>\d+)\/edit/)
@link_reference_pattern ||= super("feature_flags", %r{(?<feature_flag>\d+)/edit})
end
def reference_postfix

View File

@ -33,7 +33,7 @@ module Packages
end
def path_valid?(major)
m = /\/v(\d+)$/i.match(@name)
m = %r{/v(\d+)$}i.match(@name)
case major
when 0, 1

View File

@ -115,7 +115,25 @@ module Auth
#
ensure_container_repository!(path, authorized_actions)
{ type: type, name: path.to_s, actions: authorized_actions }
{
type: type,
name: path.to_s,
actions: authorized_actions,
migration_eligible: migration_eligible(requested_project, authorized_actions)
}.compact
end
def migration_eligible(project, actions)
return unless actions.include?('push')
return unless Feature.enabled?(:container_registry_migration_phase1)
# The migration process will start by allowing only specific test and gitlab-org projects using the
# `container_registry_migration_phase1_allow` FF. We'll then move on to a percentage rollout using this same FF.
# To remove the risk of impacting enterprise customers that rely heavily on the registry during the percentage
# rollout, we'll add their top-level group/namespace to the `container_registry_migration_phase1_deny` FF. Later,
# we'll remove them manually from this deny list, and their new repositories will become eligible.
Feature.disabled?(:container_registry_migration_phase1_deny, project.root_ancestor) &&
Feature.enabled?(:container_registry_migration_phase1_allow, project)
end
##

View File

@ -41,7 +41,7 @@ module Packages
end
def search_for_single_package(query)
name, version, username, _ = query.split(/[@\/]/)
name, version, username, _ = query.split(%r{[@/]})
full_path = Packages::Conan::Metadatum.full_path_from(package_username: username)
project = Project.find_by_full_path(full_path)
return unless Ability.allowed?(current_user, :read_package, project)

View File

@ -65,7 +65,7 @@ module Projects
# TODO: Support LFS sync over SSH
# https://gitlab.com/gitlab-org/gitlab/-/issues/249587
return unless remote_mirror.url =~ /\Ahttps?:\/\//i
return unless remote_mirror.url =~ %r{\Ahttps?://}i
return unless remote_mirror.password_auth?
Lfs::PushService.new(

View File

@ -15,6 +15,6 @@ class ProjectServiceWorker # rubocop:disable Scalability/IdempotentWorker
integration.execute(data)
rescue StandardError => error
integration_class = integration&.class&.name || "Not Found"
logger.error class: self.class.name, service_class: integration_class, message: error.message
Gitlab::ErrorTracking.log_exception(error, integration_class: integration_class)
end
end

View File

@ -0,0 +1,8 @@
---
name: container_registry_migration_phase1
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/63907
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/335703
milestone: '14.1'
type: development
group: group::package
default_enabled: false

View File

@ -0,0 +1,8 @@
---
name: container_registry_migration_phase1_allow
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/63907
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/335705
milestone: '14.1'
type: development
group: group::package
default_enabled: false

View File

@ -0,0 +1,8 @@
---
name: container_registry_migration_phase1_deny
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/63907
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/335706
milestone: '14.1'
type: development
group: group::package
default_enabled: false

View File

@ -61,7 +61,7 @@ RspecProfiling.configure do |config|
RspecProfiling::Run.prepend(RspecProfilingExt::Run)
config.collector = RspecProfilingExt::Collectors::CSVWithTimestamps
config.csv_path = -> do
prefix = "#{ENV['CI_JOB_NAME']}-".gsub(/[ \/]/, '-') if ENV['CI_JOB_NAME']
prefix = "#{ENV['CI_JOB_NAME']}-".gsub(%r{[ /]}, '-') if ENV['CI_JOB_NAME']
"rspec_profiling/#{prefix}#{Time.now.to_i}-#{SecureRandom.hex(8)}-rspec-data.csv"
end
end

View File

@ -104,32 +104,19 @@ the following. This balances the load between `host1.example.com` and
1. Save the file and [restart GitLab](restart_gitlab.md#installations-from-source) for the changes to take effect.
### Enable the load balancer for Sidekiq
### Load balancing for Sidekiq
Sidekiq mostly writes to the database, which means that most of its traffic hits the
primary database.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/334494) in GitLab 14.1, load balancing for Sidekick is enabled by default.
Some background jobs can use database replicas to read application state.
Sidekiq jobs mostly write to the primary database, but there are read-only jobs that can benefit
from the use of Sidekiq load balancing.
These jobs can use load balancing and database replicas to read the application state.
This allows to offload the primary database.
Load balancing is disabled by default in Sidekiq. When enabled, we can define
[the data consistency](../development/sidekiq_style_guide.md#job-data-consistency-strategies)
For Sidekiq, we can define
[data consistency](../development/sidekiq_style_guide.md#job-data-consistency-strategies)
requirements for a specific job.
To enable it, define the `ENABLE_LOAD_BALANCING_FOR_SIDEKIQ` variable to the environment, as shown below.
For Omnibus installations:
```ruby
gitlab_rails['env'] = {"ENABLE_LOAD_BALANCING_FOR_SIDEKIQ" => "true"}
```
For installations from source:
```shell
export ENABLE_LOAD_BALANCING_FOR_SIDEKIQ="true"
```
## Service Discovery
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/5883) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.0.

View File

@ -215,6 +215,24 @@ NOTE:
`inplace_chroot` option might not work with the other features, such as [Pages Access Control](#access-control).
The [GitLab Pages README](https://gitlab.com/gitlab-org/gitlab-pages#caveats) has more information about caveats and workarounds.
### Jailing mechanism disabled by default for API-based configuration
Starting from GitLab 14.1 the [jailing/chroot mechanism is disabled by default](https://gitlab.com/gitlab-org/gitlab-pages/-/issues/589).
If you are using API-based configuration and the new [Zip storage architecture](#zip-storage)
there is nothing you need to do.
If you run into any problems, [open a new issue](https://gitlab.com/gitlab-org/gitlab-pages/-/issues/new)
and enable the jail again by setting the environment variable:
1. Edit `/etc/gitlab/gitlab.rb`.
1. Set the `DAEMON_ENABLE_JAIL` environment variable to `true` for GitLab Pages:
```ruby
gitlab_pages['env']['DAEMON_ENABLE_JAIL'] = "true"
```
1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
### Global settings
Below is a table of all configuration settings known to Pages in Omnibus GitLab,
@ -1366,6 +1384,10 @@ GitLab 14.0 introduces a number of changes to GitLab Pages which may require man
The most common problem is when using [`inplace_chroot`](#dial-tcp-lookup-gitlabexamplecom-and-x509-certificate-signed-by-unknown-authority).
NOTE:
Starting from 14.1, the chroot/jailing mechanism is
[disabled by default for API-based configuration](#jailing-mechanism-disabled-by-default-for-api-based-configuration).
WARNING:
As the last resort you can temporarily enable legacy storage and configuration mechanisms. Support for them [will be removed in GitLab 14.3](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/6166), so GitLab Pages will stop working if don't resolve the underlying issue.

View File

@ -175,7 +175,7 @@ class CleanupEpicsWithNullDescription < ActiveRecord::Migration[6.0]
end
```
#### Validate the text limit (next release)
#### Validate the `NOT NULL` constraint (next release)
Validating the `NOT NULL` constraint will scan the whole table and make sure that each record is correct.
@ -201,7 +201,7 @@ end
## `NOT NULL` constraints on large tables
If you have to clean up a text column for a [high-traffic table](../migration_style_guide.md#high-traffic-tables)
If you have to clean up a nullable column for a [high-traffic table](../migration_style_guide.md#high-traffic-tables)
(for example, the `artifacts` in `ci_builds`), your background migration will go on for a while and
it will need an additional [background migration cleaning up](../background_migrations.md#cleaning-up)
in the release after adding the data migration.

View File

@ -478,7 +478,7 @@ of reading a stale record is non-zero due to replicas potentially lagging behind
When the number of jobs that rely on the database increases, ensuring immediate data consistency
can put unsustainable load on the primary database server. We therefore added the ability to use
[database load-balancing in Sidekiq workers](../administration/database_load_balancing.md#enable-the-load-balancer-for-sidekiq).
[database load balancing for Sidekiq workers](../administration/database_load_balancing.md#load-balancing-for-sidekiq).
By configuring a worker's `data_consistency` field, we can then allow the scheduler to target read replicas
under several strategies outlined below.

View File

@ -1004,6 +1004,17 @@ Using Sidekiq directly is still supported until 14.0. So if you're experiencing
1. Restart GitLab.
1. [Create an issue](https://gitlab.com/gitlab-org/gitlab/-/issues/-/new) describing the problem.
### Prometheus server setup
You can configure the Prometheus server in `config/gitlab.yml`:
```yaml
# example
prometheus:
enabled: true
server_address: '10.1.2.3:9090'
```
## Troubleshooting
### "You appear to have cloned an empty repository."

View File

@ -0,0 +1,43 @@
---
stage: Monitor
group: Monitor
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# Escalation Policies **(PREMIUM)**
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/4638) in [GitLab Premium](https://about.gitlab.com/pricing/) 14.1.
Escalation Policies protect your company from missed critical alerts. Escalation Policies contain
time-boxed steps that automatically page the next responder in the escalation step if the responder
in the previous step has not responded. You can create an escalation policy in the GitLab project
where you manage [On-call schedules](oncall_schedules.md).
## Add an escalation policy
If you have at least Maintainer [permissions](../../user/permissions.md),
you can create an escalation policy:
1. Go to **Operations > Escalation Policies** and select **Add an escalation policy**.
1. In the **Add escalation policy** form, enter the policy's name and description, and create
escalation rules to follow when a primary responder misses an alert.
1. Select **Add escalation policy**.
![Escalation Policy](img/escalation_policy_v14_1.png)
### Edit an escalation policy
Follow these steps to update an escalation policy:
1. Go to **Operations > Escalation Policies** and select the **Pencil** icon on the top right of the
policy card, across from the policy name.
1. In the **Edit policy** form, edit the information you wish to update.
1. Select the **Edit policy** button to save your changes.
### Delete an escalation policy
Follow these steps to delete a policy:
1. Go to **Operations > Escalation Policies** and select the **Trash Can** icon on the top right of
the policy card.
1. In the **Delete escalation policy** window, select the **Delete escalation policy** button.

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -201,7 +201,7 @@ upgrade paths.
| Target version | Your version | Supported upgrade path | Note |
| --------------------- | ------------ | ------------------------ | ---- |
| `14.1.0` | `13.9.2` | `13.9.2` -> `13.12.6` -> `14.0.3` -> `14.1.0` | Two intermediate versions are required: `13.12` and `14.0`, then `14.1.0`. |
| `14.1.0` | `13.9.2` | `13.9.2` -> `13.12.6` -> `14.0.5` -> `14.1.0` | Two intermediate versions are required: `13.12` and `14.0`, then `14.1.0`. |
| `13.5.4` | `12.9.2` | `12.9.2` -> `12.10.14` -> `13.0.14` -> `13.1.11` -> `13.5.4` | Three intermediate versions are required: `12.10`, `13.0` and `13.1`, then `13.5.4`. |
| `13.2.10` | `11.5.0` | `11.5.0` -> `11.11.8` -> `12.0.12` -> `12.1.17` -> `12.10.14` -> `13.0.14` -> `13.1.11` -> `13.2.10` | Six intermediate versions are required: `11.11`, `12.0`, `12.1`, `12.10`, `13.0` and `13.1`, then `13.2.10`. |
| `12.10.14` | `11.3.4` | `11.3.4` -> `11.11.8` -> `12.0.12` -> `12.1.17` -> `12.10.14` | Three intermediate versions are required: `11.11`, `12.0` and `12.1`, then `12.10.14`. |

View File

@ -98,7 +98,7 @@ Expected batched background migration for the given configuration to be marked a
To fix this error:
1. Update to either 14.0.3 or 14.1.
1. Update to either 14.0.5 or 14.1.
1. [Check the status](#check-the-status-of-background-migrations) of the batched background migration from the error message, and make sure it is listed as finished. If it is still active, either wait until it is done, or finalize it manually using the command suggested in the error, for example:
```shell

View File

@ -38,7 +38,7 @@ Once built, a chart can be uploaded to the `stable` channel with `curl` or `helm
```shell
curl --request POST \
--form 'chart=@mychart.tgz' \
--form 'chart=@mychart-0.1.0.tgz' \
--user <username>:<personal_access_token> \
https://gitlab.example.com/api/v4/projects/1/packages/helm/api/stable/charts
```
@ -50,6 +50,25 @@ Once built, a chart can be uploaded to the `stable` channel with `curl` or `helm
helm push mychart-0.1.0.tgz project-1
```
## Use CI/CD to publish a Helm package
To publish a Helm package automated through [GitLab CI/CD](../../../ci/index.md), you can use
`CI_JOB_TOKEN` in place of the personal access token in your commands.
For example:
```yaml
image: curlimages/curl:latest
stages:
- upload
upload:
stage: upload
script:
- 'curl --request POST --user gitlab-ci-token:$CI_JOB_TOKEN --form "chart=@mychart-0.1.0.tgz" "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/helm/api/stable/charts"'
```
## Install a package
To install the latest version of a chart, use the following command:

View File

@ -85,7 +85,6 @@ module Gitlab
# Returns true if load balancing is to be enabled.
def self.enable?
return false if Gitlab::Runtime.rake?
return false if Gitlab::Runtime.sidekiq? && !Gitlab::Utils.to_boolean(ENV['ENABLE_LOAD_BALANCING_FOR_SIDEKIQ'], default: false)
return false unless self.configured?
true

View File

@ -16,10 +16,7 @@ module QA
Page::Main::Menu.perform(&:go_to_create_project)
Page::Project::New.perform do |project_page|
project_page.click_import_project
project_page.click_github_link
end
go_to_import_page
Page::Project::Import::Github.perform do |import_page|
import_page.add_personal_access_token(github_personal_access_token)
@ -28,6 +25,13 @@ module QA
end
end
def go_to_import_page
Page::Project::New.perform do |project_page|
project_page.click_import_project
project_page.click_github_link
end
end
def fabricate_via_api!
super
rescue ResourceURLMissingError

View File

@ -40,7 +40,7 @@ module QA
Page::MergeRequest::Show.perform do |mr_widget|
Support::Retrier.retry_until(max_attempts: 5, sleep_interval: 5) do
mr_widget.has_pipeline_status?(/Pipeline #\d+ passed/)
mr_widget.has_pipeline_status?('passed')
end
expect(mr_widget).to have_content('Test coverage 66.67%')
end

View File

@ -27,6 +27,15 @@ RSpec.describe Projects::ServiceHookLogsController do
specify do
expect(response).to be_successful
end
it 'renders a 404 if the hook does not exist' do
log_params
integration.service_hook.destroy!
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
describe 'POST #retry' do
@ -37,5 +46,14 @@ RSpec.describe Projects::ServiceHookLogsController do
expect_any_instance_of(described_class).to receive(:set_hook_execution_notice)
expect(subject).to redirect_to(edit_project_service_path(project, integration))
end
it 'renders a 404 if the hook does not exist' do
log_params
integration.service_hook.destroy!
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
end

View File

@ -372,4 +372,36 @@ RSpec.describe 'Admin::Users::User' do
end
end
end
context 'when user has an unconfirmed email', :js do
let(:unconfirmed_user) { create(:user, :unconfirmed) }
where(:path_helper) do
[
[-> (user) { admin_user_path(user) }],
[-> (user) { projects_admin_user_path(user) }],
[-> (user) { keys_admin_user_path(user) }],
[-> (user) { admin_user_identities_path(user) }],
[-> (user) { admin_user_impersonation_tokens_path(user) }]
]
end
with_them do
it "allows an admin to force confirmation of the user's email", :aggregate_failures do
visit path_helper.call(unconfirmed_user)
click_button 'Confirm user'
page.within('[role="dialog"]') do
expect(page).to have_content("Confirm user #{unconfirmed_user.name}?")
expect(page).to have_content('This user has an unconfirmed email address. You may force a confirmation.')
click_button 'Confirm user'
end
expect(page).to have_content('Successfully confirmed')
expect(page).not_to have_button('Confirm user')
end
end
end
end

View File

@ -1,6 +1,6 @@
import { GlButton, GlFormInput } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import DeleteUserModal from '~/pages/admin/users/components/delete_user_modal.vue';
import DeleteUserModal from '~/admin/users/components/modals/delete_user_modal.vue';
import OncallSchedulesList from '~/vue_shared/components/oncall_schedules_list.vue';
import ModalStub from './stubs/modal_stub';

View File

@ -1,5 +1,5 @@
import { mount } from '@vue/test-utils';
import UserModalManager from '~/pages/admin/users/components/user_modal_manager.vue';
import UserModalManager from '~/admin/users/components/modals/user_modal_manager.vue';
import ModalStub from './stubs/modal_stub';
describe('Users admin page Modal Manager', () => {

View File

@ -115,7 +115,7 @@ describe('Global Search Store Actions', () => {
});
describe('when groupId is set', () => {
it('calls Api.groupProjects', () => {
it('calls Api.groupProjects with expected parameters', () => {
actions.fetchProjects({ commit: mockCommit, state });
expect(Api.groupProjects).toHaveBeenCalledWith(
@ -123,6 +123,8 @@ describe('Global Search Store Actions', () => {
state.query.search,
{
order_by: 'similarity',
include_subgroups: true,
with_shared: false,
},
expect.any(Function),
);

View File

@ -239,8 +239,9 @@ RSpec.describe GitlabRoutingHelper do
let(:blob) { snippet.blobs.first }
let(:ref) { 'snippet-test-ref' }
let(:args) { {} }
let(:path) { blob.path }
subject { gitlab_raw_snippet_blob_url(snippet, blob.path, ref, **args) }
subject { gitlab_raw_snippet_blob_url(snippet, path, ref, **args) }
it_behaves_like 'snippet blob raw url'
@ -248,7 +249,7 @@ RSpec.describe GitlabRoutingHelper do
let(:args) { { inline: true } }
let(:snippet) { personal_snippet }
it { expect(subject).to eq("http://test.host/-/snippets/#{snippet.id}/raw/#{ref}/#{blob.path}?inline=true") }
it { expect(subject).to eq("http://test.host/-/snippets/#{snippet.id}/raw/#{ref}/#{path}?inline=true") }
end
context 'without a ref' do
@ -257,7 +258,17 @@ RSpec.describe GitlabRoutingHelper do
let(:expected_ref) { snippet.repository.root_ref }
it 'uses the root ref' do
expect(subject).to eq("http://test.host/-/snippets/#{snippet.id}/raw/#{expected_ref}/#{blob.path}")
expect(subject).to eq("http://test.host/-/snippets/#{snippet.id}/raw/#{expected_ref}/#{path}")
end
context 'when snippet does not have a repository' do
let(:snippet) { create(:personal_snippet) }
let(:path) { 'example' }
let(:expected_ref) { Gitlab::DefaultBranch.value }
it 'uses the instance deafult branch' do
expect(subject).to eq("http://test.host/-/snippets/#{snippet.id}/raw/#{expected_ref}/#{path}")
end
end
end
end

View File

@ -142,10 +142,10 @@ RSpec.describe Gitlab::Database::LoadBalancing do
expect(described_class.enable?).to eq(false)
end
it 'returns false when Sidekiq is being used' do
it 'returns true when Sidekiq is being used' do
allow(Gitlab::Runtime).to receive(:sidekiq?).and_return(true)
expect(described_class.enable?).to eq(false)
expect(described_class.enable?).to eq(true)
end
it 'returns false when running inside a Rake task' do
@ -170,18 +170,6 @@ RSpec.describe Gitlab::Database::LoadBalancing do
expect(described_class.enable?).to eq(true)
end
context 'when ENABLE_LOAD_BALANCING_FOR_SIDEKIQ environment variable is set' do
before do
stub_env('ENABLE_LOAD_BALANCING_FOR_SIDEKIQ', 'true')
end
it 'returns true when Sidekiq is being used' do
allow(Gitlab::Runtime).to receive(:sidekiq?).and_return(true)
expect(described_class.enable?).to eq(true)
end
end
end
describe '.configured?' do

View File

@ -9,11 +9,12 @@ RSpec.describe Integration do
let_it_be(:project) { create(:project, group: group) }
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to belong_to :group }
it { is_expected.to have_one :service_hook }
it { is_expected.to have_one :jira_tracker_data }
it { is_expected.to have_one :issue_tracker_data }
it { is_expected.to belong_to(:project).inverse_of(:integrations) }
it { is_expected.to belong_to(:group).inverse_of(:integrations) }
it { is_expected.to have_one(:service_hook).inverse_of(:integration).with_foreign_key(:service_id) }
it { is_expected.to have_one(:issue_tracker_data).autosave(true).inverse_of(:integration).with_foreign_key(:service_id).class_name('Integrations::IssueTrackerData') }
it { is_expected.to have_one(:jira_tracker_data).autosave(true).inverse_of(:integration).with_foreign_key(:service_id).class_name('Integrations::JiraTrackerData') }
it { is_expected.to have_one(:open_project_tracker_data).autosave(true).inverse_of(:integration).with_foreign_key(:service_id).class_name('Integrations::OpenProjectTrackerData') }
end
describe 'validations' do

View File

@ -3,11 +3,6 @@
require 'spec_helper'
RSpec.describe Integrations::Asana do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
context 'active' do
before do
@ -42,7 +37,6 @@ RSpec.describe Integrations::Asana do
allow(@asana).to receive_messages(
project: project,
project_id: project.id,
service_hook: true,
api_key: 'verySecret',
restrict_to_branch: 'master'
)

View File

@ -5,11 +5,6 @@ require 'spec_helper'
RSpec.describe Integrations::Assembla do
include StubRequests
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe "Execute" do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
@ -19,7 +14,6 @@ RSpec.describe Integrations::Assembla do
allow(@assembla_integration).to receive_messages(
project_id: project.id,
project: project,
service_hook: true,
token: 'verySecret',
subdomain: 'project_name'
)

View File

@ -22,11 +22,6 @@ RSpec.describe Integrations::Bamboo, :use_clean_rails_memory_store_caching do
)
end
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
context 'when active' do
before do

View File

@ -28,7 +28,6 @@ RSpec.describe Integrations::BaseChatNotification do
allow(chat_integration).to receive_messages(
project: project,
project_id: project.id,
service_hook: true,
webhook: webhook_url
)

View File

@ -3,11 +3,6 @@
require 'spec_helper'
RSpec.describe Integrations::Bugzilla do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
context 'when integration is active' do
before do

View File

@ -12,16 +12,14 @@ RSpec.describe Integrations::Buildkite, :use_clean_rails_memory_store_caching do
described_class.create!(
project: project,
properties: {
service_hook: true,
project_url: 'https://buildkite.com/organization-name/example-pipeline',
token: 'secret-sauce-webhook-token:secret-sauce-status-token'
}
)
end
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
it_behaves_like Integrations::HasWebHook do
let(:hook_url) { 'https://webhook.buildkite.com/deliver/secret-sauce-webhook-token' }
end
describe 'Validations' do
@ -66,9 +64,9 @@ RSpec.describe Integrations::Buildkite, :use_clean_rails_memory_store_caching do
.to change { integration.service_hook.enable_ssl_verification }.from(false).to(true)
end
describe '#webhook_url' do
describe '#hook_url' do
it 'returns the webhook url' do
expect(integration.webhook_url).to eq(
expect(integration.hook_url).to eq(
'https://webhook.buildkite.com/deliver/secret-sauce-webhook-token'
)
end

View File

@ -5,11 +5,6 @@ require 'spec_helper'
RSpec.describe Integrations::Campfire do
include StubRequests
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
context 'when integration is active' do
before do
@ -37,7 +32,6 @@ RSpec.describe Integrations::Campfire do
allow(@campfire_integration).to receive_messages(
project_id: project.id,
project: project,
service_hook: true,
token: 'verySecret',
subdomain: 'project-name',
room: 'test-room'

View File

@ -3,11 +3,6 @@
require 'spec_helper'
RSpec.describe Integrations::Confluence do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
before do
subject.active = active

View File

@ -3,11 +3,6 @@
require 'spec_helper'
RSpec.describe Integrations::CustomIssueTracker do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
context 'when integration is active' do
before do

View File

@ -38,9 +38,9 @@ RSpec.describe Integrations::Datadog do
let(:pipeline_data) { Gitlab::DataBuilder::Pipeline.build(pipeline) }
let(:build_data) { Gitlab::DataBuilder::Build.build(build) }
describe 'associations' do
it { is_expected.to belong_to(:project) }
it { is_expected.to have_one(:service_hook) }
it_behaves_like Integrations::HasWebHook do
let(:integration) { instance }
let(:hook_url) { "#{described_class::URL_TEMPLATE % { datadog_domain: dd_site }}?dd-api-key=#{api_key}&env=#{dd_env}&service=#{dd_service}" }
end
describe 'validations' do

View File

@ -35,7 +35,6 @@ RSpec.describe Integrations::Discord do
allow(subject).to receive_messages(
project: project,
project_id: project.id,
service_hook: true,
webhook: webhook_url
)

View File

@ -5,11 +5,6 @@ require 'spec_helper'
RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do
include ReactiveCachingHelpers
describe 'associations' do
it { is_expected.to belong_to(:project) }
it { is_expected.to have_one(:service_hook) }
end
describe 'validations' do
context 'active' do
before do
@ -32,7 +27,15 @@ RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do
end
shared_context :drone_ci_integration do
let(:drone) { described_class.new }
subject(:drone) do
described_class.new(
project: project,
active: true,
drone_url: drone_url,
token: token
)
end
let(:project) { create(:project, :repository, name: 'project') }
let(:path) { project.full_path }
let(:drone_url) { 'http://drone.example.com' }
@ -45,16 +48,6 @@ RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do
let(:build_page) { "#{drone_url}/gitlab/#{path}/redirect/commits/#{sha}?branch=#{branch}" }
let(:commit_status_path) { "#{drone_url}/gitlab/#{path}/commits/#{sha}?branch=#{branch}&access_token=#{token}" }
before do
allow(drone).to receive_messages(
project_id: project.id,
project: project,
active: true,
drone_url: drone_url,
token: token
)
end
def stub_request(status: 200, body: nil)
body ||= %q({"status":"success"})
@ -66,6 +59,20 @@ RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do
end
end
it_behaves_like Integrations::HasWebHook do
include_context :drone_ci_integration
let(:integration) { drone }
let(:hook_url) { "#{drone_url}/hook?owner=#{project.namespace.full_path}&name=#{project.path}&access_token=#{token}" }
it 'does not create a hook if project is not present' do
integration.project = nil
integration.instance = true
expect { integration.save! }.not_to change(ServiceHook, :count)
end
end
describe "integration page/path methods" do
include_context :drone_ci_integration
@ -137,10 +144,17 @@ RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do
Gitlab::DataBuilder::Push.build_sample(project, user)
end
it do
service_hook = double
expect(service_hook).to receive(:execute)
expect(drone).to receive(:service_hook).and_return(service_hook)
it 'executes the webhook' do
expect(drone).to receive(:execute_web_hook!).with(push_sample_data)
drone.execute(push_sample_data)
end
it 'does not try to execute the webhook if the integration is not in a project' do
drone.project = nil
drone.instance = true
expect(drone).not_to receive(:execute_web_hook!)
drone.execute(push_sample_data)
end

View File

@ -3,11 +3,6 @@
require 'spec_helper'
RSpec.describe Integrations::Ewm do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
context 'when integration is active' do
before do

View File

@ -3,11 +3,6 @@
require 'spec_helper'
RSpec.describe Integrations::ExternalWiki do
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
context 'when integration is active' do
before do

View File

@ -3,11 +3,6 @@
require 'spec_helper'
RSpec.describe Integrations::Flowdock do
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
context 'when integration is active' do
before do
@ -38,7 +33,6 @@ RSpec.describe Integrations::Flowdock do
allow(flowdock_integration).to receive_messages(
project_id: project.id,
project: project,
service_hook: true,
token: 'verySecret'
)
WebMock.stub_request(:post, api_url)

View File

@ -5,11 +5,6 @@ require 'socket'
require 'json'
RSpec.describe Integrations::Irker do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
context 'when integration is active' do
before do
@ -46,7 +41,6 @@ RSpec.describe Integrations::Irker do
active: true,
project: project,
project_id: project.id,
service_hook: true,
server_host: @irker_server.addr[2],
server_port: @irker_server.addr[1],
default_irc_uri: 'irc://chat.freenode.net/',

View File

@ -24,9 +24,9 @@ RSpec.describe Integrations::Jenkins do
let(:jenkins_authorization) { "Basic " + ::Base64.strict_encode64(jenkins_username + ':' + jenkins_password) }
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
it_behaves_like Integrations::HasWebHook do
let(:integration) { described_class.new(jenkins_params) }
let(:hook_url) { "http://#{ERB::Util.url_encode jenkins_username}:#{ERB::Util.url_encode jenkins_password}@jenkins.example.com/project/my_project" }
end
describe 'username validation' do

View File

@ -109,11 +109,6 @@ RSpec.describe Integrations::Jira do
end
end
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe '.reference_pattern' do
using RSpec::Parameterized::TableSyntax

View File

@ -6,11 +6,6 @@ RSpec.describe Integrations::MicrosoftTeams do
let(:chat_integration) { described_class.new }
let(:webhook_url) { 'https://example.gitlab.com/' }
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
context 'when integration is active' do
before do
@ -45,7 +40,6 @@ RSpec.describe Integrations::MicrosoftTeams do
allow(chat_integration).to receive_messages(
project: project,
project_id: project.id,
service_hook: true,
webhook: webhook_url
)
@ -142,7 +136,6 @@ RSpec.describe Integrations::MicrosoftTeams do
allow(chat_integration).to receive_messages(
project: project,
project_id: project.id,
service_hook: true,
webhook: webhook_url
)
@ -224,7 +217,6 @@ RSpec.describe Integrations::MicrosoftTeams do
before do
allow(chat_integration).to receive_messages(
project: project,
service_hook: true,
webhook: webhook_url
)
end

View File

@ -27,9 +27,4 @@ RSpec.describe Integrations::OpenProject do
it { is_expected.not_to validate_presence_of(:project_identifier_code) }
end
end
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
end

View File

@ -24,9 +24,9 @@ RSpec.describe Integrations::Packagist do
let(:packagist_server) { 'https://packagist.example.com' }
let(:project) { create(:project) }
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
it_behaves_like Integrations::HasWebHook do
let(:integration) { described_class.new(packagist_params) }
let(:hook_url) { "#{packagist_server}/api/update-package?username=#{packagist_username}&apiToken=#{packagist_token}" }
end
describe '#execute' do

View File

@ -5,11 +5,6 @@ require 'spec_helper'
RSpec.describe Integrations::Pivotaltracker do
include StubRequests
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
context 'when integration is active' do
before do

View File

@ -12,10 +12,6 @@ RSpec.describe Integrations::Prometheus, :use_clean_rails_memory_store_caching,
let(:integration) { project.prometheus_integration }
describe "Associations" do
it { is_expected.to belong_to :project }
end
context 'redirects' do
it 'does not follow redirects' do
redirect_to = 'https://redirected.example.com'

View File

@ -5,11 +5,6 @@ require 'spec_helper'
RSpec.describe Integrations::Pushover do
include StubRequests
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
context 'when integration is active' do
before do
@ -51,7 +46,6 @@ RSpec.describe Integrations::Pushover do
allow(pushover).to receive_messages(
project: project,
project_id: project.id,
service_hook: true,
api_key: api_key,
user_key: user_key,
device: device,

View File

@ -3,11 +3,6 @@
require 'spec_helper'
RSpec.describe Integrations::Redmine do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
# if redmine is set in setting the urls are set to defaults
# therefore the validation passes as the values are not nil

View File

@ -22,11 +22,6 @@ RSpec.describe Integrations::Teamcity, :use_clean_rails_memory_store_caching do
)
end
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
context 'when integration is active' do
before do

View File

@ -3,11 +3,6 @@
require 'spec_helper'
RSpec.describe Integrations::Youtrack do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
context 'when integration is active' do
before do

View File

@ -6,4 +6,96 @@ RSpec.describe Auth::ContainerRegistryAuthenticationService do
include AdminModeHelper
it_behaves_like 'a container registry auth service'
context 'when in migration mode' do
include_context 'container registry auth service context'
let_it_be(:current_user) { create(:user) }
let_it_be(:project) { create(:project) }
before do
project.add_developer(current_user)
end
shared_examples 'an unmodified token' do
it_behaves_like 'a valid token'
it { expect(payload['access']).not_to include(have_key('migration_eligible')) }
end
shared_examples 'a modified token with migration eligibility' do |eligible|
it_behaves_like 'a valid token'
it { expect(payload['access']).to include(include('migration_eligible' => eligible)) }
end
shared_examples 'a modified token' do
context 'with a non eligible root ancestor and project' do
before do
stub_feature_flags(container_registry_migration_phase1_deny: project.root_ancestor)
stub_feature_flags(container_registry_migration_phase1_allow: false)
end
it_behaves_like 'a modified token with migration eligibility', false
end
context 'with a non eligible root ancestor and eligible project' do
before do
stub_feature_flags(container_registry_migration_phase1_deny: false)
stub_feature_flags(container_registry_migration_phase1_deny: project.root_ancestor)
stub_feature_flags(container_registry_migration_phase1_allow: project)
end
it_behaves_like 'a modified token with migration eligibility', false
end
context 'with an eligible root ancestor and non eligible project' do
before do
stub_feature_flags(container_registry_migration_phase1_deny: false)
stub_feature_flags(container_registry_migration_phase1_allow: false)
end
it_behaves_like 'a modified token with migration eligibility', false
end
context 'with an eligible root ancestor and project' do
before do
stub_feature_flags(container_registry_migration_phase1_deny: false)
stub_feature_flags(container_registry_migration_phase1_allow: project)
end
it_behaves_like 'a modified token with migration eligibility', true
end
end
context 'with pull action' do
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:pull"] }
end
it_behaves_like 'an unmodified token'
end
context 'with push action' do
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:push"] }
end
it_behaves_like 'a modified token'
end
context 'with multiple actions including push' do
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:pull,push,delete"] }
end
it_behaves_like 'a modified token'
end
context 'with multiple actions excluding push' do
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:pull,delete"] }
end
it_behaves_like 'an unmodified token'
end
end
end

View File

@ -18,6 +18,8 @@ Integration.available_integration_names.each do |service|
hash.merge!(k => 'https://example.atlassian.net/wiki')
elsif service == 'datadog' && k == :datadog_site
hash.merge!(k => 'datadoghq.com')
elsif service == 'packagist' && k == :server
hash.merge!(k => 'https://packagist.example.com')
elsif k =~ /^(.*_url|url|webhook)/
hash.merge!(k => "http://example.com")
elsif service_klass.method_defined?("#{k}?")

View File

@ -0,0 +1,97 @@
# frozen_string_literal: true
RSpec.shared_examples Integrations::HasWebHook do
include AfterNextHelpers
describe 'callbacks' do
it 'calls #update_web_hook! when enabled' do
expect(integration).to receive(:update_web_hook!)
integration.active = true
integration.save!
end
it 'does not call #update_web_hook! when disabled' do
expect(integration).not_to receive(:update_web_hook!)
integration.active = false
integration.save!
end
it 'does not call #update_web_hook! when validation fails' do
expect(integration).not_to receive(:update_web_hook!)
integration.active = true
integration.project = nil
expect(integration.save).to be(false)
end
end
describe '#hook_url' do
it 'returns a string' do
expect(integration.hook_url).to be_a(String)
end
end
describe '#hook_ssl_verification' do
it 'returns a boolean' do
expect(integration.hook_ssl_verification).to be_in([true, false])
end
end
describe '#update_web_hook!' do
def call
integration.update_web_hook!
end
it 'creates or updates a service hook' do
expect { call }.to change(ServiceHook, :count).by(1)
expect(integration.service_hook.url).to eq(hook_url)
integration.service_hook.update!(url: 'http://other.com')
expect { call }.to change { integration.service_hook.reload.url }.from('http://other.com').to(hook_url)
end
it 'raises an error if the service hook could not be saved' do
call
integration.service_hook.integration = nil
expect { call }.to raise_error(ActiveRecord::RecordInvalid)
end
it 'does not attempt to save the service hook if there are no changes' do
call
expect(integration.service_hook).not_to receive(:save!)
call
end
end
describe '#execute_web_hook!' do
let(:args) { ['foo', [1, 2, 3]] }
def call
integration.execute_web_hook!(*args)
end
it 'creates the webhook if necessary and executes it' do
expect_next(ServiceHook).to receive(:execute).with(*args)
expect { call }.to change(ServiceHook, :count).by(1)
expect(integration.service_hook).to receive(:execute).with(*args)
expect { call }.not_to change(ServiceHook, :count)
end
it 'raises an error if the service hook could not be saved' do
expect_next(ServiceHook).to receive(:execute).with(*args)
call
integration.service_hook.integration = nil
expect(integration.service_hook).not_to receive(:execute)
expect { call }.to raise_error(ActiveRecord::RecordInvalid)
end
end
end

View File

@ -157,6 +157,10 @@ end
RSpec.shared_examples 'a container registry auth service' do
include_context 'container registry auth service context'
before do
stub_feature_flags(container_registry_migration_phase1: false)
end
describe '#full_access_token' do
let_it_be(:project) { create(:project) }

View File

@ -3,22 +3,24 @@ require 'spec_helper'
RSpec.describe ProjectServiceWorker, '#perform' do
let(:worker) { described_class.new }
let(:service) { Integrations::Jira.new }
let(:integration) { Integrations::Jira.new }
before do
allow(Integration).to receive(:find).and_return(service)
allow(Integration).to receive(:find).and_return(integration)
end
it 'executes service with given data' do
it 'executes integration with given data' do
data = { test: 'test' }
expect(service).to receive(:execute).with(data)
expect(integration).to receive(:execute).with(data)
worker.perform(1, data)
end
it 'logs error messages' do
allow(service).to receive(:execute).and_raise(StandardError, 'invalid URL')
expect(Sidekiq.logger).to receive(:error).with({ class: described_class.name, service_class: service.class.name, message: "invalid URL" })
error = StandardError.new('invalid URL')
allow(integration).to receive(:execute).and_raise(error)
expect(Gitlab::ErrorTracking).to receive(:log_exception).with(error, integration_class: 'Integrations::Jira')
worker.perform(1, {})
end