Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
5d0c43f60d
commit
a00537e412
|
|
@ -1 +1 @@
|
|||
1.50.0
|
||||
1.51.0
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { updateHistory, setUrlParams } from '~/lib/utils/url_utility';
|
|||
import { __ } from '~/locale';
|
||||
import { FILTERED_SEARCH_TERM } from '~/vue_shared/components/filtered_search_bar/constants';
|
||||
import FilteredSearch from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
|
||||
import { AssigneeFilterType } from '~/boards/constants';
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
|
|
@ -37,6 +38,7 @@ export default {
|
|||
authorUsername,
|
||||
labelName,
|
||||
assigneeUsername,
|
||||
assigneeId,
|
||||
search,
|
||||
milestoneTitle,
|
||||
iterationId,
|
||||
|
|
@ -63,6 +65,13 @@ export default {
|
|||
});
|
||||
}
|
||||
|
||||
if (assigneeId) {
|
||||
filteredSearchValue.push({
|
||||
type: 'assignee',
|
||||
value: { data: assigneeId, operator: '=' },
|
||||
});
|
||||
}
|
||||
|
||||
if (types) {
|
||||
filteredSearchValue.push({
|
||||
type: 'type',
|
||||
|
|
@ -211,6 +220,7 @@ export default {
|
|||
authorUsername,
|
||||
labelName,
|
||||
assigneeUsername,
|
||||
assigneeId,
|
||||
search,
|
||||
milestoneTitle,
|
||||
types,
|
||||
|
|
@ -246,6 +256,7 @@ export default {
|
|||
author_username: authorUsername,
|
||||
'label_name[]': labelName,
|
||||
assignee_username: assigneeUsername,
|
||||
assignee_id: assigneeId,
|
||||
milestone_title: milestoneTitle,
|
||||
iteration_id: iterationId,
|
||||
search,
|
||||
|
|
@ -295,7 +306,11 @@ export default {
|
|||
filterParams.authorUsername = filter.value.data;
|
||||
break;
|
||||
case 'assignee':
|
||||
filterParams.assigneeUsername = filter.value.data;
|
||||
if (Object.values(AssigneeFilterType).includes(filter.value.data)) {
|
||||
filterParams.assigneeId = filter.value.data;
|
||||
} else {
|
||||
filterParams.assigneeUsername = filter.value.data;
|
||||
}
|
||||
break;
|
||||
case 'type':
|
||||
filterParams.types = filter.value.data;
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ export const FilterFields = {
|
|||
/* eslint-disable @gitlab/require-i18n-strings */
|
||||
export const AssigneeFilterType = {
|
||||
any: 'Any',
|
||||
none: 'None',
|
||||
};
|
||||
|
||||
export const MilestoneFilterType = {
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ export default {
|
|||
};
|
||||
</script>
|
||||
<template>
|
||||
<gl-skeleton-loading v-if="loading" :lines="2" class="h-auto pt-2 pb-1" />
|
||||
<gl-skeleton-loading v-if="loading" :lines="2" />
|
||||
<gl-path v-else :key="selectedStage.id" :items="stages" @selected="onSelectStage">
|
||||
<template #default="{ pathItem, pathId }">
|
||||
<gl-popover
|
||||
|
|
|
|||
|
|
@ -59,7 +59,9 @@ export default {
|
|||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="gl-mt-3 gl-py-2 gl-px-3 bg-gray-light border-top border-bottom">
|
||||
<div
|
||||
class="gl-mt-3 gl-py-2 gl-px-3 gl-bg-gray-10 gl-border-b-1 gl-border-b-solid gl-border-t-1 gl-border-t-solid gl-border-gray-100"
|
||||
>
|
||||
<filter-bar
|
||||
data-testid="vsa-filter-bar"
|
||||
class="filtered-search-box gl-display-flex gl-mb-2 gl-mr-3 gl-border-none"
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
import $ from 'jquery';
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
export default () => {
|
||||
$('.js-experiment-feature-toggle').on('change', (e) => {
|
||||
const el = e.target;
|
||||
|
||||
Cookies.set(el.name, el.value, {
|
||||
expires: 365 * 10,
|
||||
});
|
||||
|
||||
document.body.scrollTop = 0;
|
||||
window.location.reload();
|
||||
});
|
||||
};
|
||||
|
|
@ -8,7 +8,6 @@ import CommitBlock from '../../components/commit_block.vue';
|
|||
|
||||
export default {
|
||||
styles: {
|
||||
top: '75px',
|
||||
width: '290px',
|
||||
},
|
||||
name: 'BridgeSidebar',
|
||||
|
|
@ -37,17 +36,37 @@ export default {
|
|||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
topPosition: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
rootStyle() {
|
||||
return { ...this.$options.styles, top: `${this.topPosition}px` };
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.setTopPosition();
|
||||
},
|
||||
methods: {
|
||||
onSidebarButtonClick() {
|
||||
this.$emit('toggleSidebar');
|
||||
},
|
||||
setTopPosition() {
|
||||
const navbarEl = document.querySelector('.js-navbar');
|
||||
|
||||
if (navbarEl) {
|
||||
this.topPosition = navbarEl.getBoundingClientRect().bottom;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<aside
|
||||
class="gl-fixed gl-right-0 gl-px-5 gl-bg-gray-10 gl-h-full gl-border-l-solid gl-border-1 gl-border-gray-100 gl-z-index-200 gl-overflow-hidden"
|
||||
:style="this.$options.styles"
|
||||
:style="rootStyle"
|
||||
>
|
||||
<div class="gl-py-5 gl-display-flex gl-align-items-center">
|
||||
<tooltip-on-truncate :title="bridgeJob.name" truncate-target="child"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
import { initRunnerDetail } from '~/runner/runner_details';
|
||||
import { initAdminRunnerEdit } from '~/runner/admin_runner_edit';
|
||||
|
||||
initRunnerDetail();
|
||||
initAdminRunnerEdit();
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import getRunnerQuery from '../graphql/get_runner.query.graphql';
|
|||
import { captureException } from '../sentry_utils';
|
||||
|
||||
export default {
|
||||
name: 'RunnerDetailsApp',
|
||||
name: 'AdminRunnerEditApp',
|
||||
components: {
|
||||
RunnerHeader,
|
||||
RunnerUpdateForm,
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import createDefaultClient from '~/lib/graphql';
|
||||
import RunnerDetailsApp from './runner_details_app.vue';
|
||||
import AdminRunnerEditApp from './admin_runner_edit_app.vue';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
export const initRunnerDetail = (selector = '#js-runner-details') => {
|
||||
export const initAdminRunnerEdit = (selector = '#js-admin-runner-edit') => {
|
||||
const el = document.querySelector(selector);
|
||||
|
||||
if (!el) {
|
||||
|
|
@ -22,7 +22,7 @@ export const initRunnerDetail = (selector = '#js-runner-details') => {
|
|||
el,
|
||||
apolloProvider,
|
||||
render(h) {
|
||||
return h(RunnerDetailsApp, {
|
||||
return h(AdminRunnerEditApp, {
|
||||
props: {
|
||||
runnerId,
|
||||
},
|
||||
|
|
@ -162,9 +162,9 @@ export default {
|
|||
See https://gitlab.com/gitlab-org/gitlab/-/issues/334802
|
||||
-->
|
||||
<gl-button
|
||||
v-if="canUpdate && runner.adminUrl"
|
||||
v-if="canUpdate && runner.editAdminUrl"
|
||||
v-gl-tooltip.hover.viewport="$options.I18N_EDIT"
|
||||
:href="runner.adminUrl"
|
||||
:href="runner.editAdminUrl"
|
||||
:aria-label="$options.I18N_EDIT"
|
||||
icon="pencil"
|
||||
data-testid="edit-runner"
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {
|
|||
import {
|
||||
modelToUpdateMutationVariables,
|
||||
runnerToModel,
|
||||
} from 'ee_else_ce/runner/runner_details/runner_update_form_utils';
|
||||
} from 'ee_else_ce/runner/runner_update_form_utils';
|
||||
import { createAlert, VARIANT_SUCCESS } from '~/flash';
|
||||
import { __ } from '~/locale';
|
||||
import { captureException } from '~/runner/sentry_utils';
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ query getRunners(
|
|||
nodes {
|
||||
...RunnerNode
|
||||
adminUrl
|
||||
editAdminUrl
|
||||
}
|
||||
pageInfo {
|
||||
...PageInfo
|
||||
|
|
|
|||
|
|
@ -1,64 +0,0 @@
|
|||
/* eslint-disable func-names, consistent-return, one-var, class-methods-use-this */
|
||||
|
||||
import $ from 'jquery';
|
||||
import { visitUrl } from './lib/utils/url_utility';
|
||||
|
||||
export default class TreeView {
|
||||
constructor() {
|
||||
this.initKeyNav();
|
||||
// Code browser tree slider
|
||||
// Make the entire tree-item row clickable, but not if clicking another link (like a commit message)
|
||||
$('.tree-content-holder .tree-item').on('click', function (e) {
|
||||
const $clickedEl = $(e.target);
|
||||
const path = $('.tree-item-file-name a', this).attr('href');
|
||||
if (!$clickedEl.is('a') && !$clickedEl.is('.str-truncated')) {
|
||||
if (e.metaKey || e.which === 2) {
|
||||
e.preventDefault();
|
||||
return window.open(path, '_blank');
|
||||
}
|
||||
return visitUrl(path);
|
||||
}
|
||||
});
|
||||
// Show the "Loading commit data" for only the first element
|
||||
$('span.log_loading').first().removeClass('hide');
|
||||
}
|
||||
|
||||
initKeyNav() {
|
||||
const li = $('tr.tree-item');
|
||||
let liSelected = null;
|
||||
return $('body').keydown((e) => {
|
||||
let next, path;
|
||||
if ($('input:focus').length > 0 && (e.which === 38 || e.which === 40)) {
|
||||
return false;
|
||||
}
|
||||
if (e.which === 40) {
|
||||
if (liSelected) {
|
||||
next = liSelected.next();
|
||||
if (next.length > 0) {
|
||||
liSelected.removeClass('selected');
|
||||
liSelected = next.addClass('selected');
|
||||
}
|
||||
} else {
|
||||
liSelected = li.eq(0).addClass('selected');
|
||||
}
|
||||
return $(liSelected).focus();
|
||||
} else if (e.which === 38) {
|
||||
if (liSelected) {
|
||||
next = liSelected.prev();
|
||||
if (next.length > 0) {
|
||||
liSelected.removeClass('selected');
|
||||
liSelected = next.addClass('selected');
|
||||
}
|
||||
} else {
|
||||
liSelected = li.last().addClass('selected');
|
||||
}
|
||||
return $(liSelected).focus();
|
||||
} else if (e.which === 13) {
|
||||
path = $('.tree-item.selected .tree-item-file-name a').attr('href');
|
||||
if (path) {
|
||||
return visitUrl(path);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import { compact } from 'lodash';
|
|||
import createFlash from '~/flash';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
import { DEFAULT_LABEL_ANY } from '../constants';
|
||||
import { DEFAULT_NONE_ANY } from '../constants';
|
||||
|
||||
import BaseToken from './base_token.vue';
|
||||
|
||||
|
|
@ -36,7 +36,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
defaultAuthors() {
|
||||
return this.config.defaultAuthors || [DEFAULT_LABEL_ANY];
|
||||
return this.config.defaultAuthors || DEFAULT_NONE_ANY;
|
||||
},
|
||||
preloadedAuthors() {
|
||||
return this.config.preloadedAuthors || [];
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ class Admin::RunnersController < Admin::ApplicationController
|
|||
# future iterations. For now, this route will have a
|
||||
# redirect until this new view is developed. See more:
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/347856
|
||||
redirect_to edit_admin_runner_path(runner)
|
||||
redirect_to edit_admin_runner_path(runner) unless Feature.enabled?(:runner_read_only_admin_view, default_enabled: :yaml)
|
||||
end
|
||||
|
||||
def edit
|
||||
|
|
|
|||
|
|
@ -9,6 +9,37 @@ module Projects
|
|||
|
||||
def show
|
||||
render_403 unless can?(current_user, :read_security_configuration, project)
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json do
|
||||
render status: :ok, json: configuration.to_h
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def configuration
|
||||
if unify_configuration_enabled?
|
||||
configuration_presenter
|
||||
else
|
||||
{}
|
||||
end
|
||||
end
|
||||
|
||||
def configuration_presenter
|
||||
::Projects::Security::ConfigurationPresenter.new(project,
|
||||
**presenter_attributes,
|
||||
current_user: current_user)
|
||||
end
|
||||
|
||||
def presenter_attributes
|
||||
{}
|
||||
end
|
||||
|
||||
def unify_configuration_enabled?
|
||||
Feature.enabled?(:unify_security_configuration, project, default_enabled: :yaml)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -190,5 +190,10 @@ module Routable
|
|||
route || build_route(source: self)
|
||||
route.path = build_full_path
|
||||
route.name = build_full_name
|
||||
route.namespace = if is_a?(Namespace)
|
||||
self
|
||||
elsif is_a?(Project)
|
||||
self.project_namespace
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ class Email < ApplicationRecord
|
|||
|
||||
belongs_to :user, optional: false
|
||||
|
||||
validates :email, presence: true, uniqueness: true
|
||||
validate :validate_email_format
|
||||
validates :email, presence: true, uniqueness: true, devise_email: true
|
||||
|
||||
validate :unique_email, if: ->(email) { email.email_changed? }
|
||||
|
||||
scope :confirmed, -> { where.not(confirmed_at: nil) }
|
||||
|
|
@ -33,10 +33,6 @@ class Email < ApplicationRecord
|
|||
self.errors.add(:email, 'has already been taken') if primary_email_of_another_user?
|
||||
end
|
||||
|
||||
def validate_email_format
|
||||
self.errors.add(:email, I18n.t(:invalid, scope: 'valid_email.validations.email')) unless ValidateEmail.valid?(self.email)
|
||||
end
|
||||
|
||||
# once email is confirmed, update the gpg signatures
|
||||
def update_invalid_gpg_signatures
|
||||
user.update_invalid_gpg_signatures if confirmed?
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ class Namespace < ApplicationRecord
|
|||
has_many :projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
||||
has_many :project_statistics
|
||||
has_one :namespace_settings, inverse_of: :namespace, class_name: 'NamespaceSetting', autosave: true
|
||||
has_one :namespace_route, foreign_key: :namespace_id, autosave: true, inverse_of: :namespace, class_name: 'Route'
|
||||
has_one :namespace_route, foreign_key: :namespace_id, autosave: false, inverse_of: :namespace, class_name: 'Route'
|
||||
|
||||
has_many :runner_namespaces, inverse_of: :namespace, class_name: 'Ci::RunnerNamespace'
|
||||
has_many :runners, through: :runner_namespaces, source: :runner, class_name: 'Ci::Runner'
|
||||
|
|
|
|||
|
|
@ -1,11 +1,16 @@
|
|||
- add_page_specific_style 'page_bundles/ci_status'
|
||||
|
||||
- title = "##{@runner.id} (#{@runner.short_sha})"
|
||||
- breadcrumb_title title
|
||||
- page_title title
|
||||
- add_to_breadcrumbs _('Runners'), admin_runners_path
|
||||
- runner_name = "##{@runner.id} (#{@runner.short_sha})"
|
||||
- if Feature.enabled?(:runner_read_only_admin_view)
|
||||
- breadcrumb_title _('Edit')
|
||||
- page_title _('Edit'), runner_name
|
||||
- add_to_breadcrumbs _('Runners'), admin_runners_path
|
||||
- add_to_breadcrumbs runner_name, admin_runner_path(@runner)
|
||||
- else
|
||||
- breadcrumb_title runner_name
|
||||
- page_title runner_name
|
||||
|
||||
#js-runner-details{ data: {runner_id: @runner.id} }
|
||||
#js-admin-runner-edit{ data: {runner_id: @runner.id} }
|
||||
|
||||
.row
|
||||
.col-md-6
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
- add_page_specific_style 'page_bundles/ci_status'
|
||||
|
||||
- title = "##{@runner.id} (#{@runner.short_sha})"
|
||||
- breadcrumb_title title
|
||||
- page_title title
|
||||
- add_to_breadcrumbs _('Runners'), admin_runners_path
|
||||
|
||||
-# Empty view in development behind feature flag runner_read_only_admin_view
|
||||
|
|
@ -90,37 +90,6 @@ class EmailReceiverWorker # rubocop:disable Scalability/IdempotentWorker
|
|||
def handle_failure(error)
|
||||
return unless raw.present?
|
||||
|
||||
can_retry = false
|
||||
reason =
|
||||
case error
|
||||
when Gitlab::Email::UnknownIncomingEmail
|
||||
s_("EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface.")
|
||||
when Gitlab::Email::SentNotificationNotFoundError
|
||||
s_("EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface.")
|
||||
when Gitlab::Email::ProjectNotFound
|
||||
s_("EmailError|We couldn't find the project. Please check if there's any typo.")
|
||||
when Gitlab::Email::EmptyEmailError
|
||||
can_retry = true
|
||||
s_("EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies.")
|
||||
when Gitlab::Email::UserNotFoundError
|
||||
s_("EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface.")
|
||||
when Gitlab::Email::UserBlockedError
|
||||
s_("EmailError|Your account has been blocked. If you believe this is in error, contact a staff member.")
|
||||
when Gitlab::Email::UserNotAuthorizedError
|
||||
s_("EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member.")
|
||||
when Gitlab::Email::NoteableNotFoundError
|
||||
s_("EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member.")
|
||||
when Gitlab::Email::InvalidAttachment
|
||||
error.message
|
||||
when Gitlab::Email::InvalidRecordError
|
||||
can_retry = true
|
||||
error.message
|
||||
end
|
||||
|
||||
if reason
|
||||
receiver.mail.body = nil
|
||||
|
||||
EmailRejectionMailer.rejection(reason, receiver.mail.encoded, can_retry).deliver_later
|
||||
end
|
||||
Gitlab::Email::FailureHandler.handle(receiver, error)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: runner_read_only_admin_view
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/77682
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/350164
|
||||
milestone: '14.7'
|
||||
type: development
|
||||
group: group::runner
|
||||
default_enabled: false
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: unify_security_configuration
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/76866
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/350177
|
||||
milestone: '14.7'
|
||||
type: development
|
||||
group: group::composition analysis
|
||||
default_enabled: false
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RemoveProjectsCiPendingBuildsFk < Gitlab::Database::Migration[1.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
with_lock_retries do
|
||||
remove_foreign_key_if_exists(:ci_pending_builds, :projects, name: "fk_rails_480669c3b3")
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
add_concurrent_foreign_key(:ci_pending_builds, :projects, name: "fk_rails_480669c3b3", column: :project_id, target_column: :id, on_delete: "cascade")
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RemoveUsersCiJobTokenProjectScopeLinksAddedByIdFk < Gitlab::Database::Migration[1.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
with_lock_retries do
|
||||
remove_foreign_key_if_exists(:ci_job_token_project_scope_links, :users, name: "fk_rails_35f7f506ce")
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
add_concurrent_foreign_key(:ci_job_token_project_scope_links, :users, name: "fk_rails_35f7f506ce", column: :added_by_id, target_column: :id, on_delete: :nullify)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RemoveUsersCiPipelineSchedulesOwnerIdFk < Gitlab::Database::Migration[1.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
with_lock_retries do
|
||||
remove_foreign_key_if_exists(:ci_pipeline_schedules, :users, name: "fk_9ea99f58d2")
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
add_concurrent_foreign_key(:ci_pipeline_schedules, :users, name: "fk_9ea99f58d2", column: :owner_id, target_column: :id, on_delete: :nullify)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
0cb120b0036b3f472edb57fcb8a52877d399edf8ff1f416ce76497d6aa8265d7
|
||||
|
|
@ -0,0 +1 @@
|
|||
4a90811aace678528b75171868fb178ab885d5aac885048e8eacecaf8b0ee374
|
||||
|
|
@ -0,0 +1 @@
|
|||
983e5522b2798ca0d17ca3fd848f527b12afdbdd1482a2d420c4c6ce4fa2c9c4
|
||||
|
|
@ -29516,9 +29516,6 @@ ALTER TABLE ONLY protected_environments
|
|||
ALTER TABLE ONLY alert_management_alerts
|
||||
ADD CONSTRAINT fk_9e49e5c2b7 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY ci_pipeline_schedules
|
||||
ADD CONSTRAINT fk_9ea99f58d2 FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE ONLY protected_branch_push_access_levels
|
||||
ADD CONSTRAINT fk_9ffc86a3d9 FOREIGN KEY (protected_branch_id) REFERENCES protected_branches(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
@ -30245,9 +30242,6 @@ ALTER TABLE ONLY metrics_dashboard_annotations
|
|||
ALTER TABLE ONLY wiki_page_slugs
|
||||
ADD CONSTRAINT fk_rails_358b46be14 FOREIGN KEY (wiki_page_meta_id) REFERENCES wiki_page_meta(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY ci_job_token_project_scope_links
|
||||
ADD CONSTRAINT fk_rails_35f7f506ce FOREIGN KEY (added_by_id) REFERENCES users(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE ONLY board_labels
|
||||
ADD CONSTRAINT fk_rails_362b0600a3 FOREIGN KEY (label_id) REFERENCES labels(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
@ -30368,9 +30362,6 @@ ALTER TABLE ONLY vulnerability_feedback
|
|||
ALTER TABLE ONLY user_custom_attributes
|
||||
ADD CONSTRAINT fk_rails_47b91868a8 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY ci_pending_builds
|
||||
ADD CONSTRAINT fk_rails_480669c3b3 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY upcoming_reconciliations
|
||||
ADD CONSTRAINT fk_rails_497b4938ac FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
# Install the GitLab Agent Server (KAS) **(FREE SELF)**
|
||||
|
||||
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3834) in GitLab 13.10, the GitLab Agent Server (KAS) became available on GitLab.com under `wss://kas.gitlab.com`.
|
||||
> [Moved](https://gitlab.com/groups/gitlab-org/-/epics/6290) from GitLab Premium to GitLab Free in 14.5.
|
||||
|
||||
The GitLab Agent Server (KAS) is a GitLab backend service dedicated to
|
||||
managing the [GitLab Agent](../../user/clusters/agent/index.md).
|
||||
|
||||
The KAS is already installed and available in GitLab.com under `wss://kas.gitlab.com`.
|
||||
See [how to use GitLab.com's KAS](../../user/clusters/agent/install/index.md#set-up-the-agent-server).
|
||||
This document describes how to install a KAS for GitLab self-managed instances.
|
||||
|
||||
## Installation options
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ and running quickly.
|
|||
|-------|-------------|--------------------|
|
||||
| [GitLab 101](https://gitlab.edcast.com/pathways/copy-of-gitlab-certification) | Learn the basics of GitLab in this certification course. | **{star}** |
|
||||
| <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Use GitLab for DevOps](https://www.youtube.com/watch?v=7q9Y1Cv-ib0) (12m 34s) | Use GitLab through the entire DevOps lifecycle, from planning to monitoring. | **{star}** |
|
||||
| [It's all connected in GitLab](https://about.gitlab.com/blog/2016/03/08/gitlab-tutorial-its-all-connected/) | Learn how to cross-link and reference your work. | **{star}** |
|
||||
| [Use Markdown at GitLab](../user/markdown.md) | GitLab Flavored Markdown (GFM) is used in many areas of GitLab, for example, in merge requests. | **{star}** |
|
||||
| [GitLab 201](https://gitlab.edcast.com/pathways/ECL-44010cf6-7a9c-4b9b-b684-fa08508a3252) | Go beyond the basics to learn more about using GitLab for your work. | |
|
||||
| Learn GitLab project | You might already have the **Learn GitLab** project, which has tutorial-style issues to help you learn GitLab. If not, download [this export file](https://gitlab.com/gitlab-org/gitlab/-/blob/master/vendor/project_templates/learn_gitlab_ultimate.tar.gz) and [import it to a new project](../user/project/settings/import_export.md#import-a-project-and-its-data). | |
|
||||
|
|
|
|||
|
|
@ -19,11 +19,8 @@ Only CI/CD jobs set in the configuration project can access one of the configure
|
|||
|
||||
## Prerequisites
|
||||
|
||||
- A running [`kas` instance](install/index.md#set-up-the-agent-server).
|
||||
- A [configuration repository](install/index.md#define-a-configuration-repository) with an agent config file
|
||||
installed (`.gitlab/agents/<agent-name>/config.yaml`).
|
||||
- A [registered agent](install/index.md#register-an-agent-with-gitlab).
|
||||
- The agent [installed in the cluster](install/index.md#install-the-agent-into-the-cluster).
|
||||
- An existing Kubernetes cluster.
|
||||
- An agent [installed on your cluster](install/index.md#install-the-agent-into-the-cluster).
|
||||
|
||||
## Use the CI/CD Tunnel to run Kubernetes commands from GitLab CI/CD
|
||||
|
||||
|
|
|
|||
|
|
@ -10,33 +10,21 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
To get started with the Agent, install it in your cluster.
|
||||
|
||||
Pre-requisites:
|
||||
## Prerequisites **(SELF)**
|
||||
|
||||
- An existing Kubernetes cluster.
|
||||
- An account on GitLab.
|
||||
- On self-managed GitLab instances, a GitLab administrator needs to set up the [GitLab Agent Server (KAS)](../../../../administration/clusters/kas.md).
|
||||
|
||||
## Installation steps
|
||||
|
||||
To install the [Agent](../index.md) in your cluster:
|
||||
|
||||
1. [Set up the Agent Server](#set-up-the-agent-server) for your GitLab instance.
|
||||
1. [Define a configuration repository](#define-a-configuration-repository).
|
||||
1. [Register an agent with GitLab](#register-an-agent-with-gitlab).
|
||||
1. [Install the agent into the cluster](#install-the-agent-into-the-cluster).
|
||||
1. [Generate and copy a Secret token used to connect to the agent](#create-the-kubernetes-secret).
|
||||
1. [Create manifest files](#create-manifest-files).
|
||||
|
||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> Watch a GitLab 14.2 [walking-through video](https://www.youtube.com/watch?v=XuBpKtsgGkE) with this process.
|
||||
|
||||
### Set up the Agent Server
|
||||
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3834) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.10, the GitLab Agent Server (KAS) became available on GitLab.com under `wss://kas.gitlab.com`.
|
||||
|
||||
To use the KAS:
|
||||
|
||||
- If you are a self-managed user, follow the instructions to [install the Agent Server](../../../../administration/clusters/kas.md).
|
||||
- If you are a GitLab.com user, when you [set up the configuration repository](#define-a-configuration-repository) for your agent, use `wss://kas.gitlab.com` as the `--kas-address`.
|
||||
|
||||
### Define a configuration repository
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/259669) in GitLab 13.7, the Agent manifest configuration can be added to multiple directories (or subdirectories) of its repository.
|
||||
|
|
@ -62,7 +50,7 @@ WARNING:
|
|||
The agent is only recognized if you use `.yaml` extension for the `config.yaml` file. The extension `.yml` is **not** recognized.
|
||||
|
||||
You **don't have to add any content** to this file when you create it. The fact that the file exists
|
||||
tells GitLab that this is an agent configuration file. It doesn't do anything so far, but, later on, you can use this
|
||||
tells GitLab that this is an agent configuration file and enables the [CI/CD tunnel](../ci_cd_tunnel.md#example-for-a-kubectl-command-using-the-cicd-tunnel). Later on, you can use this
|
||||
file to [configure the agent](../repository.md) by setting up parameters such as:
|
||||
|
||||
- Groups and projects that can access the agent via the [CI/CD Tunnel](../ci_cd_tunnel.md).
|
||||
|
|
@ -71,10 +59,6 @@ file to [configure the agent](../repository.md) by setting up parameters such as
|
|||
|
||||
To see all the settings available, read the [Agent configuration repository documentation](../repository.md).
|
||||
|
||||
### Access your cluster from GitLab CI/CD
|
||||
|
||||
Use the [CI/CD Tunnel](../ci_cd_tunnel.md#example-for-a-kubectl-command-using-the-cicd-tunnel) to access your cluster from GitLab CI/CD.
|
||||
|
||||
### Register an agent with GitLab
|
||||
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/5786) in GitLab 14.1, you can create a new Agent record directly from the GitLab UI.
|
||||
|
|
@ -92,242 +76,53 @@ In GitLab:
|
|||
1. The form reveals your registration token. Securely store this secret token as you cannot view it again.
|
||||
1. Copy the command under **Recommended installation method**.
|
||||
|
||||
### Install the agent into the cluster
|
||||
|
||||
In your computer:
|
||||
|
||||
1. Open your local terminal and connect to your cluster.
|
||||
1. Run the command you copied from the installation form.
|
||||
1. Run the command you copied when registering your cluster in the previous step.
|
||||
|
||||
### Install the agent into the cluster
|
||||
See the following sections to learn about customizing the installation.
|
||||
|
||||
To install the in-cluster component of the Agent, first you need to define a namespace. To create a new namespace,
|
||||
for example, `gitlab-kubernetes-agent`, run:
|
||||
## Simple installation method
|
||||
|
||||
The command provided by GitLab does the following things:
|
||||
|
||||
- Creates a namespace for the deployment (`gitlab-kubernetes-agent`).
|
||||
- Sets up a service account with `cluster-admin` rights. Read more on [how you can restrict this service account](#customize-the-permissions-for-the-agentk-service-account).
|
||||
- Creates a `Secret` resource for the agent registration token.
|
||||
- Creates a `Deployment` resource for the `agentk` pod.
|
||||
|
||||
The one-liner installer can be customized at the command line. To find out the various options the above Docker container supports, run:
|
||||
|
||||
```shell
|
||||
kubectl create namespace gitlab-kubernetes-agent
|
||||
```
|
||||
|
||||
To perform a one-liner installation, run the command below. Make sure to replace:
|
||||
|
||||
- `your-agent-token` with the token received from the previous step (identified as `secret` in the JSON output).
|
||||
- `gitlab-kubernetes-agent` with the namespace you defined in the previous step.
|
||||
- `wss://kas.gitlab.example.com` with the configured access of the Agent Server (KAS). For GitLab.com users, the KAS is available under `wss://kas.gitlab.com`.
|
||||
- `--agent-version=vX.Y.Z` with the latest released patch version matching your GitLab installation's major and minor versions. For example, for GitLab v13.9.0, use `--agent-version=v13.9.1`. You can find your GitLab version under the "Help/Help" menu.
|
||||
|
||||
```shell
|
||||
docker run --pull=always --rm registry.gitlab.com/gitlab-org/cluster-integration/gitlab-agent/cli:stable generate --agent-token=your-agent-token --kas-address=wss://kas.gitlab.example.com --agent-version=vX.Y.Z --namespace gitlab-kubernetes-agent | kubectl apply -f -
|
||||
docker run --pull=always --rm registry.gitlab.com/gitlab-org/cluster-integration/gitlab-agent/cli:stable generate --help
|
||||
```
|
||||
|
||||
WARNING:
|
||||
`--agent-version stable` can be used to refer to the latest stable release at the time when the command runs. It's fine for
|
||||
testing purposes but for production please make sure to specify a matching version explicitly.
|
||||
|
||||
To find out the various options the above Docker container supports, run:
|
||||
|
||||
```shell
|
||||
docker run --pull=always --rm registry.gitlab.com/gitlab-org/cluster-integration/gitlab-agent/cli:stable generate --help
|
||||
```
|
||||
|
||||
## Advanced installation
|
||||
## Advanced installation method
|
||||
|
||||
For more advanced configurations, we recommend to use [the `kpt` based installation method](https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent/-/tree/master/build/deployment/gitlab-agent).
|
||||
|
||||
Otherwise, follow the manual installation steps described below.
|
||||
|
||||
### Create the Kubernetes secret
|
||||
### Customize the permissions for the `agentk` service account
|
||||
|
||||
After generating the token, you must apply it to the Kubernetes cluster.
|
||||
The GitLab Agent for Kubernetes allows you to fully own your cluster and requires only the permissions you give. Still, for easy getting started, by default the generated manifests provide `cluster-admin` rights to the agent.
|
||||
|
||||
To create your Secret, run:
|
||||
As part of the advanced installation method, you can restrict the agent access rights using Kustomize overlays. [An example is commented out](https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent/-/blob/master/build/deployment/gitlab-agent/cluster/kustomization.yaml) in the `kpt` package you retrieved as part of the installation.
|
||||
|
||||
```shell
|
||||
kubectl create secret generic -n gitlab-kubernetes-agent gitlab-kubernetes-agent-token --from-literal=token='YOUR_AGENT_TOKEN'
|
||||
```
|
||||
To create restricted permissions:
|
||||
|
||||
The following example file contains the
|
||||
Kubernetes resources required for the Agent to be installed. You can modify this
|
||||
example [`resources.yml` file](#example-resourcesyml-file) in the following ways:
|
||||
1. Copy the `cluster` directory.
|
||||
1. Edit the `kustomization.yaml` and `components/*` files based on your requirements.
|
||||
1. Run `kustomize build <your copied directory> | kubectl apply -f -` to apply your configuration.
|
||||
|
||||
- Replace `namespace: gitlab-kubernetes-agent` with `namespace: <YOUR-DESIRED-NAMESPACE>`.
|
||||
- You can configure `kas-address` (Agent Server) in several ways.
|
||||
The agent can use the WebSockets or gRPC protocols to connect to the Agent Server.
|
||||
Select the option appropriate for your cluster configuration and GitLab architecture:
|
||||
- The `wss` scheme (an encrypted WebSockets connection) is specified by default
|
||||
after you install the `gitlab-kas` sub-chart, or enable `gitlab-kas` for Omnibus GitLab.
|
||||
When using the sub-chart, you must set `wss://kas.host.tld:443` as
|
||||
`kas-address`, where `host.tld` is the domain you've setup for your GitLab installation.
|
||||
When using Omnibus GitLab, you must set `wss://GitLab.host.tld:443/-/kubernetes-agent/` as
|
||||
`kas-address`, where `GitLab.host.tld` is your GitLab hostname.
|
||||
- When using the sub-chart, specify the `ws` scheme (such as `ws://kas.host.tld:80`)
|
||||
to use an unencrypted WebSockets connection.
|
||||
When using the Omnibus GitLab, specify the `ws` scheme (such as `ws://GitLab.host.tld:80/-/kubernetes-agent/`).
|
||||
- Specify the `grpc` scheme if both Agent and Server are installed in one cluster.
|
||||
In this case, you may specify `kas-address` value as
|
||||
`grpc://gitlab-kas.<your-namespace>:8150`) to use gRPC directly, where `gitlab-kas`
|
||||
is the name of the service created by `gitlab-kas` chart, and `<your-namespace>`
|
||||
is the namespace where the chart was installed.
|
||||
- Specify the `grpcs` scheme to use an encrypted gRPC connection.
|
||||
- When deploying KAS through the [GitLab chart](https://docs.gitlab.com/charts/), it's possible to customize the
|
||||
`kas-address` for `wss` and `ws` schemes to whatever you need.
|
||||
Check the [chart's KAS Ingress documentation](https://docs.gitlab.com/charts/charts/gitlab/kas/#ingress)
|
||||
to learn more about it.
|
||||
- In the near future, Omnibus GitLab intends to provision `gitlab-kas` under a sub-domain by default, instead of the `/-/kubernetes-agent/` path. Please follow [this issue](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/5784) for details.
|
||||
- If you defined your own secret name, replace `gitlab-kubernetes-agent-token` with your
|
||||
secret name in the `secretName:` section.
|
||||
|
||||
To apply this file, run the following command:
|
||||
|
||||
```shell
|
||||
kubectl apply -n gitlab-kubernetes-agent -f ./resources.yml
|
||||
```
|
||||
|
||||
To review your configuration, run the following command:
|
||||
|
||||
```shell
|
||||
$ kubectl get pods -n gitlab-kubernetes-agent
|
||||
|
||||
NAMESPACE NAME READY STATUS RESTARTS AGE
|
||||
gitlab-kubernetes-agent gitlab-kubernetes-agent-77689f7dcb-5skqk 1/1 Running 0 51s
|
||||
```
|
||||
|
||||
#### Example `resources.yml` file
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: gitlab-kubernetes-agent
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: gitlab-kubernetes-agent
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: gitlab-kubernetes-agent
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: gitlab-kubernetes-agent
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: gitlab-kubernetes-agent
|
||||
spec:
|
||||
serviceAccountName: gitlab-kubernetes-agent
|
||||
containers:
|
||||
- name: agent
|
||||
# Make sure to specify a matching version for production
|
||||
image: "registry.gitlab.com/gitlab-org/cluster-integration/gitlab-agent/agentk:vX.Y.Z"
|
||||
args:
|
||||
- --token-file=/config/token
|
||||
- --kas-address
|
||||
- wss://kas.host.tld:443 # replace this line with the line below if using Omnibus GitLab or GitLab.com.
|
||||
# - wss://gitlab.host.tld:443/-/kubernetes-agent/
|
||||
# - wss://kas.gitlab.com # for GitLab.com users, use this KAS.
|
||||
# - grpc://host.docker.internal:8150 # use this attribute when connecting from Docker.
|
||||
volumeMounts:
|
||||
- name: token-volume
|
||||
mountPath: /config
|
||||
volumes:
|
||||
- name: token-volume
|
||||
secret:
|
||||
secretName: gitlab-kubernetes-agent-token
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 0
|
||||
maxUnavailable: 1
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: gitlab-kubernetes-agent-write
|
||||
rules:
|
||||
- resources:
|
||||
- '*'
|
||||
apiGroups:
|
||||
- '*'
|
||||
verbs:
|
||||
- create
|
||||
- update
|
||||
- delete
|
||||
- patch
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: gitlab-kubernetes-agent-write-binding
|
||||
roleRef:
|
||||
name: gitlab-kubernetes-agent-write
|
||||
kind: ClusterRole
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
subjects:
|
||||
- name: gitlab-kubernetes-agent
|
||||
kind: ServiceAccount
|
||||
namespace: gitlab-kubernetes-agent
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: gitlab-kubernetes-agent-read
|
||||
rules:
|
||||
- resources:
|
||||
- '*'
|
||||
apiGroups:
|
||||
- '*'
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: gitlab-kubernetes-agent-read-binding
|
||||
roleRef:
|
||||
name: gitlab-kubernetes-agent-read
|
||||
kind: ClusterRole
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
subjects:
|
||||
- name: gitlab-kubernetes-agent
|
||||
kind: ServiceAccount
|
||||
namespace: gitlab-kubernetes-agent
|
||||
```
|
||||
|
||||
### Create manifest files
|
||||
|
||||
In a previous step, you configured a `config.yaml` to point to the GitLab projects
|
||||
the Agent should synchronize. Agent monitors each of those projects for changes to the manifest files it contains. You can auto-generate manifest files with a
|
||||
templating engine or other means.
|
||||
|
||||
The agent is authorized to download manifests for the configuration
|
||||
project, and public projects. Support for other private projects is
|
||||
planned in the issue [Agent authorization for private manifest
|
||||
projects](https://gitlab.com/gitlab-org/gitlab/-/issues/220912).
|
||||
|
||||
Each time you push a change to a monitored manifest repository, the Agent logs the change:
|
||||
|
||||
```plaintext
|
||||
2020-09-15_14:09:04.87946 gitlab-k8s-agent : time="2020-09-15T10:09:04-04:00" level=info msg="Config: new commit" agent_id=1 commit_id=e6a3651f1faa2e928fe6120e254c122451be4eea
|
||||
```
|
||||
|
||||
#### Example manifest file
|
||||
|
||||
This file creates a minimal `ConfigMap`:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: demo-map
|
||||
namespace: gitlab-kubernetes-agent # Can be any namespace managed by you that the agent has access to.
|
||||
data:
|
||||
key: value
|
||||
```
|
||||
The above setup allows you to regularly update from the upstream package using `kpt pkg update gitlab-agent --strategy resource-merge` and maintain your customizations at the same time.
|
||||
|
||||
## Example projects
|
||||
|
||||
|
|
|
|||
|
|
@ -30,11 +30,15 @@ module API
|
|||
end
|
||||
post "/*mailbox_type" do
|
||||
worker = Gitlab::MailRoom.worker_for(params[:mailbox_type])
|
||||
raw = request.body.read
|
||||
begin
|
||||
worker.perform_async(request.body.read)
|
||||
rescue Gitlab::SidekiqMiddleware::SizeLimiter::ExceedLimitError => e
|
||||
worker.perform_async(raw)
|
||||
rescue Gitlab::SidekiqMiddleware::SizeLimiter::ExceedLimitError
|
||||
receiver = Gitlab::Email::Receiver.new(raw)
|
||||
reason = Gitlab::Email::FailureHandler.handle(receiver, Gitlab::Email::EmailTooLarge.new)
|
||||
|
||||
status 400
|
||||
break { success: false, message: e.message }
|
||||
break { success: false, message: reason }
|
||||
end
|
||||
|
||||
status 200
|
||||
|
|
|
|||
|
|
@ -43,6 +43,9 @@ ci_pending_builds:
|
|||
- table: namespaces
|
||||
column: namespace_id
|
||||
on_delete: async_delete
|
||||
- table: projects
|
||||
column: project_id
|
||||
on_delete: async_delete
|
||||
ci_resource_groups:
|
||||
- table: projects
|
||||
column: project_id
|
||||
|
|
|
|||
|
|
@ -18,5 +18,6 @@ module Gitlab
|
|||
InvalidMergeRequestError = Class.new(InvalidRecordError)
|
||||
UnknownIncomingEmail = Class.new(ProcessingError)
|
||||
InvalidAttachment = Class.new(ProcessingError)
|
||||
EmailTooLarge = Class.new(ProcessingError)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Email
|
||||
module FailureHandler
|
||||
def self.handle(receiver, error)
|
||||
can_retry = false
|
||||
reason =
|
||||
case error
|
||||
when Gitlab::Email::UnknownIncomingEmail
|
||||
s_("EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface.")
|
||||
when Gitlab::Email::SentNotificationNotFoundError
|
||||
s_("EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface.")
|
||||
when Gitlab::Email::ProjectNotFound
|
||||
s_("EmailError|We couldn't find the project. Please check if there's any typo.")
|
||||
when Gitlab::Email::EmptyEmailError
|
||||
can_retry = true
|
||||
s_("EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies.")
|
||||
when Gitlab::Email::UserNotFoundError
|
||||
s_("EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface.")
|
||||
when Gitlab::Email::UserBlockedError
|
||||
s_("EmailError|Your account has been blocked. If you believe this is in error, contact a staff member.")
|
||||
when Gitlab::Email::UserNotAuthorizedError
|
||||
s_("EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member.")
|
||||
when Gitlab::Email::NoteableNotFoundError
|
||||
s_("EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member.")
|
||||
when Gitlab::Email::InvalidAttachment
|
||||
error.message
|
||||
when Gitlab::Email::InvalidRecordError
|
||||
can_retry = true
|
||||
error.message
|
||||
when Gitlab::Email::EmailTooLarge
|
||||
s_("EmailError|We couldn't process your email because it is too large. Please create your issue or comment through the web interface.")
|
||||
end
|
||||
|
||||
if reason
|
||||
receiver.mail.body = nil
|
||||
|
||||
EmailRejectionMailer.rejection(reason, receiver.mail.encoded, can_retry).deliver_later
|
||||
end
|
||||
|
||||
reason
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -10877,12 +10877,6 @@ msgstr ""
|
|||
msgid "DastProfiles|Could not create the site profile. Please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles|Could not delete saved scan. Please refresh the page, or try again later."
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles|Could not delete saved scans:"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles|Could not delete scanner profile. Please refresh the page, or try again later."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -10895,9 +10889,6 @@ msgstr ""
|
|||
msgid "DastProfiles|Could not delete site profiles:"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles|Could not fetch saved scans. Please refresh the page, or try again later."
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles|Could not fetch scanner profiles. Please refresh the page, or try again later."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -10910,9 +10901,6 @@ msgstr ""
|
|||
msgid "DastProfiles|Could not update the site profile. Please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles|DAST Scan"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles|Debug messages"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -10985,9 +10973,6 @@ msgstr ""
|
|||
msgid "DastProfiles|No scanner profiles created yet"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles|No scans saved yet"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles|No site profiles created yet"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -11015,9 +11000,6 @@ msgstr ""
|
|||
msgid "DastProfiles|Rest API"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles|Run scan"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles|Run the AJAX spider, in addition to the traditional spider, to crawl the target site."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -11027,12 +11009,6 @@ msgstr ""
|
|||
msgid "DastProfiles|Save profile"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles|Saved Scans"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles|Scan"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles|Scan mode"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -11045,9 +11021,6 @@ msgstr ""
|
|||
msgid "DastProfiles|Scanner name"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles|Schedule"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles|Select branch"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -11069,9 +11042,6 @@ msgstr ""
|
|||
msgid "DastProfiles|Spider timeout"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles|Target"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles|Target URL"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -13066,6 +13036,9 @@ msgstr ""
|
|||
msgid "EmailError|We couldn't find the project. Please check if there's any typo."
|
||||
msgstr ""
|
||||
|
||||
msgid "EmailError|We couldn't process your email because it is too large. Please create your issue or comment through the web interface."
|
||||
msgstr ""
|
||||
|
||||
msgid "EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,16 @@ RSpec.describe Admin::RunnersController do
|
|||
create(:ci_build, runner: runner, project: project)
|
||||
end
|
||||
|
||||
it 'redirects to the runner edit page' do
|
||||
it 'shows a runner show page' do
|
||||
get :show, params: { id: runner.id }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to render_template(:show)
|
||||
end
|
||||
|
||||
it 'when runner_read_only_admin_view is off, redirects to the runner edit page' do
|
||||
stub_feature_flags(runner_read_only_admin_view: false)
|
||||
|
||||
get :show, params: { id: runner.id }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:redirect)
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ RSpec.describe Profiles::EmailsController do
|
|||
end
|
||||
|
||||
context 'when email address is invalid' do
|
||||
let(:email) { 'invalid.@example.com' }
|
||||
let(:email) { 'invalid@@example.com' }
|
||||
|
||||
it 'does not send an email confirmation' do
|
||||
expect { subject }.not_to change { ActionMailer::Base.deliveries.size }
|
||||
|
|
|
|||
|
|
@ -36,6 +36,31 @@ RSpec.describe Projects::Security::ConfigurationController do
|
|||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to render_template(:show)
|
||||
end
|
||||
|
||||
it 'responds with configuration data json' do
|
||||
get :show, params: { namespace_id: project.namespace, project_id: project, format: :json }
|
||||
|
||||
features = json_response['features']
|
||||
sast_feature = features.find { |feature| feature['type'] == 'sast' }
|
||||
dast_feature = features.find { |feature| feature['type'] == 'dast' }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(sast_feature['available']).to be_truthy
|
||||
expect(dast_feature['available']).to be_falsey
|
||||
end
|
||||
|
||||
context 'with feature flag unify_security_configuration turned off' do
|
||||
before do
|
||||
stub_feature_flags(unify_security_configuration: false)
|
||||
end
|
||||
|
||||
it 'responds with empty configuration data json' do
|
||||
get :show, params: { namespace_id: project.namespace, project_id: project, format: :json }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -480,7 +480,8 @@ RSpec.describe "Admin Runners" do
|
|||
describe 'runner page breadcrumbs' do
|
||||
it 'contains the current runner id and token' do
|
||||
page.within '[data-testid="breadcrumb-links"]' do
|
||||
expect(page.find('h2')).to have_content("##{runner.id} (#{runner.short_sha})")
|
||||
expect(page).to have_link("##{runner.id} (#{runner.short_sha})")
|
||||
expect(page.find('h2')).to have_content("Edit")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -34,7 +34,9 @@ RSpec.describe 'Issue board filters', :js do
|
|||
it 'and submit one as filter', :aggregate_failures do
|
||||
expect(find('.board:nth-child(1)')).to have_selector('.board-card', count: 2)
|
||||
|
||||
expect_filtered_search_dropdown_results(filter_dropdown, 3)
|
||||
wait_for_requests
|
||||
|
||||
expect_filtered_search_dropdown_results(filter_dropdown, 4)
|
||||
|
||||
click_on user.username
|
||||
filter_submit.click
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ RSpec.describe 'Profile > Emails' do
|
|||
end
|
||||
|
||||
it 'does not add an invalid email' do
|
||||
fill_in('Email', with: 'test.@example.com')
|
||||
fill_in('Email', with: 'test@@example.com')
|
||||
click_button('Add email address')
|
||||
|
||||
email = user.emails.find_by(email: email)
|
||||
|
|
|
|||
|
|
@ -118,6 +118,7 @@ describe('BoardFilteredSearch', () => {
|
|||
it('sets the url params to the correct results', async () => {
|
||||
const mockFilters = [
|
||||
{ type: 'author', value: { data: 'root', operator: '=' } },
|
||||
{ type: 'assignee', value: { data: 'root', operator: '=' } },
|
||||
{ type: 'label', value: { data: 'label', operator: '=' } },
|
||||
{ type: 'label', value: { data: 'label2', operator: '=' } },
|
||||
{ type: 'milestone', value: { data: 'New Milestone', operator: '=' } },
|
||||
|
|
@ -133,7 +134,26 @@ describe('BoardFilteredSearch', () => {
|
|||
title: '',
|
||||
replace: true,
|
||||
url:
|
||||
'http://test.host/?author_username=root&label_name[]=label&label_name[]=label2&milestone_title=New+Milestone&iteration_id=3341&types=INCIDENT&weight=2&release_tag=v1.0.0',
|
||||
'http://test.host/?author_username=root&label_name[]=label&label_name[]=label2&assignee_username=root&milestone_title=New+Milestone&iteration_id=3341&types=INCIDENT&weight=2&release_tag=v1.0.0',
|
||||
});
|
||||
});
|
||||
|
||||
describe('when assignee is passed a wildcard value', () => {
|
||||
const url = (arg) => `http://test.host/?assignee_id=${arg}`;
|
||||
|
||||
it.each([
|
||||
['None', url('None')],
|
||||
['Any', url('Any')],
|
||||
])('sets the url param %s', (assigneeParam, expected) => {
|
||||
const mockFilters = [{ type: 'assignee', value: { data: assigneeParam, operator: '=' } }];
|
||||
jest.spyOn(urlUtility, 'updateHistory');
|
||||
findFilteredSearch().vm.$emit('onFilter', mockFilters);
|
||||
|
||||
expect(urlUtility.updateHistory).toHaveBeenCalledWith({
|
||||
title: '',
|
||||
replace: true,
|
||||
url: expected,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,6 +7,14 @@ import { mockCommit, mockJob } from '../mock_data';
|
|||
describe('Bridge Sidebar', () => {
|
||||
let wrapper;
|
||||
|
||||
const MockHeaderEl = {
|
||||
getBoundingClientRect() {
|
||||
return {
|
||||
bottom: '40',
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const createComponent = ({ featureFlag } = {}) => {
|
||||
wrapper = shallowMount(BridgeSidebar, {
|
||||
provide: {
|
||||
|
|
@ -44,6 +52,17 @@ describe('Bridge Sidebar', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('styles', () => {
|
||||
beforeEach(async () => {
|
||||
jest.spyOn(document, 'querySelector').mockReturnValue(MockHeaderEl);
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('calculates root styles correctly', () => {
|
||||
expect(wrapper.attributes('style')).toBe('width: 290px; top: 40px;');
|
||||
});
|
||||
});
|
||||
|
||||
describe('sidebar expansion', () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { createAlert } from '~/flash';
|
|||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import RunnerHeader from '~/runner/components/runner_header.vue';
|
||||
import getRunnerQuery from '~/runner/graphql/get_runner.query.graphql';
|
||||
import RunnerDetailsApp from '~/runner/runner_details/runner_details_app.vue';
|
||||
import AdminRunnerEditApp from '~//runner/admin_runner_edit/admin_runner_edit_app.vue';
|
||||
import { captureException } from '~/runner/sentry_utils';
|
||||
|
||||
import { runnerData } from '../mock_data';
|
||||
|
|
@ -21,14 +21,14 @@ const mockRunnerId = `${getIdFromGraphQLId(mockRunnerGraphqlId)}`;
|
|||
const localVue = createLocalVue();
|
||||
localVue.use(VueApollo);
|
||||
|
||||
describe('RunnerDetailsApp', () => {
|
||||
describe('AdminRunnerEditApp', () => {
|
||||
let wrapper;
|
||||
let mockRunnerQuery;
|
||||
|
||||
const findRunnerHeader = () => wrapper.findComponent(RunnerHeader);
|
||||
|
||||
const createComponentWithApollo = ({ props = {}, mountFn = shallowMount } = {}) => {
|
||||
wrapper = mountFn(RunnerDetailsApp, {
|
||||
wrapper = mountFn(AdminRunnerEditApp, {
|
||||
localVue,
|
||||
apolloProvider: createMockApollo([[getRunnerQuery, mockRunnerQuery]]),
|
||||
propsData: {
|
||||
|
|
@ -77,7 +77,7 @@ describe('RunnerDetailsApp', () => {
|
|||
it('error is reported to sentry', () => {
|
||||
expect(captureException).toHaveBeenCalledWith({
|
||||
error: new Error('Network error: Error!'),
|
||||
component: 'RunnerDetailsApp',
|
||||
component: 'AdminRunnerEditApp',
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -47,7 +47,7 @@ describe('RunnerTypeCell', () => {
|
|||
runner: {
|
||||
id: mockRunner.id,
|
||||
shortSha: mockRunner.shortSha,
|
||||
adminUrl: mockRunner.adminUrl,
|
||||
editAdminUrl: mockRunner.editAdminUrl,
|
||||
userPermissions: mockRunner.userPermissions,
|
||||
active: mockRunner.active,
|
||||
...runner,
|
||||
|
|
@ -103,7 +103,7 @@ describe('RunnerTypeCell', () => {
|
|||
it('Displays the runner edit link with the correct href', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findEditBtn().attributes('href')).toBe(mockRunner.adminUrl);
|
||||
expect(findEditBtn().attributes('href')).toBe(mockRunner.editAdminUrl);
|
||||
});
|
||||
|
||||
it('Does not render the runner edit link when user cannot update', () => {
|
||||
|
|
@ -117,9 +117,9 @@ describe('RunnerTypeCell', () => {
|
|||
expect(findEditBtn().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('Does not render the runner edit link when adminUrl is not provided', () => {
|
||||
it('Does not render the runner edit link when editAdminUrl is not provided', () => {
|
||||
createComponent({
|
||||
adminUrl: null,
|
||||
editAdminUrl: null,
|
||||
});
|
||||
|
||||
expect(findEditBtn().exists()).toBe(false);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
import { ACCESS_LEVEL_NOT_PROTECTED } from '~/runner/constants';
|
||||
import {
|
||||
modelToUpdateMutationVariables,
|
||||
runnerToModel,
|
||||
} from '~/runner/runner_details/runner_update_form_utils';
|
||||
import { modelToUpdateMutationVariables, runnerToModel } from '~/runner/runner_update_form_utils';
|
||||
|
||||
const mockId = 'gid://gitlab/Ci::Runner/1';
|
||||
const mockDescription = 'Runner Desc.';
|
||||
|
|
@ -23,7 +20,7 @@ const mockModel = {
|
|||
tagList: 'tag-1, tag-2',
|
||||
};
|
||||
|
||||
describe('~/runner/runner_details/runner_update_form_utils', () => {
|
||||
describe('~/runner/runner_update_form_utils', () => {
|
||||
describe('runnerToModel', () => {
|
||||
it('collects all model data', () => {
|
||||
expect(runnerToModel(mockRunner)).toEqual(mockModel);
|
||||
|
|
@ -10,10 +10,7 @@ import waitForPromises from 'helpers/wait_for_promises';
|
|||
import createFlash from '~/flash';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
|
||||
import {
|
||||
DEFAULT_LABEL_ANY,
|
||||
DEFAULT_NONE_ANY,
|
||||
} from '~/vue_shared/components/filtered_search_bar/constants';
|
||||
import { DEFAULT_NONE_ANY } from '~/vue_shared/components/filtered_search_bar/constants';
|
||||
import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue';
|
||||
import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
|
||||
|
||||
|
|
@ -276,7 +273,7 @@ describe('AuthorToken', () => {
|
|||
expect(wrapper.find(GlDropdownDivider).exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('renders `DEFAULT_LABEL_ANY` as default suggestions', async () => {
|
||||
it('renders `DEFAULT_NONE_ANY` as default suggestions', async () => {
|
||||
wrapper = createComponent({
|
||||
active: true,
|
||||
config: { ...mockAuthorToken, preloadedAuthors: mockPreloadedAuthors },
|
||||
|
|
@ -287,8 +284,9 @@ describe('AuthorToken', () => {
|
|||
|
||||
const suggestions = wrapper.findAll(GlFilteredSearchSuggestion);
|
||||
|
||||
expect(suggestions).toHaveLength(1 + currentUserLength);
|
||||
expect(suggestions.at(0).text()).toBe(DEFAULT_LABEL_ANY.text);
|
||||
expect(suggestions).toHaveLength(2 + currentUserLength);
|
||||
expect(suggestions.at(0).text()).toBe(DEFAULT_NONE_ANY[0].text);
|
||||
expect(suggestions.at(1).text()).toBe(DEFAULT_NONE_ANY[1].text);
|
||||
});
|
||||
|
||||
it('emits listeners in the base-token', () => {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Email::FailureHandler do
|
||||
let(:raw_message) { fixture_file('emails/valid_reply.eml') }
|
||||
let(:receiver) { Gitlab::Email::Receiver.new(raw_message) }
|
||||
|
||||
context 'email processing errors' do
|
||||
where(:error, :message, :can_retry) do
|
||||
[
|
||||
[Gitlab::Email::UnknownIncomingEmail, "We couldn't figure out what the email is for", false],
|
||||
[Gitlab::Email::SentNotificationNotFoundError, "We couldn't figure out what the email is in reply to", false],
|
||||
[Gitlab::Email::ProjectNotFound, "We couldn't find the project", false],
|
||||
[Gitlab::Email::EmptyEmailError, "It appears that the email is blank", true],
|
||||
[Gitlab::Email::UserNotFoundError, "We couldn't figure out what user corresponds to the email", false],
|
||||
[Gitlab::Email::UserBlockedError, "Your account has been blocked", false],
|
||||
[Gitlab::Email::UserNotAuthorizedError, "You are not allowed to perform this action", false],
|
||||
[Gitlab::Email::NoteableNotFoundError, "The thread you are replying to no longer exists", false],
|
||||
[Gitlab::Email::InvalidAttachment, "Could not deal with that", false],
|
||||
[Gitlab::Email::InvalidRecordError, "The note could not be created for the following reasons", true],
|
||||
[Gitlab::Email::EmailTooLarge, "it is too large", false]
|
||||
]
|
||||
end
|
||||
|
||||
with_them do
|
||||
it "sends out a rejection email for #{params[:error]}" do
|
||||
perform_enqueued_jobs do
|
||||
described_class.handle(receiver, error.new(message))
|
||||
end
|
||||
|
||||
email = ActionMailer::Base.deliveries.last
|
||||
expect(email).not_to be_nil
|
||||
expect(email.to).to match_array(["jake@adventuretime.ooo"])
|
||||
expect(email.subject).to include("Rejected")
|
||||
expect(email.body.parts.last.to_s).to include(message)
|
||||
end
|
||||
|
||||
it 'strips out the body before passing to EmailRejectionMailer' do
|
||||
mail = Mail.new(raw_message)
|
||||
mail.body = nil
|
||||
|
||||
expect(EmailRejectionMailer).to receive(:rejection).with(match(message), mail.encoded, can_retry).and_call_original
|
||||
|
||||
described_class.handle(receiver, error.new(message))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'non-processing errors' do
|
||||
where(:error) do
|
||||
[
|
||||
[Gitlab::Email::AutoGeneratedEmailError.new("")],
|
||||
[ActiveRecord::StatementTimeout.new("StatementTimeout")],
|
||||
[RateLimitedService::RateLimitedError.new(key: :issues_create, rate_limiter: nil)]
|
||||
]
|
||||
end
|
||||
|
||||
with_them do
|
||||
it "does not send a rejection email for #{params[:error]}" do
|
||||
perform_enqueued_jobs do
|
||||
described_class.handle(receiver, error)
|
||||
end
|
||||
|
||||
expect(ActionMailer::Base.deliveries).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -9,6 +9,11 @@ RSpec.describe Ci::JobToken::ProjectScopeLink do
|
|||
|
||||
let_it_be(:project) { create(:project) }
|
||||
|
||||
it_behaves_like 'cleanup by a loose foreign key' do
|
||||
let!(:parent) { create(:user) }
|
||||
let!(:model) { create(:ci_job_token_project_scope_link, added_by: parent) }
|
||||
end
|
||||
|
||||
describe 'unique index' do
|
||||
let!(:link) { create(:ci_job_token_project_scope_link) }
|
||||
|
||||
|
|
|
|||
|
|
@ -228,4 +228,9 @@ RSpec.describe Ci::PendingBuild do
|
|||
let!(:parent) { create(:namespace) }
|
||||
let!(:model) { create(:ci_pending_build, namespace: parent) }
|
||||
end
|
||||
|
||||
it_behaves_like 'cleanup by a loose foreign key' do
|
||||
let!(:parent) { create(:project) }
|
||||
let!(:model) { create(:ci_pending_build, project: parent) }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -23,6 +23,11 @@ RSpec.describe Ci::PipelineSchedule do
|
|||
subject { build(:ci_pipeline_schedule, project: project) }
|
||||
end
|
||||
|
||||
it_behaves_like 'cleanup by a loose foreign key' do
|
||||
let!(:parent) { create(:user) }
|
||||
let!(:model) { create(:ci_pipeline_schedule, owner: parent) }
|
||||
end
|
||||
|
||||
describe 'validations' do
|
||||
it 'does not allow invalid cron patterns' do
|
||||
pipeline_schedule = build(:ci_pipeline_schedule, cron: '0 0 0 * *')
|
||||
|
|
|
|||
|
|
@ -141,6 +141,11 @@ RSpec.describe Group, 'Routable', :with_clean_rails_cache do
|
|||
end
|
||||
end
|
||||
|
||||
it 'creates route with namespace referencing group' do
|
||||
expect(group.route).not_to be_nil
|
||||
expect(group.route.namespace).to eq(group)
|
||||
end
|
||||
|
||||
describe '.where_full_path_in' do
|
||||
context 'without any paths' do
|
||||
it 'returns an empty relation' do
|
||||
|
|
@ -208,30 +213,20 @@ RSpec.describe Project, 'Routable', :with_clean_rails_cache do
|
|||
it_behaves_like 'routable resource with parent' do
|
||||
let_it_be(:record) { project }
|
||||
end
|
||||
|
||||
it 'creates route with namespace referencing project namespace' do
|
||||
expect(project.route).not_to be_nil
|
||||
expect(project.route.namespace).to eq(project.project_namespace)
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.describe Namespaces::ProjectNamespace, 'Routable', :with_clean_rails_cache do
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project_namespace) do
|
||||
# For now we create only project namespace w/o project, otherwise same path
|
||||
# would be used for project and project namespace.
|
||||
# This can be removed when route is created automatically for project namespaces.
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/346448
|
||||
create(:project_namespace, project: nil, parent: group,
|
||||
visibility_level: Gitlab::VisibilityLevel::PUBLIC,
|
||||
path: 'foo', name: 'foo').tap do |project_namespace|
|
||||
Route.create!(source: project_namespace, path: project_namespace.full_path,
|
||||
name: project_namespace.full_name)
|
||||
end
|
||||
end
|
||||
|
||||
# we have couple of places where we use generic Namespace, in that case
|
||||
# we don't want to include ProjectNamespace routes yet
|
||||
it 'ignores project namespace when searching for generic namespace' do
|
||||
redirect_route = create(:redirect_route, source: project_namespace)
|
||||
|
||||
expect(Namespace.find_by_full_path(project_namespace.full_path)).to be_nil
|
||||
expect(Namespace.find_by_full_path(redirect_route.path, follow_redirects: true)).to be_nil
|
||||
it 'skips route creation for the resource' do
|
||||
expect do
|
||||
described_class.create!(project: nil, parent: group, visibility_level: Gitlab::VisibilityLevel::PUBLIC, path: 'foo', name: 'foo')
|
||||
end.not_to change { Route.count }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ RSpec.describe Email do
|
|||
end
|
||||
|
||||
describe 'validations' do
|
||||
it_behaves_like 'an object with RFC3696 compliant email-formatted attributes', :email do
|
||||
it_behaves_like 'an object with email-formatted attributes', :email do
|
||||
subject { build(:email) }
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -436,7 +436,7 @@ RSpec.describe User do
|
|||
subject { build(:user) }
|
||||
end
|
||||
|
||||
it_behaves_like 'an object with RFC3696 compliant email-formatted attributes', :public_email, :notification_email do
|
||||
it_behaves_like 'an object with email-formatted attributes', :public_email, :notification_email do
|
||||
subject { create(:user).tap { |user| user.emails << build(:email, email: email_value, confirmed_at: Time.current) } }
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -100,17 +100,26 @@ RSpec.describe API::Internal::MailRoom do
|
|||
)
|
||||
end
|
||||
|
||||
it 'responds with 400 bad request' do
|
||||
Sidekiq::Testing.fake! do
|
||||
expect do
|
||||
post api("/internal/mail_room/incoming_email"), headers: auth_headers, params: email_content
|
||||
end.not_to change { EmailReceiverWorker.jobs.size }
|
||||
it 'responds with 400 bad request and replies with a failure message' do
|
||||
perform_enqueued_jobs do
|
||||
Sidekiq::Testing.fake! do
|
||||
expect do
|
||||
post api("/internal/mail_room/incoming_email"), headers: auth_headers, params: email_content
|
||||
end.not_to change { EmailReceiverWorker.jobs.size }
|
||||
end
|
||||
end
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(Gitlab::Json.parse(response.body)).to match a_hash_including(
|
||||
{ "success" => false, "message" => "EmailReceiverWorker job exceeds payload size limit" }
|
||||
"success" => false,
|
||||
"message" => "We couldn't process your email because it is too large. Please create your issue or comment through the web interface."
|
||||
)
|
||||
|
||||
email = ActionMailer::Base.deliveries.last
|
||||
expect(email).not_to be_nil
|
||||
expect(email.to).to match_array(["jake@adventuretime.ooo"])
|
||||
expect(email.subject).to include("Rejected")
|
||||
expect(email.body.parts.last.to_s).to include("We couldn't process your email")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -21,87 +21,45 @@ RSpec.describe EmailReceiverWorker, :mailer do
|
|||
context "when an error occurs" do
|
||||
before do
|
||||
allow_any_instance_of(Gitlab::Email::Receiver).to receive(:execute).and_raise(error)
|
||||
expect(Sidekiq.logger).to receive(:error).with(hash_including('exception.class' => error.class.name)).and_call_original
|
||||
end
|
||||
|
||||
context 'when the error is Gitlab::Email::EmptyEmailError' do
|
||||
context 'when error is a processing error' do
|
||||
let(:error) { Gitlab::Email::EmptyEmailError.new }
|
||||
|
||||
it 'sends out a rejection email' do
|
||||
perform_enqueued_jobs do
|
||||
described_class.new.perform(raw_message)
|
||||
it 'triggers email failure handler' do
|
||||
expect(Gitlab::Email::FailureHandler).to receive(:handle) do |receiver, received_error|
|
||||
expect(receiver).to be_a(Gitlab::Email::Receiver)
|
||||
expect(receiver.mail.encoded).to eql(Mail::Message.new(raw_message).encoded)
|
||||
expect(received_error).to be(error)
|
||||
end
|
||||
|
||||
email = ActionMailer::Base.deliveries.last
|
||||
expect(email).not_to be_nil
|
||||
expect(email.to).to eq(["jake@adventuretime.ooo"])
|
||||
expect(email.subject).to include("Rejected")
|
||||
described_class.new.perform(raw_message)
|
||||
end
|
||||
|
||||
it 'strips out the body before passing to EmailRejectionMailer' do
|
||||
mail = Mail.new(raw_message)
|
||||
mail.body = nil
|
||||
|
||||
expect(EmailRejectionMailer).to receive(:rejection).with(anything, mail.encoded, anything).and_call_original
|
||||
it 'logs the error' do
|
||||
expect(Sidekiq.logger).to receive(:error).with(hash_including('exception.class' => error.class.name)).and_call_original
|
||||
|
||||
described_class.new.perform(raw_message)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the error is Gitlab::Email::AutoGeneratedEmailError' do
|
||||
let(:error) { Gitlab::Email::AutoGeneratedEmailError.new }
|
||||
|
||||
it 'does not send out any rejection email' do
|
||||
perform_enqueued_jobs do
|
||||
described_class.new.perform(raw_message)
|
||||
end
|
||||
|
||||
should_not_email_anyone
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the error is Gitlab::Email::InvalidAttachment' do
|
||||
let(:error) { Gitlab::Email::InvalidAttachment.new("Could not deal with that") }
|
||||
|
||||
it 'reports the error to the sender' do
|
||||
perform_enqueued_jobs do
|
||||
described_class.new.perform(raw_message)
|
||||
end
|
||||
|
||||
email = ActionMailer::Base.deliveries.last
|
||||
expect(email).not_to be_nil
|
||||
expect(email.to).to eq(["jake@adventuretime.ooo"])
|
||||
expect(email.body.parts.last.to_s).to include("Could not deal with that")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the error is ActiveRecord::StatementTimeout' do
|
||||
context 'when error is not a processing error' do
|
||||
let(:error) { ActiveRecord::StatementTimeout.new("Statement timeout") }
|
||||
|
||||
it 'does not report the error to the sender' do
|
||||
expect(Gitlab::ErrorTracking).to receive(:track_exception).with(error).and_call_original
|
||||
|
||||
perform_enqueued_jobs do
|
||||
described_class.new.perform(raw_message)
|
||||
it 'triggers email failure handler' do
|
||||
expect(Gitlab::Email::FailureHandler).to receive(:handle) do |receiver, received_error|
|
||||
expect(receiver).to be_a(Gitlab::Email::Receiver)
|
||||
expect(receiver.mail.encoded).to eql(Mail::Message.new(raw_message).encoded)
|
||||
expect(received_error).to be(error)
|
||||
end
|
||||
|
||||
email = ActionMailer::Base.deliveries.last
|
||||
expect(email).to be_nil
|
||||
described_class.new.perform(raw_message)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the error is RateLimitedService::RateLimitedError' do
|
||||
let(:error) { RateLimitedService::RateLimitedError.new(key: :issues_create, rate_limiter: Gitlab::ApplicationRateLimiter) }
|
||||
|
||||
it 'does not report the error to the sender' do
|
||||
it 'reports the error' do
|
||||
expect(Gitlab::ErrorTracking).to receive(:track_exception).with(error).and_call_original
|
||||
|
||||
perform_enqueued_jobs do
|
||||
described_class.new.perform(raw_message)
|
||||
end
|
||||
|
||||
email = ActionMailer::Base.deliveries.last
|
||||
expect(email).to be_nil
|
||||
described_class.new.perform(raw_message)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue