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/_gitlab_import_modal.html.haml'
- 'app/views/projects/_home_panel.html.haml' - 'app/views/projects/_home_panel.html.haml'
- 'app/views/projects/_import_project_pane.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/_readme.html.haml'
- 'app/views/projects/artifacts/_artifact.html.haml' - 'app/views/projects/artifacts/_artifact.html.haml'
- 'app/views/projects/artifacts/_tree_file.html.haml' - 'app/views/projects/artifacts/_tree_file.html.haml'

View File

@ -2473,50 +2473,3 @@ Gitlab/NamespacedClass:
- 'spec/support/sidekiq_middleware.rb' - 'spec/support/sidekiq_middleware.rb'
- 'spec/tasks/gitlab/task_helpers_spec.rb' - 'spec/tasks/gitlab/task_helpers_spec.rb'
- 'spec/uploaders/object_storage_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' source 'https://rubygems.org'
gem 'rails', '~> 6.0.3.1' gem 'rails', '~> 6.0.3.1'

View File

@ -1,4 +1,6 @@
#!/usr/bin/env rake #!/usr/bin/env rake
# frozen_string_literal: true
# Add your own tasks in files placed in lib/tasks ending in .rake, # 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. # 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'; import issuableInitBulkUpdateSidebar from './issuable_init_bulk_update_sidebar';
export default class IssuableIndex { export default class IssuableIndex {
constructor(pagePrefix) { constructor(pagePrefix) {
issuableInitBulkUpdateSidebar.init(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 { ISSUABLE_INDEX } from '~/pages/projects/constants';
import initIssuablesList from '~/issues_list'; import initIssuablesList from '~/issues_list';
import initManualOrdering from '~/manual_ordering'; import initManualOrdering from '~/manual_ordering';
import initIssuableByEmail from '~/issuable/init_issuable_by_email';
IssuableFilteredSearchTokenKeys.addExtraTokensForIssues(); IssuableFilteredSearchTokenKeys.addExtraTokensForIssues();
@ -24,3 +25,4 @@ new UsersSelect();
initManualOrdering(); initManualOrdering();
initIssuablesList(); 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 IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
import { FILTERED_SEARCH } from '~/pages/constants'; import { FILTERED_SEARCH } from '~/pages/constants';
import { ISSUABLE_INDEX } from '~/pages/projects/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 new IssuableIndex(ISSUABLE_INDEX.MERGE_REQUEST); // eslint-disable-line no-new
@ -19,3 +20,5 @@ initFilteredSearch({
new UsersSelect(); // eslint-disable-line no-new new UsersSelect(); // eslint-disable-line no-new
new ShortcutsNavigation(); // 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'; import initActivityCharts from '~/analytics/product_analytics/activity_charts_bundle';
document.addEventListener('DOMContentLoaded', () => initActivityCharts()); initActivityCharts();

View File

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

View File

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

View File

@ -9,11 +9,10 @@ export const SEVERITY_LEVELS = {
UNKNOWN: s__('severity|Unknown'), UNKNOWN: s__('severity|Unknown'),
}; };
export const DEFAULT_PAGE = 'OPERATIONS';
/* eslint-disable @gitlab/require-i18n-strings */ /* eslint-disable @gitlab/require-i18n-strings */
export const PAGE_CONFIG = { export const PAGE_CONFIG = {
OPERATIONS: { OPERATIONS: {
TITLE: 'OPERATIONS',
// Tracks snowplow event when user views alert details // Tracks snowplow event when user views alert details
TRACK_ALERTS_DETAILS_VIEWS_OPTIONS: { TRACK_ALERTS_DETAILS_VIEWS_OPTIONS: {
category: 'Alert Management', category: 'Alert Management',
@ -26,4 +25,7 @@ export const PAGE_CONFIG = {
label: 'Status', 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 AlertDetails from './components/alert_details.vue';
import sidebarStatusQuery from './graphql/queries/alert_sidebar_status.query.graphql'; import sidebarStatusQuery from './graphql/queries/alert_sidebar_status.query.graphql';
import createRouter from './router'; import createRouter from './router';
import { DEFAULT_PAGE, PAGE_CONFIG } from './constants'; import { PAGE_CONFIG } from './constants';
Vue.use(VueApollo); Vue.use(VueApollo);
export default (selector) => { export default (selector) => {
const domEl = document.querySelector(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 router = createRouter();
const resolvers = { const resolvers = {
@ -52,16 +52,19 @@ export default (selector) => {
const provide = { const provide = {
projectPath, projectPath,
alertId, alertId,
page,
projectIssuesPath, projectIssuesPath,
projectId, projectId,
}; };
if (page === DEFAULT_PAGE) { if (page === PAGE_CONFIG.OPERATIONS.TITLE) {
const { TRACK_ALERTS_DETAILS_VIEWS_OPTIONS, TRACK_ALERT_STATUS_UPDATE_OPTIONS } = PAGE_CONFIG[ const { TRACK_ALERTS_DETAILS_VIEWS_OPTIONS, TRACK_ALERT_STATUS_UPDATE_OPTIONS } = PAGE_CONFIG[
page page
]; ];
provide.trackAlertsDetailsViewsOptions = TRACK_ALERTS_DETAILS_VIEWS_OPTIONS; provide.trackAlertsDetailsViewsOptions = TRACK_ALERTS_DETAILS_VIEWS_OPTIONS;
provide.trackAlertStatusUpdateOptions = TRACK_ALERT_STATUS_UPDATE_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 // eslint-disable-next-line no-new

View File

@ -5,7 +5,7 @@
$bs-input-focus-border: #80bdff; $bs-input-focus-border: #80bdff;
$bs-input-focus-box-shadow: rgba(0, 123, 255, 0.25); $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,
.gl-button.btn-link:hover, .gl-button.btn-link:hover,
.gl-button.btn-link:focus, .gl-button.btn-link:focus,
@ -34,6 +34,7 @@
.ide-pipeline .top-bar .controllers .controllers-buttons, .ide-pipeline .top-bar .controllers .controllers-buttons,
.controllers-buttons svg, .controllers-buttons svg,
.nav-links li a.active, .nav-links li a.active,
.gl-tabs-nav li a.gl-tab-nav-item-active,
.md-area.is-focused { .md-area.is-focused {
color: var(--ide-text-color, $gl-text-color); color: var(--ide-text-color, $gl-text-color);
} }
@ -44,13 +45,15 @@
} }
.nav-links:not(.quick-links) li:not(.md-header-toolbar) a, .nav-links:not(.quick-links) li:not(.md-header-toolbar) a,
.gl-tabs-nav li a,
.dropdown-menu-inner-content, .dropdown-menu-inner-content,
.file-row .file-row-icon svg, .file-row .file-row-icon svg,
.file-row:hover .file-row-icon svg { .file-row:hover .file-row-icon svg {
color: var(--ide-text-color-secondary, $gl-text-color-secondary); 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, &:hover a,
&.active a, &.active a,
a:hover, a:hover,
@ -61,6 +64,10 @@
border-color: var(--ide-input-border, $gray-darkest); 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 { .drag-handle:hover {
@ -142,6 +149,7 @@
.md table:not(.code) tbody td, .md table:not(.code) tbody td,
.md table:not(.code) tr th, .md table:not(.code) tr th,
.nav-links:not(.quick-links), .nav-links:not(.quick-links),
.gl-tabs-nav,
.common-note-form .md-area.is-focused .nav-links { .common-note-form .md-area.is-focused .nav-links {
border-color: var(--ide-border-color-alt, $white-dark); border-color: var(--ide-border-color-alt, $white-dark);
} }
@ -175,6 +183,10 @@
border-color: var(--ide-highlight-accent, $gl-text-color); 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 // for other themes, suppress different avatar default colors for simplicity
.avatar-container { .avatar-container {
&, &,
@ -304,6 +316,11 @@
border-color: var(--ide-dropdown-hover-background, $border-color); 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 { .divider {
background-color: var(--ide-dropdown-hover-background, $gray-100); background-color: var(--ide-dropdown-hover-background, $gray-100);
border-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-right: 1px solid var(--ide-border-color, $white-dark);
border-bottom: 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); background-color: var(--ide-highlight-background, $white);
border-bottom-color: transparent; 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 { .multi-file-tab {
@ -634,7 +671,8 @@ $ide-commit-header-height: 48px;
height: 100%; height: 100%;
} }
.nav-links { .nav-links,
.gl-tabs-nav {
height: 30px; height: 30px;
} }
@ -976,17 +1014,25 @@ $ide-commit-header-height: 48px;
} }
.ide-nav-form { .ide-nav-form {
.nav-links li { .nav-links li,
.gl-tabs-nav li {
width: 50%; width: 50%;
padding-left: 0; padding-left: 0;
padding-right: 0; padding-right: 0;
a { a {
text-align: center; 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); 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 { .email-modal-input-group {
margin-bottom: 10px; margin-bottom: 10px;

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
xml.title "#{current_user.name} issues" xml.title "#{current_user.name} issues"
xml.link href: url_for(safe_params), rel: "self", type: "application/atom+xml" 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.title "Activity"
xml.link href: dashboard_projects_url(rss_url_options), rel: "self", type: "application/atom+xml" 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" 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) return unless event.visible_to_user?(current_user)
event = event.present event = event.present

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
xml.title "#{@group.name} issues" xml.title "#{@group.name} issues"
xml.link href: url_for(safe_params), rel: "self", type: "application/atom+xml" 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.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, rss_url_options), rel: "self", type: "application/atom+xml"
xml.link href: group_url(@group), rel: "alternate", type: "text/html" 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.entry do
xml.id project_issue_url(issue.project, issue) xml.id project_issue_url(issue.project, issue)
xml.link href: 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 = Icalendar::Calendar.new
cal.prodid = '-//GitLab//NONSGML GitLab//EN' cal.prodid = '-//GitLab//NONSGML GitLab//EN'
cal.x_wr_calname = 'GitLab Issues' cal.x_wr_calname = 'GitLab Issues'

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
xml.instruct! xml.instruct!
xml.feed 'xmlns' => 'http://www.w3.org/2005/Atom', 'xmlns:media' => 'http://search.yahoo.com/mrss/' do xml.feed 'xmlns' => 'http://www.w3.org/2005/Atom', 'xmlns:media' => 'http://search.yahoo.com/mrss/' do
xml << yield 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.entry do
xml.id project_commit_url(@project, id: commit.id) xml.id project_commit_url(@project, id: commit.id)
xml.link href: 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.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, rss_url_options), rel: "self", type: "application/atom+xml"
xml.link href: project_commits_url(@project, @ref), rel: "alternate", type: "text/html" 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 # rubocop: disable CodeReuse/ActiveRecord
xml.title "#{@project.name} issues" xml.title "#{@project.name} issues"
xml.link href: url_for(safe_params), rel: "self", type: "application/atom+xml" xml.link href: url_for(safe_params), rel: "self", type: "application/atom+xml"

View File

@ -3,6 +3,7 @@
- page_title _("Issues") - page_title _("Issues")
- new_issue_email = @project.new_issuable_address(current_user, 'issue') - new_issue_email = @project.new_issuable_address(current_user, 'issue')
- add_page_specific_style 'page_bundles/issues_list' - add_page_specific_style 'page_bundles/issues_list'
- issuable_type = 'issue'
= content_for :meta_tags do = content_for :meta_tags do
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@project.name} issues") = auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@project.name} issues")
@ -24,7 +25,8 @@
.issues-holder .issues-holder
= render 'issues' = render 'issues'
- if new_issue_email - 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 - else
- new_project_issue_button_path = @project.archived? ? false : new_project_issue_path(@project) - 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 = 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) - @can_bulk_update = can?(current_user, :admin_merge_request, @project)
- merge_project = merge_request_source_project_for_project(@project) - merge_project = merge_request_source_project_for_project(@project)
- new_merge_request_path = project_new_merge_request_path(merge_project) if merge_project - new_merge_request_path = project_new_merge_request_path(merge_project) if merge_project
- issuable_type = 'merge_request'
- page_title _("Merge Requests") - page_title _("Merge Requests")
- new_merge_request_email = @project.new_issuable_address(current_user, 'merge_request') - new_merge_request_email = @project.new_issuable_address(current_user, 'merge_request')
@ -21,6 +22,7 @@
.merge-requests-holder .merge-requests-holder
= render 'merge_requests' = render 'merge_requests'
- if new_merge_request_email - 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 - else
= render 'shared/empty_states/merge_requests', button_path: new_merge_request_path = 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.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, rss_url_options), rel: "self", type: "application/atom+xml"
xml.link href: project_url(@project), rel: "alternate", type: "text/html" 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) commit = @repository.commit(tag.dereferenced_target)
release = @releases.find { |r| r.tag == tag.name } release = @releases.find { |r| r.tag == tag.name }
tag_url = project_tag_url(@project, 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.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, rss_url_options), rel: 'self', type: 'application/atom+xml'
xml.link href: project_tags_url(@project, @ref), rel: 'alternate', type: 'text/html' 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 = 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 %span.term.str-truncated.gl-font-weight-bold.gl-ml-2= issuable.title
.gl-text-gray-500.gl-my-3 .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 .description.term.col-sm-10.gl-px-0
= highlight_and_truncate_issuable(issuable, @search_term, @search_highlight) = 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.title "#{@user.name} activity"
xml.link href: user_url(@user, :atom), rel: "self", type: "application/atom+xml" xml.link href: user_url(@user, :atom), rel: "self", type: "application/atom+xml"
xml.link href: user_url(@user), rel: "alternate", type: "text/html" xml.link href: user_url(@user), rel: "alternate", type: "text/html"

View File

@ -1,6 +1,6 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
# frozen_string_literal: false # frozen_string_literal: true
require 'active_support/core_ext/object/to_query' require 'active_support/core_ext/object/to_query'
require 'optparse' require 'optparse'
@ -9,12 +9,12 @@ require 'rainbow/refinement'
using Rainbow using Rainbow
module Secpick module Secpick
BRANCH_PREFIX = 'security'.freeze BRANCH_PREFIX = 'security'
STABLE_SUFFIX = 'stable'.freeze 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 class SecurityFix
def initialize def initialize
@ -27,12 +27,12 @@ module Secpick
def source_branch def source_branch
branch = "#{@options[:branch]}-#{@options[:version]}" branch = "#{@options[:branch]}-#{@options[:version]}"
branch.prepend("#{BRANCH_PREFIX}-") unless branch.start_with?("#{BRANCH_PREFIX}-") branch = "#{BRANCH_PREFIX}-#{branch}" unless branch.start_with?("#{BRANCH_PREFIX}-")
branch.freeze branch
end end
def stable_branch def stable_branch
"#{@options[:version]}-#{STABLE_SUFFIX}-ee".freeze "#{@options[:version]}-#{STABLE_SUFFIX}-ee"
end end
def git_commands 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 # 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) # /usr/local/bundle/gems/git-1.4.0/lib/git/lib.rb:956:in `command': (Danger::DSLError)
# [!] Invalid `Dangerfile` file: # [!] Invalid `Dangerfile` file:

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
# rubocop:disable Style/SignalException # rubocop:disable Style/SignalException
THROUGHPUT_LABELS = [ 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; 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 ( CREATE TABLE security_scans (
id bigint NOT NULL, id bigint NOT NULL,
created_at timestamp with time zone 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_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 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); 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 ALTER TABLE ONLY security_findings
ADD CONSTRAINT security_findings_pkey PRIMARY KEY (id); 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 ALTER TABLE ONLY security_scans
ADD CONSTRAINT security_scans_pkey PRIMARY KEY (id); 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_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_description_trigram ON sprints USING gin (description gin_trgm_ops);
CREATE INDEX index_sprints_on_due_date ON sprints USING btree (due_date); 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 ALTER TABLE ONLY trending_projects
ADD CONSTRAINT fk_rails_09feecd872 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; 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 ALTER TABLE ONLY project_deploy_tokens
ADD CONSTRAINT fk_rails_0aca134388 FOREIGN KEY (deploy_token_id) REFERENCES deploy_tokens(id) ON DELETE CASCADE; 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 ALTER TABLE ONLY ci_refs
ADD CONSTRAINT fk_rails_4249db8cc3 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; 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 ALTER TABLE ONLY ci_resources
ADD CONSTRAINT fk_rails_430336af2d FOREIGN KEY (resource_group_id) REFERENCES ci_resource_groups(id) ON DELETE CASCADE; 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_calls`: total number of calls to Elasticsearch
1. `elasticsearch_duration_s`: total time taken by Elasticsearch calls 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 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. 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). [Available reference architectures](index.md#available-reference-architectures).
> - **Supported users (approximate):** 10,000 > - **Supported users (approximate):** 10,000
> - **High Availability:** Yes > - **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: 20 RPS > - **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 | | Service | Nodes | Configuration | GCP | AWS | Azure |
|--------------------------------------------|-------------|-------------------------|-----------------|-------------|----------| |--------------------------------------------|-------------|-------------------------|-----------------|-------------|----------|
| External load balancing node | 1 | 2 vCPU, 1.8 GB memory | n1-highcpu-2 | `c5.large` | F2s 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 | | 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 | | 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 | | 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 | | 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 - 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 - 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 - 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 | | 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 | | Gitaly Cluster | 3 | 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 | | Praefect | 3 | 2 vCPU, 1.8 GB memory | n1-highcpu-2 | c5.large | F2s v2 |
| GitLab Rails | 3 | 32 vCPU, 28.8 GB memory | n1-highcpu-32 | `c5.9xlarge` | F32s v2 | | Praefect PostgreSQL | 1+* | 2 vCPU, 1.8 GB memory | n1-highcpu-2 | c5.large | F2s v2 |
| Monitoring node | 1 | 4 vCPU, 3.6 GB memory | n1-highcpu-4 | `c5.xlarge` | F4s 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 | | 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 ```plantuml
stateDiagram-v2 @startuml 10k
[*] --> LoadBalancer card "**External Load Balancer**" as elb #6a9be7
LoadBalancer --> ApplicationServer card "**Internal Load Balancer**" as ilb #9370DB
ApplicationServer --> BackgroundJobs together {
ApplicationServer --> Gitaly collections "**GitLab Rails** x3" as gitlab #32CD32
ApplicationServer --> Redis_Cache collections "**Sidekiq** x4" as sidekiq #ff8dd1
ApplicationServer --> Redis_Queues }
ApplicationServer --> PgBouncer
PgBouncer --> Database
ApplicationServer --> ObjectStorage
BackgroundJobs --> ObjectStorage
ApplicationMonitoring -->ApplicationServer together {
ApplicationMonitoring -->PgBouncer card "**Prometheus + Grafana**" as monitor #7FFFD4
ApplicationMonitoring -->Database collections "**Consul** x3" as consul #e76a9b
ApplicationMonitoring -->BackgroundJobs }
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 praefect -[#FF8C00]-> gitaly
Consul --> PgBouncer praefect -[#FF8C00]> praefect_postgres
Redis_Cache --> Consul }
Redis_Queues --> Consul
BackgroundJobs --> Consul
state Consul { card "Database" as database {
"Consul_1..3" collections "**PGBouncer** x3" as pgbouncer #4EA7FF
} card "**PostgreSQL** (Primary)" as postgres_primary #4EA7FF
collections "**PostgreSQL** (Secondary) x2" as postgres_secondary #4EA7FF
state Database { pgbouncer -[#4EA7FF]-> postgres_primary
"PG_Primary_Node" postgres_primary .[#4EA7FF]> postgres_secondary
"PG_Secondary_Node_1..2" }
}
state Redis_Cache { card "redis" as redis {
"R_Cache_Primary_Node" collections "**Redis Persistent** x3" as redis_persistent #FF6347
"R_Cache_Replica_Node_1..2" collections "**Redis Cache** x3" as redis_cache #FF6347
"R_Cache_Sentinel_1..3" collections "**Redis Persistent Sentinel** x3" as redis_persistent_sentinel #FF6347
} collections "**Redis Cache Sentinel** x3"as redis_cache_sentinel #FF6347
state Redis_Queues { redis_persistent <.[#FF6347]- redis_persistent_sentinel
"R_Queues_Primary_Node" redis_cache <.[#FF6347]- redis_cache_sentinel
"R_Queues_Replica_Node_1..2" }
"R_Queues_Sentinel_1..3"
}
state Gitaly { cloud "**Object Storage**" as object_storage #white
"Gitaly_1..2"
}
state BackgroundJobs { elb -[#6a9be7]-> gitlab
"Sidekiq_1..4" elb -[#6a9be7]--> monitor
}
state ApplicationServer { gitlab -[#32CD32]> sidekiq
"GitLab_Rails_1..3" gitlab -[#32CD32]--> ilb
} gitlab -[#32CD32]-> object_storage
gitlab -[#32CD32]---> redis
gitlab -[hidden]-> monitor
gitlab -[hidden]-> consul
state LoadBalancer { sidekiq -[#ff8dd1]--> ilb
"LoadBalancer_1" sidekiq -[#ff8dd1]-> object_storage
} sidekiq -[#ff8dd1]---> redis
sidekiq -[hidden]-> monitor
sidekiq -[hidden]-> consul
state ApplicationMonitoring { ilb -[#9370DB]-> gitaly_cluster
"Prometheus" ilb -[#9370DB]-> database
"Grafana"
}
state PgBouncer { consul .[#e76a9b]u-> gitlab
"Internal_Load_Balancer" consul .[#e76a9b]u-> sidekiq
"PgBouncer_1..3" 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 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 is recommended instead of using NFS. Using an object storage service also
doesn't require you to provision and maintain a node. 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 ## Setup components
To set up GitLab and its components to accommodate up to 10,000 users: 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. 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 Consul](#configure-consul).
1. [Configure PostgreSQL](#configure-postgresql), the database for GitLab. 1. [Configure PostgreSQL](#configure-postgresql), the database for GitLab.
1. [Configure PgBouncer](#configure-pgbouncer). 1. [Configure PgBouncer](#configure-pgbouncer).
1. [Configure the internal load balancing node](#configure-the-internal-load-balancer).
1. [Configure Redis](#configure-redis). 1. [Configure Redis](#configure-redis).
1. [Configure Gitaly](#configure-gitaly), 1. [Configure Gitaly Cluster](#configure-gitaly-cluster),
which provides access to the Git repositories. provides access to the Git repositories.
1. [Configure Sidekiq](#configure-sidekiq). 1. [Configure Sidekiq](#configure-sidekiq).
1. [Configure the main GitLab Rails application](#configure-gitlab-rails) 1. [Configure the main GitLab Rails application](#configure-gitlab-rails)
to run Puma/Unicorn, Workhorse, GitLab Shell, and to serve all frontend 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.83`: Sentinel - Queues 3
- `10.6.0.91`: Gitaly 1 - `10.6.0.91`: Gitaly 1
- `10.6.0.92`: Gitaly 2 - `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.101`: Sidekiq 1
- `10.6.0.102`: Sidekiq 2 - `10.6.0.102`: Sidekiq 2
- `10.6.0.103`: Sidekiq 3 - `10.6.0.103`: Sidekiq 3
@ -308,6 +329,71 @@ Configure DNS for an alternate SSH hostname such as `altssh.gitlab.example.com`.
</a> </a>
</div> </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 ## Configure Consul
The following IPs will be used as an example: The following IPs will be used as an example:
@ -662,52 +748,6 @@ The following IPs will be used as an example:
</a> </a>
</div> </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 ## Configure Redis
Using [Redis](https://redis.io/) in scalable environment is possible using a **Primary** x **Replica** 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> </a>
</div> </div>
## Configure Gitaly ## Configure Gitaly Cluster
NOTE: [Gitaly Cluster](../gitaly/praefect.md) is a GitLab provided and recommended fault tolerant solution for storing Git repositories.
[Gitaly Cluster](../gitaly/praefect.md) support 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.
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](../gitaly/index.md) server node requirements are dependent on data, The recommended cluster setup includes the following components:
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 - 3 Gitaly nodes: Replicated storage of Git repositories.
repository storage requirements, you may require additional Gitaly server nodes. - 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 Due to Gitaly having notable input and output requirements, we strongly
recommend that all Gitaly nodes use solid-state drives (SSDs). These SSDs 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, environment's workload. If you're running the environment on a Cloud provider,
refer to their documentation about how to configure IOPS correctly. 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 For configuring Gitaly you should note the following:
[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).
NOTE: - `git_data_dirs` should be configured to reflect the storage path for the specific Gitaly node
The token referred to throughout the Gitaly documentation is an arbitrary - `auth_token` should be the same as `praefect_internal_token`
password selected by the administrator. This token is unrelated to tokens
created for the GitLab API or other similar web API tokens.
This section describes how to configure two Gitaly servers, with the following The following IPs will be used as an example:
IPs and domain names:
- `10.6.0.91`: Gitaly 1 (`gitaly1.internal`) - `10.6.0.91`: Gitaly 1
- `10.6.0.92`: Gitaly 2 (`gitaly2.internal`) - `10.6.0.92`: Gitaly 2
- `10.6.0.93`: Gitaly 3
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
On each node: On each node:
@ -1364,21 +1653,9 @@ On each node:
1. Edit the Gitaly server node's `/etc/gitlab/gitlab.rb` file to configure 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: 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 ```ruby
# /etc/gitlab/gitlab.rb # /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 # Avoid running unnecessary services on the Gitaly server
postgresql['enable'] = false postgresql['enable'] = false
redis['enable'] = false redis['enable'] = false
@ -1407,36 +1684,42 @@ On each node:
# firewalls to restrict access to this address/port. # firewalls to restrict access to this address/port.
# Comment out following line if you only want to support TLS connections # Comment out following line if you only want to support TLS connections
gitaly['listen_addr'] = "0.0.0.0:8075" 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: 1. Append the following to `/etc/gitlab/gitlab.rb` for each respective server:
- On `gitaly1.internal`: - On Gitaly node 1:
```ruby ```ruby
git_data_dirs({ git_data_dirs({
'default' => { "gitaly-1" => {
'path' => '/var/opt/gitlab/git-data' "path" => "/var/opt/gitlab/git-data"
}, }
'storage1' => {
'path' => '/mnt/gitlab/git-data'
},
}) })
``` ```
- On `gitaly2.internal`: - On Gitaly node 2:
```ruby ```ruby
git_data_dirs({ git_data_dirs({
'storage2' => { "gitaly-2" => {
'path' => '/mnt/gitlab/git-data' "path" => "/var/opt/gitlab/git-data"
}, }
}) })
``` ```
<!-- - On Gitaly node 3:
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
--> 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 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 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). 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 Praefect supports TLS encryption. To communicate with a Praefect instance that listens
with a Gitaly instance that listens for secure connections you will need to use `tls://` URL for secure connections, you must:
scheme in the `gitaly_address` of the corresponding storage entry in the GitLab configuration.
You will need to bring your own certificates as this isn't provided automatically. - Use a `tls://` URL scheme in the `gitaly_address` of the corresponding storage entry
The certificate, or its certificate authority, must be installed on all Gitaly in the GitLab configuration.
nodes (including the Gitaly node using the certificate) and on all client nodes - Bring your own certificates because this isn't provided automatically. The certificate
that communicate with it following the procedure described in corresponding to each Praefect server must be installed on that Praefect server.
[GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates).
NOTE: Additionally the certificate, or its certificate authority, must be installed on all Gitaly servers
The self-signed certificate must specify the address you use to access the and on all Praefect clients that communicate with it following the procedure described in
Gitaly server. If you are addressing the Gitaly server by a hostname, you can [GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates) (and repeated below).
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).
It's possible to configure Gitaly servers with both an unencrypted listening Note the following:
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.
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 ```shell
sudo mkdir -p /etc/gitlab/ssl sudo mkdir -p /etc/gitlab/ssl
@ -1480,27 +1773,34 @@ To configure Gitaly with TLS:
sudo chmod 644 key.pem cert.pem 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: 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 ```ruby
gitaly['tls_listen_addr'] = "0.0.0.0:9999" praefect['tls_listen_addr'] = "0.0.0.0:3305"
gitaly['certificate_path'] = "/etc/gitlab/ssl/cert.pem" praefect['certificate_path'] = "/etc/gitlab/ssl/cert.pem"
gitaly['key_path'] = "/etc/gitlab/ssl/key.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). 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 ### ### 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({ git_data_dirs({
'default' => { 'gitaly_address' => 'tcp://gitaly1.internal:8075' }, "default" => {
'storage1' => { 'gitaly_address' => 'tcp://gitaly1.internal:8075' }, "gitaly_address" => "tcp://10.6.0.40:2305", # internal load balancer IP
'storage2' => { 'gitaly_address' => 'tcp://gitaly2.internal:8075' }, "gitaly_token" => '<praefect_external_token>'
}
}) })
gitlab_rails['gitaly_token'] = 'YOUR_TOKEN'
####################################### #######################################
### Postgres ### ### 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_port'] = 6432
gitlab_rails['db_password'] = '<postgresql_user_password>' gitlab_rails['db_password'] = '<postgresql_user_password>'
gitlab_rails['db_adapter'] = 'postgresql' gitlab_rails['db_adapter'] = 'postgresql'
@ -1669,17 +1972,14 @@ On each node perform the following:
```ruby ```ruby
external_url 'https://gitlab.example.com' external_url 'https://gitlab.example.com'
# Gitaly and GitLab use two shared secrets for authentication, one to authenticate gRPC requests # git_data_dirs get configured for the Praefect virtual storage
# to Gitaly, and a second for authentication callbacks from GitLab-Shell to the GitLab internal API. # Address is Interal Load Balancer for Praefect
# The following two values must be the same as their respective values # Token is praefect_external_token
# of the Gitaly setup
gitlab_rails['gitaly_token'] = 'gitalysecret'
gitlab_shell['secret_token'] = 'shellsecret'
git_data_dirs({ git_data_dirs({
'default' => { 'gitaly_address' => 'tcp://gitaly1.internal:8075' }, "default" => {
'storage1' => { 'gitaly_address' => 'tcp://gitaly1.internal:8075' }, "gitaly_address" => "tcp://10.6.0.40:2305", # internal load balancer IP
'storage2' => { 'gitaly_address' => 'tcp://gitaly2.internal:8075' }, "gitaly_token" => '<praefect_external_token>'
}
}) })
## Disable components that will not be on the GitLab application server ## 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. 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`: `git_data_dirs` entry is configured with `tls` instead of `tcp`:
```ruby ```ruby
git_data_dirs({ git_data_dirs({
'default' => { 'gitaly_address' => 'tls://gitaly1.internal:9999' }, "default" => {
'storage1' => { 'gitaly_address' => 'tls://gitaly1.internal:9999' }, "gitaly_address" => "tls://10.6.0.40:2305", # internal load balancer IP
'storage2' => { 'gitaly_address' => 'tls://gitaly2.internal:9999' }, "gitaly_token" => '<praefect_external_token>'
}
}) })
``` ```

View File

@ -18,6 +18,7 @@ many organizations .
> - **Supported users (approximate):** 1,000 > - **Supported users (approximate):** 1,000
> - **High Availability:** No. For a highly-available environment, you can > - **High Availability:** No. For a highly-available environment, you can
> follow the [3K reference architecture](3k_users.md). > 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 | | Users | Configuration | GCP | AWS | Azure |
|--------------|-------------------------|----------------|-----------------|----------------| |--------------|-------------------------|----------------|-----------------|----------------|

View File

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

View File

@ -13,7 +13,7 @@ full list of reference architectures, see
> - **Supported users (approximate):** 50,000 > - **Supported users (approximate):** 50,000
> - **High Availability:** Yes > - **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 | | Service | Nodes | Configuration | GCP | AWS | Azure |
|-----------------------------------------|-------------|-------------------------|-----------------|--------------|----------| |-----------------------------------------|-------------|-------------------------|-----------------|--------------|----------|

View File

@ -20,7 +20,7 @@ costly-to-operate environment by using the
> - **Supported users (approximate):** 5,000 > - **Supported users (approximate):** 5,000
> - **High Availability:** Yes > - **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 | | Service | Nodes | Configuration | GCP | AWS | Azure |
|--------------------------------------------|-------------|-------------------------|----------------|-------------|----------| |--------------------------------------------|-------------|-------------------------|----------------|-------------|----------|

View File

@ -29,7 +29,8 @@ per 1,000 users:
- API: 20 RPS - API: 20 RPS
- Web: 2 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 For GitLab instances with less than 2,000 users, it's recommended that you use
the [default setup](#automated-backups) by 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 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 ```shell
sudo gitlab-rake gitlab:cleanup:orphan_job_artifact_files LIMIT=100 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 The following table describes the rate limits for GitLab.com, both before and
after the limits change in January, 2021: after the limits change in January, 2021:
| Rate limit | Before 2021-01-18 | From 2021-01-18 | | 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 | | **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 | | **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 | | **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 | | **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 | | **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 | | **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 More details are available on the rate limits for [protected
paths](#protected-paths-throttle) and [raw paths](#protected-paths-throttle) and [raw

View File

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

View File

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

View File

@ -16,7 +16,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\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 "" msgstr ""
msgid " %{start} to %{end}" msgid " %{start} to %{end}"
@ -8481,6 +8481,9 @@ msgstr ""
msgid "Create new" msgid "Create new"
msgstr "" msgstr ""
msgid "Create new %{name} by email"
msgstr ""
msgid "Create new Value Stream" msgid "Create new Value Stream"
msgstr "" msgstr ""
@ -10863,6 +10866,9 @@ msgstr ""
msgid "Email Notification" msgid "Email Notification"
msgstr "" msgstr ""
msgid "Email a new %{name} to this project"
msgstr ""
msgid "Email address to use for Support Desk" msgid "Email address to use for Support Desk"
msgstr "" msgstr ""
@ -10935,12 +10941,6 @@ msgstr ""
msgid "EmailParticipantsWarning|and %{moreCount} more" msgid "EmailParticipantsWarning|and %{moreCount} more"
msgstr "" msgstr ""
msgid "EmailToken|reset it"
msgstr ""
msgid "EmailToken|resetting..."
msgstr ""
msgid "Emails" msgid "Emails"
msgstr "" msgstr ""
@ -11226,21 +11226,15 @@ msgstr ""
msgid "Enter one or more user ID separated by commas" msgid "Enter one or more user ID separated by commas"
msgstr "" 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." 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 "" 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}." msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr "" msgstr ""
@ -23934,6 +23928,9 @@ msgstr ""
msgid "Protip:" msgid "Protip:"
msgstr "" msgstr ""
msgid "Protip: %{linkStart}Auto DevOps%{linkEnd} uses Kubernetes clusters to deploy your code!"
msgstr ""
msgid "Protocol" msgid "Protocol"
msgstr "" 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." 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 "" 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." msgid "The tag name can't be changed for an existing release."
msgstr "" msgstr ""
@ -29926,6 +29926,9 @@ msgstr ""
msgid "This is a merge train pipeline" msgid "This is a merge train pipeline"
msgstr "" 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." msgid "This is a security log of important events involving your account."
msgstr "" msgstr ""
@ -30223,6 +30226,9 @@ msgstr ""
msgid "Threat Monitoring" msgid "Threat Monitoring"
msgstr "" msgstr ""
msgid "ThreatMonitoring|Alert Details"
msgstr ""
msgid "ThreatMonitoring|Alerts" msgid "ThreatMonitoring|Alerts"
msgstr "" msgstr ""
@ -33349,6 +33355,9 @@ msgstr ""
msgid "You can create a new %{link}." msgid "You can create a new %{link}."
msgstr "" 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}" msgid "You can create a new Personal Access Token by visiting %{link}"
msgstr "" msgstr ""

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,5 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
# frozen_string_literal: true
# This script is used both in CI and in local development 'rspec' runs. # 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 # This file contains environment settings for gitaly when it's running
# as part of the gitlab-ce/ee test suite. # as part of the gitlab-ce/ee test suite.
# #
@ -52,7 +54,7 @@ module GitalyTest
if ENV['CI'] if ENV['CI']
bundle_path = File.expand_path('../vendor/gitaly-ruby', __dir__) bundle_path = File.expand_path('../vendor/gitaly-ruby', __dir__)
env_hash['BUNDLE_FLAGS'] << " --path=#{bundle_path}" env_hash['BUNDLE_FLAGS'] += " --path=#{bundle_path}"
end end
env_hash env_hash

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,7 +12,7 @@ RSpec.describe 'Issues > User creates issue by email' do
project.add_developer(user) project.add_developer(user)
end end
describe 'new issue by email' do describe 'new issue by email', :js do
shared_examples 'show the email in the modal' do shared_examples 'show the email in the modal' do
let(:issue) { create(:issue, project: project) } 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 page.within '#issuable-email-modal' do
email = project.new_issuable_address(user, 'issue') 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 end
end end

View File

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

View File

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

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