Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-02-12 12:09:02 +00:00
parent 7f5e08060f
commit 49d26b2348
113 changed files with 1830 additions and 558 deletions

View File

@ -160,7 +160,6 @@ linters:
- 'app/views/projects/_gitlab_import_modal.html.haml'
- 'app/views/projects/_home_panel.html.haml'
- 'app/views/projects/_import_project_pane.html.haml'
- 'app/views/projects/_issuable_by_email.html.haml'
- 'app/views/projects/_readme.html.haml'
- 'app/views/projects/artifacts/_artifact.html.haml'
- 'app/views/projects/artifacts/_tree_file.html.haml'

View File

@ -2473,50 +2473,3 @@ Gitlab/NamespacedClass:
- 'spec/support/sidekiq_middleware.rb'
- 'spec/tasks/gitlab/task_helpers_spec.rb'
- 'spec/uploaders/object_storage_spec.rb'
# WIP: https://gitlab.com/gitlab-org/gitlab/-/issues/299105
Style/FrozenStringLiteralComment:
Exclude:
- 'Gemfile'
- 'Rakefile'
- 'app/views/dashboard/issues.atom.builder'
- 'app/views/dashboard/projects/index.atom.builder'
- 'app/views/events/_event.atom.builder'
- 'app/views/groups/issues.atom.builder'
- 'app/views/groups/show.atom.builder'
- 'app/views/issues/_issue.atom.builder'
- 'app/views/issues/_issues_calendar.ics.ruby'
- 'app/views/layouts/xml.atom.builder'
- 'app/views/projects/commits/_commit.atom.builder'
- 'app/views/projects/commits/show.atom.builder'
- 'app/views/projects/issues/index.atom.builder'
- 'app/views/projects/show.atom.builder'
- 'app/views/projects/tags/_tag.atom.builder'
- 'app/views/projects/tags/index.atom.builder'
- 'app/views/users/show.atom.builder'
- 'bin/secpick'
- 'danger/changes_size/Dangerfile'
- 'danger/metadata/Dangerfile'
- 'scripts/flaky_examples/detect-new-flaky-examples'
- 'scripts/flaky_examples/prune-old-flaky-examples'
- 'scripts/gather-test-memory-data'
- 'scripts/generate-gems-memory-metrics-static'
- 'scripts/generate-gems-size-metrics-static'
- 'scripts/generate-memory-metrics-on-boot'
- 'scripts/generate-test-mapping'
- 'scripts/gitaly-test-build'
- 'scripts/gitaly-test-spawn'
- 'scripts/gitaly_test.rb'
- 'scripts/insert-rspec-profiling-data'
- 'scripts/lint-rugged'
- 'scripts/merge-html-reports'
- 'scripts/merge-reports'
- 'scripts/merge-simplecov'
- 'scripts/no-ee-check'
- 'scripts/pack-test-mapping'
- 'scripts/static-analysis'
- 'scripts/sync-reports'
- 'scripts/unpack-test-mapping'
- 'scripts/update-feature-categories'
- 'scripts/used-feature-flags'
- 'scripts/verify-tff-mapping'

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
source 'https://rubygems.org'
gem 'rails', '~> 6.0.3.1'

View File

@ -1,4 +1,6 @@
#!/usr/bin/env rake
# frozen_string_literal: true
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.

View File

@ -0,0 +1 @@
export const POPOVER_TARGET_ID = 'feature-highlight-trigger';

View File

@ -0,0 +1,101 @@
<script>
import {
GlPopover,
GlSprintf,
GlLink,
GlButton,
GlSafeHtmlDirective as SafeHtml,
} from '@gitlab/ui';
import clusterPopover from '@gitlab/svgs/dist/illustrations/cluster_popover.svg';
import { __ } from '~/locale';
import { dismiss } from './feature_highlight_helper';
import { POPOVER_TARGET_ID } from './constants';
export default {
components: {
GlPopover,
GlSprintf,
GlLink,
GlButton,
},
directives: {
SafeHtml,
},
props: {
autoDevopsHelpPath: {
type: String,
required: true,
},
highlightId: {
type: String,
required: true,
},
dismissEndpoint: {
type: String,
required: true,
},
},
data() {
return {
dismissed: false,
triggerHidden: false,
};
},
methods: {
dismiss() {
dismiss(this.dismissEndpoint, this.highlightId);
this.$refs.popover.$emit('close');
this.dismissed = true;
},
hideTrigger() {
if (this.dismissed) {
this.triggerHidden = true;
}
},
},
clusterPopover,
targetId: POPOVER_TARGET_ID,
i18n: {
highlightMessage: __('Allows you to add and manage Kubernetes clusters.'),
autoDevopsProTipMessage: __(
'Protip: %{linkStart}Auto DevOps%{linkEnd} uses Kubernetes clusters to deploy your code!',
),
dismissButtonLabel: __('Got it!'),
},
};
</script>
<template>
<div class="gl-ml-3">
<span v-if="!triggerHidden" :id="$options.targetId" class="feature-highlight"></span>
<gl-popover
ref="popover"
:target="$options.targetId"
:css-classes="['feature-highlight-popover']"
triggers="hover"
container="body"
placement="right"
boundary="viewport"
@hidden="hideTrigger"
>
<span
v-safe-html="$options.clusterPopover"
class="feature-highlight-illustration gl-display-flex gl-justify-content-center gl-py-4 gl-w-full"
></span>
<div class="gl-px-4 gl-py-5">
<p>
{{ $options.i18n.highlightMessage }}
</p>
<p>
<gl-sprintf :message="$options.i18n.autoDevopsProTipMessage">
<template #link="{ content }">
<gl-link class="gl-font-sm" :href="autoDevopsHelpPath">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
<gl-button size="small" icon="thumb-up" variant="confirm" @click="dismiss">
{{ $options.i18n.dismissButtonLabel }}
</gl-button>
</div>
</gl-popover>
</div>
</template>

View File

@ -0,0 +1,28 @@
import Vue from 'vue';
const init = async () => {
const el = document.querySelector('.js-feature-highlight');
if (!el) {
return null;
}
const { autoDevopsHelpPath, highlight: highlightId, dismissEndpoint } = el.dataset;
const { default: FeatureHighlight } = await import(
/* webpackChunkName: 'feature_highlight' */ './feature_highlight_popover.vue'
);
return new Vue({
el,
render: (h) =>
h(FeatureHighlight, {
props: {
autoDevopsHelpPath,
highlightId,
dismissEndpoint,
},
}),
});
};
export default init;

View File

@ -0,0 +1,169 @@
<script>
import {
GlButton,
GlModal,
GlModalDirective,
GlTooltipDirective,
GlSprintf,
GlLink,
GlFormInputGroup,
GlIcon,
} from '@gitlab/ui';
import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
import { sprintf, __ } from '~/locale';
import axios from '~/lib/utils/axios_utils';
export default {
name: 'IssuableByEmail',
components: {
GlButton,
GlModal,
GlSprintf,
GlLink,
GlFormInputGroup,
GlIcon,
ModalCopyButton,
},
directives: {
GlModal: GlModalDirective,
GlTooltip: GlTooltipDirective,
},
inject: {
initialEmail: {
default: null,
},
issuableType: {
default: '',
},
emailsHelpPagePath: {
default: '',
},
quickActionsHelpPath: {
default: '',
},
markdownHelpPath: {
default: '',
},
resetPath: {
default: '',
},
},
data() {
return {
email: this.initialEmail,
// eslint-disable-next-line @gitlab/require-i18n-strings
issuableName: this.issuableType === 'issue' ? 'issue' : 'merge request',
};
},
computed: {
mailToLink() {
const subject = sprintf(__('Enter the %{name} title'), {
name: this.issuableName,
});
const body = sprintf(__('Enter the %{name} description'), {
name: this.issuableName,
});
// eslint-disable-next-line @gitlab/require-i18n-strings
return `mailto:${this.email}?subject=${subject}&body=${body}`;
},
},
methods: {
async resetIncomingEmailToken() {
try {
const {
data: { new_address: newAddress },
} = await axios.put(this.resetPath);
this.email = newAddress;
} catch {
this.$toast.show(__('There was an error when reseting email token.'), { type: 'error' });
}
},
cancelHandler() {
this.$refs.modal.hide();
},
},
modalId: 'issuable-email-modal',
};
</script>
<template>
<div>
<gl-button v-gl-modal="$options.modalId" variant="link" data-testid="issuable-email-modal-btn"
><gl-sprintf :message="__('Email a new %{name} to this project')"
><template #name>{{ issuableName }}</template></gl-sprintf
></gl-button
>
<gl-modal ref="modal" :modal-id="$options.modalId">
<template #modal-title>
<gl-sprintf :message="__('Create new %{name} by email')">
<template #name>{{ issuableName }}</template>
</gl-sprintf>
</template>
<p>
<gl-sprintf
:message="
__(
'You can create a new %{name} inside this project by sending an email to the following email address:',
)
"
>
<template #name>{{ issuableName }}</template>
</gl-sprintf>
</p>
<gl-form-input-group :value="email" readonly select-on-click class="gl-mb-4">
<template #append>
<modal-copy-button :text="email" :title="__('Copy')" :modal-id="$options.modalId" />
<gl-button
v-gl-tooltip.hover
:href="mailToLink"
:title="__('Send email')"
icon="mail"
data-testid="mail-to-btn"
/>
</template>
</gl-form-input-group>
<p>
<gl-sprintf
:message="
__(
'The subject will be used as the title of the new issue, and the message will be the description. %{quickActionsLinkStart}Quick actions%{quickActionsLinkEnd} and styling with %{markdownLinkStart}Markdown%{markdownLinkEnd} are supported.',
)
"
>
<template #quickActionsLink="{ content }">
<gl-link :href="quickActionsHelpPath" target="_blank">{{ content }}</gl-link>
</template>
<template #markdownLink="{ content }">
<gl-link :href="markdownHelpPath" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
<p>
<gl-sprintf
:message="
__(
'This is a private email address %{helpIcon} generated just for you. Anyone who gets ahold of it can create issues or merge requests as if they were you. You should %{resetLinkStart}reset it%{resetLinkEnd} if that ever happens.',
)
"
>
<template #helpIcon>
<gl-link :href="emailsHelpPagePath" target="_blank"
><gl-icon class="gl-text-blue-600" name="question-o"
/></gl-link>
</template>
<template #resetLink="{ content }">
<gl-button
variant="link"
data-testid="incoming-email-token-reset"
@click="resetIncomingEmailToken"
>{{ content }}</gl-button
>
</template>
</gl-sprintf>
</p>
<template #modal-footer>
<gl-button category="secondary" @click="cancelHandler">{{ s__('Cancel') }}</gl-button>
</template>
</gl-modal>
</div>
</template>

View File

@ -0,0 +1,35 @@
import Vue from 'vue';
import { GlToast } from '@gitlab/ui';
import IssuableByEmail from './components/issuable_by_email.vue';
Vue.use(GlToast);
export default () => {
const el = document.querySelector('.js-issueable-by-email');
if (!el) return null;
const {
initialEmail,
issuableType,
emailsHelpPagePath,
quickActionsHelpPath,
markdownHelpPath,
resetPath,
} = el.dataset;
return new Vue({
el,
provide: {
initialEmail,
issuableType,
emailsHelpPagePath,
quickActionsHelpPath,
markdownHelpPath,
resetPath,
},
render(h) {
return h(IssuableByEmail);
},
});
};

View File

@ -1,35 +1,7 @@
import $ from 'jquery';
import axios from './lib/utils/axios_utils';
import { deprecatedCreateFlash as flash } from './flash';
import { s__, __ } from './locale';
import issuableInitBulkUpdateSidebar from './issuable_init_bulk_update_sidebar';
export default class IssuableIndex {
constructor(pagePrefix) {
issuableInitBulkUpdateSidebar.init(pagePrefix);
IssuableIndex.resetIncomingEmailToken();
}
static resetIncomingEmailToken() {
const $resetToken = $('.incoming-email-token-reset');
$resetToken.on('click', (e) => {
e.preventDefault();
$resetToken.text(s__('EmailToken|resetting...'));
axios
.put($resetToken.attr('href'))
.then(({ data }) => {
$('#issuable_email').val(data.new_address).focus();
$resetToken.text(s__('EmailToken|reset it'));
})
.catch(() => {
flash(__('There was an error when reseting email token.'));
$resetToken.text(s__('EmailToken|reset it'));
});
});
}
}

View File

