Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-01-13 12:14:38 +00:00
parent 5d0c43f60d
commit a00537e412
63 changed files with 498 additions and 535 deletions

View File

@ -1 +1 @@
1.50.0
1.51.0

View File

@ -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;

View File

@ -120,6 +120,7 @@ export const FilterFields = {
/* eslint-disable @gitlab/require-i18n-strings */
export const AssigneeFilterType = {
any: 'Any',
none: 'None',
};
export const MilestoneFilterType = {

View File

@ -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

View File

@ -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"

View File

@ -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();
});
};

View File

@ -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"

View File

@ -1,3 +1,3 @@
import { initRunnerDetail } from '~/runner/runner_details';
import { initAdminRunnerEdit } from '~/runner/admin_runner_edit';
initRunnerDetail();
initAdminRunnerEdit();

View File

@ -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,

View File

@ -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,
},

View File

@ -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"

View File

@ -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';

View File

@ -26,6 +26,7 @@ query getRunners(
nodes {
...RunnerNode
adminUrl
editAdminUrl
}
pageInfo {
...PageInfo

View File

@ -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);
}
}
});
}
}

View File

@ -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 || [];

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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?

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
0cb120b0036b3f472edb57fcb8a52877d399edf8ff1f416ce76497d6aa8265d7

View File

@ -0,0 +1 @@
4a90811aace678528b75171868fb178ab885d5aac885048e8eacecaf8b0ee374

View File

@ -0,0 +1 @@
983e5522b2798ca0d17ca3fd848f527b12afdbdd1482a2d420c4c6ce4fa2c9c4

View File

@ -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;

View File

@ -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

View File

@ -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). | |

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -18,5 +18,6 @@ module Gitlab
InvalidMergeRequestError = Class.new(InvalidRecordError)
UnknownIncomingEmail = Class.new(ProcessingError)
InvalidAttachment = Class.new(ProcessingError)
EmailTooLarge = Class.new(ProcessingError)
end
end

View File

@ -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

View File

@ -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 ""

View File

@ -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)

View File

@ -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 }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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,
});
});
});
});

View File

@ -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();

View File

@ -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',
});
});

View File

@ -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);

View File

@ -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);

View File

@ -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', () => {

View File

@ -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

View File

@ -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) }

View File

@ -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

View File

@ -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 * *')

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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