Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
7f5e08060f
commit
49d26b2348
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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'
|
|
||||||
|
|
|
||||||
2
Gemfile
2
Gemfile
|
|
@ -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'
|
||||||
|
|
|
||||||
2
Rakefile
2
Rakefile
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export const POPOVER_TARGET_ID = 'feature-highlight-trigger';
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
@ -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'));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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')"
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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?
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 } ×
|
|
||||||
.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.
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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} · 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} · opened %{issuable_created} by %{author} · 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)
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
16
bin/secpick
16
bin/secpick
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Replace bootstrap modal in issuable_by_email HAML template
|
||||||
|
merge_request: 53599
|
||||||
|
author:
|
||||||
|
type: changed
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Cleanup incorrect data in projects.has_external_wiki
|
||||||
|
merge_request: 53790
|
||||||
|
author:
|
||||||
|
type: fixed
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Add updated_at output to search results
|
||||||
|
merge_request: 53958
|
||||||
|
author:
|
||||||
|
type: changed
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Add Security Orchestration Policy Configuration
|
||||||
|
merge_request: 53743
|
||||||
|
author:
|
||||||
|
type: added
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Migration to add new Premium and Ultimate plan records
|
||||||
|
merge_request: 53465
|
||||||
|
author:
|
||||||
|
type: added
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Fix bug rendering snippet activity
|
||||||
|
merge_request: 53993
|
||||||
|
author:
|
||||||
|
type: fixed
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# rubocop:disable Style/SignalException
|
# rubocop:disable Style/SignalException
|
||||||
|
|
||||||
THROUGHPUT_LABELS = [
|
THROUGHPUT_LABELS = [
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
c5a780e5b5e62043fb04e77ebf89f7d04dfc9bfdc70df8d89c16a3f3fd960ea3
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
4df2229fca7652cb836c867b621da91f1194899c50bb0a8be2fbae58f29dc788
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
601d67a2911c461881064ec18a2246ef9e5b2835eb0fdf40e701c9360e19eca4
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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>'
|
||||||
|
}
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 |
|
||||||
|--------------|-------------------------|----------------|-----------------|----------------|
|
|--------------|-------------------------|----------------|-----------------|----------------|
|
||||||
|
|
|
||||||
|
|
@ -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 |
|
||||||
|-----------------------------------------|-------------|-------------------------|-----------------|-------------|----------|
|
|-----------------------------------------|-------------|-------------------------|-----------------|-------------|----------|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 |
|
||||||
|--------------------------------------------|-------------|-----------------------|----------------|-------------|---------|
|
|--------------------------------------------|-------------|-----------------------|----------------|-------------|---------|
|
||||||
|
|
|
||||||
|
|
@ -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 |
|
||||||
|-----------------------------------------|-------------|-------------------------|-----------------|--------------|----------|
|
|-----------------------------------------|-------------|-------------------------|-----------------|--------------|----------|
|
||||||
|
|
|
||||||
|
|
@ -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 |
|
||||||
|--------------------------------------------|-------------|-------------------------|----------------|-------------|----------|
|
|--------------------------------------------|-------------|-------------------------|----------------|-------------|----------|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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?
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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} · opened %{issuable_created} by %{author}"
|
msgid " %{project_name}#%{issuable_iid} · opened %{issuable_created} by %{author} · 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 ""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#!/usr/bin/env ruby
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'json'
|
require 'json'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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`
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#!/usr/bin/env ruby
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'csv'
|
require 'csv'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#!/usr/bin/env ruby
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'fileutils'
|
require 'fileutils'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#!/usr/bin/env ruby
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'nokogiri'
|
require 'nokogiri'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#!/usr/bin/env ruby
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'json'
|
require 'json'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#!/usr/bin/env ruby
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'set'
|
require 'set'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#!/usr/bin/env ruby
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'set'
|
require 'set'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
Loading…
Reference in New Issue