@ -9,6 +9,7 @@ import { FILTERED_SEARCH } from '~/pages/constants';
import { ISSUABLE_INDEX } from '~/pages/projects/constants';
import initIssuablesList from '~/issues_list';
import initManualOrdering from '~/manual_ordering';
import initIssuableByEmail from '~/issuable/init_issuable_by_email';
IssuableFilteredSearchTokenKeys.addExtraTokensForIssues();
@ -24,3 +25,4 @@ new UsersSelect();
initManualOrdering();
initIssuablesList();
initIssuableByEmail();

View File

@ -6,6 +6,7 @@ import initFilteredSearch from '~/pages/search/init_filtered_search';
import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
import { FILTERED_SEARCH } from '~/pages/constants';
import { ISSUABLE_INDEX } from '~/pages/projects/constants';
import initIssuableByEmail from '~/issuable/init_issuable_by_email';
new IssuableIndex(ISSUABLE_INDEX.MERGE_REQUEST); // eslint-disable-line no-new
@ -19,3 +20,5 @@ initFilteredSearch({
new UsersSelect(); // eslint-disable-line no-new
new ShortcutsNavigation(); // eslint-disable-line no-new
initIssuableByEmail();

View File

@ -1,3 +1,3 @@
import initActivityCharts from '~/analytics/product_analytics/activity_charts_bundle';
document.addEventListener('DOMContentLoaded', () => initActivityCharts());
initActivityCharts();

View File

@ -83,6 +83,9 @@ export default {
alertId: {
default: '',
},
isThreatMonitoringPage: {
default: false,
},
projectId: {
default: '',
},
@ -364,7 +367,11 @@ export default {
</alert-summary-row>
<alert-details-table :alert="alert" :loading="loading" />
</gl-tab>
<gl-tab :data-testid="$options.tabsConfig[1].id" :title="$options.tabsConfig[1].title">
<gl-tab
v-if="isThreatMonitoringPage"
:data-testid="$options.tabsConfig[1].id"
:title="$options.tabsConfig[1].title"
>
<alert-metrics :dashboard-url="alert.metricsDashboardUrl" />
</gl-tab>
<gl-tab :data-testid="$options.tabsConfig[2].id" :title="$options.tabsConfig[2].title">

View File

@ -19,6 +19,10 @@ export default {
projectId: {
default: '',
},
// TODO remove this limitation in https://gitlab.com/gitlab-org/gitlab/-/issues/296717
isThreatMonitoringPage: {
default: false,
},
},
props: {
alert: {
@ -62,6 +66,7 @@ export default {
@alert-error="$emit('alert-error', $event)"
/>
<sidebar-status
v-if="!isThreatMonitoringPage"
:project-path="projectPath"
:alert="alert"
@toggle-sidebar="$emit('toggle-sidebar')"

View File

@ -9,11 +9,10 @@ export const SEVERITY_LEVELS = {
UNKNOWN: s__('severity|Unknown'),
};
export const DEFAULT_PAGE = 'OPERATIONS';
/* eslint-disable @gitlab/require-i18n-strings */
export const PAGE_CONFIG = {
OPERATIONS: {
TITLE: 'OPERATIONS',
// Tracks snowplow event when user views alert details
TRACK_ALERTS_DETAILS_VIEWS_OPTIONS: {
category: 'Alert Management',
@ -26,4 +25,7 @@ export const PAGE_CONFIG = {
label: 'Status',
},
},
THREAT_MONITORING: {
TITLE: 'THREAT_MONITORING',
},
};

View File

@ -6,13 +6,13 @@ import createDefaultClient from '~/lib/graphql';
import AlertDetails from './components/alert_details.vue';
import sidebarStatusQuery from './graphql/queries/alert_sidebar_status.query.graphql';
import createRouter from './router';
import { DEFAULT_PAGE, PAGE_CONFIG } from './constants';
import { PAGE_CONFIG } from './constants';
Vue.use(VueApollo);
export default (selector) => {
const domEl = document.querySelector(selector);
const { alertId, projectPath, projectIssuesPath, projectId, page = DEFAULT_PAGE } = domEl.dataset;
const { alertId, projectPath, projectIssuesPath, projectId, page } = domEl.dataset;
const router = createRouter();
const resolvers = {
@ -52,16 +52,19 @@ export default (selector) => {
const provide = {
projectPath,
alertId,
page,
projectIssuesPath,
projectId,
};
if (page === DEFAULT_PAGE) {
if (page === PAGE_CONFIG.OPERATIONS.TITLE) {
const { TRACK_ALERTS_DETAILS_VIEWS_OPTIONS, TRACK_ALERT_STATUS_UPDATE_OPTIONS } = PAGE_CONFIG[
page
];
provide.trackAlertsDetailsViewsOptions = TRACK_ALERTS_DETAILS_VIEWS_OPTIONS;
provide.trackAlertStatusUpdateOptions = TRACK_ALERT_STATUS_UPDATE_OPTIONS;
} else if (page === PAGE_CONFIG.THREAT_MONITORING.TITLE) {
provide.isThreatMonitoringPage = true;
}
// eslint-disable-next-line no-new

View File

@ -5,7 +5,7 @@
$bs-input-focus-border: #80bdff;
$bs-input-focus-box-shadow: rgba(0, 123, 255, 0.25);
a:not(.btn):not(.gl-tab-nav-item),
a:not(.btn),
.gl-button.btn-link,
.gl-button.btn-link:hover,
.gl-button.btn-link:focus,
@ -34,6 +34,7 @@
.ide-pipeline .top-bar .controllers .controllers-buttons,
.controllers-buttons svg,
.nav-links li a.active,
.gl-tabs-nav li a.gl-tab-nav-item-active,
.md-area.is-focused {
color: var(--ide-text-color, $gl-text-color);
}
@ -44,13 +45,15 @@
}
.nav-links:not(.quick-links) li:not(.md-header-toolbar) a,
.gl-tabs-nav li a,
.dropdown-menu-inner-content,
.file-row .file-row-icon svg,
.file-row:hover .file-row-icon svg {
color: var(--ide-text-color-secondary, $gl-text-color-secondary);
}
.nav-links:not(.quick-links) li:not(.md-header-toolbar) {
.nav-links:not(.quick-links) li:not(.md-header-toolbar),
.gl-tabs-nav li {
&:hover a,
&.active a,
a:hover,
@ -61,6 +64,10 @@
border-color: var(--ide-input-border, $gray-darkest);
}
}
a.gl-tab-nav-item-active {
box-shadow: inset 0 -2px 0 0 var(--ide-input-border, $gray-darkest);
}
}
.drag-handle:hover {
@ -142,6 +149,7 @@
.md table:not(.code) tbody td,
.md table:not(.code) tr th,
.nav-links:not(.quick-links),
.gl-tabs-nav,
.common-note-form .md-area.is-focused .nav-links {
border-color: var(--ide-border-color-alt, $white-dark);
}
@ -175,6 +183,10 @@
border-color: var(--ide-highlight-accent, $gl-text-color);
}
.gl-tabs-nav li a.gl-tab-nav-item-active {
box-shadow: inset 0 -2px 0 0 var(--ide-highlight-accent, $gl-text-color);
}
// for other themes, suppress different avatar default colors for simplicity
.avatar-container {
&,
@ -304,6 +316,11 @@
border-color: var(--ide-dropdown-hover-background, $border-color);
}
.gl-tabs-nav {
background-color: var(--ide-dropdown-hover-background, $white);
box-shadow: inset 0 -2px 0 0 var(--ide-dropdown-hover-background, $border-color);
}
.divider {
background-color: var(--ide-dropdown-hover-background, $gray-100);
border-color: var(--ide-dropdown-hover-background, $gray-100);

View File

@ -97,7 +97,8 @@ $ide-commit-header-height: 48px;
border-right: 1px solid var(--ide-border-color, $white-dark);
border-bottom: 1px solid var(--ide-border-color, $white-dark);
&.active {
&.active,
.gl-tab-nav-item-active {
background-color: var(--ide-highlight-background, $white);
border-bottom-color: transparent;
}
@ -114,6 +115,42 @@ $ide-commit-header-height: 48px;
}
}
}
.gl-tab-content {
padding: 0;
}
.gl-tabs-nav {
border-width: 0;
li {
padding: 0 !important;
background: transparent !important;
border: 0 !important;
a {
display: flex;
align-items: center;
padding: $grid-size $gl-padding !important;
box-shadow: none !important;
font-weight: normal !important;
background-color: var(--ide-background-hover, $gray-normal);
border-right: 1px solid var(--ide-border-color, $white-dark);
border-bottom: 1px solid var(--ide-border-color, $white-dark);
&.gl-tab-nav-item-active {
background-color: var(--ide-highlight-background, $white);
border-color: var(--ide-border-color, $white-dark);
border-bottom-color: transparent;
}
.multi-file-tab-close svg {
top: 0;
}
}
}
}
}
.multi-file-tab {
@ -634,7 +671,8 @@ $ide-commit-header-height: 48px;
height: 100%;
}
.nav-links {
.nav-links,
.gl-tabs-nav {
height: 30px;
}
@ -976,17 +1014,25 @@ $ide-commit-header-height: 48px;
}
.ide-nav-form {
.nav-links li {
.nav-links li,
.gl-tabs-nav li {
width: 50%;
padding-left: 0;
padding-right: 0;
a {
text-align: center;
font-size: 14px;
line-height: 30px;
&:not(.active) {
&:not(.active),
&:not(.gl-tab-nav-item-active) {
background-color: var(--ide-dropdown-background, $gray-light);
}
&.gl-tab-nav-item-active {
font-weight: bold;
}
}
}

View File

@ -108,18 +108,6 @@ ul.related-merge-requests > li {
}
}
.issuable-email-modal-btn {
padding: 0;
color: $blue-600;
background-color: transparent;
border: 0;
outline: 0;
&:hover {
text-decoration: underline;
}
}
.email-modal-input-group {
margin-bottom: 10px;

View File

@ -178,8 +178,8 @@ module EventsHelper
def event_note_target_url(event)
if event.commit_note?
project_commit_url(event.project, event.note_target, anchor: dom_id(event.target))
elsif event.project_snippet_note?
project_snippet_url(event.project, event.note_target, anchor: dom_id(event.target))
elsif event.snippet_note?
gitlab_snippet_url(event.note_target, anchor: dom_id(event.target))
elsif event.issue_note?
project_issue_url(event.project, id: event.note_target, anchor: dom_id(event.target))
elsif event.merge_request_note?

View File

@ -20,7 +20,8 @@ module Projects::AlertManagementHelper
'alert-id' => alert_id,
'project-path' => project.full_path,
'project-id' => project.id,
'project-issues-path' => project_issues_path(project)
'project-issues-path' => project_issues_path(project),
'page' => 'OPERATIONS'
}
end

View File

@ -294,10 +294,14 @@ class Event < ApplicationRecord
note? && target && target.for_merge_request?
end
def project_snippet_note?
def snippet_note?
note? && target && target.for_snippet?
end
def project_snippet_note?
note? && target && target.for_project_snippet?
end
def personal_snippet_note?
note? && target && target.for_personal_snippet?
end

View File

@ -259,6 +259,10 @@ class Note < ApplicationRecord
noteable_type == 'AlertManagement::Alert'
end
def for_project_snippet?
noteable.is_a?(ProjectSnippet)
end
def for_personal_snippet?
noteable.is_a?(PersonalSnippet)
end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
# rubocop: disable CodeReuse/ActiveRecord
xml.title "#{current_user.name} issues"
xml.link href: url_for(safe_params), rel: "self", type: "application/atom+xml"

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
xml.title "Activity"
xml.link href: dashboard_projects_url(rss_url_options), rel: "self", type: "application/atom+xml"
xml.link href: dashboard_projects_url, rel: "alternate", type: "text/html"

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
return unless event.visible_to_user?(current_user)
event = event.present

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
# rubocop: disable CodeReuse/ActiveRecord
xml.title "#{@group.name} issues"
xml.link href: url_for(safe_params), rel: "self", type: "application/atom+xml"

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
xml.title "#{@group.name} activity"
xml.link href: group_url(@group, rss_url_options), rel: "self", type: "application/atom+xml"
xml.link href: group_url(@group), rel: "alternate", type: "text/html"

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
xml.entry do
xml.id project_issue_url(issue.project, issue)
xml.link href: project_issue_url(issue.project, issue)

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
cal = Icalendar::Calendar.new
cal.prodid = '-//GitLab//NONSGML GitLab//EN'
cal.x_wr_calname = 'GitLab Issues'

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
xml.instruct!
xml.feed 'xmlns' => 'http://www.w3.org/2005/Atom', 'xmlns:media' => 'http://search.yahoo.com/mrss/' do
xml << yield

View File

@ -1,49 +0,0 @@
- name = issuable_type == 'issue' ? 'issue' : 'merge request'
.issuable-footer.text-center
%button.issuable-email-modal-btn{ type: "button", data: { toggle: "modal", target: "#issuable-email-modal" } }
Email a new #{name} to this project
#issuable-email-modal.modal.fade{ tabindex: "-1", role: "dialog" }
.modal-dialog{ role: "document" }
.modal-content
.modal-header
%h4.modal-title
Create new #{name} by email
%button.close{ type: "button", "data-dismiss": "modal", "aria-label" => _('Close') }
%span{ "aria-hidden": true } &times;
.modal-body
%p
You can create a new #{name} inside this project by sending an email to the following email address:
.email-modal-input-group.input-group
= text_field_tag :issuable_email, email, class: "monospace js-select-on-focus form-control", readonly: true
.input-group-append
= clipboard_button(target: '#issuable_email', class: 'btn btn-clipboard input-group-text btn-transparent d-none d-sm-block')
- if issuable_type == 'issue'
- enter_title_text = _('Enter the issue title')
- enter_description_text = _('Enter the issue description')
- else
- enter_title_text = _('Enter the merge request title')
- enter_description_text = _('Enter the merge request description')
= mail_to email, class: 'btn btn-clipboard btn-transparent',
subject: enter_title_text,
body: enter_description_text,
title: _('Send email'),
data: { toggle: 'tooltip', placement: 'bottom' } do
= sprite_icon('mail')
%p
= render 'by_email_description'
%p
This is a private email address
%span<
= link_to help_page_path('development/emails', anchor: 'email-namespace'), target: '_blank', rel: 'noopener', aria: { label: 'Learn more about incoming email addresses' } do
= sprite_icon('question-o')
generated just for you.
Anyone who gets ahold of it can create issues or merge requests as if they were you.
You should
= link_to 'reset it', new_issuable_address_project_path(@project, issuable_type: issuable_type), class: 'incoming-email-token-reset'
if that ever happens.

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
xml.entry do
xml.id project_commit_url(@project, id: commit.id)
xml.link href: project_commit_url(@project, id: commit.id)

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
xml.title "#{@project.name}:#{@ref} commits"
xml.link href: project_commits_url(@project, @ref, rss_url_options), rel: "self", type: "application/atom+xml"
xml.link href: project_commits_url(@project, @ref), rel: "alternate", type: "text/html"

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
# rubocop: disable CodeReuse/ActiveRecord
xml.title "#{@project.name} issues"
xml.link href: url_for(safe_params), rel: "self", type: "application/atom+xml"

View File

@ -3,6 +3,7 @@
- page_title _("Issues")
- new_issue_email = @project.new_issuable_address(current_user, 'issue')
- add_page_specific_style 'page_bundles/issues_list'
- issuable_type = 'issue'
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@project.name} issues")
@ -24,7 +25,8 @@
.issues-holder
= render 'issues'
- if new_issue_email
= render 'projects/issuable_by_email', email: new_issue_email, issuable_type: 'issue'
.issuable-footer.text-center
.js-issueable-by-email{ data: { initial_email: new_issue_email, issuable_type: issuable_type, emails_help_page_path: help_page_path('development/emails', anchor: 'email-namespace'), quick_actions_help_path: help_page_path('user/project/quick_actions'), markdown_help_path: help_page_path('user/markdown'), reset_path: new_issuable_address_project_path(@project, issuable_type: issuable_type) } }
- else
- new_project_issue_button_path = @project.archived? ? false : new_project_issue_path(@project)
= render 'shared/empty_states/issues', new_project_issue_button_path: new_project_issue_button_path, show_import_button: true

View File

@ -1,6 +1,7 @@
- @can_bulk_update = can?(current_user, :admin_merge_request, @project)
- merge_project = merge_request_source_project_for_project(@project)
- new_merge_request_path = project_new_merge_request_path(merge_project) if merge_project
- issuable_type = 'merge_request'
- page_title _("Merge Requests")
- new_merge_request_email = @project.new_issuable_address(current_user, 'merge_request')
@ -21,6 +22,7 @@
.merge-requests-holder
= render 'merge_requests'
- if new_merge_request_email
= render 'projects/issuable_by_email', email: new_merge_request_email, issuable_type: 'merge_request'
.issuable-footer.text-center
.js-issueable-by-email{ data: { initial_email: new_merge_request_email, issuable_type: issuable_type, emails_help_page_path: help_page_path('development/emails', anchor: 'email-namespace'), quick_actions_help_path: help_page_path('user/project/quick_actions'), markdown_help_path: help_page_path('user/markdown'), reset_path: new_issuable_address_project_path(@project, issuable_type: issuable_type) } }
- else
= render 'shared/empty_states/merge_requests', button_path: new_merge_request_path

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
xml.title "#{@project.name} activity"
xml.link href: project_url(@project, rss_url_options), rel: "self", type: "application/atom+xml"
xml.link href: project_url(@project), rel: "alternate", type: "text/html"

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
commit = @repository.commit(tag.dereferenced_target)
release = @releases.find { |r| r.tag == tag.name }
tag_url = project_tag_url(@project, tag.name)

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
xml.title "#{@project.name} tags"
xml.link href: project_tags_url(@project, @ref, rss_url_options), rel: 'self', type: 'application/atom+xml'
xml.link href: project_tags_url(@project, @ref), rel: 'alternate', type: 'text/html'

View File

@ -5,6 +5,6 @@
= link_to issuable_path(issuable), data: { track_event: 'click_text', track_label: "#{issuable.class.name.downcase}_title", track_property: 'search_result' }, class: 'gl-w-full' do
%span.term.str-truncated.gl-font-weight-bold.gl-ml-2= issuable.title
.gl-text-gray-500.gl-my-3
= sprintf(s_(' %{project_name}#%{issuable_iid} &middot; opened %{issuable_created} by %{author}'), { project_name: issuable.project.full_name, issuable_iid: issuable.iid, issuable_created: time_ago_with_tooltip(issuable.created_at, placement: 'bottom'), author: link_to_member(@project, issuable.author, avatar: false) }).html_safe
= sprintf(s_(' %{project_name}#%{issuable_iid} &middot; opened %{issuable_created} by %{author} &middot; updated %{issuable_updated}'), { project_name: issuable.project.full_name, issuable_iid: issuable.iid, issuable_created: time_ago_with_tooltip(issuable.created_at, placement: 'bottom'), issuable_updated: time_ago_with_tooltip(issuable.updated_at, placement: 'bottom'), author: link_to_member(@project, issuable.author, avatar: false) }).html_safe
.description.term.col-sm-10.gl-px-0
= highlight_and_truncate_issuable(issuable, @search_term, @search_highlight)

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
xml.title "#{@user.name} activity"
xml.link href: user_url(@user, :atom), rel: "self", type: "application/atom+xml"
xml.link href: user_url(@user), rel: "alternate", type: "text/html"

View File

@ -1,6 +1,6 @@
#!/usr/bin/env ruby
# frozen_string_literal: false
# frozen_string_literal: true
require 'active_support/core_ext/object/to_query'
require 'optparse'
@ -9,12 +9,12 @@ require 'rainbow/refinement'
using Rainbow
module Secpick
BRANCH_PREFIX = 'security'.freeze
STABLE_SUFFIX = 'stable'.freeze
BRANCH_PREFIX = 'security'
STABLE_SUFFIX = 'stable'
DEFAULT_REMOTE = 'security'.freeze
DEFAULT_REMOTE = 'security'
SECURITY_MR_URL = 'https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/new'.freeze
SECURITY_MR_URL = 'https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/new'
class SecurityFix
def initialize
@ -27,12 +27,12 @@ module Secpick
def source_branch
branch = "#{@options[:branch]}-#{@options[:version]}"
branch.prepend("#{BRANCH_PREFIX}-") unless branch.start_with?("#{BRANCH_PREFIX}-")
branch.freeze
branch = "#{BRANCH_PREFIX}-#{branch}" unless branch.start_with?("#{BRANCH_PREFIX}-")
branch
end
def stable_branch
"#{@options[:version]}-#{STABLE_SUFFIX}-ee".freeze
"#{@options[:version]}-#{STABLE_SUFFIX}-ee"
end
def git_commands

View File

@ -0,0 +1,5 @@
---
title: Replace bootstrap modal in issuable_by_email HAML template
merge_request: 53599
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Cleanup incorrect data in projects.has_external_wiki
merge_request: 53790
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Add updated_at output to search results
merge_request: 53958
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Add Security Orchestration Policy Configuration
merge_request: 53743
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Migration to add new Premium and Ultimate plan records
merge_request: 53465
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Fix bug rendering snippet activity
merge_request: 53993
author:
type: fixed

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
# FIXME: git.info_for_file raises the following error
# /usr/local/bundle/gems/git-1.4.0/lib/git/lib.rb:956:in `command': (Danger::DSLError)
# [!] Invalid `Dangerfile` file:

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
# rubocop:disable Style/SignalException
THROUGHPUT_LABELS = [

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
class CreateSecurityOrchestrationPolicyConfigurations < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
INDEX_PREFIX = 'index_sop_configs_'
def up
table_comment = { owner: 'group::container security', description: 'Configuration used to store relationship between project and security policy repository' }
create_table_with_constraints :security_orchestration_policy_configurations, comment: table_comment.to_json do |t|
t.references :project, null: false, foreign_key: { to_table: :projects, on_delete: :cascade }, index: { name: INDEX_PREFIX + 'on_project_id', unique: true }
t.references :security_policy_management_project, null: false, foreign_key: { to_table: :projects, on_delete: :restrict }, index: { name: INDEX_PREFIX + 'on_security_policy_management_project_id', unique: true }
t.timestamps_with_timezone
end
end
def down
with_lock_retries do
drop_table :security_orchestration_policy_configurations, force: :cascade
end
end
end

View File

@ -0,0 +1,88 @@
# frozen_string_literal: true
class CleanupProjectsWithBadHasExternalWikiData < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
TMP_INDEX_NAME = 'tmp_index_projects_on_id_where_has_external_wiki_is_true'.freeze
BATCH_SIZE = 100
disable_ddl_transaction!
class Service < ActiveRecord::Base
include EachBatch
belongs_to :project
self.table_name = 'services'
self.inheritance_column = :_type_disabled
end
class Project < ActiveRecord::Base
include EachBatch
self.table_name = 'projects'
end
def up
update_projects_with_active_external_wikis
update_projects_without_active_external_wikis
end
def down
# no-op : can't go back to incorrect data
end
private
def update_projects_with_active_external_wikis
# 11 projects are scoped in this query on GitLab.com.
scope = Service.where(active: true, type: 'ExternalWikiService').where.not(project_id: nil)
scope.each_batch(of: BATCH_SIZE) do |relation|
scope_with_projects = relation
.joins(:project)
.select('project_id')
.merge(Project.where(has_external_wiki: false).where(pending_delete: false).where(archived: false))
execute(<<~SQL)
WITH project_ids_to_update (id) AS (
#{scope_with_projects.to_sql}
)
UPDATE projects SET has_external_wiki = true WHERE id IN (SELECT id FROM project_ids_to_update)
SQL
end
end
def update_projects_without_active_external_wikis
# Add a temporary index to speed up the scoping of projects.
index_where = <<~SQL
(
"projects"."has_external_wiki" = TRUE
)
AND "projects"."pending_delete" = FALSE
AND "projects"."archived" = FALSE
SQL
add_concurrent_index(:projects, :id, where: index_where, name: TMP_INDEX_NAME)
services_sub_query = Service
.select('1')
.where('services.project_id = projects.id')
.where(type: 'ExternalWikiService')
.where(active: true)
# 322 projects are scoped in this query on GitLab.com.
Project.where(index_where).each_batch(of: BATCH_SIZE) do |relation|
relation_with_exists_query = relation.where('NOT EXISTS (?)', services_sub_query)
execute(<<~SQL)
WITH project_ids_to_update (id) AS (
#{relation_with_exists_query.select(:id).to_sql}
)
UPDATE projects SET has_external_wiki = false WHERE id IN (SELECT id FROM project_ids_to_update)
SQL
end
# Drop the temporary index.
remove_concurrent_index_by_name(:projects, TMP_INDEX_NAME)
end
end

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
class AddNewPostEoaPlans < ActiveRecord::Migration[6.0]
DOWNTIME = false
def up
execute "INSERT INTO plans (name, title, created_at, updated_at) VALUES ('premium', 'Premium (Formerly Silver)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)"
execute "INSERT INTO plans (name, title, created_at, updated_at) VALUES ('ultimate', 'Ultimate (Formerly Gold)', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)"
end
def down
execute "DELETE FROM plans WHERE name IN ('premium', 'ultimate')"
end
end

View File

@ -0,0 +1 @@
c5a780e5b5e62043fb04e77ebf89f7d04dfc9bfdc70df8d89c16a3f3fd960ea3

View File

@ -0,0 +1 @@
4df2229fca7652cb836c867b621da91f1194899c50bb0a8be2fbae58f29dc788

View File

@ -0,0 +1 @@
601d67a2911c461881064ec18a2246ef9e5b2835eb0fdf40e701c9360e19eca4

View File

@ -16971,6 +16971,25 @@ CREATE SEQUENCE security_findings_id_seq
ALTER SEQUENCE security_findings_id_seq OWNED BY security_findings.id;
CREATE TABLE security_orchestration_policy_configurations (
id bigint NOT NULL,
project_id bigint NOT NULL,
security_policy_management_project_id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL
);
COMMENT ON TABLE security_orchestration_policy_configurations IS '{"owner":"group::container security","description":"Configuration used to store relationship between project and security policy repository"}';
CREATE SEQUENCE security_orchestration_policy_configurations_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE security_orchestration_policy_configurations_id_seq OWNED BY security_orchestration_policy_configurations.id;
CREATE TABLE security_scans (
id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
@ -19289,6 +19308,8 @@ ALTER TABLE ONLY scim_oauth_access_tokens ALTER COLUMN id SET DEFAULT nextval('s
ALTER TABLE ONLY security_findings ALTER COLUMN id SET DEFAULT nextval('security_findings_id_seq'::regclass);
ALTER TABLE ONLY security_orchestration_policy_configurations ALTER COLUMN id SET DEFAULT nextval('security_orchestration_policy_configurations_id_seq'::regclass);
ALTER TABLE ONLY security_scans ALTER COLUMN id SET DEFAULT nextval('security_scans_id_seq'::regclass);
ALTER TABLE ONLY self_managed_prometheus_alert_events ALTER COLUMN id SET DEFAULT nextval('self_managed_prometheus_alert_events_id_seq'::regclass);
@ -20794,6 +20815,9 @@ ALTER TABLE ONLY scim_oauth_access_tokens
ALTER TABLE ONLY security_findings
ADD CONSTRAINT security_findings_pkey PRIMARY KEY (id);
ALTER TABLE ONLY security_orchestration_policy_configurations
ADD CONSTRAINT security_orchestration_policy_configurations_pkey PRIMARY KEY (id);
ALTER TABLE ONLY security_scans
ADD CONSTRAINT security_scans_pkey PRIMARY KEY (id);
@ -23353,6 +23377,10 @@ CREATE INDEX index_software_licenses_on_spdx_identifier ON software_licenses USI
CREATE UNIQUE INDEX index_software_licenses_on_unique_name ON software_licenses USING btree (name);
CREATE UNIQUE INDEX index_sop_configs_on_project_id ON security_orchestration_policy_configurations USING btree (project_id);
CREATE UNIQUE INDEX index_sop_configs_on_security_policy_management_project_id ON security_orchestration_policy_configurations USING btree (security_policy_management_project_id);
CREATE INDEX index_sprints_on_description_trigram ON sprints USING gin (description gin_trgm_ops);
CREATE INDEX index_sprints_on_due_date ON sprints USING btree (due_date);
@ -24780,6 +24808,9 @@ ALTER TABLE ONLY ci_subscriptions_projects
ALTER TABLE ONLY trending_projects
ADD CONSTRAINT fk_rails_09feecd872 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY security_orchestration_policy_configurations
ADD CONSTRAINT fk_rails_0a22dcd52d FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY project_deploy_tokens
ADD CONSTRAINT fk_rails_0aca134388 FOREIGN KEY (deploy_token_id) REFERENCES deploy_tokens(id) ON DELETE CASCADE;
@ -25119,6 +25150,9 @@ ALTER TABLE ONLY epic_issues
ALTER TABLE ONLY ci_refs
ADD CONSTRAINT fk_rails_4249db8cc3 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY security_orchestration_policy_configurations
ADD CONSTRAINT fk_rails_42ed6c25ec FOREIGN KEY (security_policy_management_project_id) REFERENCES projects(id) ON DELETE RESTRICT;
ALTER TABLE ONLY ci_resources
ADD CONSTRAINT fk_rails_430336af2d FOREIGN KEY (resource_group_id) REFERENCES ci_resource_groups(id) ON DELETE CASCADE;

View File

@ -93,8 +93,6 @@ which correspond to:
1. `elasticsearch_calls`: total number of calls to Elasticsearch
1. `elasticsearch_duration_s`: total time taken by Elasticsearch calls
1. `elasticsearch_timed_out_count`: total number of calls to Elasticsearch that
timed out and therefore returned partial results
ActionCable connection and subscription events are also logged to this file and they follow the same
format above. The `method`, `path`, and `format` fields are not applicable, and are always empty.

View File

@ -12,100 +12,110 @@ full list of reference architectures, see
[Available reference architectures](index.md#available-reference-architectures).
> - **Supported users (approximate):** 10,000
> - **High Availability:** Yes
> - **Test requests per second (RPS) rates:** API: 200 RPS, Web: 20 RPS, Git: 20 RPS
> - **High Availability:** Yes ([Praefect](#configure-praefect-postgresql) needs a third-party PostgreSQL solution for HA)
> - **Test requests per second (RPS) rates:** API: 200 RPS, Web: 20 RPS, Git (Pull): 20 RPS, Git (Push): 4 RPS
| Service | Nodes | Configuration | GCP | AWS | Azure |
|--------------------------------------------|-------------|-------------------------|-----------------|-------------|----------|
| External load balancing node | 1 | 2 vCPU, 1.8 GB memory | n1-highcpu-2 | `c5.large` | F2s v2 |
| Consul | 3 | 2 vCPU, 1.8 GB memory | n1-highcpu-2 | `c5.large` | F2s v2 |
| PostgreSQL | 3 | 4 vCPU, 15 GB memory | n1-standard-4 | `m5.xlarge` | D4s v3 |
| PgBouncer | 3 | 2 vCPU, 1.8 GB memory | n1-highcpu-2 | `c5.large` | F2s v2 |
| Internal load balancing node | 1 | 2 vCPU, 1.8 GB memory | n1-highcpu-2 | `c5.large` | F2s v2 |
| Redis - Cache | 3 | 4 vCPU, 15 GB memory | n1-standard-4 | `m5.xlarge` | D4s v3 |
| Redis - Queues / Shared State | 3 | 4 vCPU, 15 GB memory | n1-standard-4 | `m5.xlarge` | D4s v3 |
| Redis Sentinel - Cache | 3 | 1 vCPU, 1.7 GB memory | g1-small | `t3.small` | B1MS |
| Redis Sentinel - Queues / Shared State | 3 | 1 vCPU, 1.7 GB memory | g1-small | `t3.small` | B1MS |
| Gitaly | 2 (minimum) | 16 vCPU, 60 GB memory | n1-standard-16 | `m5.4xlarge` | D16s v3 |
| Sidekiq | 4 | 4 vCPU, 15 GB memory | n1-standard-4 | `m5.xlarge` | D4s v3 |
| GitLab Rails | 3 | 32 vCPU, 28.8 GB memory | n1-highcpu-32 | `c5.9xlarge` | F32s v2 |
| Monitoring node | 1 | 4 vCPU, 3.6 GB memory | n1-highcpu-4 | `c5.xlarge` | F4s v2 |
| External load balancing node | 1 | 2 vCPU, 1.8 GB memory | n1-highcpu-2 | c5.large | F2s v2 |
| Consul | 3 | 2 vCPU, 1.8 GB memory | n1-highcpu-2 | c5.large | F2s v2 |
| PostgreSQL | 3 | 8 vCPU, 30 GB memory | n1-standard-8 | m5.2xlarge | D8s v3 |
| PgBouncer | 3 | 2 vCPU, 1.8 GB memory | n1-highcpu-2 | c5.large | F2s v2 |
| Internal load balancing node | 1 | 2 vCPU, 1.8 GB memory | n1-highcpu-2 | c5.large | F2s v2 |
| Redis - Cache | 3 | 4 vCPU, 15 GB memory | n1-standard-4 | m5.xlarge | D4s v3 |
| Redis - Queues / Shared State | 3 | 4 vCPU, 15 GB memory | n1-standard-4 | m5.xlarge | D4s v3 |
| Redis Sentinel - Cache | 3 | 1 vCPU, 1.7 GB memory | g1-small | t3.small | B1MS |
| Redis Sentinel - Queues / Shared State | 3 | 1 vCPU, 1.7 GB memory | g1-small | t3.small | B1MS |
| Gitaly Cluster | 3 | 16 vCPU, 60 GB memory | n1-standard-16 | m5.4xlarge | D16s v3 |
| Praefect | 3 | 2 vCPU, 1.8 GB memory | n1-highcpu-2 | c5.large | F2s v2 |
| Praefect PostgreSQL | 1+* | 2 vCPU, 1.8 GB memory | n1-highcpu-2 | c5.large | F2s v2 |
| Sidekiq | 4 | 4 vCPU, 15 GB memory | n1-standard-4 | m5.xlarge | D4s v3 |
| GitLab Rails | 3 | 32 vCPU, 28.8 GB memory | n1-highcpu-32 | c5.9xlarge | F32s v2 |
| Monitoring node | 1 | 4 vCPU, 3.6 GB memory | n1-highcpu-4 | c5.xlarge | F4s v2 |
| Object storage | n/a | n/a | n/a | n/a | n/a |
| NFS server | 1 | 4 vCPU, 3.6 GB memory | n1-highcpu-4 | `c5.xlarge` | F4s v2 |
| NFS server | 1 | 4 vCPU, 3.6 GB memory | n1-highcpu-4 | c5.xlarge | F4s v2 |
```mermaid
stateDiagram-v2
[*] --> LoadBalancer
LoadBalancer --> ApplicationServer
```plantuml
@startuml 10k
card "**External Load Balancer**" as elb #6a9be7
card "**Internal Load Balancer**" as ilb #9370DB
ApplicationServer --> BackgroundJobs
ApplicationServer --> Gitaly
ApplicationServer --> Redis_Cache
ApplicationServer --> Redis_Queues
ApplicationServer --> PgBouncer
PgBouncer --> Database
ApplicationServer --> ObjectStorage
BackgroundJobs --> ObjectStorage
together {
collections "**GitLab Rails** x3" as gitlab #32CD32
collections "**Sidekiq** x4" as sidekiq #ff8dd1
}
ApplicationMonitoring -->ApplicationServer
ApplicationMonitoring -->PgBouncer
ApplicationMonitoring -->Database
ApplicationMonitoring -->BackgroundJobs
together {
card "**Prometheus + Grafana**" as monitor #7FFFD4
collections "**Consul** x3" as consul #e76a9b
}
ApplicationServer --> Consul
card "Gitaly Cluster" as gitaly_cluster {
collections "**Praefect** x3" as praefect #FF8C00
collections "**Gitaly** x3" as gitaly #FF8C00
card "**Praefect PostgreSQL***\n//Non fault-tolerant//" as praefect_postgres #FF8C00
Consul --> Database
Consul --> PgBouncer
Redis_Cache --> Consul
Redis_Queues --> Consul
BackgroundJobs --> Consul
praefect -[#FF8C00]-> gitaly
praefect -[#FF8C00]> praefect_postgres
}
state Consul {
"Consul_1..3"
}
card "Database" as database {
collections "**PGBouncer** x3" as pgbouncer #4EA7FF
card "**PostgreSQL** (Primary)" as postgres_primary #4EA7FF
collections "**PostgreSQL** (Secondary) x2" as postgres_secondary #4EA7FF
state Database {
"PG_Primary_Node"
"PG_Secondary_Node_1..2"
}
pgbouncer -[#4EA7FF]-> postgres_primary
postgres_primary .[#4EA7FF]> postgres_secondary
}
state Redis_Cache {
"R_Cache_Primary_Node"
"R_Cache_Replica_Node_1..2"
"R_Cache_Sentinel_1..3"
}
card "redis" as redis {
collections "**Redis Persistent** x3" as redis_persistent #FF6347
collections "**Redis Cache** x3" as redis_cache #FF6347
collections "**Redis Persistent Sentinel** x3" as redis_persistent_sentinel #FF6347
collections "**Redis Cache Sentinel** x3"as redis_cache_sentinel #FF6347
state Redis_Queues {
"R_Queues_Primary_Node"
"R_Queues_Replica_Node_1..2"
"R_Queues_Sentinel_1..3"
}
redis_persistent <.[#FF6347]- redis_persistent_sentinel
redis_cache <.[#FF6347]- redis_cache_sentinel
}
state Gitaly {
"Gitaly_1..2"
}
cloud "**Object Storage**" as object_storage #white
state BackgroundJobs {
"Sidekiq_1..4"
}
elb -[#6a9be7]-> gitlab
elb -[#6a9be7]--> monitor
state ApplicationServer {
"GitLab_Rails_1..3"
}
gitlab -[#32CD32]> sidekiq
gitlab -[#32CD32]--> ilb
gitlab -[#32CD32]-> object_storage
gitlab -[#32CD32]---> redis
gitlab -[hidden]-> monitor
gitlab -[hidden]-> consul
state LoadBalancer {
"LoadBalancer_1"
}
sidekiq -[#ff8dd1]--> ilb
sidekiq -[#ff8dd1]-> object_storage
sidekiq -[#ff8dd1]---> redis
sidekiq -[hidden]-> monitor
sidekiq -[hidden]-> consul
state ApplicationMonitoring {
"Prometheus"
"Grafana"
}
ilb -[#9370DB]-> gitaly_cluster
ilb -[#9370DB]-> database
state PgBouncer {
"Internal_Load_Balancer"
"PgBouncer_1..3"
}
consul .[#e76a9b]u-> gitlab
consul .[#e76a9b]u-> sidekiq
consul .[#e76a9b]> monitor
consul .[#e76a9b]-> database
consul .[#e76a9b]-> gitaly_cluster
consul .[#e76a9b,norank]--> redis
monitor .[#7FFFD4]u-> gitlab
monitor .[#7FFFD4]u-> sidekiq
monitor .[#7FFFD4]> consul
monitor .[#7FFFD4]-> database
monitor .[#7FFFD4]-> gitaly_cluster
monitor .[#7FFFD4,norank]--> redis
monitor .[#7FFFD4]> ilb
monitor .[#7FFFD4,norank]u--> elb
@enduml
```
The Google Cloud Platform (GCP) architectures were built and tested using the
@ -120,19 +130,25 @@ uploads, or artifacts), using an [object storage service](#configure-the-object-
is recommended instead of using NFS. Using an object storage service also
doesn't require you to provision and maintain a node.
It's also worth noting that at this time [Praefect requires its own database server](../gitaly/praefect.md#postgresql) and
that to achieve full High Availability a third party PostgreSQL database solution will be required.
We hope to offer a built in solutions for these restrictions in the future but in the meantime a non HA PostgreSQL server
can be set up via Omnibus GitLab, which the above specs reflect. Refer to the following issues for more information: [`omnibus-gitlab#5919`](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/5919) & [`gitaly#3398`](https://gitlab.com/gitlab-org/gitaly/-/issues/3398)
## Setup components
To set up GitLab and its components to accommodate up to 10,000 users:
1. [Configure the external load balancing node](#configure-the-external-load-balancer)
1. [Configure the external load balancer](#configure-the-external-load-balancer)
to handle the load balancing of the GitLab application services nodes.
1. [Configure the internal load balancer](#configure-the-internal-load-balancer).
to handle the load balancing of GitLab application internal connections.
1. [Configure Consul](#configure-consul).
1. [Configure PostgreSQL](#configure-postgresql), the database for GitLab.
1. [Configure PgBouncer](#configure-pgbouncer).
1. [Configure the internal load balancing node](#configure-the-internal-load-balancer).
1. [Configure Redis](#configure-redis).
1. [Configure Gitaly](#configure-gitaly),
which provides access to the Git repositories.
1. [Configure Gitaly Cluster](#configure-gitaly-cluster),
provides access to the Git repositories.
1. [Configure Sidekiq](#configure-sidekiq).
1. [Configure the main GitLab Rails application](#configure-gitlab-rails)
to run Puma/Unicorn, Workhorse, GitLab Shell, and to serve all frontend
@ -178,6 +194,11 @@ The following list includes descriptions of each server and its assigned IP:
- `10.6.0.83`: Sentinel - Queues 3
- `10.6.0.91`: Gitaly 1
- `10.6.0.92`: Gitaly 2
- `10.6.0.93`: Gitaly 3
- `10.6.0.131`: Praefect 1
- `10.6.0.132`: Praefect 2
- `10.6.0.133`: Praefect 3
- `10.6.0.141`: Praefect PostgreSQL 1 (non HA)
- `10.6.0.101`: Sidekiq 1
- `10.6.0.102`: Sidekiq 2
- `10.6.0.103`: Sidekiq 3
@ -308,6 +329,71 @@ Configure DNS for an alternate SSH hostname such as `altssh.gitlab.example.com`.
</a>
</div>
## Configure the internal load balancer
The Internal Load Balancer is used to balance any internal connections the GitLab environment requires
such as connections to [PgBouncer](#configure-pgbouncer) and [Praefect](#configure-praefect) (Gitaly Cluster).
Note that it's a separate node from the External Load Balancer and shouldn't have any access externally.
The following IP will be used as an example:
- `10.6.0.40`: Internal Load Balancer
Here's how you could do it with [HAProxy](https://www.haproxy.org/):
```plaintext
global
log /dev/log local0
log localhost local1 notice
log stdout format raw local0
defaults
log global
default-server inter 10s fall 3 rise 2
balance leastconn
frontend internal-pgbouncer-tcp-in
bind *:6432
mode tcp
option tcplog
default_backend pgbouncer
frontend internal-praefect-tcp-in
bind *:2305
mode tcp
option tcplog
option clitcpka
default_backend praefect
backend pgbouncer
mode tcp
option tcp-check
server pgbouncer1 10.6.0.21:6432 check
server pgbouncer2 10.6.0.22:6432 check
server pgbouncer3 10.6.0.23:6432 check
backend praefect
mode tcp
option tcp-check
option srvtcpka
server praefect1 10.6.0.131:2305 check
server praefect2 10.6.0.132:2305 check
server praefect3 10.6.0.133:2305 check
```
Refer to your preferred Load Balancer's documentation for further guidance.
<div align="right">
<a type="button" class="btn btn-default" href="#setup-components">
Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
</a>
</div>
## Configure Consul
The following IPs will be used as an example:
@ -662,52 +748,6 @@ The following IPs will be used as an example:
</a>
</div>
### Configure the internal load balancer
If you're running more than one PgBouncer node as recommended, then at this time you'll need to set
up a TCP internal load balancer to serve each correctly.
The following IP will be used as an example:
- `10.6.0.40`: Internal Load Balancer
Here's how you could do it with [HAProxy](https://www.haproxy.org/):
```plaintext
global
log /dev/log local0
log localhost local1 notice
log stdout format raw local0
defaults
log global
default-server inter 10s fall 3 rise 2
balance leastconn
frontend internal-pgbouncer-tcp-in
bind *:6432
mode tcp
option tcplog
default_backend pgbouncer
backend pgbouncer
mode tcp
option tcp-check
server pgbouncer1 10.6.0.21:6432 check
server pgbouncer2 10.6.0.22:6432 check
server pgbouncer3 10.6.0.23:6432 check
```
Refer to your preferred Load Balancer's documentation for further guidance.
<div align="right">
<a type="button" class="btn btn-default" href="#setup-components">
Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
</a>
</div>
## Configure Redis
Using [Redis](https://redis.io/) in scalable environment is possible using a **Primary** x **Replica**
@ -1302,19 +1342,283 @@ To configure the Sentinel Queues server:
</a>
</div>
## Configure Gitaly
## Configure Gitaly Cluster
NOTE:
[Gitaly Cluster](../gitaly/praefect.md) support
for the Reference Architectures is being
worked on as a [collaborative effort](https://gitlab.com/gitlab-org/quality/reference-architectures/-/issues/1) between the Quality Engineering and Gitaly teams. When this component has been verified
some Architecture specs will likely change as a result to support the new
and improved designed.
[Gitaly Cluster](../gitaly/praefect.md) is a GitLab provided and recommended fault tolerant solution for storing Git repositories.
In this configuration, every Git repository is stored on every Gitaly node in the cluster, with one being designated the primary, and failover occurs automatically if the primary node goes down.
[Gitaly](../gitaly/index.md) server node requirements are dependent on data,
specifically the number of projects and those projects' sizes. It's recommended
that a Gitaly server node stores no more than 5 TB of data. Depending on your
repository storage requirements, you may require additional Gitaly server nodes.
The recommended cluster setup includes the following components:
- 3 Gitaly nodes: Replicated storage of Git repositories.
- 3 Praefect nodes: Router and transaction manager for Gitaly Cluster.
- 1 Praefect PostgreSQL node: Database server for Praefect. A third-party solution
is required for Praefect database connections to be made highly available.
- 1 load balancer: A load balancer is required for Praefect. The
[internal load balancer](#configure-the-internal-load-balancer) will be used.
This section will detail how to configure the recommended standard setup in order.
For more advanced setups refer to the [standalone Gitaly Cluster documentation](../gitaly/praefect.md).
### Configure Praefect PostgreSQL
Praefect, the routing and transaction manager for Gitaly Cluster, requires its own database server to store data on Gitaly Cluster status.
If you want to have a highly available setup, Praefect requires a third-party PostgreSQL database.
A built-in solution is being [worked on](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/5919).
#### Praefect non-HA PostgreSQL standalone using Omnibus GitLab
The following IPs will be used as an example:
- `10.6.0.141`: Praefect PostgreSQL
First, make sure to [install](https://about.gitlab.com/install/)
the Linux GitLab package in the Praefect PostgreSQL node. Following the steps,
install the necessary dependencies from step 1, and add the
GitLab package repository from step 2. When installing GitLab
in the second step, do not supply the `EXTERNAL_URL` value.
1. SSH in to the Praefect PostgreSQL node.
1. Create a strong password to be used for the Praefect PostgreSQL user. Take note of this password as `<praefect_postgresql_password>`.
1. Generate the password hash for the Praefect PostgreSQL username/password pair. This assumes you will use the default
username of `praefect` (recommended). The command will request the password `<praefect_postgresql_password>`
and confirmation. Use the value that is output by this command in the next
step as the value of `<praefect_postgresql_password_hash>`:
```shell
sudo gitlab-ctl pg-password-md5 praefect
```
1. Edit `/etc/gitlab/gitlab.rb` replacing values noted in the `# START user configuration` section:
```ruby
# Disable all components except PostgreSQL and Consul
roles ['postgres_role']
repmgr['enable'] = false
patroni['enable'] = false
# PostgreSQL configuration
postgresql['listen_address'] = '0.0.0.0'
postgresql['max_connections'] = 200
gitlab_rails['auto_migrate'] = false
# Configure the Consul agent
consul['enable'] = true
## Enable service discovery for Prometheus
consul['monitoring_service_discovery'] = true
# START user configuration
# Please set the real values as explained in Required Information section
#
# Replace PRAEFECT_POSTGRESQL_PASSWORD_HASH with a generated md5 value
postgresql['sql_user_password'] = "<praefect_postgresql_password_hash>"
# Replace XXX.XXX.XXX.XXX/YY with Network Address
postgresql['trust_auth_cidr_addresses'] = %w(10.6.0.0/24)
# Set the network addresses that the exporters will listen on for monitoring
node_exporter['listen_address'] = '0.0.0.0:9100'
postgres_exporter['listen_address'] = '0.0.0.0:9187'
## The IPs of the Consul server nodes
## You can also use FQDNs and intermix them with IPs
consul['configuration'] = {
retry_join: %w(10.6.0.11 10.6.0.12 10.6.0.13),
}
#
# END user configuration
```
1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
1. Follow the [post configuration](#praefect-postgresql-post-configuration).
<div align="right">
<a type="button" class="btn btn-default" href="#setup-components">
Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
</a>
</div>
#### Praefect HA PostgreSQL third-party solution
[As noted](#configure-praefect-postgresql), a third-party PostgreSQL solution for
Praefect's database is recommended if aiming for full High Availability.
There are many third-party solutions for PostgreSQL HA. The solution selected must have the following to work with Praefect:
- A static IP for all connections that doesn't change on failover.
- [`LISTEN`](https://www.postgresql.org/docs/12/sql-listen.html) SQL functionality must be supported.
Examples of the above could include [Google's Cloud SQL](https://cloud.google.com/sql/docs/postgres/high-availability#normal) or [Amazon RDS](https://aws.amazon.com/rds/).
Once the database is set up, follow the [post configuration](#praefect-postgresql-post-configuration).
#### Praefect PostgreSQL post-configuration
After the Praefect PostgreSQL server has been set up, you'll then need to configure the user and database for Praefect to use.
We recommend the user be named `praefect` and the database `praefect_production`, and these can be configured as standard in PostgreSQL.
The password for the user is the same as the one you configured earlier as `<praefect_postgresql_password>`.
This is how this would work with a Omnibus GitLab PostgreSQL setup:
1. SSH in to the Praefect PostgreSQL node.
1. Connect to the PostgreSQL server with administrative access.
The `gitlab-psql` user should be used here for this as it's added by default in Omnibus.
The database `template1` is used because it is created by default on all PostgreSQL servers.
```shell
/opt/gitlab/embedded/bin/psql -U gitlab-psql -d template1 -h POSTGRESQL_SERVER_ADDRESS
```
1. Create the new user `praefect`, replacing `<praefect_postgresql_password>`:
```shell
CREATE ROLE praefect WITH LOGIN CREATEDB PASSWORD <praefect_postgresql_password>;
```
1. Reconnect to the PostgreSQL server, this time as the `praefect` user:
```shell
/opt/gitlab/embedded/bin/psql -U praefect -d template1 -h POSTGRESQL_SERVER_ADDRESS
```
1. Create a new database `praefect_production`:
```shell
CREATE DATABASE praefect_production WITH ENCODING=UTF8;
```
<div align="right">
<a type="button" class="btn btn-default" href="#setup-components">
Back to setup components <i class="fa fa-angle-double-up" aria-hidden="true"></i>
</a>
</div>
### Configure Praefect
Praefect is the router and transaction manager for Gitaly Cluster and all connections to Gitaly go through
it. This section details how to configure it.
Praefect requires several secret tokens to secure communications across the Cluster:
- `<praefect_external_token>`: Used for repositories hosted on your Gitaly cluster and can only be accessed by Gitaly clients that carry this token.
- `<praefect_internal_token>`: Used for replication traffic inside your Gitaly cluster. This is distinct from `praefect_external_token` because Gitaly clients must not be able to access internal nodes of the Praefect cluster directly; that could lead to data loss.
- `<praefect_postgresql_password>`: The Praefect PostgreSQL password defined in the previous section is also required as part of this setup.
Gitaly Cluster nodes are configured in Praefect via a `virtual storage`. Each storage contains
the details of each Gitaly node that makes up the cluster. Each storage is also given a name
and this name is used in several areas of the config. In this guide, the name of the storage will be
`default`. Also, this guide is geared towards new installs, if upgrading an existing environment
to use Gitaly Cluster, you may need to use a different name.
Refer to the [Praefect documentation](../gitaly/praefect.md#praefect) for more info.
The following IPs will be used as an example:
- `10.6.0.131`: Praefect 1
- `10.6.0.132`: Praefect 2
- `10.6.0.133`: Praefect 3
To configure the Praefect nodes, on each one:
1. SSH in to the Praefect server.
1. [Download and install](https://about.gitlab.com/install/) the Omnibus GitLab
package of your choice. Be sure to follow _only_ installation steps 1 and 2
on the page.
1. Edit the `/etc/gitlab/gitlab.rb` file to configure Praefect:
```ruby
# Avoid running unnecessary services on the Gitaly server
postgresql['enable'] = false
redis['enable'] = false
nginx['enable'] = false
puma['enable'] = false
unicorn['enable'] = false
sidekiq['enable'] = false
gitlab_workhorse['enable'] = false
grafana['enable'] = false
# If you run a separate monitoring node you can disable these services
alertmanager['enable'] = false
prometheus['enable'] = false
# Praefect Configuration
praefect['enable'] = true
praefect['listen_addr'] = '0.0.0.0:2305'
gitlab_rails['rake_cache_clear'] = false
gitlab_rails['auto_migrate'] = false
# Configure the Consul agent
consul['enable'] = true
## Enable service discovery for Prometheus
consul['monitoring_service_discovery'] = true
# START user configuration
# Please set the real values as explained in Required Information section
#
# Praefect External Token
# This is needed by clients outside the cluster (like GitLab Shell) to communicate with the Praefect cluster
praefect['auth_token'] = '<praefect_external_token>'
# Praefect Database Settings
praefect['database_host'] = '10.6.0.141'
praefect['database_port'] = 5432
# `no_proxy` settings must always be a direct connection for caching
praefect['database_host_no_proxy'] = '10.6.0.141'
praefect['database_port_no_proxy'] = 5432
praefect['database_dbname'] = 'praefect_production'
praefect['database_user'] = 'praefect'
praefect['database_password'] = '<praefect_postgresql_password>'
# Praefect Virtual Storage config
# Name of storage hash must match storage name in git_data_dirs on GitLab
# server ('praefect') and in git_data_dirs on Gitaly nodes ('gitaly-1')
praefect['virtual_storages'] = {
'default' => {
'gitaly-1' => {
'address' => 'tcp://10.6.0.91:8075',
'token' => '<praefect_internal_token>',
'primary' => true
},
'gitaly-2' => {
'address' => 'tcp://10.6.0.92:8075',
'token' => '<praefect_internal_token>'
},
'gitaly-3' => {
'address' => 'tcp://10.6.0.93:8075',
'token' => '<praefect_internal_token>'
},
}
}
# Set the network addresses that the exporters will listen on for monitoring
node_exporter['listen_address'] = '0.0.0.0:9100'
praefect['prometheus_listen_addr'] = '0.0.0.0:9652'
## The IPs of the Consul server nodes
## You can also use FQDNs and intermix them with IPs
consul['configuration'] = {
retry_join: %w(10.6.0.11 10.6.0.12 10.6.0.13),
}
#
# END user configuration
```
1. Copy the `/etc/gitlab/gitlab-secrets.json` file from your Consul server, and
then replace the file of the same name on this server. If that file isn't on
this server, add the file from your Consul server to this server.
1. Save the file, and then [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
### Configure Gitaly
The [Gitaly](../gitaly/index.md) server nodes that make up the cluster have
requirements that are dependent on data, specifically the number of projects
and those projects' sizes. It's recommended that a Gitaly Cluster stores
no more than 5 TB of data on each node. Depending on your
repository storage requirements, you may require additional Gitaly Clusters.
Due to Gitaly having notable input and output requirements, we strongly
recommend that all Gitaly nodes use solid-state drives (SSDs). These SSDs
@ -1325,36 +1629,21 @@ adjusted to greater or lesser values depending on the scale of your
environment's workload. If you're running the environment on a Cloud provider,
refer to their documentation about how to configure IOPS correctly.
Be sure to note the following items:
Gitaly servers must not be exposed to the public internet, as Gitaly's network
traffic is unencrypted by default. The use of a firewall is highly recommended
to restrict access to the Gitaly server. Another option is to
[use TLS](#gitaly-cluster-tls-support).
- The GitLab Rails application shards repositories into
[repository storage paths](../repository_storage_paths.md).
- A Gitaly server can host one or more storage paths.
- A GitLab server can use one or more Gitaly server nodes.
- Gitaly addresses must be specified to be correctly resolvable for all Gitaly
clients.
- Gitaly servers must not be exposed to the public internet, as Gitaly's network
traffic is unencrypted by default. The use of a firewall is highly recommended
to restrict access to the Gitaly server. Another option is to
[use TLS](#gitaly-tls-support).
For configuring Gitaly you should note the following:
NOTE:
The token referred to throughout the Gitaly documentation is an arbitrary
password selected by the administrator. This token is unrelated to tokens
created for the GitLab API or other similar web API tokens.
- `git_data_dirs` should be configured to reflect the storage path for the specific Gitaly node
- `auth_token` should be the same as `praefect_internal_token`
This section describes how to configure two Gitaly servers, with the following
IPs and domain names:
The following IPs will be used as an example:
- `10.6.0.91`: Gitaly 1 (`gitaly1.internal`)
- `10.6.0.92`: Gitaly 2 (`gitaly2.internal`)
Assumptions about your servers include having the secret token be `gitalysecret`,
and that your GitLab installation has three repository storages:
- `default` on Gitaly 1
- `storage1` on Gitaly 1
- `storage2` on Gitaly 2
- `10.6.0.91`: Gitaly 1
- `10.6.0.92`: Gitaly 2
- `10.6.0.93`: Gitaly 3
On each node:
@ -1364,21 +1653,9 @@ On each node:
1. Edit the Gitaly server node's `/etc/gitlab/gitlab.rb` file to configure
storage paths, enable the network listener, and to configure the token:
<!--
updates to following example must also be made at
https://gitlab.com/gitlab-org/charts/gitlab/blob/master/doc/advanced/external-gitaly/external-omnibus-gitaly.md#configure-omnibus-gitlab
-->
```ruby
# /etc/gitlab/gitlab.rb
# Gitaly and GitLab use two shared secrets for authentication, one to authenticate gRPC requests
# to Gitaly, and a second for authentication callbacks from GitLab-Shell to the GitLab internal API.
# The following two values must be the same as their respective values
# of the GitLab Rails application setup
gitaly['auth_token'] = 'gitalysecret'
gitlab_shell['secret_token'] = 'shellsecret'
# Avoid running unnecessary services on the Gitaly server
postgresql['enable'] = false
redis['enable'] = false
@ -1407,36 +1684,42 @@ On each node:
# firewalls to restrict access to this address/port.
# Comment out following line if you only want to support TLS connections
gitaly['listen_addr'] = "0.0.0.0:8075"
# Gitaly Auth Token
# Should be the same as praefect_internal_token
gitaly['auth_token'] = '<praefect_internal_token>'
```
1. Append the following to `/etc/gitlab/gitlab.rb` for each respective server:
- On `gitaly1.internal`:
- On Gitaly node 1:
```ruby
git_data_dirs({
'default' => {
'path' => '/var/opt/gitlab/git-data'
},
'storage1' => {
'path' => '/mnt/gitlab/git-data'
},
"gitaly-1" => {
"path" => "/var/opt/gitlab/git-data"
}
})
```
- On `gitaly2.internal`:
- On Gitaly node 2:
```ruby
git_data_dirs({
'storage2' => {
'path' => '/mnt/gitlab/git-data'
},
"gitaly-2" => {
"path" => "/var/opt/gitlab/git-data"
}
})
```
<!--
updates to following example must also be made at
https://gitlab.com/gitlab-org/charts/gitlab/blob/master/doc/advanced/external-gitaly/external-omnibus-gitaly.md#configure-omnibus-gitlab
-->
- On Gitaly node 3:
```ruby
git_data_dirs({
"gitaly-3" => {
"path" => "/var/opt/gitlab/git-data"
}
})
```
1. Copy the `/etc/gitlab/gitlab-secrets.json` file from your Consul server, and
then replace the file of the same name on this server. If that file isn't on
@ -1444,34 +1727,44 @@ On each node:
1. Save the file, and then [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
### Gitaly TLS support
### Gitaly Cluster TLS support
Gitaly supports TLS encryption. To be able to communicate
with a Gitaly instance that listens for secure connections you will need to use `tls://` URL
scheme in the `gitaly_address` of the corresponding storage entry in the GitLab configuration.
Praefect supports TLS encryption. To communicate with a Praefect instance that listens
for secure connections, you must:
You will need to bring your own certificates as this isn't provided automatically.
The certificate, or its certificate authority, must be installed on all Gitaly
nodes (including the Gitaly node using the certificate) and on all client nodes
that communicate with it following the procedure described in
[GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates).
- Use a `tls://` URL scheme in the `gitaly_address` of the corresponding storage entry
in the GitLab configuration.
- Bring your own certificates because this isn't provided automatically. The certificate
corresponding to each Praefect server must be installed on that Praefect server.
NOTE:
The self-signed certificate must specify the address you use to access the
Gitaly server. If you are addressing the Gitaly server by a hostname, you can
either use the Common Name field for this, or add it as a Subject Alternative
Name. If you are addressing the Gitaly server by its IP address, you must add it
as a Subject Alternative Name to the certificate.
[gRPC does not support using an IP address as Common Name in a certificate](https://github.com/grpc/grpc/issues/2691).
Additionally the certificate, or its certificate authority, must be installed on all Gitaly servers
and on all Praefect clients that communicate with it following the procedure described in
[GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates) (and repeated below).
It's possible to configure Gitaly servers with both an unencrypted listening
address (`listen_addr`) and an encrypted listening address (`tls_listen_addr`)
at the same time. This allows you to do a gradual transition from unencrypted to
encrypted traffic, if necessary.
Note the following:
To configure Gitaly with TLS:
- The certificate must specify the address you use to access the Praefect server. If
addressing the Praefect server by:
1. Create the `/etc/gitlab/ssl` directory and copy your key and certificate there:
- Hostname, you can either use the Common Name field for this, or add it as a Subject
Alternative Name.
- IP address, you must add it as a Subject Alternative Name to the certificate.
- You can configure Praefect servers with both an unencrypted listening address
`listen_addr` and an encrypted listening address `tls_listen_addr` at the same time.
This allows you to do a gradual transition from unencrypted to encrypted traffic, if
necessary.
- The Internal Load Balancer will also access to the certificates and need to be configured
to allow for TLS passthrough.
Refer to the load balancers documentation on how to configure this.
To configure Praefect with TLS:
1. Create certificates for Praefect servers.
1. On the Praefect servers, create the `/etc/gitlab/ssl` directory and copy your key
and certificate there:
```shell
sudo mkdir -p /etc/gitlab/ssl
@ -1480,27 +1773,34 @@ To configure Gitaly with TLS:
sudo chmod 644 key.pem cert.pem
```
1. Copy the cert to `/etc/gitlab/trusted-certs` so Gitaly will trust the cert when
calling into itself:
```shell
sudo cp /etc/gitlab/ssl/cert.pem /etc/gitlab/trusted-certs/
```
1. Edit `/etc/gitlab/gitlab.rb` and add:
<!--
updates to following example must also be made at
https://gitlab.com/gitlab-org/charts/gitlab/blob/master/doc/advanced/external-gitaly/external-omnibus-gitaly.md#configure-omnibus-gitlab
-->
```ruby
gitaly['tls_listen_addr'] = "0.0.0.0:9999"
gitaly['certificate_path'] = "/etc/gitlab/ssl/cert.pem"
gitaly['key_path'] = "/etc/gitlab/ssl/key.pem"
praefect['tls_listen_addr'] = "0.0.0.0:3305"
praefect['certificate_path'] = "/etc/gitlab/ssl/cert.pem"
praefect['key_path'] = "/etc/gitlab/ssl/key.pem"
```
1. Delete `gitaly['listen_addr']` to allow only encrypted connections.
1. Save the file and [reconfigure](../restart_gitlab.md#omnibus-gitlab-reconfigure).
1. On the Praefect clients (including each Gitaly server), copy the certificates,
or their certificate authority, into `/etc/gitlab/trusted-certs`:
```shell
sudo cp cert.pem /etc/gitlab/trusted-certs/
```
1. On the Praefect clients (except Gitaly servers), edit `git_data_dirs` in
`/etc/gitlab/gitlab.rb` as follows:
```ruby
git_data_dirs({
"default" => {
"gitaly_address" => 'tls://LOAD_BALANCER_SERVER_ADDRESS:2305',
"gitaly_token" => 'PRAEFECT_EXTERNAL_TOKEN'
}
})
```
1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
@ -1586,17 +1886,20 @@ To configure the Sidekiq nodes, on each one:
### Gitaly ###
#######################################
# git_data_dirs get configured for the Praefect virtual storage
# Address is Internal Load Balancer for Praefect
# Token is praefect_external_token
git_data_dirs({
'default' => { 'gitaly_address' => 'tcp://gitaly1.internal:8075' },
'storage1' => { 'gitaly_address' => 'tcp://gitaly1.internal:8075' },
'storage2' => { 'gitaly_address' => 'tcp://gitaly2.internal:8075' },
"default" => {
"gitaly_address" => "tcp://10.6.0.40:2305", # internal load balancer IP
"gitaly_token" => '<praefect_external_token>'
}
})
gitlab_rails['gitaly_token'] = 'YOUR_TOKEN'
#######################################
### Postgres ###
#######################################
gitlab_rails['db_host'] = '10.6.0.20' # internal load balancer IP
gitlab_rails['db_host'] = '10.6.0.40' # internal load balancer IP
gitlab_rails['db_port'] = 6432
gitlab_rails['db_password'] = '<postgresql_user_password>'
gitlab_rails['db_adapter'] = 'postgresql'
@ -1669,17 +1972,14 @@ On each node perform the following:
```ruby
external_url 'https://gitlab.example.com'
# Gitaly and GitLab use two shared secrets for authentication, one to authenticate gRPC requests
# to Gitaly, and a second for authentication callbacks from GitLab-Shell to the GitLab internal API.
# The following two values must be the same as their respective values
# of the Gitaly setup
gitlab_rails['gitaly_token'] = 'gitalysecret'
gitlab_shell['secret_token'] = 'shellsecret'
# git_data_dirs get configured for the Praefect virtual storage
# Address is Interal Load Balancer for Praefect
# Token is praefect_external_token
git_data_dirs({
'default' => { 'gitaly_address' => 'tcp://gitaly1.internal:8075' },
'storage1' => { 'gitaly_address' => 'tcp://gitaly1.internal:8075' },
'storage2' => { 'gitaly_address' => 'tcp://gitaly2.internal:8075' },
"default" => {
"gitaly_address" => "tcp://10.6.0.40:2305", # internal load balancer IP
"gitaly_token" => '<praefect_external_token>'
}
})
## Disable components that will not be on the GitLab application server
@ -1739,14 +2039,15 @@ On each node perform the following:
```
1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
1. If you're using [Gitaly with TLS support](#gitaly-tls-support), make sure the
1. If you're using [Gitaly with TLS support](#gitaly-cluster-tls-support), make sure the
`git_data_dirs` entry is configured with `tls` instead of `tcp`:
```ruby
git_data_dirs({
'default' => { 'gitaly_address' => 'tls://gitaly1.internal:9999' },
'storage1' => { 'gitaly_address' => 'tls://gitaly1.internal:9999' },
'storage2' => { 'gitaly_address' => 'tls://gitaly2.internal:9999' },
"default" => {
"gitaly_address" => "tls://10.6.0.40:2305", # internal load balancer IP
"gitaly_token" => '<praefect_external_token>'
}
})
```

View File

@ -18,6 +18,7 @@ many organizations .
> - **Supported users (approximate):** 1,000
> - **High Availability:** No. For a highly-available environment, you can
> follow the [3K reference architecture](3k_users.md).
> - **Test requests per second (RPS) rates:** API: 20 RPS, Web: 2 RPS, Git (Pull): 2 RPS, Git (Push): 1 RPS
| Users | Configuration | GCP | AWS | Azure |
|--------------|-------------------------|----------------|-----------------|----------------|

View File

@ -13,7 +13,7 @@ full list of reference architectures, see
> - **Supported users (approximate):** 25,000
> - **High Availability:** Yes
> - **Test requests per second (RPS) rates:** API: 500 RPS, Web: 50 RPS, Git: 50 RPS
> - **Test requests per second (RPS) rates:** API: 500 RPS, Web: 50 RPS, Git (Pull): 50 RPS, Git (Push): 10 RPS
| Service | Nodes | Configuration | GCP | AWS | Azure |
|-----------------------------------------|-------------|-------------------------|-----------------|-------------|----------|

View File

@ -14,7 +14,7 @@ For a full list of reference architectures, see
> - **Supported users (approximate):** 2,000
> - **High Availability:** No. For a highly-available environment, you can
> follow the [3K reference architecture](3k_users.md).
> - **Test requests per second (RPS) rates:** API: 40 RPS, Web: 4 RPS, Git: 4 RPS
> - **Test requests per second (RPS) rates:** API: 40 RPS, Web: 4 RPS, Git (Pull): 4 RPS, Git (Push): 1 RPS
| Service | Nodes | Configuration | GCP | AWS | Azure |
|------------------------------------------|--------|-------------------------|----------------|--------------|---------|
@ -27,44 +27,32 @@ For a full list of reference architectures, see
| Object storage | n/a | n/a | n/a | n/a | n/a |
| NFS server (optional, not recommended) | 1 | 4 vCPU, 3.6 GB memory | n1-highcpu-4 | `c5.xlarge` | F4s v2 |
```mermaid
stateDiagram-v2
[*] --> LoadBalancer
LoadBalancer --> ApplicationServer
```plantuml
@startuml 2k
card "**External Load Balancer**" as elb #6a9be7
ApplicationServer --> Gitaly
ApplicationServer --> Redis
ApplicationServer --> Database
ApplicationServer --> ObjectStorage
collections "**GitLab Rails** x3" as gitlab #32CD32
card "**Prometheus + Grafana**" as monitor #7FFFD4
card "**Gitaly**" as gitaly #FF8C00
card "**PostgreSQL**" as postgres #4EA7FF
card "**Redis**" as redis #FF6347
cloud "**Object Storage**" as object_storage #white
ApplicationMonitoring -->ApplicationServer
ApplicationMonitoring -->Redis
ApplicationMonitoring -->Database
elb -[#6a9be7]-> gitlab
elb -[#6a9be7]--> monitor
gitlab -[#32CD32]--> gitaly
gitlab -[#32CD32]--> postgres
gitlab -[#32CD32]-> object_storage
gitlab -[#32CD32]--> redis
state Database {
"PG_Node"
}
state Redis {
"Redis_Node"
}
monitor .[#7FFFD4]u-> gitlab
monitor .[#7FFFD4]-> gitaly
monitor .[#7FFFD4]-> postgres
monitor .[#7FFFD4,norank]--> redis
monitor .[#7FFFD4,norank]u--> elb
state Gitaly {
"Gitaly"
}
state ApplicationServer {
"AppServ_1..2"
}
state LoadBalancer {
"LoadBalancer"
}
state ApplicationMonitoring {
"Prometheus"
"Grafana"
}
@enduml
```
The Google Cloud Platform (GCP) architectures were built and tested using the

View File

@ -21,7 +21,7 @@ For a full list of reference architectures, see
> - **Supported users (approximate):** 3,000
> - **High Availability:** Yes
> - **Test requests per second (RPS) rates:** API: 60 RPS, Web: 6 RPS, Git: 6 RPS
> - **Test requests per second (RPS) rates:** API: 60 RPS, Web: 6 RPS, Git (Pull): 6 RPS, Git (Push): 1 RPS
| Service | Nodes | Configuration | GCP | AWS | Azure |
|--------------------------------------------|-------------|-----------------------|----------------|-------------|---------|

View File

@ -13,7 +13,7 @@ full list of reference architectures, see
> - **Supported users (approximate):** 50,000
> - **High Availability:** Yes
> - **Test requests per second (RPS) rates:** API: 1000 RPS, Web: 100 RPS, Git: 100 RPS
> - **Test requests per second (RPS) rates:** API: 1000 RPS, Web: 100 RPS, Git (Pull): 100 RPS, Git (Push): 20 RPS
| Service | Nodes | Configuration | GCP | AWS | Azure |
|-----------------------------------------|-------------|-------------------------|-----------------|--------------|----------|

View File

@ -20,7 +20,7 @@ costly-to-operate environment by using the
> - **Supported users (approximate):** 5,000
> - **High Availability:** Yes
> - **Test requests per second (RPS) rates:** API: 100 RPS, Web: 10 RPS, Git: 10 RPS
> - **Test requests per second (RPS) rates:** API: 100 RPS, Web: 10 RPS, Git (Pull): 10 RPS, Git (Push): 2 RPS
| Service | Nodes | Configuration | GCP | AWS | Azure |
|--------------------------------------------|-------------|-------------------------|----------------|-------------|----------|

View File

@ -29,7 +29,8 @@ per 1,000 users:
- API: 20 RPS
- Web: 2 RPS
- Git: 2 RPS
- Git (Pull): 2 RPS
- Git (Push): 0.4 RPS (rounded to nearest integer)
For GitLab instances with less than 2,000 users, it's recommended that you use
the [default setup](#automated-backups) by

View File

@ -173,7 +173,7 @@ delete the files:
sudo gitlab-rake gitlab:cleanup:orphan_job_artifact_files DRY_RUN=false
```
You can also limit the number of files to delete with `LIMIT`:
You can also limit the number of files to delete with `LIMIT` (default `100`):
```shell
sudo gitlab-rake gitlab:cleanup:orphan_job_artifact_files LIMIT=100

View File

@ -534,14 +534,16 @@ limiting responses](#rate-limiting-responses).
The following table describes the rate limits for GitLab.com, both before and
after the limits change in January, 2021:
| Rate limit | Before 2021-01-18 | From 2021-01-18 |
|:--------------------------------------------------------------------------|:----------------------------|:------------------------------|
| **Protected paths** (for a given **IP address**) | **10** requests per minute | **10** requests per minute |
| **Raw endpoint** traffic (for a given **project, commit, and file path**) | **300** requests per minute | **300** requests per minute |
| **Unauthenticated** traffic (from a given **IP address**) | No specific limit | **500** requests per minute |
| **Authenticated** API traffic (for a given **user**) | No specific limit | **2,000** requests per minute |
| **Authenticated** non-API HTTP traffic (for a given **user**) | No specific limit | **1,000** requests per minute |
| **All** traffic (from a given **IP address**) | **600** requests per minute | **2,000** requests per minute |
| Rate limit | Before 2021-01-18 | From 2021-01-18 | From 2021-02-12 |
|:--------------------------------------------------------------------------|:----------------------------|:------------------------------|:------------------------------|
| **Protected paths** (for a given **IP address**) | **10** requests per minute | **10** requests per minute | **10** requests per minute |
| **Raw endpoint** traffic (for a given **project, commit, and file path**) | **300** requests per minute | **300** requests per minute | **300** requests per minute |
| **Unauthenticated** traffic (from a given **IP address**) | No specific limit | **500** requests per minute | **500** requests per minute |
| **Authenticated** API traffic (for a given **user**) | No specific limit | **2,000** requests per minute | **2,000** requests per minute |
| **Authenticated** non-API HTTP traffic (for a given **user**) | No specific limit | **1,000** requests per minute | **1,000** requests per minute |
| **All** traffic (from a given **IP address**) | **600** requests per minute | **2,000** requests per minute | **2,000** requests per minute |
| **Issue creation** | | **300** requests per minute | **300** requests per minute |
| **Note creation** (on issues and merge requests) | | **300** requests per minute | **60** requests per minute |
More details are available on the rate limits for [protected
paths](#protected-paths-throttle) and [raw

View File

@ -9,15 +9,12 @@ module Gitlab
start = Time.now
headers = (headers || {})
.reverse_merge({ 'X-Opaque-Id': Labkit::Correlation::CorrelationId.current_or_new_id })
response = super
super
ensure
if ::Gitlab::SafeRequestStore.active?
duration = (Time.now - start)
::Gitlab::Instrumentation::ElasticsearchTransport.increment_request_count
::Gitlab::Instrumentation::ElasticsearchTransport.increment_timed_out_count if response&.body&.dig('timed_out')
::Gitlab::Instrumentation::ElasticsearchTransport.add_duration(duration)
::Gitlab::Instrumentation::ElasticsearchTransport.add_call_details(duration, method, path, params, body)
end
@ -28,7 +25,6 @@ module Gitlab
ELASTICSEARCH_REQUEST_COUNT = :elasticsearch_request_count
ELASTICSEARCH_CALL_DURATION = :elasticsearch_call_duration
ELASTICSEARCH_CALL_DETAILS = :elasticsearch_call_details
ELASTICSEARCH_TIMED_OUT_COUNT = :elasticsearch_timed_out_count
def self.get_request_count
::Gitlab::SafeRequestStore[ELASTICSEARCH_REQUEST_COUNT] || 0
@ -53,15 +49,6 @@ module Gitlab
::Gitlab::SafeRequestStore[ELASTICSEARCH_CALL_DURATION] += duration
end
def self.increment_timed_out_count
::Gitlab::SafeRequestStore[ELASTICSEARCH_TIMED_OUT_COUNT] ||= 0
::Gitlab::SafeRequestStore[ELASTICSEARCH_TIMED_OUT_COUNT] += 1
end
def self.get_timed_out_count
::Gitlab::SafeRequestStore[ELASTICSEARCH_TIMED_OUT_COUNT] || 0
end
def self.add_call_details(duration, method, path, params, body)
return unless Gitlab::PerformanceBar.enabled_for_request?

View File

@ -15,7 +15,6 @@ module Gitlab
:rugged_duration_s,
:elasticsearch_calls,
:elasticsearch_duration_s,
:elasticsearch_timed_out_count,
*::Gitlab::Memory::Instrumentation::KEY_MAPPING.values,
*::Gitlab::Instrumentation::Redis.known_payload_keys,
*::Gitlab::Metrics::Subscribers::ActiveRecord::DB_COUNTERS,
@ -80,7 +79,6 @@ module Gitlab
payload[:elasticsearch_calls] = elasticsearch_calls
payload[:elasticsearch_duration_s] = Gitlab::Instrumentation::ElasticsearchTransport.query_time
payload[:elasticsearch_timed_out_count] = Gitlab::Instrumentation::ElasticsearchTransport.get_timed_out_count
end
def instrument_external_http(payload)

View File

@ -16,7 +16,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
msgid " %{project_name}#%{issuable_iid} &middot; opened %{issuable_created} by %{author}"
msgid " %{project_name}#%{issuable_iid} &middot; opened %{issuable_created} by %{author} &middot; updated %{issuable_updated}"
msgstr ""
msgid " %{start} to %{end}"
@ -8481,6 +8481,9 @@ msgstr ""
msgid "Create new"
msgstr ""
msgid "Create new %{name} by email"
msgstr ""
msgid "Create new Value Stream"
msgstr ""
@ -10863,6 +10866,9 @@ msgstr ""
msgid "Email Notification"
msgstr ""
msgid "Email a new %{name} to this project"
msgstr ""
msgid "Email address to use for Support Desk"
msgstr ""
@ -10935,12 +10941,6 @@ msgstr ""
msgid "EmailParticipantsWarning|and %{moreCount} more"
msgstr ""
msgid "EmailToken|reset it"
msgstr ""
msgid "EmailToken|resetting..."
msgstr ""
msgid "Emails"
msgstr ""
@ -11226,21 +11226,15 @@ msgstr ""
msgid "Enter one or more user ID separated by commas"
msgstr ""
msgid "Enter the %{name} description"
msgstr ""
msgid "Enter the %{name} title"
msgstr ""
msgid "Enter the code from the two-factor app on your mobile device. If you've lost your device, you may enter one of your recovery codes."
msgstr ""
msgid "Enter the issue description"
msgstr ""
msgid "Enter the issue title"
msgstr ""
msgid "Enter the merge request description"
msgstr ""
msgid "Enter the merge request title"
msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
@ -23934,6 +23928,9 @@ msgstr ""
msgid "Protip:"
msgstr ""
msgid "Protip: %{linkStart}Auto DevOps%{linkEnd} uses Kubernetes clusters to deploy your code!"
msgstr ""
msgid "Protocol"
msgstr ""
@ -29320,6 +29317,9 @@ msgstr ""
msgid "The status of the table below only applies to the default branch and is based on the %{linkStart}latest pipeline%{linkEnd}. Once you've enabled a scan for the default branch, any subsequent feature branch you create will include the scan."
msgstr ""
msgid "The subject will be used as the title of the new issue, and the message will be the description. %{quickActionsLinkStart}Quick actions%{quickActionsLinkEnd} and styling with %{markdownLinkStart}Markdown%{markdownLinkEnd} are supported."
msgstr ""
msgid "The tag name can't be changed for an existing release."
msgstr ""
@ -29926,6 +29926,9 @@ msgstr ""
msgid "This is a merge train pipeline"
msgstr ""
msgid "This is a private email address %{helpIcon} generated just for you. Anyone who gets ahold of it can create issues or merge requests as if they were you. You should %{resetLinkStart}reset it%{resetLinkEnd} if that ever happens."
msgstr ""
msgid "This is a security log of important events involving your account."
msgstr ""
@ -30223,6 +30226,9 @@ msgstr ""
msgid "Threat Monitoring"
msgstr ""
msgid "ThreatMonitoring|Alert Details"
msgstr ""
msgid "ThreatMonitoring|Alerts"
msgstr ""
@ -33349,6 +33355,9 @@ msgstr ""
msgid "You can create a new %{link}."
msgstr ""
msgid "You can create a new %{name} inside this project by sending an email to the following email address:"
msgstr ""
msgid "You can create a new Personal Access Token by visiting %{link}"
msgstr ""

View File

@ -1,4 +1,5 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'json'

View File

@ -1,4 +1,5 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
# lib/rspec_flaky/flaky_examples_collection.rb is requiring
# `active_support/hash_with_indifferent_access`, and we install the `activesupport`

View File

@ -1,4 +1,5 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'csv'

View File

@ -1,4 +1,5 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
abort "usage: #{__FILE__} <memory_bundle_objects_file_name>" unless ARGV.length == 1
memory_bundle_objects_file_name = ARGV.first

View File

@ -1,4 +1,5 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
abort "usage: #{__FILE__} <memory_bundle_mem_file_name>" unless ARGV.length == 1
memory_bundle_mem_file_name = ARGV.first

View File

@ -1,4 +1,5 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
abort "usage: #{__FILE__} <memory_bundle_mem_file_name>" unless ARGV.length == 1
memory_bundle_mem_file_name = ARGV.first

View File

@ -1,4 +1,5 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'json'
require_relative '../tooling/lib/tooling/test_map_generator'

View File

@ -1,4 +1,5 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'fileutils'

View File

@ -1,4 +1,5 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
# This script is used both in CI and in local development 'rspec' runs.

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
# This file contains environment settings for gitaly when it's running
# as part of the gitlab-ce/ee test suite.
#
@ -52,7 +54,7 @@ module GitalyTest
if ENV['CI']
bundle_path = File.expand_path('../vendor/gitaly-ruby', __dir__)
env_hash['BUNDLE_FLAGS'] << " --path=#{bundle_path}"
env_hash['BUNDLE_FLAGS'] += " --path=#{bundle_path}"
end
env_hash

View File

@ -1,4 +1,5 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'csv'
require 'rspec_profiling'

View File

@ -1,4 +1,5 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
ALLOWED = [
# https://gitlab.com/gitlab-org/gitaly/issues/760

View File

@ -1,4 +1,5 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'nokogiri'

View File

@ -1,4 +1,5 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'json'

View File

@ -1,4 +1,5 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require_relative '../spec/simplecov_env'
SimpleCovEnv.configure_profile

View File

@ -1,4 +1,6 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
ee_path = File.join(File.expand_path(__dir__), '../ee')
if Dir.exist?(ee_path)

View File

@ -1,4 +1,5 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'json'
require_relative '../tooling/lib/tooling/test_map_packer'

View File

@ -1,4 +1,5 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
# We don't have auto-loading here
require_relative '../lib/gitlab'

View File

@ -1,4 +1,5 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'rubygems'
require 'fog/aws'

View File

@ -1,4 +1,5 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'json'
require_relative '../tooling/lib/tooling/test_map_packer'

View File

@ -1,4 +1,5 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'uri'
require 'net/http'

View File

@ -1,4 +1,5 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'set'

View File

@ -1,4 +1,5 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'set'

View File

@ -18,9 +18,9 @@ RSpec.describe 'Issue Boards', :js do
project.add_maintainer(user)
project.add_maintainer(user2)
set_cookie('sidebar_collapsed', 'true')
sign_in(user)
set_cookie('sidebar_collapsed', 'true')
end
context 'no lists' do

View File

@ -12,7 +12,7 @@ RSpec.describe 'Issues > User creates issue by email' do
project.add_developer(user)
end
describe 'new issue by email' do
describe 'new issue by email', :js do
shared_examples 'show the email in the modal' do
let(:issue) { create(:issue, project: project) }
@ -28,7 +28,7 @@ RSpec.describe 'Issues > User creates issue by email' do
page.within '#issuable-email-modal' do
email = project.new_issuable_address(user, 'issue')
expect(page).to have_selector("input[value='#{email}']")
expect(page.find('input[type="text"]').value).to eq email
end
end
end

View File

@ -16,17 +16,17 @@ RSpec.describe 'Issues > User resets their incoming email token' do
end
it 'changes incoming email address token', :js do
find('.issuable-email-modal-btn').click
previous_token = find('input#issuable_email').value
find('.incoming-email-token-reset').click
page.find('[data-testid="issuable-email-modal-btn"]').click
wait_for_requests
page.within '#issuable-email-modal' do
previous_token = page.find('input[type="text"]').value
page.find('[data-testid="incoming-email-token-reset"]').click
expect(page).to have_no_field('issuable_email', with: previous_token)
new_token = project.new_issuable_address(user.reload, 'issue')
expect(page).to have_field(
'issuable_email',
with: new_token
)
wait_for_requests
expect(page.find('input[type="text"]').value).not_to eq previous_token
new_token = project.new_issuable_address(user.reload, 'issue')
expect(page.find('input[type="text"]').value).to eq new_token
end
end
end

View File

@ -38,7 +38,9 @@ jest.mock('@gitlab/ui/dist/components/base/popover/popover.js', () => ({
required: false,
default: () => [],
},
...Object.fromEntries(['target', 'triggers', 'placement'].map((prop) => [prop, {}])),
...Object.fromEntries(
['target', 'triggers', 'placement', 'boundary', 'container'].map((prop) => [prop, {}]),
),
},
render(h) {
return h(

Some files were not shown because too many files have changed in this diff Show More