diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-06-14 15:09:48 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-06-14 15:09:48 +0000 |
commit | 81f257d72ef398933453d215019c850f57dae252 (patch) | |
tree | e7236ae76f96afb207d3ea85164b88c429b6f43c | |
parent | b82c4935ecc86d1429710163287f5bd7d75bf226 (diff) | |
download | gitlab-ce-81f257d72ef398933453d215019c850f57dae252.tar.gz |
Add latest changes from gitlab-org/gitlab@master
153 files changed, 1814 insertions, 987 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 6610dbfd397..6cbd9e875df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 13.12.4 (2021-06-14) + +### Fixed (3 changes) + +- [Add alias method usage_ping_enabled?](gitlab-org/gitlab@eb8755115a2a7045b6291171aaa0c7ae76f43fec) ([merge request](gitlab-org/gitlab!63974)) +- [Fix MR diff compare with previous version](gitlab-org/gitlab@96d4df8bb4eb5f148e4dabcc4c3eea88ad27cbc3) ([merge request](gitlab-org/gitlab!63974)) +- [Fix double render in project's git URL redirect](gitlab-org/gitlab@be4059b7a5cddd2e70fa760d8935b1d170068759) ([merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/62053)) + ## 13.12.3 (2021-06-07) ### Added (1 change) diff --git a/app/assets/javascripts/api/groups_api.js b/app/assets/javascripts/api/groups_api.js index d4ba46656e6..d6c9e1d42cc 100644 --- a/app/assets/javascripts/api/groups_api.js +++ b/app/assets/javascripts/api/groups_api.js @@ -3,9 +3,9 @@ import { buildApiUrl } from './api_utils'; import { DEFAULT_PER_PAGE } from './constants'; const GROUPS_PATH = '/api/:version/groups.json'; +const DESCENDANT_GROUPS_PATH = '/api/:version/groups/:id/descendant_groups'; -export function getGroups(query, options, callback = () => {}) { - const url = buildApiUrl(GROUPS_PATH); +const axiosGet = (url, query, options, callback) => { return axios .get(url, { params: { @@ -19,4 +19,14 @@ export function getGroups(query, options, callback = () => {}) { return data; }); +}; + +export function getGroups(query, options, callback = () => {}) { + const url = buildApiUrl(GROUPS_PATH); + return axiosGet(url, query, options, callback); +} + +export function getDescendentGroups(parentGroupId, query, options, callback = () => {}) { + const url = buildApiUrl(DESCENDANT_GROUPS_PATH.replace(':id', parentGroupId)); + return axiosGet(url, query, options, callback); } diff --git a/app/assets/javascripts/boards/components/board_content_sidebar.vue b/app/assets/javascripts/boards/components/board_content_sidebar.vue index f83927ea4e7..16a8a9d253f 100644 --- a/app/assets/javascripts/boards/components/board_content_sidebar.vue +++ b/app/assets/javascripts/boards/components/board_content_sidebar.vue @@ -11,6 +11,7 @@ import SidebarAssigneesWidget from '~/sidebar/components/assignees/sidebar_assig import SidebarConfidentialityWidget from '~/sidebar/components/confidential/sidebar_confidentiality_widget.vue'; import SidebarDateWidget from '~/sidebar/components/date/sidebar_date_widget.vue'; import SidebarSubscriptionsWidget from '~/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue'; +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; export default { headerHeight: `${contentTop()}px`, @@ -26,7 +27,10 @@ export default { SidebarDropdownWidget, BoardSidebarWeightInput: () => import('ee_component/boards/components/sidebar/board_sidebar_weight_input.vue'), + IterationSidebarDropdownWidget: () => + import('ee_component/sidebar/components/iteration_sidebar_dropdown_widget.vue'), }, + mixins: [glFeatureFlagMixin()], inject: { multipleAssigneesFeatureAvailable: { default: false, @@ -103,17 +107,31 @@ export default { :issuable-type="issuableType" data-testid="sidebar-milestones" /> - <sidebar-dropdown-widget - v-if="iterationFeatureAvailable" - :iid="activeBoardItem.iid" - issuable-attribute="iteration" - :workspace-path="projectPathForActiveIssue" - :attr-workspace-path="groupPathForActiveIssue" - :issuable-type="issuableType" - class="gl-mt-5" - data-testid="iteration-edit" - data-qa-selector="iteration_container" - /> + <template v-if="!glFeatures.iterationCadences"> + <sidebar-dropdown-widget + v-if="iterationFeatureAvailable" + :iid="activeBoardItem.iid" + issuable-attribute="iteration" + :workspace-path="projectPathForActiveIssue" + :attr-workspace-path="groupPathForActiveIssue" + :issuable-type="issuableType" + class="gl-mt-5" + data-testid="iteration-edit" + data-qa-selector="iteration_container" + /> + </template> + <template v-else> + <iteration-sidebar-dropdown-widget + v-if="iterationFeatureAvailable" + :iid="activeBoardItem.iid" + :workspace-path="projectPathForActiveIssue" + :attr-workspace-path="groupPathForActiveIssue" + :issuable-type="issuableType" + class="gl-mt-5" + data-testid="iteration-edit" + data-qa-selector="iteration_container" + /> + </template> </div> <board-sidebar-time-tracker class="swimlanes-sidebar-time-tracker" /> <sidebar-date-widget diff --git a/app/assets/javascripts/content_editor/components/toolbar_link_button.vue b/app/assets/javascripts/content_editor/components/toolbar_link_button.vue index 369eadceff9..f706080eaa1 100644 --- a/app/assets/javascripts/content_editor/components/toolbar_link_button.vue +++ b/app/assets/javascripts/content_editor/components/toolbar_link_button.vue @@ -6,6 +6,7 @@ import { GlFormInputGroup, GlDropdownDivider, GlDropdownItem, + GlTooltipDirective as GlTooltip, } from '@gitlab/ui'; import { Editor as TiptapEditor } from '@tiptap/vue-2'; import { hasSelection } from '../services/utils'; @@ -21,6 +22,9 @@ export default { GlDropdownItem, GlButton, }, + directives: { + GlTooltip, + }, props: { tiptapEditor: { type: TiptapEditor, @@ -68,6 +72,9 @@ export default { </script> <template> <gl-dropdown + v-gl-tooltip + :aria-label="__('Insert link')" + :title="__('Insert link')" :toggle-class="{ active: isActive }" size="small" category="tertiary" diff --git a/app/assets/javascripts/content_editor/components/top_toolbar.vue b/app/assets/javascripts/content_editor/components/top_toolbar.vue index 4da230cbde6..07fdd3147e2 100644 --- a/app/assets/javascripts/content_editor/components/top_toolbar.vue +++ b/app/assets/javascripts/content_editor/components/top_toolbar.vue @@ -72,7 +72,11 @@ export default { :tiptap-editor="contentEditor.tiptapEditor" @execute="trackToolbarControlExecution" /> - <toolbar-link-button :tiptap-editor="contentEditor.tiptapEditor" /> + <toolbar-link-button + data-testid="link" + :tiptap-editor="contentEditor.tiptapEditor" + @execute="trackToolbarControlExecution" + /> <divider /> <toolbar-button data-testid="blockquote" diff --git a/app/assets/javascripts/invite_members/components/group_select.vue b/app/assets/javascripts/invite_members/components/group_select.vue index bac8914c374..2d1e57a1177 100644 --- a/app/assets/javascripts/invite_members/components/group_select.vue +++ b/app/assets/javascripts/invite_members/components/group_select.vue @@ -7,9 +7,9 @@ import { GlSearchBoxByType, } from '@gitlab/ui'; import { debounce } from 'lodash'; -import Api from '~/api'; import { s__ } from '~/locale'; -import { SEARCH_DELAY } from '../constants'; +import { getGroups, getDescendentGroups } from '~/rest_api'; +import { SEARCH_DELAY, GROUP_FILTERS } from '../constants'; export default { name: 'GroupSelect', @@ -23,6 +23,18 @@ export default { model: { prop: 'selectedGroup', }, + props: { + groupsFilter: { + type: String, + required: false, + default: GROUP_FILTERS.ALL, + }, + parentGroupId: { + type: Number, + required: false, + default: null, + }, + }, data() { return { isFetching: false, @@ -50,7 +62,7 @@ export default { methods: { retrieveGroups: debounce(function debouncedRetrieveGroups() { this.isFetching = true; - return Api.groups(this.searchTerm, this.$options.defaultFetchOptions) + return this.fetchGroups() .then((response) => { this.groups = response.map((group) => ({ id: group.id, @@ -69,6 +81,18 @@ export default { this.$emit('input', this.selectedGroup); }, + fetchGroups() { + switch (this.groupsFilter) { + case GROUP_FILTERS.DESCENDANT_GROUPS: + return getDescendentGroups( + this.parentGroupId, + this.searchTerm, + this.$options.defaultFetchOptions, + ); + default: + return getGroups(this.searchTerm, this.$options.defaultFetchOptions); + } + }, }, i18n: { dropdownText: s__('GroupSelect|Select a group'), diff --git a/app/assets/javascripts/invite_members/components/invite_members_modal.vue b/app/assets/javascripts/invite_members/components/invite_members_modal.vue index 0db464bb657..84c8594c6b6 100644 --- a/app/assets/javascripts/invite_members/components/invite_members_modal.vue +++ b/app/assets/javascripts/invite_members/components/invite_members_modal.vue @@ -16,7 +16,7 @@ import GroupSelect from '~/invite_members/components/group_select.vue'; import MembersTokenSelect from '~/invite_members/components/members_token_select.vue'; import { BV_SHOW_MODAL, BV_HIDE_MODAL } from '~/lib/utils/constants'; import { s__, sprintf } from '~/locale'; -import { INVITE_MEMBERS_IN_COMMENT } from '../constants'; +import { INVITE_MEMBERS_IN_COMMENT, GROUP_FILTERS } from '../constants'; import eventHub from '../event_hub'; export default { @@ -54,6 +54,16 @@ export default { type: Number, required: true, }, + groupSelectFilter: { + type: String, + required: false, + default: GROUP_FILTERS.ALL, + }, + groupSelectParentId: { + type: Number, + required: false, + default: null, + }, helpLink: { type: String, required: true, @@ -293,7 +303,12 @@ export default { :aria-labelledby="$options.membersTokenSelectLabelId" :placeholder="$options.labels[inviteeType].placeHolder" /> - <group-select v-if="isInviteGroup" v-model="groupToBeSharedWith" /> + <group-select + v-if="isInviteGroup" + v-model="groupToBeSharedWith" + :groups-filter="groupSelectFilter" + :parent-group-id="groupSelectParentId" + /> </div> <label class="gl-font-weight-bold gl-mt-3">{{ $options.labels.accessLevel }}</label> diff --git a/app/assets/javascripts/invite_members/constants.js b/app/assets/javascripts/invite_members/constants.js index a651b81c60e..0c5538d5b86 100644 --- a/app/assets/javascripts/invite_members/constants.js +++ b/app/assets/javascripts/invite_members/constants.js @@ -1,3 +1,8 @@ export const SEARCH_DELAY = 200; export const INVITE_MEMBERS_IN_COMMENT = 'invite_members_in_comment'; + +export const GROUP_FILTERS = { + ALL: 'all', + DESCENDANT_GROUPS: 'descendant_groups', +}; diff --git a/app/assets/javascripts/invite_members/init_invite_members_modal.js b/app/assets/javascripts/invite_members/init_invite_members_modal.js index fc77bd53ba4..7501e9f4e6e 100644 --- a/app/assets/javascripts/invite_members/init_invite_members_modal.js +++ b/app/assets/javascripts/invite_members/init_invite_members_modal.js @@ -21,6 +21,8 @@ export default function initInviteMembersModal() { isProject: parseBoolean(el.dataset.isProject), accessLevels: JSON.parse(el.dataset.accessLevels), defaultAccessLevel: parseInt(el.dataset.defaultAccessLevel, 10), + groupSelectFilter: el.dataset.groupsFilter, + groupSelectParentId: parseInt(el.dataset.parentId, 10), }, }), }); diff --git a/app/assets/javascripts/issues_list/components/issues_list_app.vue b/app/assets/javascripts/issues_list/components/issues_list_app.vue index 4ba2bc3a8e3..d5cab77f26c 100644 --- a/app/assets/javascripts/issues_list/components/issues_list_app.vue +++ b/app/assets/javascripts/issues_list/components/issues_list_app.vue @@ -205,6 +205,19 @@ export default { return convertToSearchQuery(this.filterTokens) || undefined; }, searchTokens() { + let preloadedAuthors = []; + + if (gon.current_user_id) { + preloadedAuthors = [ + { + id: gon.current_user_id, + name: gon.current_user_fullname, + username: gon.current_username, + avatar_url: gon.current_user_avatar_url, + }, + ]; + } + const tokens = [ { type: TOKEN_TYPE_AUTHOR, @@ -215,6 +228,7 @@ export default { unique: true, defaultAuthors: [], fetchAuthors: this.fetchUsers, + preloadedAuthors, }, { type: TOKEN_TYPE_ASSIGNEE, @@ -225,6 +239,7 @@ export default { unique: !this.hasMultipleIssueAssigneesFeature, defaultAuthors: DEFAULT_NONE_ANY, fetchAuthors: this.fetchUsers, + preloadedAuthors, }, { type: TOKEN_TYPE_MILESTONE, diff --git a/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue b/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue index edbe9441e57..6da2e3a47e8 100644 --- a/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue +++ b/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue @@ -9,17 +9,28 @@ import { UNAVAILABLE_ADMIN_FEATURE_TEXT, } from '~/packages_and_registries/settings/project/constants'; import expirationPolicyQuery from '~/packages_and_registries/settings/project/graphql/queries/get_expiration_policy.query.graphql'; +import CleanupPolicyEnabledAlert from '~/packages_and_registries/shared/components/cleanup_policy_enabled_alert.vue'; +import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue'; import SettingsForm from './settings_form.vue'; export default { components: { + SettingsBlock, SettingsForm, + CleanupPolicyEnabledAlert, GlAlert, GlSprintf, GlLink, }, - inject: ['projectPath', 'isAdmin', 'adminSettingsPath', 'enableHistoricEntries'], + inject: [ + 'projectPath', + 'isAdmin', + 'adminSettingsPath', + 'enableHistoricEntries', + 'helpPagePath', + 'showCleanupPolicyOnAlert', + ], i18n: { UNAVAILABLE_FEATURE_TITLE, UNAVAILABLE_FEATURE_INTRO_TEXT, @@ -75,32 +86,53 @@ export default { </script> <template> - <div> - <settings-form - v-if="!isDisabled" - v-model="workingCopy" - :is-loading="$apollo.queries.containerExpirationPolicy.loading" - :is-edited="isEdited" - @reset="restoreOriginal" - /> - <template v-else> - <gl-alert - v-if="showDisabledFormMessage" - :dismissible="false" - :title="$options.i18n.UNAVAILABLE_FEATURE_TITLE" - variant="tip" - > - {{ $options.i18n.UNAVAILABLE_FEATURE_INTRO_TEXT }} + <section data-testid="registry-settings-app"> + <cleanup-policy-enabled-alert v-if="showCleanupPolicyOnAlert" :project-path="projectPath" /> + <settings-block default-expanded> + <template #title> {{ __('Clean up image tags') }}</template> + <template #description> + <span data-testid="description"> + <gl-sprintf + :message=" + __( + 'Save space and find images in the container Registry. remove unneeded tags and keep only the ones you want. %{linkStart}How does cleanup work?%{linkEnd}', + ) + " + > + <template #link="{ content }"> + <gl-link :href="helpPagePath" target="_blank">{{ content }}</gl-link> + </template> + </gl-sprintf> + </span> + </template> + <template #default> + <settings-form + v-if="!isDisabled" + v-model="workingCopy" + :is-loading="$apollo.queries.containerExpirationPolicy.loading" + :is-edited="isEdited" + @reset="restoreOriginal" + /> + <template v-else> + <gl-alert + v-if="showDisabledFormMessage" + :dismissible="false" + :title="$options.i18n.UNAVAILABLE_FEATURE_TITLE" + variant="tip" + > + {{ $options.i18n.UNAVAILABLE_FEATURE_INTRO_TEXT }} - <gl-sprintf :message="unavailableFeatureMessage"> - <template #link="{ content }"> - <gl-link :href="adminSettingsPath" target="_blank">{{ content }}</gl-link> - </template> - </gl-sprintf> - </gl-alert> - <gl-alert v-else-if="fetchSettingsError" variant="warning" :dismissible="false"> - <gl-sprintf :message="$options.i18n.FETCH_SETTINGS_ERROR_MESSAGE" /> - </gl-alert> - </template> - </div> + <gl-sprintf :message="unavailableFeatureMessage"> + <template #link="{ content }"> + <gl-link :href="adminSettingsPath" target="_blank">{{ content }}</gl-link> + </template> + </gl-sprintf> + </gl-alert> + <gl-alert v-else-if="fetchSettingsError" variant="warning" :dismissible="false"> + <gl-sprintf :message="$options.i18n.FETCH_SETTINGS_ERROR_MESSAGE" /> + </gl-alert> + </template> + </template> + </settings-block> + </section> </template> diff --git a/app/assets/javascripts/packages_and_registries/settings/project/registry_settings_bundle.js b/app/assets/javascripts/packages_and_registries/settings/project/registry_settings_bundle.js index 65af6f846aa..2a3e2c28fa6 100644 --- a/app/assets/javascripts/packages_and_registries/settings/project/registry_settings_bundle.js +++ b/app/assets/javascripts/packages_and_registries/settings/project/registry_settings_bundle.js @@ -19,6 +19,8 @@ export default () => { projectPath, adminSettingsPath, tagsRegexHelpPagePath, + helpPagePath, + showCleanupPolicyOnAlert, } = el.dataset; return new Vue({ el, @@ -32,6 +34,8 @@ export default () => { projectPath, adminSettingsPath, tagsRegexHelpPagePath, + helpPagePath, + showCleanupPolicyOnAlert: parseBoolean(showCleanupPolicyOnAlert), }, render(createElement) { return createElement('registry-settings-app', {}); diff --git a/app/assets/javascripts/packages_and_registries/shared/components/cleanup_policy_enabled_alert.vue b/app/assets/javascripts/packages_and_registries/shared/components/cleanup_policy_enabled_alert.vue new file mode 100644 index 00000000000..d51c62e0623 --- /dev/null +++ b/app/assets/javascripts/packages_and_registries/shared/components/cleanup_policy_enabled_alert.vue @@ -0,0 +1,54 @@ +<script> +import { GlSprintf, GlAlert, GlLink } from '@gitlab/ui'; +import { s__ } from '~/locale'; +import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; + +export default { + components: { + GlAlert, + GlLink, + GlSprintf, + LocalStorageSync, + }, + props: { + projectPath: { + type: String, + required: true, + }, + cleanupPoliciesSettingsPath: { + type: String, + required: false, + default: '', + }, + }, + data() { + return { + dismissed: false, + }; + }, + computed: { + storageKey() { + return `cleanup_policy_enabled_for_project_${this.projectPath}`; + }, + }, + i18n: { + message: s__( + 'ContainerRegistry|Cleanup policies are now available for this project. %{linkStart}Click here to get started.%{linkEnd}', + ), + }, +}; +</script> + +<template> + <local-storage-sync v-model="dismissed" :storage-key="storageKey"> + <gl-alert v-if="!dismissed" class="gl-mt-2" dismissible @dismiss="dismissed = true"> + <gl-sprintf :message="$options.i18n.message"> + <template #link="{ content }"> + <gl-link v-if="cleanupPoliciesSettingsPath" :href="cleanupPoliciesSettingsPath">{{ + content + }}</gl-link> + </template> + </gl-sprintf> + </gl-alert> + </local-storage-sync> +</template> diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue index 159c619e16c..d0ec5668d21 100644 --- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue +++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue @@ -1,7 +1,15 @@ <script> -import { GlFormRadio, GlFormRadioGroup, GlLink, GlSprintf } from '@gitlab/ui'; +import { + GlFormRadio, + GlFormRadioGroup, + GlIcon, + GlLink, + GlSprintf, + GlTooltipDirective, +} from '@gitlab/ui'; import { getWeekdayNames } from '~/lib/utils/datetime_utility'; -import { s__, sprintf } from '~/locale'; +import { __, s__, sprintf } from '~/locale'; +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; const KEY_EVERY_DAY = 'everyDay'; const KEY_EVERY_WEEK = 'everyWeek'; @@ -12,15 +20,25 @@ export default { components: { GlFormRadio, GlFormRadioGroup, + GlIcon, GlLink, GlSprintf, }, + directives: { + GlTooltip: GlTooltipDirective, + }, + mixins: [glFeatureFlagMixin()], props: { initialCronInterval: { type: String, required: false, default: '', }, + dailyLimit: { + type: String, + required: false, + default: '', + }, }, data() { return { @@ -80,6 +98,17 @@ export default { weekday() { return getWeekdayNames()[this.randomWeekDayIndex]; }, + parsedDailyLimit() { + return this.dailyLimit ? (24 * 60) / this.dailyLimit : null; + }, + scheduleDailyLimitMsg() { + return sprintf( + __( + 'Scheduled pipelines cannot run more frequently than once per %{limit} minutes. A pipeline configured to run more frequently only starts after %{limit} minutes have elapsed since the last time it ran.', + ), + { limit: this.parsedDailyLimit }, + ); + }, }, watch: { cronInterval() { @@ -111,6 +140,11 @@ export default { generateRandomDay() { return Math.floor(Math.random() * 28); }, + showDailyLimitMessage({ value }) { + return ( + value === KEY_CUSTOM && this.glFeatures.ciDailyLimitForPipelineSchedules && this.dailyLimit + ); + }, }, }; </script> @@ -131,7 +165,15 @@ export default { </gl-link> </template> </gl-sprintf> + <template v-else>{{ option.text }}</template> + + <gl-icon + v-if="showDailyLimitMessage(option)" + v-gl-tooltip.hover + name="question" + :title="scheduleDailyLimitMsg" + /> </gl-form-radio> </gl-form-radio-group> <input diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js index ce0e573fed2..9056c76d6ca 100644 --- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js +++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js @@ -12,6 +12,7 @@ Vue.use(Translate); function initIntervalPatternInput() { const intervalPatternMount = document.getElementById('interval-pattern-input'); const initialCronInterval = intervalPatternMount?.dataset?.initialInterval; + const dailyLimit = intervalPatternMount.dataset?.dailyLimit; return new Vue({ el: intervalPatternMount, @@ -22,6 +23,7 @@ function initIntervalPatternInput() { return createElement('interval-pattern-input', { props: { initialCronInterval, + dailyLimit, }, }); }, diff --git a/app/assets/javascripts/registry/explorer/index.js b/app/assets/javascripts/registry/explorer/index.js index f66839a74bf..1f82fd7f238 100644 --- a/app/assets/javascripts/registry/explorer/index.js +++ b/app/assets/javascripts/registry/explorer/index.js @@ -34,6 +34,7 @@ export default () => { expirationPolicy, isGroupPage, isAdmin, + showCleanupPolicyOnAlert, showUnfinishedTagCleanupCallout, ...config } = el.dataset; @@ -64,6 +65,7 @@ export default () => { expirationPolicy: expirationPolicy ? JSON.parse(expirationPolicy) : undefined, isGroupPage: parseBoolean(isGroupPage), isAdmin: parseBoolean(isAdmin), + showCleanupPolicyOnAlert: parseBoolean(showCleanupPolicyOnAlert), showUnfinishedTagCleanupCallout: parseBoolean(showUnfinishedTagCleanupCallout), }, /* eslint-disable @gitlab/require-i18n-strings */ diff --git a/app/assets/javascripts/registry/explorer/pages/list.vue b/app/assets/javascripts/registry/explorer/pages/list.vue index 589b88d7bbe..3c8790fa6e5 100644 --- a/app/assets/javascripts/registry/explorer/pages/list.vue +++ b/app/assets/javascripts/registry/explorer/pages/list.vue @@ -11,6 +11,7 @@ import { import { get } from 'lodash'; import getContainerRepositoriesQuery from 'shared_queries/container_registry/get_container_repositories.query.graphql'; import createFlash from '~/flash'; +import CleanupPolicyEnabledAlert from '~/packages_and_registries/shared/components/cleanup_policy_enabled_alert.vue'; import { FILTERED_SEARCH_TERM } from '~/packages_and_registries/shared/constants'; import { extractFilterAndSorting } from '~/packages_and_registries/shared/utils'; import Tracking from '~/tracking'; @@ -61,6 +62,7 @@ export default { RegistryHeader, DeleteImage, RegistrySearch, + CleanupPolicyEnabledAlert, }, directives: { GlTooltip: GlTooltipDirective, @@ -283,6 +285,12 @@ export default { </gl-sprintf> </gl-alert> + <cleanup-policy-enabled-alert + v-if="config.showCleanupPolicyOnAlert" + :project-path="config.projectPath" + :cleanup-policies-settings-path="config.cleanupPoliciesSettingsPath" + /> + <gl-empty-state v-if="config.characterError" :title="$options.i18n.CONNECTION_ERROR_TITLE" diff --git a/app/assets/javascripts/security_configuration/components/redesigned_app.vue b/app/assets/javascripts/security_configuration/components/redesigned_app.vue index c2d57e8f0c8..d8a12f4a792 100644 --- a/app/assets/javascripts/security_configuration/components/redesigned_app.vue +++ b/app/assets/javascripts/security_configuration/components/redesigned_app.vue @@ -1,8 +1,10 @@ <script> import { GlTab, GlTabs, GlSprintf, GlLink } from '@gitlab/ui'; import { __, s__ } from '~/locale'; +import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue'; import FeatureCard from './feature_card.vue'; import SectionLayout from './section_layout.vue'; +import UpgradeBanner from './upgrade_banner.vue'; export const i18n = { compliance: s__('SecurityConfiguration|Compliance'), @@ -25,6 +27,8 @@ export default { GlSprintf, FeatureCard, SectionLayout, + UpgradeBanner, + UserCalloutDismisser, }, props: { augmentedSecurityFeatures: { @@ -52,6 +56,11 @@ export default { }, }, computed: { + canUpgrade() { + return [...this.augmentedSecurityFeatures, ...this.augmentedComplianceFeatures].some( + ({ available }) => !available, + ); + }, canViewCiHistory() { return Boolean(this.gitlabCiPresent && this.gitlabCiHistoryPath); }, @@ -65,6 +74,12 @@ export default { <h1 class="gl-font-size-h1">{{ $options.i18n.securityConfiguration }}</h1> </header> + <user-callout-dismisser v-if="canUpgrade" feature-name="security_configuration_upgrade_banner"> + <template #default="{ dismiss, shouldShowCallout }"> + <upgrade-banner v-if="shouldShowCallout" @close="dismiss" /> + </template> + </user-callout-dismisser> + <gl-tabs content-class="gl-pt-6"> <gl-tab data-testid="security-testing-tab" :title="$options.i18n.securityTesting"> <section-layout :heading="$options.i18n.securityTesting"> diff --git a/app/assets/javascripts/security_configuration/components/upgrade_banner.vue b/app/assets/javascripts/security_configuration/components/upgrade_banner.vue new file mode 100644 index 00000000000..ca0f9e5c85a --- /dev/null +++ b/app/assets/javascripts/security_configuration/components/upgrade_banner.vue @@ -0,0 +1,45 @@ +<script> +import { GlBanner } from '@gitlab/ui'; +import { s__ } from '~/locale'; + +export default { + components: { + GlBanner, + }, + inject: ['upgradePath'], + i18n: { + title: s__('SecurityConfiguration|Secure your project with Ultimate'), + bodyStart: s__( + `SecurityConfiguration|GitLab Ultimate checks your application for security vulnerabilities + that may lead to unauthorized access, data leaks, and denial of service + attacks. Its features include:`, + ), + bodyListItems: [ + s__('SecurityConfiguration|Vulnerability details and statistics in the merge request.'), + s__('SecurityConfiguration|High-level vulnerability statistics across projects and groups.'), + s__('SecurityConfiguration|Runtime security metrics for application environments.'), + ], + bodyEnd: s__( + 'SecurityConfiguration|With the information provided, you can immediately begin risk analysis and remediation within GitLab.', + ), + buttonText: s__('SecurityConfiguration|Upgrade or start a free trial'), + }, +}; +</script> + +<template> + <gl-banner + :title="$options.i18n.title" + :button-text="$options.i18n.buttonText" + :button-link="upgradePath" + v-on="$listeners" + > + <p>{{ $options.i18n.bodyStart }}</p> + <ul> + <li v-for="bodyListItem in $options.i18n.bodyListItems" :key="bodyListItem"> + {{ bodyListItem }} + </li> + </ul> + <p>{{ $options.i18n.bodyEnd }}</p> + </gl-banner> +</template> diff --git a/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue b/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue index 277e1400bf2..c80ccc928b3 100644 --- a/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue +++ b/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue @@ -293,9 +293,17 @@ export default { <span v-else-if="!currentAttribute" class="gl-text-gray-500"> {{ $options.i18n.none }} </span> - <gl-link v-else class="gl-text-gray-900! gl-font-weight-bold" :href="attributeUrl"> - {{ attributeTitle }} - </gl-link> + <slot + v-else + name="value" + :attributeTitle="attributeTitle" + :attributeUrl="attributeUrl" + :currentAttribute="currentAttribute" + > + <gl-link class="gl-text-gray-900! gl-font-weight-bold" :href="attributeUrl"> + {{ attributeTitle }} + </gl-link> + </slot> </div> </template> <template #default> @@ -327,16 +335,24 @@ export default { <gl-dropdown-text v-if="emptyPropsList"> {{ i18n.noAttributesFound }} </gl-dropdown-text> - <gl-dropdown-item - v-for="attrItem in attributesList" - :key="attrItem.id" - :is-check-item="true" - :is-checked="isAttributeChecked(attrItem.id)" - :data-testid="`${issuableAttribute}-items`" - @click="updateAttribute(attrItem.id)" + <slot + v-else + name="list" + :attributesList="attributesList" + :isAttributeChecked="isAttributeChecked" + :updateAttribute="updateAttribute" > - {{ attrItem.title }} - </gl-dropdown-item> + <gl-dropdown-item + v-for="attrItem in attributesList" + :key="attrItem.id" + :is-check-item="true" + :is-checked="isAttributeChecked(attrItem.id)" + :data-testid="`${issuableAttribute}-items`" + @click="updateAttribute(attrItem.id)" + > + {{ attrItem.title }} + </gl-dropdown-item> + </slot> </template> </gl-dropdown> </template> diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue index db8d67d86dc..2e7b3e149b2 100644 --- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue @@ -32,14 +32,7 @@ export default { return { authors: this.config.initialAuthors || [], defaultAuthors: this.config.defaultAuthors || [DEFAULT_LABEL_ANY], - preloadedAuthors: [ - { - id: gon.current_user_id, - name: gon.current_user_fullname, - username: gon.current_username, - avatar_url: gon.current_user_avatar_url, - }, - ], + preloadedAuthors: this.config.preloadedAuthors || [], loading: false, }; }, diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue index 6bd67a4cdf0..fb6b9e4bc0d 100644 --- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue @@ -83,7 +83,10 @@ export default { return Boolean(this.recentTokenValuesStorageKey); }, recentTokenIds() { - return this.recentTokenValues.map((tokenValue) => tokenValue.id || tokenValue.name); + return this.recentTokenValues.map((tokenValue) => tokenValue[this.valueIdentifier]); + }, + preloadedTokenIds() { + return this.preloadedTokenValues.map((tokenValue) => tokenValue[this.valueIdentifier]); }, currentTokenValue() { if (this.fnCurrentTokenValue) { @@ -103,7 +106,9 @@ export default { return this.searchKey ? this.tokenValues : this.tokenValues.filter( - (tokenValue) => !this.recentTokenIds.includes(tokenValue[this.valueIdentifier]), + (tokenValue) => + !this.recentTokenIds.includes(tokenValue[this.valueIdentifier]) && + !this.preloadedTokenIds.includes(tokenValue[this.valueIdentifier]), ); }, }, @@ -125,7 +130,15 @@ export default { }, DEBOUNCE_DELAY); }, handleTokenValueSelected(activeTokenValue) { - if (this.isRecentTokenValuesEnabled && activeTokenValue) { + // Make sure that; + // 1. Recently used values feature is enabled + // 2. User has actually selected a value + // 3. Selected value is not part of preloaded list. + if ( + this.isRecentTokenValuesEnabled && + activeTokenValue && + !this.preloadedTokenIds.includes(activeTokenValue[this.valueIdentifier]) + ) { setTokenValueToRecentlyUsed(this.recentTokenValuesStorageKey, activeTokenValue); } }, diff --git a/app/controllers/groups/boards_controller.rb b/app/controllers/groups/boards_controller.rb index a6235d8fc04..3d8cdd766bf 100644 --- a/app/controllers/groups/boards_controller.rb +++ b/app/controllers/groups/boards_controller.rb @@ -10,6 +10,7 @@ class Groups::BoardsController < Groups::ApplicationController push_frontend_feature_flag(:graphql_board_lists, group, default_enabled: false) push_frontend_feature_flag(:board_multi_select, group, default_enabled: :yaml) push_frontend_feature_flag(:swimlanes_buffered_rendering, group, default_enabled: :yaml) + push_frontend_feature_flag(:iteration_cadences, group, default_enabled: :yaml) end feature_category :boards diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index a755d242d4a..cf6d34b2042 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -33,6 +33,7 @@ class GroupsController < Groups::ApplicationController before_action do push_frontend_feature_flag(:vue_issuables_list, @group) + push_frontend_feature_flag(:iteration_cadences, @group, default_enabled: :yaml) end before_action :export_rate_limit, only: [:export, :download_export] diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb index ee8bacbac61..43c9046f850 100644 --- a/app/controllers/projects/boards_controller.rb +++ b/app/controllers/projects/boards_controller.rb @@ -10,6 +10,7 @@ class Projects::BoardsController < Projects::ApplicationController push_frontend_feature_flag(:swimlanes_buffered_rendering, project, default_enabled: :yaml) push_frontend_feature_flag(:graphql_board_lists, project, default_enabled: :yaml) push_frontend_feature_flag(:board_multi_select, project, default_enabled: :yaml) + push_frontend_feature_flag(:iteration_cadences, project&.group, default_enabled: :yaml) end feature_category :boards diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 848463bc3ec..295213bd38c 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -46,6 +46,7 @@ class Projects::IssuesController < Projects::ApplicationController push_frontend_feature_flag(:usage_data_design_action, project, default_enabled: true) push_frontend_feature_flag(:improved_emoji_picker, project, default_enabled: :yaml) push_frontend_feature_flag(:vue_issues_list, project) + push_frontend_feature_flag(:iteration_cadences, project&.group, default_enabled: :yaml) end before_action only: :show do diff --git a/app/controllers/projects/pipeline_schedules_controller.rb b/app/controllers/projects/pipeline_schedules_controller.rb index 4af7508b935..006cb8a2201 100644 --- a/app/controllers/projects/pipeline_schedules_controller.rb +++ b/app/controllers/projects/pipeline_schedules_controller.rb @@ -10,6 +10,10 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController before_action :authorize_update_pipeline_schedule!, except: [:index, :new, :create, :play] before_action :authorize_admin_pipeline_schedule!, only: [:destroy] + before_action do + push_frontend_feature_flag(:ci_daily_limit_for_pipeline_schedules, @project, default_enabled: :yaml) + end + feature_category :continuous_integration # rubocop: disable CodeReuse/ActiveRecord diff --git a/app/graphql/mutations/ci/runners_registration_token/reset.rb b/app/graphql/mutations/ci/runners_registration_token/reset.rb new file mode 100644 index 00000000000..e1cdd9a22a5 --- /dev/null +++ b/app/graphql/mutations/ci/runners_registration_token/reset.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +module Mutations + module Ci + module RunnersRegistrationToken + class Reset < BaseMutation + graphql_name 'RunnersRegistrationTokenReset' + + authorize :update_runners_registration_token + + ScopeID = ::GraphQL::ID_TYPE + + argument :type, ::Types::Ci::RunnerTypeEnum, + required: true, + description: 'Scope of the object to reset the token for.' + + argument :id, ScopeID, + required: false, + description: 'ID of the project or group to reset the token for. Omit if resetting instance runner token.' + + field :token, + GraphQL::STRING_TYPE, + null: true, + description: 'The runner token after mutation.' + + def resolve(**args) + { + token: reset_token(**args), + errors: [] + } + end + + private + + def find_object(type:, **args) + id = args[:id] + + case type + when 'group_type' + GitlabSchema.object_from_id(id, expected_type: ::Group) + when 'project_type' + GitlabSchema.object_from_id(id, expected_type: ::Project) + end + end + + def reset_token(type:, **args) + id = args[:id] + + case type + when 'instance_type' + raise Gitlab::Graphql::Errors::ArgumentError, "id must not be specified for '#{type}' scope" if id.present? + + authorize!(:global) + + ApplicationSetting.current.reset_runners_registration_token! + ApplicationSetting.current_without_cache.runners_registration_token + when 'group_type', 'project_type' + project_or_group = authorized_find!(type: type, id: id) + project_or_group.reset_runners_token! + project_or_group.runners_token + end + end + end + end + end +end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index 561cbf24da3..6b1146f8f09 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -101,6 +101,7 @@ module Types mount_mutation Mutations::Ci::Job::Retry mount_mutation Mutations::Ci::Runner::Update, feature_flag: :runner_graphql_query mount_mutation Mutations::Ci::Runner::Delete, feature_flag: :runner_graphql_query + mount_mutation Mutations::Ci::RunnersRegistrationToken::Reset, feature_flag: :runner_graphql_query mount_mutation Mutations::Namespace::PackageSettings::Update mount_mutation Mutations::UserCallouts::Create end diff --git a/app/helpers/invite_members_helper.rb b/app/helpers/invite_members_helper.rb index 8e0511e40f2..3c290701a5f 100644 --- a/app/helpers/invite_members_helper.rb +++ b/app/helpers/invite_members_helper.rb @@ -31,4 +31,12 @@ module InviteMembersHelper { member_human_access: member.human_access, name: member.source.name } end end + + def group_select_data(group) + if group.root_ancestor.namespace_settings.prevent_sharing_groups_outside_hierarchy + { groups_filter: 'descendant_groups', parent_id: group.root_ancestor.id } + else + {} + end + end end diff --git a/app/helpers/nav/top_nav_helper.rb b/app/helpers/nav/top_nav_helper.rb index b511a928a2c..b8ddb932b73 100644 --- a/app/helpers/nav/top_nav_helper.rb +++ b/app/helpers/nav/top_nav_helper.rb @@ -276,7 +276,11 @@ module Nav builder = ::Gitlab::Nav::TopNavMenuBuilder.new builder.add_primary_menu_item(id: 'your', title: _('Your groups'), href: dashboard_groups_path) builder.add_primary_menu_item(id: 'explore', title: _('Explore groups'), href: explore_groups_path) - builder.add_secondary_menu_item(id: 'create', title: _('Create group'), href: new_group_path) + + if current_user.can_create_group? + builder.add_secondary_menu_item(id: 'create', title: _('Create group'), href: new_group_path) + end + builder.build end end diff --git a/app/helpers/packages_helper.rb b/app/helpers/packages_helper.rb index 04465f7798c..fe41c041b4f 100644 --- a/app/helpers/packages_helper.rb +++ b/app/helpers/packages_helper.rb @@ -53,4 +53,14 @@ module PackagesHelper category = args.delete(:category) || self.class.name ::Gitlab::Tracking.event(category, event_name.to_s, **args) end + + def show_cleanup_policy_on_alert(project) + Gitlab.com? && + Gitlab.config.registry.enabled && + project.container_registry_enabled && + !Gitlab::CurrentSettings.container_expiration_policies_enable_historic_entries && + Feature.enabled?(:container_expiration_policies_historic_entry, project) && + project.container_expiration_policy.nil? && + project.container_repositories.exists? + end end diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb index 5248a80f710..586142a7646 100644 --- a/app/models/ci/job_artifact.rb +++ b/app/models/ci/job_artifact.rb @@ -18,7 +18,6 @@ module Ci ACCESSIBILITY_REPORT_FILE_TYPES = %w[accessibility].freeze NON_ERASABLE_FILE_TYPES = %w[trace].freeze TERRAFORM_REPORT_FILE_TYPES = %w[terraform].freeze - UNSUPPORTED_FILE_TYPES = %i[license_management].freeze SAST_REPORT_TYPES = %w[sast].freeze SECRET_DETECTION_REPORT_TYPES = %w[secret_detection].freeze DEFAULT_FILE_NAMES = { @@ -35,7 +34,6 @@ module Ci dependency_scanning: 'gl-dependency-scanning-report.json', container_scanning: 'gl-container-scanning-report.json', dast: 'gl-dast-report.json', - license_management: 'gl-license-management-report.json', license_scanning: 'gl-license-scanning-report.json', performance: 'performance.json', browser_performance: 'browser-performance.json', @@ -74,7 +72,6 @@ module Ci dependency_scanning: :raw, container_scanning: :raw, dast: :raw, - license_management: :raw, license_scanning: :raw, # All these file formats use `raw` as we need to store them uncompressed @@ -102,7 +99,6 @@ module Ci dependency_scanning dotenv junit - license_management license_scanning lsif metrics @@ -124,7 +120,6 @@ module Ci mount_file_store_uploader JobArtifactUploader validates :file_format, presence: true, unless: :trace?, on: :create - validate :validate_supported_file_format!, on: :create validate :validate_file_format!, unless: :trace?, on: :create before_save :set_size, if: :file_changed? @@ -199,8 +194,7 @@ module Ci container_scanning: 7, ## EE-specific dast: 8, ## EE-specific codequality: 9, ## EE-specific - license_management: 10, ## EE-specific - license_scanning: 101, ## EE-specific till 13.0 + license_scanning: 101, ## EE-specific performance: 11, ## EE-specific till 13.2 metrics: 12, ## EE-specific metrics_referee: 13, ## runner referees @@ -233,14 +227,6 @@ module Ci hashed_path: 2 } - def validate_supported_file_format! - return if Feature.disabled?(:drop_license_management_artifact, project, default_enabled: true) - - if UNSUPPORTED_FILE_TYPES.include?(self.file_type&.to_sym) - errors.add(:base, _("File format is no longer supported")) - end - end - def validate_file_format! unless TYPE_AND_FORMAT_PAIRS[self.file_type&.to_sym] == self.file_format&.to_sym errors.add(:base, _('Invalid file format with specified file type')) diff --git a/app/models/ci/pipeline_schedule.rb b/app/models/ci/pipeline_schedule.rb index b405be8620c..effe2d95a99 100644 --- a/app/models/ci/pipeline_schedule.rb +++ b/app/models/ci/pipeline_schedule.rb @@ -63,6 +63,10 @@ module Ci .execute(self, fallback_method: method(:calculate_next_run_at)) end + def daily_limit + project.actual_limits.limit_for(:ci_daily_pipeline_schedule_triggers) + end + private def worker_cron_expression diff --git a/app/models/project.rb b/app/models/project.rb index f7eba76849d..3a89a85d65d 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -421,12 +421,12 @@ class Project < ApplicationRecord delegate :last_pipeline, to: :commit, allow_nil: true delegate :external_dashboard_url, to: :metrics_setting, allow_nil: true, prefix: true delegate :dashboard_timezone, to: :metrics_setting, allow_nil: true, prefix: true - delegate :default_git_depth, :default_git_depth=, to: :ci_cd_settings, prefix: :ci - delegate :forward_deployment_enabled, :forward_deployment_enabled=, :forward_deployment_enabled?, to: :ci_cd_settings, prefix: :ci + delegate :default_git_depth, :default_git_depth=, to: :ci_cd_settings, prefix: :ci, allow_nil: true + delegate :forward_deployment_enabled, :forward_deployment_enabled=, :forward_deployment_enabled?, to: :ci_cd_settings, prefix: :ci, allow_nil: true delegate :job_token_scope_enabled, :job_token_scope_enabled=, :job_token_scope_enabled?, to: :ci_cd_settings, prefix: :ci - delegate :keep_latest_artifact, :keep_latest_artifact=, :keep_latest_artifact?, :keep_latest_artifacts_available?, to: :ci_cd_settings + delegate :keep_latest_artifact, :keep_latest_artifact=, :keep_latest_artifact?, :keep_latest_artifacts_available?, to: :ci_cd_settings, allow_nil: true delegate :restrict_user_defined_variables, :restrict_user_defined_variables=, :restrict_user_defined_variables?, - to: :ci_cd_settings + to: :ci_cd_settings, allow_nil: true delegate :actual_limits, :actual_plan_name, to: :namespace, allow_nil: true delegate :allow_merge_on_skipped_pipeline, :allow_merge_on_skipped_pipeline?, :allow_merge_on_skipped_pipeline=, :has_confluence?, :allow_editing_commit_messages?, diff --git a/app/models/user_callout.rb b/app/models/user_callout.rb index 48386a84e39..2e8ff1b7b49 100644 --- a/app/models/user_callout.rb +++ b/app/models/user_callout.rb @@ -30,7 +30,8 @@ class UserCallout < ApplicationRecord eoa_bronze_plan_banner: 28, # EE-only pipeline_needs_banner: 29, pipeline_needs_hover_tip: 30, - web_ide_ci_environments_guidance: 31 + web_ide_ci_environments_guidance: 31, + security_configuration_upgrade_banner: 32 } validates :user, presence: true diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb index 73757891cd6..35d38bac7fa 100644 --- a/app/policies/global_policy.rb +++ b/app/policies/global_policy.rb @@ -115,6 +115,7 @@ class GlobalPolicy < BasePolicy enable :approve_user enable :reject_user enable :read_usage_trends_measurement + enable :update_runners_registration_token end # We can't use `read_statistics` because the user may have different permissions for different projects diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb index dc8ecfa4333..41c5757854e 100644 --- a/app/policies/group_policy.rb +++ b/app/policies/group_policy.rb @@ -144,6 +144,7 @@ class GroupPolicy < BasePolicy enable :admin_cluster enable :read_deploy_token enable :create_jira_connect_subscription + enable :update_runners_registration_token end rule { owner }.policy do diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 4dfdbd87a34..e93c60c3710 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -419,6 +419,7 @@ class ProjectPolicy < BasePolicy enable :update_freeze_period enable :destroy_freeze_period enable :admin_feature_flags_client + enable :update_runners_registration_token end rule { public_project & metrics_dashboard_allowed }.policy do diff --git a/app/services/ci/pipeline_schedules/calculate_next_run_service.rb b/app/services/ci/pipeline_schedules/calculate_next_run_service.rb index 4a9ca5d3045..9978b2d4775 100644 --- a/app/services/ci/pipeline_schedules/calculate_next_run_service.rb +++ b/app/services/ci/pipeline_schedules/calculate_next_run_service.rb @@ -41,11 +41,11 @@ module Ci def plan_cron strong_memoize(:plan_cron) do - daily_scheduled_pipeline_limit = project.actual_limits.limit_for(:ci_daily_pipeline_schedule_triggers) + daily_limit = @schedule.daily_limit - next unless daily_scheduled_pipeline_limit + next unless daily_limit - every_x_minutes = (1.day.in_minutes / daily_scheduled_pipeline_limit).to_i + every_x_minutes = (1.day.in_minutes / daily_limit).to_i Gitlab::Ci::CronParser.parse_natural("every #{every_x_minutes} minutes", Time.zone.name) end diff --git a/app/services/web_hook_service.rb b/app/services/web_hook_service.rb index f258aa13376..77d2139b3d1 100644 --- a/app/services/web_hook_service.rb +++ b/app/services/web_hook_service.rb @@ -91,12 +91,10 @@ class WebHookService end def async_execute - if rate_limited?(hook) - log_rate_limit(hook) - else - Gitlab::ApplicationContext.with_context(hook.application_context) do - WebHookWorker.perform_async(hook.id, data, hook_name) - end + Gitlab::ApplicationContext.with_context(hook.application_context) do + break log_rate_limit if rate_limited? + + WebHookWorker.perform_async(hook.id, data, hook_name) end end @@ -177,7 +175,7 @@ class WebHookService response.body.encode('UTF-8', invalid: :replace, undef: :replace, replace: '') end - def rate_limited?(hook) + def rate_limited? return false unless Feature.enabled?(:web_hooks_rate_limit, default_enabled: :yaml) return false if rate_limit.nil? @@ -192,18 +190,13 @@ class WebHookService @rate_limit ||= hook.rate_limit end - def log_rate_limit(hook) - payload = { + def log_rate_limit + Gitlab::AuthLogger.error( message: 'Webhook rate limit exceeded', hook_id: hook.id, hook_type: hook.type, - hook_name: hook_name - } - - Gitlab::AuthLogger.error(payload) - - # Also log into application log for now, so we can use this information - # to determine suitable limits for gitlab.com - Gitlab::AppLogger.error(payload) + hook_name: hook_name, + **Gitlab::ApplicationContext.current + ) end end diff --git a/app/views/admin/application_settings/_diff_limits.html.haml b/app/views/admin/application_settings/_diff_limits.html.haml index ff22f6181b3..5351ac5abd1 100644 --- a/app/views/admin/application_settings/_diff_limits.html.haml +++ b/app/views/admin/application_settings/_diff_limits.html.haml @@ -3,10 +3,10 @@ %fieldset .form-group - = f.label :diff_max_patch_bytes, 'Maximum diff patch size in bytes', class: 'label-light' + = f.label :diff_max_patch_bytes, _('Maximum diff patch size in bytes'), class: 'label-light' = f.number_field :diff_max_patch_bytes, class: 'form-control gl-form-input' %span.form-text.text-muted - Collapse diffs larger than this size, and show a 'too large' message instead. + = _("Collapse diffs larger than this size, and show a 'too large' message instead.") = link_to sprite_icon('question-o'), help_page_path('user/admin_area/diff_limits') diff --git a/app/views/admin/broadcast_messages/_form.html.haml b/app/views/admin/broadcast_messages/_form.html.haml index fe5759ecdbf..b68c22b6942 100644 --- a/app/views/admin/broadcast_messages/_form.html.haml +++ b/app/views/admin/broadcast_messages/_form.html.haml @@ -1,10 +1,12 @@ .broadcast-message.broadcast-banner-message.gl-alert-warning.js-broadcast-banner-message-preview.gl-mt-3{ style: broadcast_message_style(@broadcast_message), class: ('gl-display-none' unless @broadcast_message.banner? ) } - = sprite_icon('bullhorn', css_class: 'vertical-align-text-top') - .js-broadcast-message-preview - - if @broadcast_message.message.present? - = render_broadcast_message(@broadcast_message) - - else - = _('Your message here') + .gl-alert-container + = sprite_icon('bullhorn', css_class: 'vertical-align-text-top') + .js-broadcast-message-preview + .gl-alert-content + - if @broadcast_message.message.present? + = render_broadcast_message(@broadcast_message) + - else + = _('Your message here') .d-flex.justify-content-center .broadcast-message.broadcast-notification-message.preview.js-broadcast-notification-message-preview.mt-2{ class: ('hidden' unless @broadcast_message.notification? ) } = sprite_icon('bullhorn', css_class: 'vertical-align-text-top') diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml index 84a9b988d22..e7e0e58f6fb 100644 --- a/app/views/admin/groups/_form.html.haml +++ b/app/views/admin/groups/_form.html.haml @@ -27,10 +27,12 @@ - if @group.new_record? .form-group.row .offset-sm-2.col-sm-10 - .gl-alert.gl-alert-info - = sprite_icon('information-o', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title') - .gl-alert-body - = render 'shared/group_tips' + .gl-alert.gl-alert- + .gl-alert-container + = sprite_icon('information-o', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title') + .gl-alert-content + .gl-alert-body + = render 'shared/group_tips' .form-actions = f.submit _('Create group'), class: "gl-button btn btn-confirm" = link_to _('Cancel'), admin_groups_path, class: "gl-button btn btn-default btn-cancel" diff --git a/app/views/groups/_invite_members_modal.html.haml b/app/views/groups/_invite_members_modal.html.haml index 69ed94e99cc..f4f3c8ce8f7 100644 --- a/app/views/groups/_invite_members_modal.html.haml +++ b/app/views/groups/_invite_members_modal.html.haml @@ -4,4 +4,4 @@ is_project: 'false', access_levels: GroupMember.access_level_roles.to_json, default_access_level: Gitlab::Access::GUEST, - help_link: help_page_url('user/permissions') } } + help_link: help_page_url('user/permissions') }.merge(group_select_data(group)) } diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index 2305bbd96f3..c5b8c5e25a3 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -1,6 +1,6 @@ - add_page_specific_style 'page_bundles/members' - page_title _('Group members') -- groups_select_tag_data = @group.root_ancestor.namespace_settings.prevent_sharing_groups_outside_hierarchy ? { groups_filter: 'descendant_groups', parent_id: @group.root_ancestor.id, skip_groups: @skip_groups } : { skip_groups: @skip_groups } +- groups_select_tag_data = group_select_data(@group).merge({ skip_groups: @skip_groups }) .js-remove-member-modal .row.gl-mt-3 diff --git a/app/views/layouts/nav/groups_dropdown/_show.html.haml b/app/views/layouts/nav/groups_dropdown/_show.html.haml index 1ace402fbed..d7b0c7150d4 100644 --- a/app/views/layouts/nav/groups_dropdown/_show.html.haml +++ b/app/views/layouts/nav/groups_dropdown/_show.html.haml @@ -12,11 +12,12 @@ = nav_link(path: 'groups#explore') do = link_to explore_groups_path, data: { track_label: "groups_dropdown_explore_groups", track_event: "click_link" } do = _('Explore groups') - = nav_link(path: 'groups/new#create-group-pane', html_options: { class: 'gl-border-0 gl-border-t-1 gl-border-solid gl-border-gray-100' }) do - = link_to new_group_path(anchor: 'create-group-pane'), data: { track_label: "groups_dropdown_create_group", track_event: "click_link" } do - = _('Create group') - = nav_link(path: 'groups/new#import-group-pane') do - = link_to new_group_path(anchor: 'import-group-pane'), data: { track_label: "groups_dropdown_import_group", track_event: "click_link" } do - = _('Import group') + - if current_user.can_create_group? + = nav_link(path: 'groups/new#create-group-pane', html_options: { class: 'gl-border-0 gl-border-t-1 gl-border-solid gl-border-gray-100' }) do + = link_to new_group_path(anchor: 'create-group-pane'), data: { track_label: "groups_dropdown_create_group", track_event: "click_link" } do + = _('Create group') + = nav_link(path: 'groups/new#import-group-pane') do + = link_to new_group_path(anchor: 'import-group-pane'), data: { track_label: "groups_dropdown_import_group", track_event: "click_link" } do + = _('Import group') .frequent-items-dropdown-content #js-groups-dropdown{ data: { user_name: current_user.username, group: group_meta } } diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml index e6c9a7166a9..9e0dd93c683 100644 --- a/app/views/projects/commits/_commits.html.haml +++ b/app/views/projects/commits/_commits.html.haml @@ -35,8 +35,10 @@ - if hidden > 0 %li.gl-alert.gl-alert-warning - = sprite_icon('warning', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title') - = n_('%s additional commit has been omitted to prevent performance issues.', '%s additional commits have been omitted to prevent performance issues.', hidden) % number_with_delimiter(hidden) + .gl-alert-container + = sprite_icon('warning', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title') + .gl-alert-content + = n_('%s additional commit has been omitted to prevent performance issues.', '%s additional commits have been omitted to prevent performance issues.', hidden) % number_with_delimiter(hidden) - if project.context_commits_enabled? && can_update_merge_request && context_commits&.empty? %button.gl-button.btn.btn-default.mt-3.add-review-item-modal-trigger{ type: "button", data: { context_commits_empty: 'true' } } diff --git a/app/views/projects/pipeline_schedules/_form.html.haml b/app/views/projects/pipeline_schedules/_form.html.haml index 628c4780cf2..66aee7dedf3 100644 --- a/app/views/projects/pipeline_schedules/_form.html.haml +++ b/app/views/projects/pipeline_schedules/_form.html.haml @@ -7,7 +7,7 @@ .form-group.row .col-md-9 = f.label :cron, _('Interval Pattern'), class: 'label-bold' - #interval-pattern-input{ data: { initial_interval: @schedule.cron } } + #interval-pattern-input{ data: { initial_interval: @schedule.cron, daily_limit: @schedule.daily_limit } } .form-group.row .col-md-9 = f.label :cron_timezone, _('Cron Timezone'), class: 'label-bold' diff --git a/app/views/projects/registry/repositories/index.html.haml b/app/views/projects/registry/repositories/index.html.haml index f56fd7f557d..bdb5f021b70 100644 --- a/app/views/projects/registry/repositories/index.html.haml +++ b/app/views/projects/registry/repositories/index.html.haml @@ -18,6 +18,8 @@ "project_path": @project.full_path, "gid_prefix": container_repository_gid_prefix, "is_admin": current_user&.admin.to_s, + "show_cleanup_policy_on_alert": show_cleanup_policy_on_alert(@project).to_s, + "cleanup_policies_settings_path": project_settings_packages_and_registries_path(@project), character_error: @character_error.to_s, user_callouts_path: user_callouts_path, user_callout_id: UserCalloutsHelper::UNFINISHED_TAG_CLEANUP_CALLOUT, diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml index d955dabd04c..ade3d40a8df 100644 --- a/app/views/projects/settings/ci_cd/show.html.haml +++ b/app/views/projects/settings/ci_cd/show.html.haml @@ -76,17 +76,7 @@ = render 'projects/triggers/index' - if settings_container_registry_expiration_policy_available?(@project) - %section.settings.no-animate#js-registry-policies{ class: ('expanded' if expanded) } - .settings-header - %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only - = _("Clean up image tags") - %button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' } - = expanded ? _('Collapse') : _('Expand') - %p - = _("Save space and find images in the Container Registry. Remove unneeded tags and keep only the ones you want.") - = link_to _('How does cleanup work?'), help_page_path('user/packages/container_registry/index', anchor: 'cleanup-policy'), target: '_blank', rel: 'noopener noreferrer' - .settings-content - = render 'projects/registry/settings/index' + = render 'projects/registry/settings/index' = render_if_exists 'projects/settings/ci_cd/auto_rollback', expanded: expanded diff --git a/app/views/projects/settings/packages_and_registries/show.html.haml b/app/views/projects/settings/packages_and_registries/show.html.haml index 561ac7b347d..626ddc20431 100644 --- a/app/views/projects/settings/packages_and_registries/show.html.haml +++ b/app/views/projects/settings/packages_and_registries/show.html.haml @@ -1,16 +1,15 @@ - breadcrumb_title _('Packages & Registries') - page_title _('Packages & Registries') - @content_class = 'limit-container-width' unless fluid_layout -- expanded = true -%section.settings.no-animate#js-registry-policies{ class: ('expanded' if expanded) } - .settings-header - %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only - = _("Clean up image tags") - %button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' } - = expanded ? _('Collapse') : _('Expand') - %p - = _("Save space and find images in the Container Registry. Remove unneeded tags and keep only the ones you want.") - = link_to _('How does cleanup work?'), help_page_path('user/packages/container_registry/index', anchor: 'cleanup-policy'), target: '_blank', rel: 'noopener noreferrer' - .settings-content - = render 'projects/registry/settings/index' +#js-registry-settings{ data: { project_id: @project.id, + project_path: @project.full_path, + cadence_options: cadence_options.to_json, + keep_n_options: keep_n_options.to_json, + older_than_options: older_than_options.to_json, + is_admin: current_user&.admin.to_s, + admin_settings_path: ci_cd_admin_application_settings_path(anchor: 'js-registry-settings'), + enable_historic_entries: container_expiration_policies_historic_entry_enabled?(@project).to_s, + help_page_path: help_page_path('user/packages/container_registry/index', anchor: 'cleanup-policy'), + show_cleanup_policy_on_alert: show_cleanup_policy_on_alert(@project).to_s, + tags_regex_help_page_path: help_page_path('user/packages/container_registry/index', anchor: 'regex-pattern-examples') } } diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 59e6cd43325..109b08bf0ec 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -1428,7 +1428,7 @@ :urgency: :high :resource_boundary: :cpu :weight: 3 - :idempotent: true + :idempotent: :tags: [] - :name: pipeline_creation:create_pipeline :worker_name: CreatePipelineWorker @@ -1610,7 +1610,7 @@ :urgency: :high :resource_boundary: :unknown :weight: 5 - :idempotent: + :idempotent: true :tags: [] - :name: pipeline_processing:stage_update :worker_name: StageUpdateWorker diff --git a/app/workers/expire_pipeline_cache_worker.rb b/app/workers/expire_pipeline_cache_worker.rb index 3c48c4ba3cd..9702fac39ba 100644 --- a/app/workers/expire_pipeline_cache_worker.rb +++ b/app/workers/expire_pipeline_cache_worker.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +# rubocop: disable Scalability/IdempotentWorker class ExpirePipelineCacheWorker include ApplicationWorker @@ -9,8 +10,12 @@ class ExpirePipelineCacheWorker queue_namespace :pipeline_cache urgency :high worker_resource_boundary :cpu + data_consistency :delayed, feature_flag: :load_balancing_for_expire_pipeline_cache_worker - idempotent! + # This worker _should_ be idempotent, but due to us moving this to data_consistency :delayed + # and an ongoing incompatibility between the two switches, we need to disable this. + # Uncomment once https://gitlab.com/gitlab-org/gitlab/-/issues/325291 is resolved + # idempotent! # rubocop: disable CodeReuse/ActiveRecord def perform(pipeline_id) @@ -21,3 +26,4 @@ class ExpirePipelineCacheWorker end # rubocop: enable CodeReuse/ActiveRecord end +# rubocop:enable Scalability/IdempotentWorker diff --git a/app/workers/pipeline_process_worker.rb b/app/workers/pipeline_process_worker.rb index 43aaac4e311..a35b32c35f2 100644 --- a/app/workers/pipeline_process_worker.rb +++ b/app/workers/pipeline_process_worker.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class PipelineProcessWorker # rubocop:disable Scalability/IdempotentWorker +class PipelineProcessWorker include ApplicationWorker sidekiq_options retry: 3 @@ -10,7 +10,9 @@ class PipelineProcessWorker # rubocop:disable Scalability/IdempotentWorker feature_category :continuous_integration urgency :high loggable_arguments 1 - data_consistency :delayed, feature_flag: :load_balancing_for_pipeline_process_worker + + idempotent! + deduplicate :until_executing, feature_flag: :ci_idempotent_pipeline_process_worker # rubocop: disable CodeReuse/ActiveRecord def perform(pipeline_id) diff --git a/config/feature_flags/development/load_balancing_for_pipeline_process_worker.yml b/config/feature_flags/development/ci_idempotent_pipeline_process_worker.yml index a37c6057701..db6aa3e88ff 100644 --- a/config/feature_flags/development/load_balancing_for_pipeline_process_worker.yml +++ b/config/feature_flags/development/ci_idempotent_pipeline_process_worker.yml @@ -1,8 +1,8 @@ --- -name: load_balancing_for_pipeline_process_worker -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61766 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/330960 -milestone: '13.12' +name: ci_idempotent_pipeline_process_worker +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/62410 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/332963 +milestone: '14.0' type: development -group: group::pipeline execution +group: group::pipeline authoring default_enabled: false diff --git a/config/feature_flags/development/iteration_cadences.yml b/config/feature_flags/development/iteration_cadences.yml new file mode 100644 index 00000000000..2a496449a6a --- /dev/null +++ b/config/feature_flags/development/iteration_cadences.yml @@ -0,0 +1,8 @@ +--- +name: iteration_cadences +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/54822 +rollout_issue_url: +milestone: '13.10' +type: development +group: group::project management +default_enabled: false diff --git a/config/feature_flags/development/drop_license_management_artifact.yml b/config/feature_flags/development/load_balancing_for_expire_pipeline_cache_worker.yml index 23c2290c07d..07c38c15230 100644 --- a/config/feature_flags/development/drop_license_management_artifact.yml +++ b/config/feature_flags/development/load_balancing_for_expire_pipeline_cache_worker.yml @@ -1,8 +1,8 @@ --- -name: drop_license_management_artifact -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/31247 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/299114 -milestone: 13.0 +name: load_balancing_for_expire_pipeline_cache_worker +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/62073 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/331360 +milestone: '14.0' type: development -group: group::composition analysis -default_enabled: true +group: group::memory +default_enabled: false diff --git a/config/metrics/counts_28d/20210216175542_ci_builds.yml b/config/metrics/counts_28d/20210216175542_ci_builds.yml index 0f3d384937c..b14eda69786 100644 --- a/config/metrics/counts_28d/20210216175542_ci_builds.yml +++ b/config/metrics/counts_28d/20210216175542_ci_builds.yml @@ -3,7 +3,7 @@ key_path: usage_activity_by_stage_monthly.verify.ci_builds description: Unique monthly builds in project product_section: ops product_stage: verify -product_group: group::continuous integration +product_group: group::pipeline execution product_category: continuous_integration value_type: number status: data_available diff --git a/config/metrics/counts_28d/20210216175544_ci_external_pipelines.yml b/config/metrics/counts_28d/20210216175544_ci_external_pipelines.yml index bc0a2051097..d2cda411e05 100644 --- a/config/metrics/counts_28d/20210216175544_ci_external_pipelines.yml +++ b/config/metrics/counts_28d/20210216175544_ci_external_pipelines.yml @@ -3,7 +3,7 @@ key_path: usage_activity_by_stage_monthly.verify.ci_external_pipelines description: Total pipelines in external repositories in a month product_section: ops product_stage: verify -product_group: group::continuous integration +product_group: group::pipeline execution product_category: continuous_integration value_type: number status: data_available @@ -16,4 +16,3 @@ tier: - free - premium - ultimate - diff --git a/config/metrics/counts_28d/20210216175546_ci_internal_pipelines.yml b/config/metrics/counts_28d/20210216175546_ci_internal_pipelines.yml index d04419aebfc..8046f9f3fd0 100644 --- a/config/metrics/counts_28d/20210216175546_ci_internal_pipelines.yml +++ b/config/metrics/counts_28d/20210216175546_ci_internal_pipelines.yml @@ -3,7 +3,7 @@ key_path: usage_activity_by_stage_monthly.verify.ci_internal_pipelines description: Total pipelines in GitLab repositories in a month product_section: ops product_stage: verify -product_group: group::continuous integration +product_group: group::pipeline execution product_category: continuous_integration value_type: number status: data_available diff --git a/config/metrics/counts_28d/20210216175550_ci_pipeline_config_repository.yml b/config/metrics/counts_28d/20210216175550_ci_pipeline_config_repository.yml index c89d4804607..a24e8038a62 100644 --- a/config/metrics/counts_28d/20210216175550_ci_pipeline_config_repository.yml +++ b/config/metrics/counts_28d/20210216175550_ci_pipeline_config_repository.yml @@ -3,7 +3,7 @@ key_path: usage_activity_by_stage_monthly.verify.ci_pipeline_config_repository description: Total Monthly Pipelines from templates in repository product_section: ops product_stage: verify -product_group: group::continuous integration +product_group: group::pipeline execution product_category: continuous_integration value_type: number status: data_available diff --git a/config/metrics/counts_28d/20210216175552_ci_pipeline_schedules.yml b/config/metrics/counts_28d/20210216175552_ci_pipeline_schedules.yml index cfea6f49eb5..7a878a5ac1e 100644 --- a/config/metrics/counts_28d/20210216175552_ci_pipeline_schedules.yml +++ b/config/metrics/counts_28d/20210216175552_ci_pipeline_schedules.yml @@ -3,7 +3,7 @@ key_path: usage_activity_by_stage_monthly.verify.ci_pipeline_schedules description: Total monthly Pipeline schedules in GitLab product_section: ops product_stage: verify -product_group: group::continuous integration +product_group: group::pipeline execution product_category: continuous_integration value_type: number status: data_available diff --git a/config/metrics/counts_28d/20210216175554_ci_pipelines.yml b/config/metrics/counts_28d/20210216175554_ci_pipelines.yml index 55b0250bbbd..19276843af1 100644 --- a/config/metrics/counts_28d/20210216175554_ci_pipelines.yml +++ b/config/metrics/counts_28d/20210216175554_ci_pipelines.yml @@ -3,7 +3,7 @@ key_path: usage_activity_by_stage_monthly.verify.ci_pipelines description: "Distinct users triggering pipelines in a month" product_section: ops product_stage: verify -product_group: group::continuous integration +product_group: group::pipeline execution product_category: continuous_integration value_type: number status: data_available diff --git a/config/metrics/counts_28d/20210216175556_ci_triggers.yml b/config/metrics/counts_28d/20210216175556_ci_triggers.yml index 7a06e45b4e4..81be64c5207 100644 --- a/config/metrics/counts_28d/20210216175556_ci_triggers.yml +++ b/config/metrics/counts_28d/20210216175556_ci_triggers.yml @@ -3,7 +3,7 @@ key_path: usage_activity_by_stage_monthly.verify.ci_triggers description: Total configured Triggers in project product_section: ops product_stage: verify -product_group: group::continuous integration +product_group: group::pipeline execution product_category: continuous_integration value_type: number status: data_available diff --git a/config/metrics/counts_all/20210216175510_ci_builds.yml b/config/metrics/counts_all/20210216175510_ci_builds.yml index 5efe8e30dfd..29aaaaa3e02 100644 --- a/config/metrics/counts_all/20210216175510_ci_builds.yml +++ b/config/metrics/counts_all/20210216175510_ci_builds.yml @@ -3,7 +3,7 @@ key_path: counts.ci_builds description: Unique builds in project product_section: ops product_stage: verify -product_group: group::continuous integration +product_group: group::pipeline execution product_category: continuous_integration value_type: number status: data_available diff --git a/config/metrics/counts_all/20210216175512_ci_internal_pipelines.yml b/config/metrics/counts_all/20210216175512_ci_internal_pipelines.yml index 3c924b71ed5..c6195245083 100644 --- a/config/metrics/counts_all/20210216175512_ci_internal_pipelines.yml +++ b/config/metrics/counts_all/20210216175512_ci_internal_pipelines.yml @@ -3,7 +3,7 @@ key_path: counts.ci_internal_pipelines description: Total pipelines in GitLab repositories product_section: ops product_stage: verify -product_group: group::continuous integration +product_group: group::pipeline execution product_category: continuous_integration value_type: number status: data_available diff --git a/config/metrics/counts_all/20210216175514_ci_external_pipelines.yml b/config/metrics/counts_all/20210216175514_ci_external_pipelines.yml index 49846e9521d..dde3fe3b962 100644 --- a/config/metrics/counts_all/20210216175514_ci_external_pipelines.yml +++ b/config/metrics/counts_all/20210216175514_ci_external_pipelines.yml @@ -3,7 +3,7 @@ key_path: counts.ci_external_pipelines description: Total pipelines in external repositories product_section: ops product_stage: verify -product_group: group::continuous integration +product_group: group::pipeline execution product_category: continuous_integration value_type: number status: data_available diff --git a/config/metrics/counts_all/20210216175518_ci_pipeline_config_repository.yml b/config/metrics/counts_all/20210216175518_ci_pipeline_config_repository.yml index 95f160bac0e..003cf90d75a 100644 --- a/config/metrics/counts_all/20210216175518_ci_pipeline_config_repository.yml +++ b/config/metrics/counts_all/20210216175518_ci_pipeline_config_repository.yml @@ -3,7 +3,7 @@ key_path: counts.ci_pipeline_config_repository description: Total Pipelines from templates in repository product_section: ops product_stage: verify -product_group: group::continuous integration +product_group: group::pipeline execution product_category: continuous_integration value_type: number status: data_available diff --git a/config/metrics/counts_all/20210216175520_ci_runners.yml b/config/metrics/counts_all/20210216175520_ci_runners.yml index 991e966b05e..7264940d5a4 100644 --- a/config/metrics/counts_all/20210216175520_ci_runners.yml +++ b/config/metrics/counts_all/20210216175520_ci_runners.yml @@ -3,7 +3,7 @@ key_path: counts.ci_runners description: Total configured Runners in project product_section: ops product_stage: verify -product_group: group::continuous integration +product_group: group::pipeline execution product_category: continuous_integration value_type: number status: data_available diff --git a/config/metrics/counts_all/20210216175521_ci_triggers.yml b/config/metrics/counts_all/20210216175521_ci_triggers.yml index 38d5b621d0f..409794377cd 100644 --- a/config/metrics/counts_all/20210216175521_ci_triggers.yml +++ b/config/metrics/counts_all/20210216175521_ci_triggers.yml @@ -3,7 +3,7 @@ key_path: counts.ci_triggers description: Total configured Triggers in project product_section: ops product_stage: verify -product_group: group::continuous integration +product_group: group::pipeline execution product_category: continuous_integration value_type: number status: data_available diff --git a/config/metrics/counts_all/20210216175523_ci_pipeline_schedules.yml b/config/metrics/counts_all/20210216175523_ci_pipeline_schedules.yml index 7c8aeac67b4..57237d36c0d 100644 --- a/config/metrics/counts_all/20210216175523_ci_pipeline_schedules.yml +++ b/config/metrics/counts_all/20210216175523_ci_pipeline_schedules.yml @@ -3,7 +3,7 @@ key_path: counts.ci_pipeline_schedules description: Pipeline schedules in GitLab product_section: ops product_stage: verify -product_group: group::continuous integration +product_group: group::pipeline execution product_category: continuous_integration value_type: number status: data_available diff --git a/config/metrics/counts_all/20210216175525_ci_builds.yml b/config/metrics/counts_all/20210216175525_ci_builds.yml index 16f1e46c94f..702c08ae286 100644 --- a/config/metrics/counts_all/20210216175525_ci_builds.yml +++ b/config/metrics/counts_all/20210216175525_ci_builds.yml @@ -3,7 +3,7 @@ key_path: usage_activity_by_stage.verify.ci_builds description: Unique count of builds in project product_section: ops product_stage: verify -product_group: group::continuous integration +product_group: group::pipeline execution product_category: continuous_integration value_type: number status: data_available diff --git a/config/metrics/counts_all/20210216175527_ci_external_pipelines.yml b/config/metrics/counts_all/20210216175527_ci_external_pipelines.yml index f97d7f31b67..53bbb30717d 100644 --- a/config/metrics/counts_all/20210216175527_ci_external_pipelines.yml +++ b/config/metrics/counts_all/20210216175527_ci_external_pipelines.yml @@ -3,7 +3,7 @@ key_path: usage_activity_by_stage.verify.ci_external_pipelines description: Total pipelines in external repositories product_section: ops product_stage: verify -product_group: group::continuous integration +product_group: group::pipeline execution product_category: continuous_integration value_type: number status: data_available diff --git a/config/metrics/counts_all/20210216175529_ci_internal_pipelines.yml b/config/metrics/counts_all/20210216175529_ci_internal_pipelines.yml index 700152cc710..e888c9a6fd2 100644 --- a/config/metrics/counts_all/20210216175529_ci_internal_pipelines.yml +++ b/config/metrics/counts_all/20210216175529_ci_internal_pipelines.yml @@ -3,7 +3,7 @@ key_path: usage_activity_by_stage.verify.ci_internal_pipelines description: Total pipelines in GitLab repositories product_section: ops product_stage: verify -product_group: group::continuous integration +product_group: group::pipeline execution product_category: continuous_integration value_type: number status: data_available diff --git a/config/metrics/counts_all/20210216175533_ci_pipeline_config_repository.yml b/config/metrics/counts_all/20210216175533_ci_pipeline_config_repository.yml index a7d45a74e72..1dcc5e885c7 100644 --- a/config/metrics/counts_all/20210216175533_ci_pipeline_config_repository.yml +++ b/config/metrics/counts_all/20210216175533_ci_pipeline_config_repository.yml @@ -3,7 +3,7 @@ key_path: usage_activity_by_stage.verify.ci_pipeline_config_repository description: Total Pipelines from templates in repository product_section: ops product_stage: verify -product_group: group::continuous integration +product_group: group::pipeline execution product_category: continuous_integration value_type: number status: data_available diff --git a/config/metrics/counts_all/20210216175535_ci_pipeline_schedules.yml b/config/metrics/counts_all/20210216175535_ci_pipeline_schedules.yml index dc34481c495..7ee753f6d32 100644 --- a/config/metrics/counts_all/20210216175535_ci_pipeline_schedules.yml +++ b/config/metrics/counts_all/20210216175535_ci_pipeline_schedules.yml @@ -3,7 +3,7 @@ key_path: usage_activity_by_stage.verify.ci_pipeline_schedules description: Pipeline schedules in GitLab product_section: ops product_stage: verify -product_group: group::continuous integration +product_group: group::pipeline execution product_category: continuous_integration value_type: number status: data_available diff --git a/config/metrics/counts_all/20210216175537_ci_pipelines.yml b/config/metrics/counts_all/20210216175537_ci_pipelines.yml index 9447661e19c..a3bcc7eafbe 100644 --- a/config/metrics/counts_all/20210216175537_ci_pipelines.yml +++ b/config/metrics/counts_all/20210216175537_ci_pipelines.yml @@ -3,7 +3,7 @@ key_path: usage_activity_by_stage.verify.ci_pipelines description: Distinct Users triggering Total pipelines product_section: ops product_stage: verify -product_group: group::continuous integration +product_group: group::pipeline execution product_category: continuous_integration value_type: number status: data_available diff --git a/config/metrics/counts_all/20210216175539_ci_triggers.yml b/config/metrics/counts_all/20210216175539_ci_triggers.yml index 85a7f097574..5b533f1e726 100644 --- a/config/metrics/counts_all/20210216175539_ci_triggers.yml +++ b/config/metrics/counts_all/20210216175539_ci_triggers.yml @@ -3,7 +3,7 @@ key_path: usage_activity_by_stage.verify.ci_triggers description: Total configured Triggers in project product_section: ops product_stage: verify -product_group: group::continuous integration +product_group: group::pipeline execution product_category: continuous_integration value_type: number status: data_available diff --git a/config/metrics/counts_all/20210502045402_ci_runners_instance_type_active.yml b/config/metrics/counts_all/20210502045402_ci_runners_instance_type_active.yml index 2d42796514d..425869d5a81 100644 --- a/config/metrics/counts_all/20210502045402_ci_runners_instance_type_active.yml +++ b/config/metrics/counts_all/20210502045402_ci_runners_instance_type_active.yml @@ -4,7 +4,7 @@ name: "count_active_instance_ci_runners" description: Total active group Runners product_section: ops product_stage: verify -product_group: group::continuous integration +product_group: group::pipeline execution product_category: continuous_integration value_type: number status: implemented diff --git a/config/metrics/counts_all/20210502050341_ci_runners_group_type_active.yml b/config/metrics/counts_all/20210502050341_ci_runners_group_type_active.yml index 49c99ce13ad..7896738aab6 100644 --- a/config/metrics/counts_all/20210502050341_ci_runners_group_type_active.yml +++ b/config/metrics/counts_all/20210502050341_ci_runners_group_type_active.yml @@ -4,7 +4,7 @@ name: "count_active_group_ci_runners" description: Total active instance Runners product_section: ops product_stage: verify -product_group: group::continuous integration +product_group: group::pipeline execution product_category: continuous_integration value_type: number status: implemented diff --git a/config/metrics/counts_all/20210502050834_ci_runners_project_type_active.yml b/config/metrics/counts_all/20210502050834_ci_runners_project_type_active.yml index 0c442466199..94d4956899d 100644 --- a/config/metrics/counts_all/20210502050834_ci_runners_project_type_active.yml +++ b/config/metrics/counts_all/20210502050834_ci_runners_project_type_active.yml @@ -4,7 +4,7 @@ name: "count_active_project_ci_runners" description: Total active project Runners product_section: ops product_stage: verify -product_group: group::continuous integration +product_group: group::pipeline execution product_category: continuous_integration value_type: number status: implemented diff --git a/config/metrics/counts_all/20210502050942_ci_runners_online.yml b/config/metrics/counts_all/20210502050942_ci_runners_online.yml index d661767095f..8922524c4c2 100644 --- a/config/metrics/counts_all/20210502050942_ci_runners_online.yml +++ b/config/metrics/counts_all/20210502050942_ci_runners_online.yml @@ -4,7 +4,7 @@ name: "counts_online_runners" description: Total online Runners product_section: ops product_stage: verify -product_group: group::continuous integration +product_group: group::pipeline execution product_category: continuous_integration value_type: number status: implemented diff --git a/config/metrics/counts_all/20210502051651_ci_runners_instance_type_active_online.yml b/config/metrics/counts_all/20210502051651_ci_runners_instance_type_active_online.yml index d5b2bb2eab8..04f9bc3e762 100644 --- a/config/metrics/counts_all/20210502051651_ci_runners_instance_type_active_online.yml +++ b/config/metrics/counts_all/20210502051651_ci_runners_instance_type_active_online.yml @@ -4,7 +4,7 @@ name: "count_instance_active_online_ci_runners" description: Total active and online instance Runners product_section: ops product_stage: verify -product_group: group::continuous integration +product_group: group::pipeline execution product_category: continuous_integration value_type: number status: implemented diff --git a/config/metrics/counts_all/20210502051922_ci_runners_group_type_active_online.yml b/config/metrics/counts_all/20210502051922_ci_runners_group_type_active_online.yml index cedf120bfc1..9c24740eca1 100644 --- a/config/metrics/counts_all/20210502051922_ci_runners_group_type_active_online.yml +++ b/config/metrics/counts_all/20210502051922_ci_runners_group_type_active_online.yml @@ -4,7 +4,7 @@ name: "count_group_active_online_ci_runners" description: Total active and online group Runners product_section: ops product_stage: verify -product_group: group::continuous integration +product_group: group::pipeline execution product_category: continuous_integration value_type: number status: implemented diff --git a/config/metrics/counts_all/20210502052036_ci_runners_project_type_active_online.yml b/config/metrics/counts_all/20210502052036_ci_runners_project_type_active_online.yml index dbedcc7540d..64c39337be2 100644 --- a/config/metrics/counts_all/20210502052036_ci_runners_project_type_active_online.yml +++ b/config/metrics/counts_all/20210502052036_ci_runners_project_type_active_online.yml @@ -4,7 +4,7 @@ name: "count_project_active_online_ci_runners" description: Total active and online project Runners product_section: ops product_stage: verify -product_group: group::continuous integration +product_group: group::pipeline execution product_category: continuous_integration value_type: number status: implemented diff --git a/db/migrate/20210601131742_update_web_hook_calls_limit.rb b/db/migrate/20210601131742_update_web_hook_calls_limit.rb new file mode 100644 index 00000000000..6af0facd17d --- /dev/null +++ b/db/migrate/20210601131742_update_web_hook_calls_limit.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class UpdateWebHookCallsLimit < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + def up + return unless Gitlab.com? + + create_or_update_plan_limit('web_hook_calls', 'free', 120) + end + + def down + return unless Gitlab.com? + + create_or_update_plan_limit('web_hook_calls', 'free', 0) + end +end diff --git a/db/schema_migrations/20210601131742 b/db/schema_migrations/20210601131742 new file mode 100644 index 00000000000..59869b190e5 --- /dev/null +++ b/db/schema_migrations/20210601131742 @@ -0,0 +1 @@ +63cd83e097a24b39a399918422950caacb6aed8d05d0d8b7bcf66f9155a0d04e
\ No newline at end of file diff --git a/doc/administration/instance_limits.md b/doc/administration/instance_limits.md index 26a69e92289..9423045e3b5 100644 --- a/doc/administration/instance_limits.md +++ b/doc/administration/instance_limits.md @@ -112,7 +112,7 @@ Limit the maximum daily member invitations allowed per group hierarchy. - GitLab.com: Free members may invite 20 members per day. - Self-managed: Invites are not limited. -### Webhook calls +### Webhook rate limit > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61151) in GitLab 13.12. > - [Deployed behind a feature flag](../user/feature_flags.md), disabled by default. diff --git a/doc/administration/monitoring/prometheus/index.md b/doc/administration/monitoring/prometheus/index.md index d0966e657d2..dd402f800e3 100644 --- a/doc/administration/monitoring/prometheus/index.md +++ b/doc/administration/monitoring/prometheus/index.md @@ -122,44 +122,28 @@ The steps below are the minimum necessary to configure a Monitoring node running 1. Edit `/etc/gitlab/gitlab.rb` and add the contents: ```ruby + roles ['monitoring_role'] + external_url 'http://gitlab.example.com' - # Enable Prometheus - prometheus['enable'] = true + # Prometheus prometheus['listen_address'] = '0.0.0.0:9090' prometheus['monitor_kubernetes'] = false - # Enable Login form - grafana['disable_login_form'] = false - - # Enable Grafana + # Grafana grafana['enable'] = true grafana['admin_password'] = 'toomanysecrets' + grafana['disable_login_form'] = false # Enable service discovery for Prometheus consul['enable'] = true consul['monitoring_service_discovery'] = true - - # The addresses can be IPs or FQDNs - consul['configuration'] = { - retry_join: %w(10.0.0.1 10.0.0.2 10.0.0.3), + consul['configuration'] = { + retry_join: %w(10.0.0.1 10.0.0.2 10.0.0.3), # The addresses can be IPs or FQDNs } - # Disable all other services - gitlab_rails['auto_migrate'] = false - alertmanager['enable'] = false - gitaly['enable'] = false - gitlab_exporter['enable'] = false - gitlab_workhorse['enable'] = false + # Nginx - For Grafana access nginx['enable'] = true - postgres_exporter['enable'] = false - postgresql['enable'] = false - redis['enable'] = false - redis_exporter['enable'] = false - sidekiq['enable'] = false - puma['enable'] = false - node_exporter['enable'] = false - gitlab_exporter['enable'] = false ``` 1. Run `sudo gitlab-ctl reconfigure` to compile the configuration. diff --git a/doc/administration/postgresql/img/pg_ha_architecture.png b/doc/administration/postgresql/img/pg_ha_architecture.png Binary files differindex ef870f652ae..5d2a4a584bf 100644 --- a/doc/administration/postgresql/img/pg_ha_architecture.png +++ b/doc/administration/postgresql/img/pg_ha_architecture.png diff --git a/doc/administration/postgresql/index.md b/doc/administration/postgresql/index.md index eabb396aeab..bce78bbccff 100644 --- a/doc/administration/postgresql/index.md +++ b/doc/administration/postgresql/index.md @@ -16,7 +16,7 @@ There are essentially three setups to choose from. This setup is for when you have installed GitLab using the [Omnibus GitLab **Enterprise Edition** (EE) package](https://about.gitlab.com/install/?version=ee). -All the tools that are needed like PostgreSQL, PgBouncer, Patroni, and repmgr are bundled in +All the tools that are needed like PostgreSQL, PgBouncer, and Patroni are bundled in the package, so you can it to set up the whole PostgreSQL infrastructure (primary, replica). [> Read how to set up PostgreSQL replication and failover using Omnibus GitLab](replication_and_failover.md) diff --git a/doc/administration/postgresql/pgbouncer.md b/doc/administration/postgresql/pgbouncer.md index fc7da04b960..e481fcb71f4 100644 --- a/doc/administration/postgresql/pgbouncer.md +++ b/doc/administration/postgresql/pgbouncer.md @@ -164,7 +164,7 @@ and [GitLab upgrades](https://docs.gitlab.com/omnibus/update/README.html#use-pos 1. To find the primary node, run the following on a database node: ```shell - sudo gitlab-ctl repmgr cluster show + sudo gitlab-ctl patroni members ``` 1. Edit `/etc/gitlab/gitlab.rb` on the application node you're performing the task on, and update diff --git a/doc/administration/postgresql/replication_and_failover.md b/doc/administration/postgresql/replication_and_failover.md index f7e8ae776b8..5d160fd2ed5 100644 --- a/doc/administration/postgresql/replication_and_failover.md +++ b/doc/administration/postgresql/replication_and_failover.md @@ -36,8 +36,8 @@ to avoid the network becoming a single point of failure. NOTE: As of GitLab 13.3, PostgreSQL 12 is shipped with Omnibus GitLab. Clustering for PostgreSQL 12 is only supported with -Patroni. See the [Patroni](#patroni) section for further details. The support for repmgr will not be extended beyond -PostgreSQL 11. +Patroni. See the [Patroni](#patroni) section for further details. Starting with GitLab 14.0, only PostgreSQL 12 is +shipped with Omnibus GitLab and thus Patroni becomes mandatory for replication and failover. ### Database node @@ -120,7 +120,6 @@ Few notes on the service itself: - The service runs under a system account, by default `gitlab-consul`. - If you are using a different username, you have to specify it through the `CONSUL_USERNAME` variable. -- A database user is created with read-only access to the `repmgr` database. - Passwords are stored in the following locations: - `/etc/gitlab/gitlab.rb`: hashed - `/var/opt/gitlab/pgbouncer/pg_auth`: hashed @@ -175,8 +174,6 @@ Few notes on the service itself: - The service runs as the same system account as the database - In the package, this is by default `gitlab-psql` - If you use a non-default user account for PgBouncer service (by default `pgbouncer`), you will have to specify this username. We will refer to this requirement with `PGBOUNCER_USERNAME`. -- The service will have a regular database user account generated for it - - This defaults to `repmgr` - Passwords are stored in the following locations: - `/etc/gitlab/gitlab.rb`: hashed, and in plain text - `/var/opt/gitlab/pgbouncer/pg_auth`: hashed @@ -197,8 +194,7 @@ When installing the GitLab package, do not supply `EXTERNAL_URL` value. #### Configuring Patroni cluster -You must enable Patroni explicitly to be able to use it (with `patroni['enable'] = true`). When Patroni is enabled, -`repmgr` is automatically disabled. +You must enable Patroni explicitly to be able to use it (with `patroni['enable'] = true`). Any PostgreSQL configuration item that controls replication, for example `wal_level`, `max_wal_senders`, etc, are strictly controlled by Patroni and will override the original settings that you make with the `postgresql[...]` configuration key. @@ -212,13 +208,13 @@ The configuration of a Patroni node is very similar to a repmgr but shorter. Whe any replication setting of PostgreSQL (it is overwritten anyway). Then you can remove any `repmgr[...]` or repmgr-specific configuration as well. Especially, make sure that you remove `postgresql['shared_preload_libraries'] = 'repmgr_funcs'`. -Here is an example similar to [the one that was done with repmgr](#configuring-repmgr-nodes): +Here is an example: ```ruby -# Disable all components except PostgreSQL, Patroni (or Repmgr), and Consul +# Disable all components except PostgreSQL, Patroni, and Consul roles['postgres_role'] -# Enable Patroni (which automatically disables Repmgr). +# Enable Patroni patroni['enable'] = true # PostgreSQL configuration @@ -266,10 +262,6 @@ Generally, when Consul cluster is ready, the first node that [reconfigures](../r becomes the leader. You do not need to sequence the nodes reconfiguration. You can run them in parallel or in any order. If you choose an arbitrary order you do not have any predetermined master. -NOTE: -As opposed to repmgr, once the nodes are reconfigured you do not need any further action or additional command to join -the replicas. - #### Enable Monitoring > [Introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/3786) in GitLab 12.0. @@ -715,22 +707,17 @@ The manual steps for this configuration are the same as for the [example recomme ## Patroni NOTE: -Using Patroni instead of Repmgr is supported for PostgreSQL 11 and required for PostgreSQL 12. +Using Patroni instead of Repmgr is supported for PostgreSQL 11 and required for PostgreSQL 12. Starting with GitLab 14.0, only PostgreSQL 12 is available and hence Patroni is mandatory to achieve failover and replication. Patroni is an opinionated solution for PostgreSQL high-availability. It takes the control of PostgreSQL, overrides its -configuration and manages its lifecycle (start, stop, restart). This is a more active approach when compared to repmgr. -Both `repmgr` and Patroni are supported and available. Patroni is the only option for PostgreSQL 12 clustering and -for cascading replication for Geo deployments. +configuration and manages its lifecycle (start, stop, restart). Patroni is the only option for PostgreSQL 12 clustering and for cascading replication for Geo deployments. The [architecture](#example-recommended-setup-manual-steps) (that was mentioned above) does not change for Patroni. You do not need any special consideration for Patroni while provisioning your database nodes. Patroni heavily relies on Consul to store the state of the cluster and elect a leader. Any failure in Consul cluster and its leader election will propagate to Patroni cluster as well. -Similar to repmgr, Patroni monitors the cluster and handles failover. When the primary node fails it works with Consul -to notify PgBouncer. However, as opposed to repmgr, on failure, Patroni handles the transitioning of the old primary to -a replica and rejoins it to the cluster automatically. So you do not need any manual operation for recovering the -cluster as you do with repmgr. +Patroni monitors the cluster and handles failover. When the primary node fails it works with Consul to notify PgBouncer. On failure, Patroni handles the transitioning of the old primary to a replica and rejoins it to the cluster automatically. With Patroni the connection flow is slightly different. Patroni on each node connects to Consul agent to join the cluster. Only after this point it decides if the node is the primary or a replica. Based on this decision, it configures @@ -828,7 +815,7 @@ For further details on this subject, see the #### Geo secondary site considerations -Similar to `repmgr`, when a Geo secondary site is replicating from a primary site that uses `Patroni` and `PgBouncer`, [replicating through PgBouncer is not supported](https://github.com/pgbouncer/pgbouncer/issues/382#issuecomment-517911529) and the secondary must replicate directly from the leader node in the `Patroni` cluster. Therefore, when there is an automatic or manual failover in the `Patroni` cluster, you will need to manually re-point your secondary site to replicate from the new leader with: +When a Geo secondary site is replicating from a primary site that uses `Patroni` and `PgBouncer`, [replicating through PgBouncer is not supported](https://github.com/pgbouncer/pgbouncer/issues/382#issuecomment-517911529) and the secondary must replicate directly from the leader node in the `Patroni` cluster. Therefore, when there is an automatic or manual failover in the `Patroni` cluster, you will need to manually re-point your secondary site to replicate from the new leader with: ```shell sudo gitlab-ctl replicate-geo-database --host=<new_leader_ip> --replication-slot=<slot_name> @@ -995,389 +982,6 @@ Reverting PostgreSQL upgrade with `gitlab-ctl revert-pg-upgrade` has the same co `gitlab-ctl pg-upgrade`. You should follow the same procedure by first stopping the replicas, then reverting the leader, and finally reverting the replicas. -## Repmgr - -NOTE: -Using Patroni instead of Repmgr is supported for PostgreSQL 11 and required for PostgreSQL 12. - -### Configuring Repmgr Nodes - -1. On the master database node, edit `/etc/gitlab/gitlab.rb` replacing values noted in the `# START user configuration` section: - - ```ruby - # Disable all components except PostgreSQL and Repmgr and Consul - roles ['postgres_role'] - - # PostgreSQL configuration - postgresql['listen_address'] = '0.0.0.0' - postgresql['hot_standby'] = 'on' - postgresql['wal_level'] = 'replica' - postgresql['shared_preload_libraries'] = 'repmgr_funcs' - - # Disable automatic database migrations - gitlab_rails['auto_migrate'] = false - - # Configure the Consul agent - consul['services'] = %w(postgresql) - - # START user configuration - # Please set the real values as explained in Required Information section - # - # Replace PGBOUNCER_PASSWORD_HASH with a generated md5 value - postgresql['pgbouncer_user_password'] = 'PGBOUNCER_PASSWORD_HASH' - # Replace POSTGRESQL_PASSWORD_HASH with a generated md5 value - postgresql['sql_user_password'] = 'POSTGRESQL_PASSWORD_HASH' - # Replace X with value of number of db nodes + 1 - postgresql['max_wal_senders'] = X - postgresql['max_replication_slots'] = X - - # Replace XXX.XXX.XXX.XXX/YY with Network Address - postgresql['trust_auth_cidr_addresses'] = %w(XXX.XXX.XXX.XXX/YY) - repmgr['trust_auth_cidr_addresses'] = %w(127.0.0.1/32 XXX.XXX.XXX.XXX/YY) - - # Replace placeholders: - # - # Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z - # with the addresses gathered for CONSUL_SERVER_NODES - consul['configuration'] = { - retry_join: %w(Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z) - } - # - # END user configuration - ``` - - > `postgres_role` was introduced with GitLab 10.3 - -1. On secondary nodes, add all the configuration specified above for primary node - to `/etc/gitlab/gitlab.rb`. In addition, append the following configuration - to inform `gitlab-ctl` that they are standby nodes initially and it need not - attempt to register them as primary node - - ```ruby - # Specify if a node should attempt to be master on initialization - repmgr['master_on_initialization'] = false - ``` - -1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect. -1. [Enable Monitoring](#enable-monitoring) - -> Please note: -> -> - If you want your database to listen on a specific interface, change the configuration: -> `postgresql['listen_address'] = '0.0.0.0'`. -> - If your PgBouncer service runs under a different user account, -> you also need to specify: `postgresql['pgbouncer_user'] = PGBOUNCER_USERNAME` in -> your configuration. - -#### Database nodes post-configuration - -##### Primary node - -Select one node as a primary node. - -1. Open a database prompt: - - ```shell - gitlab-psql -d gitlabhq_production - ``` - -1. Enable the `pg_trgm` extension: - - ```shell - CREATE EXTENSION pg_trgm; - ``` - -1. Enable the `btree_gist` extension: - - ```shell - CREATE EXTENSION btree_gist; - ``` - -1. Exit the database prompt by typing `\q` and Enter. - -1. Verify the cluster is initialized with one node: - - ```shell - gitlab-ctl repmgr cluster show - ``` - - The output should be similar to the following: - - ```plaintext - Role | Name | Upstream | Connection String - ----------+----------|----------|---------------------------------------- - * master | HOSTNAME | | host=HOSTNAME user=gitlab_repmgr dbname=gitlab_repmgr - ``` - -1. Note down the hostname or IP address in the connection string: `host=HOSTNAME`. We will - refer to the hostname in the next section as `MASTER_NODE_NAME`. If the value - is not an IP address, it will need to be a resolvable name (via DNS or - `/etc/hosts`) - -##### Secondary nodes - -1. Set up the repmgr standby: - - ```shell - gitlab-ctl repmgr standby setup MASTER_NODE_NAME - ``` - - Do note that this will remove the existing data on the node. The command - has a wait time. - - The output should be similar to the following: - - ```console - # gitlab-ctl repmgr standby setup MASTER_NODE_NAME - Doing this will delete the entire contents of /var/opt/gitlab/postgresql/data - If this is not what you want, hit Ctrl-C now to exit - To skip waiting, rerun with the -w option - Sleeping for 30 seconds - Stopping the database - Removing the data - Cloning the data - Starting the database - Registering the node with the cluster - ok: run: repmgrd: (pid 19068) 0s - ``` - -1. Verify the node now appears in the cluster: - - ```shell - gitlab-ctl repmgr cluster show - ``` - - The output should be similar to the following: - - ```plaintext - Role | Name | Upstream | Connection String - ----------+---------|-----------|------------------------------------------------ - * master | MASTER | | host=MASTER_NODE_NAME user=gitlab_repmgr dbname=gitlab_repmgr - standby | STANDBY | MASTER | host=STANDBY_HOSTNAME user=gitlab_repmgr dbname=gitlab_repmgr - ``` - -Repeat the above steps on all secondary nodes. - -#### Database checkpoint - -Before moving on, make sure the databases are configured correctly. Run the -following command on the **primary** node to verify that replication is working -properly: - -```shell -gitlab-ctl repmgr cluster show -``` - -The output should be similar to: - -```plaintext -Role | Name | Upstream | Connection String -----------+--------------|--------------|-------------------------------------------------------------------- -* master | MASTER | | host=MASTER port=5432 user=gitlab_repmgr dbname=gitlab_repmgr - standby | STANDBY | MASTER | host=STANDBY port=5432 user=gitlab_repmgr dbname=gitlab_repmgr -``` - -If the 'Role' column for any node says "FAILED", check the -[Troubleshooting section](#troubleshooting) before proceeding. - -Also, check that the check master command works successfully on each node: - -```shell -su - gitlab-consul -gitlab-ctl repmgr-check-master || echo 'This node is a standby repmgr node' -``` - -This command relies on exit codes to tell Consul whether a particular node is a master -or secondary. The most important thing here is that this command does not produce errors. -If there are errors it's most likely due to incorrect `gitlab-consul` database user permissions. -Check the [Troubleshooting section](#troubleshooting) before proceeding. - -### Repmgr failover procedure - -By default, if the master database fails, `repmgrd` should promote one of the -standby nodes to master automatically, and Consul will update PgBouncer with -the new master. - -If you need to failover manually, you have two options: - -**Shutdown the current master database** - -Run: - -```shell -gitlab-ctl stop postgresql -``` - -The automated failover process will see this and failover to one of the -standby nodes. - -**Or perform a manual failover** - -1. Ensure the old master node is not still active. -1. Login to the server that should become the new master and run: - - ```shell - gitlab-ctl repmgr standby promote - ``` - -1. If there are any other standby servers in the cluster, have them follow - the new master server: - - ```shell - gitlab-ctl repmgr standby follow NEW_MASTER - ``` - -#### Geo secondary site considerations - -When a Geo secondary site is replicating from a primary site that uses `repmgr` and `PgBouncer`, [replicating through PgBouncer is not supported](https://github.com/pgbouncer/pgbouncer/issues/382#issuecomment-517911529) and the secondary must replicate directly from the leader node in the `repmgr` cluster. Therefore, when there is a failover in the `repmgr` cluster, you will need to manually re-point your secondary site to replicate from the new leader with: - -```shell -sudo gitlab-ctl replicate-geo-database --host=<new_leader_ip> --replication-slot=<slot_name> -``` - -Otherwise, the replication will not happen anymore, even if the original node gets re-added as a follower node. This will re-sync your secondary site database and may take a long time depending on the amount of data to sync. You may also need to run `gitlab-ctl reconfigure` if replication is still not working after re-syncing. - -### Repmgr Restore procedure - -If a node fails, it can be removed from the cluster, or added back as a standby -after it has been restored to service. - -#### Remove a standby from the cluster - - From any other node in the cluster, run: - - ```shell - gitlab-ctl repmgr standby unregister --node=X - ``` - - where X is the value of node in `repmgr.conf` on the old server. - - To find this, you can use: - - ```shell - awk -F = '$1 == "node" { print $2 }' /var/opt/gitlab/postgresql/repmgr.conf - ``` - - It will output something like: - - ```plaintext - 959789412 - ``` - - Then you will use this ID to unregister the node: - - ```shell - gitlab-ctl repmgr standby unregister --node=959789412 - ``` - -#### Add a node as a standby server - - From the standby node, run: - - ```shell - gitlab-ctl repmgr standby follow NEW_MASTER - gitlab-ctl restart repmgrd - ``` - - WARNING: - When the server is brought back online, and before - you switch it to a standby node, repmgr will report that there are two masters. - If there are any clients that are still attempting to write to the old master, - there can be a split. The old master has to be resynced from - scratch by performing a `gitlab-ctl repmgr standby setup NEW_MASTER`. - -#### Add a failed master back into the cluster as a standby node - - Once `repmgrd` and PostgreSQL are running, the node will need to follow the new - as a standby node. - - ```shell - gitlab-ctl repmgr standby follow NEW_MASTER - ``` - - Once the node is following the new master as a standby, the node needs to be - [unregistered from the cluster on the new master node](#remove-a-standby-from-the-cluster). - - Once the old master node has been unregistered from the cluster, it will need - to be setup as a new standby: - - ```shell - gitlab-ctl repmgr standby setup NEW_MASTER - ``` - - Failure to unregister and read the old master node can lead to subsequent failovers - not working. - -### Alternate configurations - -#### Database authorization - -By default, any host on the database network has permission to perform -repmgr operations using PostgreSQL's `trust` method. If you do not want this -level of trust, there are alternatives: - -- Trust only specific database cluster nodes. -- Require md5 authentication. - -#### Trust specific addresses - -If you know the IP address, or FQDN of all database and PgBouncer nodes in the -cluster, you can trust only those nodes. - -In `/etc/gitlab/gitlab.rb` on all of the database nodes, set -`repmgr['trust_auth_cidr_addresses']` to an array of strings containing all of -the addresses. - -If setting to a node's FQDN, they must have a corresponding PTR record in DNS. -If setting to a node's IP address, specify it as `XXX.XXX.XXX.XXX/32`. - -For example: - -```ruby -repmgr['trust_auth_cidr_addresses'] = %w(192.168.1.44/32 db2.example.com) -``` - -#### MD5 Authentication - -If you are running on an untrusted network, repmgr can use md5 authentication -with a [`.pgpass` file](https://www.postgresql.org/docs/11/libpq-pgpass.html) -to authenticate. - -You can specify by IP address, FQDN, or by subnet, using the same format as in -the previous section: - -1. On the current master node, create a password for the `gitlab` and - `gitlab_repmgr` user: - - ```shell - gitlab-psql -d template1 - template1=# \password gitlab_repmgr - Enter password: **** - Confirm password: **** - template1=# \password gitlab - ``` - -1. On each database node: - - 1. Edit `/etc/gitlab/gitlab.rb`: - 1. Ensure `repmgr['trust_auth_cidr_addresses']` is **not** set - 1. Set `postgresql['md5_auth_cidr_addresses']` to the desired value - 1. Set `postgresql['sql_replication_user'] = 'gitlab_repmgr'` - 1. Reconfigure with `gitlab-ctl reconfigure` - 1. Restart PostgreSQL with `gitlab-ctl restart postgresql` - - 1. Create a `.pgpass` file. Enter the `gitlab_repmgr` password twice to - when asked: - - ```shell - gitlab-ctl write-pgpass --user gitlab_repmgr --hostuser gitlab-psql --database '*' - ``` - -1. On each PgBouncer node, edit `/etc/gitlab/gitlab.rb`: - 1. Ensure `gitlab_rails['db_password']` is set to the plaintext password for - the `gitlab` database user - 1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect - ## Troubleshooting ### Consul and PostgreSQL changes not taking effect @@ -1386,25 +990,10 @@ Due to the potential impacts, `gitlab-ctl reconfigure` only reloads Consul and P To restart either service, run `gitlab-ctl restart SERVICE` -For PostgreSQL, it is usually safe to restart the master node by default. Automatic failover defaults to a 1 minute timeout. Provided the database returns before then, nothing else needs to be done. To be safe, you can stop `repmgrd` on the standby nodes first with `gitlab-ctl stop repmgrd`, then start afterwards with `gitlab-ctl start repmgrd`. +For PostgreSQL, it is usually safe to restart the master node by default. Automatic failover defaults to a 1 minute timeout. Provided the database returns before then, nothing else needs to be done. On the Consul server nodes, it is important to [restart the Consul service](../consul.md#restart-consul) in a controlled manner. -### `gitlab-ctl repmgr-check-master` command produces errors - -If this command displays errors about database permissions it is likely that something failed during -install, resulting in the `gitlab-consul` database user getting incorrect permissions. Follow these -steps to fix the problem: - -1. On the master database node, connect to the database prompt - `gitlab-psql -d template1` -1. Delete the `gitlab-consul` user - `DROP USER "gitlab-consul";` -1. Exit the database prompt - `\q` -1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) to re-add the user with the proper permissions. -1. Change to the `gitlab-consul` user - `su - gitlab-consul` -1. Try the check command again - `gitlab-ctl repmgr-check-master`. - -Now there should not be errors. If errors still occur then there is another problem. - ### PgBouncer error `ERROR: pgbouncer cannot connect to server` You may get this error when running `gitlab-rake gitlab:db:configure` or you diff --git a/doc/administration/reference_architectures/10k_users.md b/doc/administration/reference_architectures/10k_users.md index fe77285abc2..bb9f1aa9ce2 100644 --- a/doc/administration/reference_architectures/10k_users.md +++ b/doc/administration/reference_architectures/10k_users.md @@ -2234,29 +2234,17 @@ To configure the Monitoring node: 1. Edit `/etc/gitlab/gitlab.rb` and add the contents: ```ruby - external_url 'http://gitlab.example.com' + roles ['monitoring_role'] - # Avoid running unnecessary services on the Prometheus server - gitaly['enable'] = false - postgresql['enable'] = false - redis['enable'] = false - puma['enable'] = false - sidekiq['enable'] = false - gitlab_workhorse['enable'] = false - alertmanager['enable'] = false - gitlab_exporter['enable'] = false + external_url 'http://gitlab.example.com' - # Enable Prometheus - prometheus['enable'] = true + # Prometheus prometheus['listen_address'] = '0.0.0.0:9090' prometheus['monitor_kubernetes'] = false - # Enable Login form - grafana['disable_login_form'] = false - - # Enable Grafana - grafana['enable'] = true + # Grafana grafana['admin_password'] = '<grafana_password>' + grafana['disable_login_form'] = false # Enable service discovery for Prometheus consul['enable'] = true @@ -2265,8 +2253,8 @@ To configure the Monitoring node: retry_join: %w(10.6.0.11 10.6.0.12 10.6.0.13) } - # Prevent database migrations from running on upgrade automatically - gitlab_rails['auto_migrate'] = false + # Nginx - For Grafana access + nginx['enable'] = true ``` 1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect. diff --git a/doc/administration/reference_architectures/25k_users.md b/doc/administration/reference_architectures/25k_users.md index df560b1c203..5a994a55d62 100644 --- a/doc/administration/reference_architectures/25k_users.md +++ b/doc/administration/reference_architectures/25k_users.md @@ -2249,29 +2249,17 @@ To configure the Monitoring node: 1. Edit `/etc/gitlab/gitlab.rb` and add the contents: ```ruby - external_url 'http://gitlab.example.com' + roles ['monitoring_role'] - # Avoid running unnecessary services on the Prometheus server - gitaly['enable'] = false - postgresql['enable'] = false - redis['enable'] = false - puma['enable'] = false - sidekiq['enable'] = false - gitlab_workhorse['enable'] = false - alertmanager['enable'] = false - gitlab_exporter['enable'] = false + external_url 'http://gitlab.example.com' - # Enable Prometheus - prometheus['enable'] = true + # Prometheus prometheus['listen_address'] = '0.0.0.0:9090' prometheus['monitor_kubernetes'] = false - # Enable Login form - grafana['disable_login_form'] = false - - # Enable Grafana - grafana['enable'] = true + # Grafana grafana['admin_password'] = '<grafana_password>' + grafana['disable_login_form'] = false # Enable service discovery for Prometheus consul['enable'] = true @@ -2280,8 +2268,8 @@ To configure the Monitoring node: retry_join: %w(10.6.0.11 10.6.0.12 10.6.0.13) } - # Prevent database migrations from running on upgrade automatically - gitlab_rails['auto_migrate'] = false + # Nginx - For Grafana access + nginx['enable'] = true ``` 1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure). diff --git a/doc/administration/reference_architectures/2k_users.md b/doc/administration/reference_architectures/2k_users.md index eea1812972b..8a657469b99 100644 --- a/doc/administration/reference_architectures/2k_users.md +++ b/doc/administration/reference_architectures/2k_users.md @@ -790,32 +790,21 @@ running [Prometheus](../monitoring/prometheus/index.md) and 1. Edit `/etc/gitlab/gitlab.rb` and add the contents: ```ruby + roles ['monitoring_role'] + external_url 'http://gitlab.example.com' - # Enable Prometheus - prometheus['enable'] = true + # Prometheus prometheus['listen_address'] = '0.0.0.0:9090' prometheus['monitor_kubernetes'] = false - # Enable Login form - grafana['disable_login_form'] = false - - # Enable Grafana + # Grafana grafana['enable'] = true - grafana['admin_password'] = 'toomanysecrets' - - # Avoid running unnecessary services on the Prometheus server - gitaly['enable'] = false - postgresql['enable'] = false - redis['enable'] = false - puma['enable'] = false - sidekiq['enable'] = false - gitlab_workhorse['enable'] = false - alertmanager['enable'] = false - gitlab_exporter['enable'] = false + grafana['admin_password'] = '<grafana_password>' + grafana['disable_login_form'] = false - # Prevent database migrations from running on upgrade automatically - gitlab_rails['auto_migrate'] = false + # Nginx - For Grafana access + nginx['enable'] = true ``` 1. Prometheus also needs some scrape configurations to pull all the data from the various diff --git a/doc/administration/reference_architectures/3k_users.md b/doc/administration/reference_architectures/3k_users.md index b75f537ddbd..f886f2feee6 100644 --- a/doc/administration/reference_architectures/3k_users.md +++ b/doc/administration/reference_architectures/3k_users.md @@ -1943,29 +1943,17 @@ running [Prometheus](../monitoring/prometheus/index.md) and 1. Edit `/etc/gitlab/gitlab.rb` and add the contents: ```ruby - external_url 'http://gitlab.example.com' + roles ['monitoring_role'] - # Avoid running unnecessary services on the Prometheus server - gitaly['enable'] = false - postgresql['enable'] = false - redis['enable'] = false - puma['enable'] = false - sidekiq['enable'] = false - gitlab_workhorse['enable'] = false - alertmanager['enable'] = false - gitlab_exporter['enable'] = false + external_url 'http://gitlab.example.com' - # Enable Prometheus - prometheus['enable'] = true + # Prometheus prometheus['listen_address'] = '0.0.0.0:9090' prometheus['monitor_kubernetes'] = false - # Enable Login form - grafana['disable_login_form'] = false - - # Enable Grafana - grafana['enable'] = true + # Grafana grafana['admin_password'] = '<grafana_password>' + grafana['disable_login_form'] = false # Enable service discovery for Prometheus consul['enable'] = true @@ -1974,8 +1962,8 @@ running [Prometheus](../monitoring/prometheus/index.md) and retry_join: %w(10.6.0.11 10.6.0.12 10.6.0.13) } - # Prevent database migrations from running on upgrade automatically - gitlab_rails['auto_migrate'] = false + # Nginx - For Grafana access + nginx['enable'] = true ``` 1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure). diff --git a/doc/administration/reference_architectures/50k_users.md b/doc/administration/reference_architectures/50k_users.md index 8986eea4026..a1d315dc330 100644 --- a/doc/administration/reference_architectures/50k_users.md +++ b/doc/administration/reference_architectures/50k_users.md @@ -2263,29 +2263,17 @@ To configure the Monitoring node: 1. Edit `/etc/gitlab/gitlab.rb` and add the contents: ```ruby - external_url 'http://gitlab.example.com' + roles ['monitoring_role'] - # Avoid running unnecessary services on the Prometheus server - gitaly['enable'] = false - postgresql['enable'] = false - redis['enable'] = false - puma['enable'] = false - sidekiq['enable'] = false - gitlab_workhorse['enable'] = false - alertmanager['enable'] = false - gitlab_exporter['enable'] = false + external_url 'http://gitlab.example.com' - # Enable Prometheus - prometheus['enable'] = true + # Prometheus prometheus['listen_address'] = '0.0.0.0:9090' prometheus['monitor_kubernetes'] = false - # Enable Login form - grafana['disable_login_form'] = false - - # Enable Grafana - grafana['enable'] = true + # Grafana grafana['admin_password'] = '<grafana_password>' + grafana['disable_login_form'] = false # Enable service discovery for Prometheus consul['enable'] = true @@ -2294,8 +2282,8 @@ To configure the Monitoring node: retry_join: %w(10.6.0.11 10.6.0.12 10.6.0.13) } - # Prevent database migrations from running on upgrade automatically - gitlab_rails['auto_migrate'] = false + # Nginx - For Grafana access + nginx['enable'] = true ``` 1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure). diff --git a/doc/administration/reference_architectures/5k_users.md b/doc/administration/reference_architectures/5k_users.md index 784e8cd6f42..1ff23569f64 100644 --- a/doc/administration/reference_architectures/5k_users.md +++ b/doc/administration/reference_architectures/5k_users.md @@ -1936,34 +1936,17 @@ running [Prometheus](../monitoring/prometheus/index.md) and 1. Edit `/etc/gitlab/gitlab.rb` and add the contents: ```ruby - external_url 'http://gitlab.example.com' + roles ['monitoring_role'] - # Avoid running unnecessary services on the Prometheus server - alertmanager['enable'] = false - gitaly['enable'] = false - gitlab_exporter['enable'] = false - gitlab_workhorse['enable'] = false - nginx['enable'] = true - postgres_exporter['enable'] = false - postgresql['enable'] = false - redis['enable'] = false - redis_exporter['enable'] = false - sidekiq['enable'] = false - puma['enable'] = false - node_exporter['enable'] = false - gitlab_exporter['enable'] = false + external_url 'http://gitlab.example.com' - # Enable Prometheus - prometheus['enable'] = true + # Prometheus prometheus['listen_address'] = '0.0.0.0:9090' prometheus['monitor_kubernetes'] = false - # Enable Login form - grafana['disable_login_form'] = false - - # Enable Grafana - grafana['enable'] = true + # Grafana grafana['admin_password'] = '<grafana_password>' + grafana['disable_login_form'] = false # Enable service discovery for Prometheus consul['enable'] = true @@ -1972,8 +1955,8 @@ running [Prometheus](../monitoring/prometheus/index.md) and retry_join: %w(10.6.0.11 10.6.0.12 10.6.0.13) } - # Prevent database migrations from running on upgrade automatically - gitlab_rails['auto_migrate'] = false + # Nginx - For Grafana access + nginx['enable'] = true ``` 1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure). diff --git a/doc/administration/reference_architectures/index.md b/doc/administration/reference_architectures/index.md index 6698737af6a..49024365e30 100644 --- a/doc/administration/reference_architectures/index.md +++ b/doc/administration/reference_architectures/index.md @@ -138,12 +138,12 @@ As long as at least one of each component is online and capable of handling the ### Automated database failover **(PREMIUM SELF)** > - Level of complexity: **High** -> - Required domain knowledge: PgBouncer, Repmgr or Patroni, shared storage, distributed systems +> - Required domain knowledge: PgBouncer, Patroni, shared storage, distributed systems By adding automatic failover for database systems, you can enable higher uptime with additional database nodes. This extends the default database with cluster management and failover policies. -[PgBouncer in conjunction with Repmgr or Patroni](../postgresql/replication_and_failover.md) +[PgBouncer in conjunction with Patroni](../postgresql/replication_and_failover.md) is recommended. ### Instance level replication with GitLab Geo **(PREMIUM SELF)** diff --git a/doc/administration/troubleshooting/debug.md b/doc/administration/troubleshooting/debug.md index c1e51001961..6861cdcde4e 100644 --- a/doc/administration/troubleshooting/debug.md +++ b/doc/administration/troubleshooting/debug.md @@ -178,7 +178,7 @@ following tips are only recommended if you do NOT mind users being affected by downtime. Otherwise skip to the next section. 1. Load the problematic URL -1. Run `sudo gdb -p <PID>` to attach to the Unicorn process. +1. Run `sudo gdb -p <PID>` to attach to the Puma process. 1. In the GDB window, type: ```plaintext @@ -186,7 +186,7 @@ downtime. Otherwise skip to the next section. ``` 1. This forces the process to generate a Ruby backtrace. Check - `/var/log/gitlab/unicorn/unicorn_stderr.log` for the backtrace. For example, you may see: + `/var/log/gitlab/puma/puma_stderr.log` for the backtrace. For example, you may see: ```plaintext from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:33:in `block in start' @@ -213,16 +213,19 @@ downtime. Otherwise skip to the next section. exit ``` -Note that if the Unicorn process terminates before you are able to run these +Note that if the Puma process terminates before you are able to run these commands, GDB will report an error. To buy more time, you can always raise the -Unicorn timeout. For omnibus users, you can edit `/etc/gitlab/gitlab.rb` and -increase it from 60 seconds to 300: +Puma worker timeout. For omnibus users, you can edit `/etc/gitlab/gitlab.rb` and +increase it from 60 seconds to 600: ```ruby -unicorn['worker_timeout'] = 300 +gitlab_rails['env'] = { + 'GITLAB_RAILS_RACK_TIMEOUT' => 600 +} ``` -For source installations, edit `config/unicorn.rb`. +For source installations, set the environment variable. +Refer to [Puma Worker timeout](https://docs.gitlab.com/omnibus/settings/puma.html#worker-timeout). [Reconfigure](../restart_gitlab.md#omnibus-gitlab-reconfigure) GitLab for the changes to take effect. @@ -267,7 +270,7 @@ is a Unicorn worker that is spinning via `top`. Try to use the `gdb` techniques above. In addition, using `strace` may help isolate issues: ```shell -strace -ttTfyyy -s 1024 -p <PID of unicorn worker> -o /tmp/unicorn.txt +strace -ttTfyyy -s 1024 -p <PID of puma worker> -o /tmp/puma.txt ``` If you cannot isolate which Unicorn worker is the issue, try to run `strace` @@ -275,10 +278,10 @@ on all the Unicorn workers to see where the [`/internal/allowed`](../../development/internal_api.md) endpoint gets stuck: ```shell -ps auwx | grep unicorn | awk '{ print " -p " $2}' | xargs strace -ttTfyyy -s 1024 -o /tmp/unicorn.txt +ps auwx | grep puma | awk '{ print " -p " $2}' | xargs strace -ttTfyyy -s 1024 -o /tmp/puma.txt ``` -The output in `/tmp/unicorn.txt` may help diagnose the root cause. +The output in `/tmp/puma.txt` may help diagnose the root cause. ## More information diff --git a/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md b/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md index f05c1af836f..92070a86a0d 100644 --- a/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md +++ b/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md @@ -288,7 +288,7 @@ To change all Jira project to use the instance-level integration settings: ```ruby jira_service_instance_id = JiraService.find_by(instance: true).id JiraService.where(active: true, instance: false, template: false, inherit_from_id: nil).find_each do |service| - service.update_attribute(inherit_from_id: jira_service_instance_id) + service.update_attribute(:inherit_from_id, jira_service_instance_id) end ``` diff --git a/doc/administration/troubleshooting/postgresql.md b/doc/administration/troubleshooting/postgresql.md index 9565b7594d6..341c6bfbc65 100644 --- a/doc/administration/troubleshooting/postgresql.md +++ b/doc/administration/troubleshooting/postgresql.md @@ -53,8 +53,7 @@ This section is for links to information elsewhere in the GitLab documentation. - [PostgreSQL scaling](../postgresql/replication_and_failover.md) - Including [troubleshooting](../postgresql/replication_and_failover.md#troubleshooting) - `gitlab-ctl repmgr-check-master` (or `gitlab-ctl patroni check-leader` if - you're using Patroni) and PgBouncer errors. + `gitlab-ctl patroni check-leader` and PgBouncer errors. - [Developer database documentation](../../development/README.md#database-guides), some of which is absolutely not for production use. Including: diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 843f5c2d413..f47b060e73b 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -3528,6 +3528,28 @@ Input type: `RunnerUpdateInput` | <a id="mutationrunnerupdateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | | <a id="mutationrunnerupdaterunner"></a>`runner` | [`CiRunner`](#cirunner) | The runner after mutation. | +### `Mutation.runnersRegistrationTokenReset` + +Available only when feature flag `runner_graphql_query` is enabled. + +Input type: `RunnersRegistrationTokenResetInput` + +#### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="mutationrunnersregistrationtokenresetclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| <a id="mutationrunnersregistrationtokenresetid"></a>`id` | [`ID`](#id) | ID of the project or group to reset the token for. Omit if resetting instance runner token. | +| <a id="mutationrunnersregistrationtokenresettype"></a>`type` | [`CiRunnerType!`](#cirunnertype) | Scope of the object to reset the token for. | + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="mutationrunnersregistrationtokenresetclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| <a id="mutationrunnersregistrationtokenreseterrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | +| <a id="mutationrunnersregistrationtokenresettoken"></a>`token` | [`String`](#string) | The runner token after mutation. | + ### `Mutation.terraformStateDelete` Input type: `TerraformStateDeleteInput` @@ -14288,7 +14310,6 @@ Iteration ID wildcard values. | <a id="jobartifactfiletypedependency_scanning"></a>`DEPENDENCY_SCANNING` | DEPENDENCY SCANNING job artifact file type. | | <a id="jobartifactfiletypedotenv"></a>`DOTENV` | DOTENV job artifact file type. | | <a id="jobartifactfiletypejunit"></a>`JUNIT` | JUNIT job artifact file type. | -| <a id="jobartifactfiletypelicense_management"></a>`LICENSE_MANAGEMENT` | LICENSE MANAGEMENT job artifact file type. | | <a id="jobartifactfiletypelicense_scanning"></a>`LICENSE_SCANNING` | LICENSE SCANNING job artifact file type. | | <a id="jobartifactfiletypeload_performance"></a>`LOAD_PERFORMANCE` | LOAD PERFORMANCE job artifact file type. | | <a id="jobartifactfiletypelsif"></a>`LSIF` | LSIF job artifact file type. | @@ -14803,6 +14824,7 @@ Name of the feature that the callout is for. | <a id="usercalloutfeaturenameenumpipeline_needs_banner"></a>`PIPELINE_NEEDS_BANNER` | Callout feature name for pipeline_needs_banner. | | <a id="usercalloutfeaturenameenumpipeline_needs_hover_tip"></a>`PIPELINE_NEEDS_HOVER_TIP` | Callout feature name for pipeline_needs_hover_tip. | | <a id="usercalloutfeaturenameenumregistration_enabled_callout"></a>`REGISTRATION_ENABLED_CALLOUT` | Callout feature name for registration_enabled_callout. | +| <a id="usercalloutfeaturenameenumsecurity_configuration_upgrade_banner"></a>`SECURITY_CONFIGURATION_UPGRADE_BANNER` | Callout feature name for security_configuration_upgrade_banner. | | <a id="usercalloutfeaturenameenumservice_templates_deprecated_callout"></a>`SERVICE_TEMPLATES_DEPRECATED_CALLOUT` | Callout feature name for service_templates_deprecated_callout. | | <a id="usercalloutfeaturenameenumsuggest_pipeline"></a>`SUGGEST_PIPELINE` | Callout feature name for suggest_pipeline. | | <a id="usercalloutfeaturenameenumsuggest_popover_dismissed"></a>`SUGGEST_POPOVER_DISMISSED` | Callout feature name for suggest_popover_dismissed. | diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index ae59c35318c..9d2ce998c25 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -3478,22 +3478,6 @@ concatenate them into a single file. Use a filename pattern (`junit: rspec-*.xml an array of filenames (`junit: [rspec-1.xml, rspec-2.xml, rspec-3.xml]`), or a combination thereof (`junit: [rspec.xml, test-results/TEST-*.xml]`). -##### `artifacts:reports:license_management` **(ULTIMATE)** - -> - Introduced in GitLab 11.5. -> - Requires GitLab Runner 11.5 and above. - -WARNING: -This artifact is still valid but is **deprecated** in favor of the -[artifacts:reports:license_scanning](#artifactsreportslicense_scanning) -introduced in GitLab 12.8. - -The `license_management` report collects [Licenses](../../user/compliance/license_compliance/index.md) -as artifacts. - -The collected License Compliance report uploads to GitLab as an artifact and is summarized in merge requests and the pipeline view. It's also used to provide data for security -dashboards. - ##### `artifacts:reports:license_scanning` **(ULTIMATE)** > - Introduced in GitLab 12.8. diff --git a/doc/development/scalability.md b/doc/development/scalability.md index ced59c064b3..b260618c220 100644 --- a/doc/development/scalability.md +++ b/doc/development/scalability.md @@ -119,7 +119,7 @@ that backup, the database can apply the WAL logs in order until the database has reached the target time. On GitLab.com, Consul and Patroni work together to coordinate failovers with -the read replicas. [Omnibus ships with both repmgr and Patroni](../administration/postgresql/replication_and_failover.md). +the read replicas. [Omnibus ships with Patroni](../administration/postgresql/replication_and_failover.md). #### Load-balancing diff --git a/doc/development/understanding_explain_plans.md b/doc/development/understanding_explain_plans.md index e0176c190d6..66dc1fef31a 100644 --- a/doc/development/understanding_explain_plans.md +++ b/doc/development/understanding_explain_plans.md @@ -704,7 +704,7 @@ Execution time: 0.113 ms ### ChatOps -[GitLab employees can also use our ChatOps solution, available in Slack using the +[GitLab team members can also use our ChatOps solution, available in Slack using the `/chatops` slash command](chatops_on_gitlabcom.md). You can use ChatOps to get a query plan by running the following: @@ -728,7 +728,7 @@ For more information about the available options, run: ### `#database-lab` -Another tool GitLab employees can use is a chatbot powered by [Joe](https://gitlab.com/postgres-ai/joe) +Another tool GitLab team members can use is a chatbot powered by [Joe](https://gitlab.com/postgres-ai/joe) which uses [Database Lab](https://gitlab.com/postgres-ai/database-lab) to instantly provide developers with their own clone of the production database. diff --git a/doc/development/usage_ping/dictionary.md b/doc/development/usage_ping/dictionary.md index 92a4a13b27d..103166fab0f 100644 --- a/doc/development/usage_ping/dictionary.md +++ b/doc/development/usage_ping/dictionary.md @@ -508,7 +508,7 @@ Unique builds in project [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216175510_ci_builds.yml) -Group: `group::continuous integration` +Group: `group::pipeline execution` Status: `data_available` @@ -520,7 +520,7 @@ Total pipelines in external repositories [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216175514_ci_external_pipelines.yml) -Group: `group::continuous integration` +Group: `group::pipeline execution` Status: `data_available` @@ -532,7 +532,7 @@ Total pipelines in GitLab repositories [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216175512_ci_internal_pipelines.yml) -Group: `group::continuous integration` +Group: `group::pipeline execution` Status: `data_available` @@ -556,7 +556,7 @@ Total Pipelines from templates in repository [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216175518_ci_pipeline_config_repository.yml) -Group: `group::continuous integration` +Group: `group::pipeline execution` Status: `data_available` @@ -568,7 +568,7 @@ Pipeline schedules in GitLab [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216175523_ci_pipeline_schedules.yml) -Group: `group::continuous integration` +Group: `group::pipeline execution` Status: `data_available` @@ -580,7 +580,7 @@ Total configured Runners in project [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216175520_ci_runners.yml) -Group: `group::continuous integration` +Group: `group::pipeline execution` Status: `data_available` @@ -592,7 +592,7 @@ Total active instance Runners [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210502050341_ci_runners_group_type_active.yml) -Group: `group::continuous integration` +Group: `group::pipeline execution` Status: `implemented` @@ -604,7 +604,7 @@ Total active and online group Runners [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210502051922_ci_runners_group_type_active_online.yml) -Group: `group::continuous integration` +Group: `group::pipeline execution` Status: `implemented` @@ -616,7 +616,7 @@ Total active group Runners [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210502045402_ci_runners_instance_type_active.yml) -Group: `group::continuous integration` +Group: `group::pipeline execution` Status: `implemented` @@ -628,7 +628,7 @@ Total active and online instance Runners [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210502051651_ci_runners_instance_type_active_online.yml) -Group: `group::continuous integration` +Group: `group::pipeline execution` Status: `implemented` @@ -640,7 +640,7 @@ Total online Runners [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210502050942_ci_runners_online.yml) -Group: `group::continuous integration` +Group: `group::pipeline execution` Status: `implemented` @@ -652,7 +652,7 @@ Total active project Runners [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210502050834_ci_runners_project_type_active.yml) -Group: `group::continuous integration` +Group: `group::pipeline execution` Status: `implemented` @@ -664,7 +664,7 @@ Total active and online project Runners [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210502052036_ci_runners_project_type_active_online.yml) -Group: `group::continuous integration` +Group: `group::pipeline execution` Status: `implemented` @@ -676,7 +676,7 @@ Total configured Triggers in project [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216175521_ci_triggers.yml) -Group: `group::continuous integration` +Group: `group::pipeline execution` Status: `data_available` @@ -5032,7 +5032,7 @@ Projects with repository mirroring enabled [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_all/20210216181920_projects_mirrored_with_pipelines_enabled.yml) -Group: `group::continuous integration` +Group: `group::pipeline execution` Status: `data_available` @@ -17240,7 +17240,7 @@ Count creator_id from projects with repository mirroring enabled. [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_all/20210216181934_projects_mirrored_with_pipelines_enabled.yml) -Group: `group::continuous integration` +Group: `group::pipeline execution` Status: `data_available` @@ -17492,7 +17492,7 @@ Unique count of builds in project [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216175525_ci_builds.yml) -Group: `group::continuous integration` +Group: `group::pipeline execution` Status: `data_available` @@ -17504,7 +17504,7 @@ Total pipelines in external repositories [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216175527_ci_external_pipelines.yml) -Group: `group::continuous integration` +Group: `group::pipeline execution` Status: `data_available` @@ -17516,7 +17516,7 @@ Total pipelines in GitLab repositories [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216175529_ci_internal_pipelines.yml) -Group: `group::continuous integration` +Group: `group::pipeline execution` Status: `data_available` @@ -17540,7 +17540,7 @@ Total Pipelines from templates in repository [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216175533_ci_pipeline_config_repository.yml) -Group: `group::continuous integration` +Group: `group::pipeline execution` Status: `data_available` @@ -17552,7 +17552,7 @@ Pipeline schedules in GitLab [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216175535_ci_pipeline_schedules.yml) -Group: `group::continuous integration` +Group: `group::pipeline execution` Status: `data_available` @@ -17564,7 +17564,7 @@ Distinct Users triggering Total pipelines [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216175537_ci_pipelines.yml) -Group: `group::continuous integration` +Group: `group::pipeline execution` Status: `data_available` @@ -17576,7 +17576,7 @@ Total configured Triggers in project [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216175539_ci_triggers.yml) -Group: `group::continuous integration` +Group: `group::pipeline execution` Status: `data_available` @@ -17600,7 +17600,7 @@ Projects with a GitHub service pipeline enabled [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_all/20210216175540_projects_reporting_ci_cd_back_to_github.yml) -Group: `group::continuous integration` +Group: `group::pipeline execution` Status: `data_available` @@ -19196,7 +19196,7 @@ Count creator_id from projects with repository mirroring enabled. [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_28d/20210216181943_projects_mirrored_with_pipelines_enabled.yml) -Group: `group::continuous integration` +Group: `group::pipeline execution` Status: `data_available` @@ -19532,7 +19532,7 @@ Unique monthly builds in project [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216175542_ci_builds.yml) -Group: `group::continuous integration` +Group: `group::pipeline execution` Status: `data_available` @@ -19544,7 +19544,7 @@ Total pipelines in external repositories in a month [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216175544_ci_external_pipelines.yml) -Group: `group::continuous integration` +Group: `group::pipeline execution` Status: `data_available` @@ -19556,7 +19556,7 @@ Total pipelines in GitLab repositories in a month [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216175546_ci_internal_pipelines.yml) -Group: `group::continuous integration` +Group: `group::pipeline execution` Status: `data_available` @@ -19580,7 +19580,7 @@ Total Monthly Pipelines from templates in repository [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216175550_ci_pipeline_config_repository.yml) -Group: `group::continuous integration` +Group: `group::pipeline execution` Status: `data_available` @@ -19592,7 +19592,7 @@ Total monthly Pipeline schedules in GitLab [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216175552_ci_pipeline_schedules.yml) -Group: `group::continuous integration` +Group: `group::pipeline execution` Status: `data_available` @@ -19604,7 +19604,7 @@ Distinct users triggering pipelines in a month [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216175554_ci_pipelines.yml) -Group: `group::continuous integration` +Group: `group::pipeline execution` Status: `data_available` @@ -19616,7 +19616,7 @@ Total configured Triggers in project [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216175556_ci_triggers.yml) -Group: `group::continuous integration` +Group: `group::pipeline execution` Status: `data_available` @@ -19640,7 +19640,7 @@ Projects with a GitHub repository mirror pipeline enabled [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_28d/20210216175558_projects_reporting_ci_cd_back_to_github.yml) -Group: `group::continuous integration` +Group: `group::pipeline execution` Status: `data_available` diff --git a/doc/topics/autodevops/quick_start_guide.md b/doc/topics/autodevops/quick_start_guide.md index 264673a587e..7d5ee10f604 100644 --- a/doc/topics/autodevops/quick_start_guide.md +++ b/doc/topics/autodevops/quick_start_guide.md @@ -186,7 +186,7 @@ The jobs are separated into stages: - Jobs suffixed with `-sast` run static analysis on the current code to check for potential security issues, and are allowed to fail ([Auto SAST](stages.md#auto-sast)) **(ULTIMATE)** - The `secret-detection` job checks for leaked secrets and is allowed to fail ([Auto Secret Detection](stages.md#auto-secret-detection)) **(ULTIMATE)** - - The `license_management` job searches the application's dependencies to determine each of their + - The `license_scanning` job searches the application's dependencies to determine each of their licenses and is allowed to fail ([Auto License Compliance](stages.md#auto-license-compliance)) **(ULTIMATE)** diff --git a/doc/user/admin_area/analytics/dev_ops_report.md b/doc/user/admin_area/analytics/dev_ops_report.md index 40c6f842bc3..0bdf51ba245 100644 --- a/doc/user/admin_area/analytics/dev_ops_report.md +++ b/doc/user/admin_area/analytics/dev_ops_report.md @@ -1,13 +1,12 @@ --- -stage: none -group: unassigned +stage: Manage +group: Optimize info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# DevOps Report +# DevOps Report **(FREE SELF)** -> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/30469) in GitLab 9.3. -> - [Renamed from Conversational Development Index](https://gitlab.com/gitlab-org/gitlab/-/issues/20976) in GitLab 12.6. +> [Renamed from Conversational Development Index](https://gitlab.com/gitlab-org/gitlab/-/issues/20976) in GitLab 12.6. The DevOps Report gives you an overview of your entire instance's adoption of [Concurrent DevOps](https://about.gitlab.com/topics/concurrent-devops/) @@ -15,7 +14,7 @@ from planning to monitoring. To see DevOps Report, go to **Admin Area > Analytics > DevOps Report**. -## DevOps Score **(FREE)** +## DevOps Score NOTE: Your GitLab instance's [usage ping](../settings/usage_statistics.md#usage-ping) must be activated in order to use this feature. @@ -34,7 +33,7 @@ Usage ping data is aggregated on GitLab servers for analysis. Your usage information is **not sent** to any other GitLab instances. If you have just started using GitLab, it may take a few weeks for data to be collected before this feature is available. -## DevOps Adoption **(ULTIMATE)** +## DevOps Adoption **(ULTIMATE SELF)** > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/247112) in GitLab 13.7 as a [Beta feature](https://about.gitlab.com/handbook/product/gitlab-the-product/#beta) > - [Deployed behind a feature flag](../../../user/feature_flags.md), disabled by default. diff --git a/doc/user/admin_area/analytics/index.md b/doc/user/admin_area/analytics/index.md index bea22745e7b..9980e3e6d9b 100644 --- a/doc/user/admin_area/analytics/index.md +++ b/doc/user/admin_area/analytics/index.md @@ -1,10 +1,10 @@ --- -stage: none -group: unassigned +stage: Manage +group: Optimize info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Instance-level analytics +# Instance-level analytics **(FREE SELF)** > [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/41416) in GitLab 11.2. @@ -12,5 +12,5 @@ Administrators have access to instance-wide analytics, as shown in **Admin Area There are several kinds of statistics: -- [DevOps Report](dev_ops_report.md): Provides an overview of your entire instance's feature usage. **(FREE)** -- [Usage Trends](usage_trends.md): Shows how much data your instance contains, and how that is changing. **(FREE)** +- [DevOps Report](dev_ops_report.md): Provides an overview of your entire instance's feature usage. +- [Usage Trends](usage_trends.md): Shows how much data your instance contains, and how that is changing. diff --git a/doc/user/admin_area/analytics/usage_trends.md b/doc/user/admin_area/analytics/usage_trends.md index 7fb23f702a4..49c81b1a965 100644 --- a/doc/user/admin_area/analytics/usage_trends.md +++ b/doc/user/admin_area/analytics/usage_trends.md @@ -4,7 +4,7 @@ group: Optimize info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Usage Trends **(FREE)** +# Usage Trends **(FREE SELF)** > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/235754) in GitLab 13.5 behind a feature flag, disabled by default. > - [Became enabled by default](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46962) in GitLab 13.6. diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md index b0130795f94..223d3363186 100644 --- a/doc/user/gitlab_com/index.md +++ b/doc/user/gitlab_com/index.md @@ -153,11 +153,13 @@ content directly from common public CDN hostnames. ## Webhooks -A limit of: +The following limits apply for [Webhooks](../project/integrations/webhooks.md): -- 100 webhooks applies to projects. -- 50 webhooks applies to groups. **(BRONZE ONLY)** -- Payload is limited to 25MB. +| Setting | GitLab.com | Default | +| ------- | ---------- | ------- | +| [Webhook rate limit](../../administration/instance_limits.md#webhook-rate-limit) | `120` calls per minute for Free tier, unlimited for all paid tiers | Unlimited +| [Number of webhooks](../../administration/instance_limits.md#number-of-webhooks) | `100` per-project, `50` per-group | `100` per-project, `50` per-group +| Maximum payload size | `25 MB` | `25 MB` ## Shared runners diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md index 33fe6150580..406b1e9ba6b 100644 --- a/doc/user/project/integrations/webhooks.md +++ b/doc/user/project/integrations/webhooks.md @@ -34,8 +34,10 @@ Webhooks are available: - Per project, at a project's **Settings > Webhooks** menu. **(FREE)** - Additionally per group, at a group's **Settings > Webhooks** menu. **(PREMIUM)** -NOTE: -On GitLab.com, the [maximum number of webhooks and their size](../../../user/gitlab_com/index.md#webhooks) per project, and per group, is limited. +GitLab.com enforces various [webhook limits](../../../user/gitlab_com/index.md#webhooks), including: + +- The maximum number of webhooks and their size, both per project, and per group. +- The number of webhook calls per minute. ## Possible uses for webhooks diff --git a/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md b/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md index 50fd6795efc..8c77714a2de 100644 --- a/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md +++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md @@ -114,7 +114,7 @@ without any `/project-name`. ##### For both root and subdomains -There are a few cases where you need point both subdomain and root +There are a few cases where you need to point both the subdomain and root domain to the same website, for instance, `example.com` and `www.example.com`. They require: diff --git a/lib/gitlab/ci/config/entry/reports.rb b/lib/gitlab/ci/config/entry/reports.rb index f2fd8ac7fd9..b34ab67cf04 100644 --- a/lib/gitlab/ci/config/entry/reports.rb +++ b/lib/gitlab/ci/config/entry/reports.rb @@ -13,7 +13,7 @@ module Gitlab ALLOWED_KEYS = %i[junit codequality sast secret_detection dependency_scanning container_scanning - dast performance browser_performance load_performance license_management license_scanning metrics lsif + dast performance browser_performance load_performance license_scanning metrics lsif dotenv cobertura terraform accessibility cluster_applications requirements coverage_fuzzing api_fuzzing].freeze @@ -36,7 +36,6 @@ module Gitlab validates :performance, array_of_strings_or_string: true validates :browser_performance, array_of_strings_or_string: true validates :load_performance, array_of_strings_or_string: true - validates :license_management, array_of_strings_or_string: true validates :license_scanning, array_of_strings_or_string: true validates :metrics, array_of_strings_or_string: true validates :lsif, array_of_strings_or_string: true diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb index 565ecfc5278..14a65ac1f5d 100644 --- a/lib/gitlab/database.rb +++ b/lib/gitlab/database.rb @@ -293,7 +293,7 @@ module Gitlab # @param [ActiveRecord::Connection] ar_connection # @return [String] def self.get_write_location(ar_connection) - use_new_load_balancer_query = Gitlab::Utils.to_boolean(ENV['USE_NEW_LOAD_BALANCER_QUERY'], default: false) + use_new_load_balancer_query = Gitlab::Utils.to_boolean(ENV['USE_NEW_LOAD_BALANCER_QUERY'], default: true) sql = if use_new_load_balancer_query <<~NEWSQL diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb index d6cc4310438..d155abefdc8 100644 --- a/lib/gitlab/database/migration_helpers.rb +++ b/lib/gitlab/database/migration_helpers.rb @@ -1091,6 +1091,25 @@ module Gitlab execute("DELETE FROM batched_background_migrations WHERE #{conditions}") end + def ensure_batched_background_migration_is_finished(job_class_name:, table_name:, column_name:, job_arguments:) + migration = Gitlab::Database::BackgroundMigration::BatchedMigration + .for_configuration(job_class_name, table_name, column_name, job_arguments).first + + configuration = { + job_class_name: job_class_name, + table_name: table_name, + column_name: column_name, + job_arguments: job_arguments + } + + if migration.nil? + Gitlab::AppLogger.warn "Could not find batched background migration for the given configuration: #{configuration}" + elsif !migration.finished? + raise "Expected batched background migration for the given configuration to be marked as 'finished', " \ + "but it is '#{migration.status}': #{configuration}" + end + end + # Returns an Array containing the indexes for the given column def indexes_for(table, column) column = column.to_s diff --git a/locale/gitlab.pot b/locale/gitlab.pot index a320412f475..40e0483e3c2 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -7966,6 +7966,9 @@ msgstr "" msgid "Collapse approvers" msgstr "" +msgid "Collapse diffs larger than this size, and show a 'too large' message instead." +msgstr "" + msgid "Collapse issues" msgstr "" @@ -8563,6 +8566,9 @@ msgstr "" msgid "ContainerRegistry|Cleanup pending" msgstr "" +msgid "ContainerRegistry|Cleanup policies are now available for this project. %{linkStart}Click here to get started.%{linkEnd}" +msgstr "" + msgid "ContainerRegistry|Cleanup policy for tags is disabled" msgstr "" @@ -13987,9 +13993,6 @@ msgstr "" msgid "File deleted" msgstr "" -msgid "File format is no longer supported" -msgstr "" - msgid "File hooks are similar to system hooks but are executed as files instead of sending data to a URL." msgstr "" @@ -16358,9 +16361,6 @@ msgstr "" msgid "How do I set up this service?" msgstr "" -msgid "How does cleanup work?" -msgstr "" - msgid "How it works" msgstr "" @@ -17492,6 +17492,9 @@ msgstr "" msgid "Insert inline code" msgstr "" +msgid "Insert link" +msgstr "" + msgid "Insert suggestion" msgstr "" @@ -20213,6 +20216,9 @@ msgstr "" msgid "Maximum delay (Minutes)" msgstr "" +msgid "Maximum diff patch size in bytes" +msgstr "" + msgid "Maximum duration of a session." msgstr "" @@ -28399,7 +28405,7 @@ msgstr "" msgid "Save pipeline schedule" msgstr "" -msgid "Save space and find images in the Container Registry. Remove unneeded tags and keep only the ones you want." +msgid "Save space and find images in the container Registry. remove unneeded tags and keep only the ones you want. %{linkStart}How does cleanup work?%{linkEnd}" msgstr "" msgid "Saved scan settings and target site settings which are reusable." @@ -28435,6 +28441,9 @@ msgstr "" msgid "Scheduled a rebase of branch %{branch}." msgstr "" +msgid "Scheduled pipelines cannot run more frequently than once per %{limit} minutes. A pipeline configured to run more frequently only starts after %{limit} minutes have elapsed since the last time it ran." +msgstr "" + msgid "Scheduled to merge this merge request (%{strategy})." msgstr "" @@ -28838,6 +28847,12 @@ msgstr "" msgid "SecurityConfiguration|Feature documentation for %{featureName}" msgstr "" +msgid "SecurityConfiguration|GitLab Ultimate checks your application for security vulnerabilities that may lead to unauthorized access, data leaks, and denial of service attacks. Its features include:" +msgstr "" + +msgid "SecurityConfiguration|High-level vulnerability statistics across projects and groups." +msgstr "" + msgid "SecurityConfiguration|Manage" msgstr "" @@ -28850,12 +28865,18 @@ msgstr "" msgid "SecurityConfiguration|Not enabled" msgstr "" +msgid "SecurityConfiguration|Runtime security metrics for application environments." +msgstr "" + msgid "SecurityConfiguration|SAST Analyzers" msgstr "" msgid "SecurityConfiguration|SAST Configuration" msgstr "" +msgid "SecurityConfiguration|Secure your project with Ultimate" +msgstr "" + msgid "SecurityConfiguration|Security Control" msgstr "" @@ -28871,12 +28892,21 @@ msgstr "" msgid "SecurityConfiguration|The status of the tools only applies to the default branch and is based on the %{linkStart}latest pipeline%{linkEnd}. Once you've enabled a scan for the default branch, any subsequent feature branch you create will include the scan." msgstr "" +msgid "SecurityConfiguration|Upgrade or start a free trial" +msgstr "" + msgid "SecurityConfiguration|Using custom settings. You won't receive automatic updates on this variable. %{anchorStart}Restore to default%{anchorEnd}" msgstr "" msgid "SecurityConfiguration|View history" msgstr "" +msgid "SecurityConfiguration|Vulnerability details and statistics in the merge request." +msgstr "" + +msgid "SecurityConfiguration|With the information provided, you can immediately begin risk analysis and remediation within GitLab." +msgstr "" + msgid "SecurityConfiguration|You can quickly enable all security scanning tools by enabling %{linkStart}Auto DevOps%{linkEnd}." msgstr "" diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index b8f57b23f68..395d3ea598c 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -508,14 +508,6 @@ FactoryBot.define do end end - trait :license_management do - options do - { - artifacts: { reports: { license_management: 'gl-license-management-report.json' } } - } - end - end - trait :license_scanning do options do { diff --git a/spec/features/groups/members/manage_groups_spec.rb b/spec/features/groups/members/manage_groups_spec.rb index 8aaf07279e0..2dfcd941b4f 100644 --- a/spec/features/groups/members/manage_groups_spec.rb +++ b/spec/features/groups/members/manage_groups_spec.rb @@ -170,6 +170,18 @@ RSpec.describe 'Groups > Members > Manage groups', :js do expect(page).to have_text group_outside_hierarchy.name end end + + context 'when the invite members group modal is enabled' do + it 'shows groups within and outside the hierarchy in search results' do + visit group_group_members_path(group) + + click_on 'Invite a group' + click_on 'Select a group' + + expect(page).to have_text group_within_hierarchy.name + expect(page).to have_text group_outside_hierarchy.name + end + end end context 'when sharing with groups outside the hierarchy is disabled' do @@ -192,6 +204,18 @@ RSpec.describe 'Groups > Members > Manage groups', :js do expect(page).not_to have_text group_outside_hierarchy.name end end + + context 'when the invite members group modal is enabled' do + it 'shows only groups within the hierarchy in search results' do + visit group_group_members_path(group) + + click_on 'Invite a group' + click_on 'Select a group' + + expect(page).to have_text group_within_hierarchy.name + expect(page).not_to have_text group_outside_hierarchy.name + end + end end end diff --git a/spec/features/projects/settings/registry_settings_spec.rb b/spec/features/projects/settings/registry_settings_spec.rb index 6a2769d11fd..1cc54b71d4a 100644 --- a/spec/features/projects/settings/registry_settings_spec.rb +++ b/spec/features/projects/settings/registry_settings_spec.rb @@ -24,14 +24,14 @@ RSpec.describe 'Project > Settings > CI/CD > Container registry tag expiration p it 'shows available section' do subject - settings_block = find('#js-registry-policies') + settings_block = find('[data-testid="registry-settings-app"]') expect(settings_block).to have_text 'Clean up image tags' end it 'saves cleanup policy submit the form' do subject - within '#js-registry-policies' do + within '[data-testid="registry-settings-app"]' do select('Every day', from: 'Run cleanup') select('50 tags per image name', from: 'Keep the most recent:') fill_in('Keep tags matching:', with: 'stable') @@ -49,7 +49,7 @@ RSpec.describe 'Project > Settings > CI/CD > Container registry tag expiration p it 'does not save cleanup policy submit form with invalid regex' do subject - within '#js-registry-policies' do + within '[data-testid="registry-settings-app"]' do fill_in('Remove tags matching:', with: '*-production') submit_button = find('[data-testid="save-button"') @@ -80,7 +80,7 @@ RSpec.describe 'Project > Settings > CI/CD > Container registry tag expiration p it 'displays the expected result' do subject - within '#js-registry-policies' do + within '[data-testid="registry-settings-app"]' do case result when :available_section expect(find('[data-testid="enable-toggle"]')).to have_content('Disabled - Tags will not be automatically deleted.') @@ -98,7 +98,7 @@ RSpec.describe 'Project > Settings > CI/CD > Container registry tag expiration p it 'does not exists' do subject - expect(page).not_to have_selector('#js-registry-policies') + expect(page).not_to have_selector('[data-testid="registry-settings-app"]') end end @@ -108,7 +108,7 @@ RSpec.describe 'Project > Settings > CI/CD > Container registry tag expiration p it 'does not exists' do subject - expect(page).not_to have_selector('#js-registry-policies') + expect(page).not_to have_selector('[data-testid="registry-settings-app"]') end end end diff --git a/spec/frontend/__helpers__/mock_user_callout_dismisser.js b/spec/frontend/__helpers__/mock_user_callout_dismisser.js new file mode 100644 index 00000000000..652f36028dc --- /dev/null +++ b/spec/frontend/__helpers__/mock_user_callout_dismisser.js @@ -0,0 +1,16 @@ +/** + * Mock factory for the UserCalloutDismisser component. + * @param {slotProps} The slot props to pass to the default slot content. + * @returns {VueComponent} + */ +export const makeMockUserCalloutDismisser = ({ + dismiss = () => {}, + shouldShowCallout = true, +} = {}) => ({ + render() { + return this.$scopedSlots.default({ + dismiss, + shouldShowCallout, + }); + }, +}); diff --git a/spec/frontend/content_editor/components/__snapshots__/toolbar_link_button_spec.js.snap b/spec/frontend/content_editor/components/__snapshots__/toolbar_link_button_spec.js.snap index 4fae9ff932c..e56c37b0dc9 100644 --- a/spec/frontend/content_editor/components/__snapshots__/toolbar_link_button_spec.js.snap +++ b/spec/frontend/content_editor/components/__snapshots__/toolbar_link_button_spec.js.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`content_editor/components/toolbar_link_button renders dropdown component 1`] = ` -"<div class=\\"dropdown b-dropdown gl-new-dropdown btn-group\\"> +"<div class=\\"dropdown b-dropdown gl-new-dropdown btn-group\\" aria-label=\\"Insert link\\" title=\\"Insert link\\"> <!----><button aria-haspopup=\\"true\\" aria-expanded=\\"false\\" type=\\"button\\" class=\\"btn dropdown-toggle btn-default btn-sm gl-button gl-dropdown-toggle btn-default-tertiary dropdown-icon-only\\"> <!----> <svg data-testid=\\"link-icon\\" role=\\"img\\" aria-hidden=\\"true\\" class=\\"dropdown-icon gl-icon s16\\"> <use href=\\"#link\\"></use> diff --git a/spec/frontend/content_editor/components/top_toolbar_spec.js b/spec/frontend/content_editor/components/top_toolbar_spec.js index 0892bc1a4da..0a1405a1774 100644 --- a/spec/frontend/content_editor/components/top_toolbar_spec.js +++ b/spec/frontend/content_editor/components/top_toolbar_spec.js @@ -48,6 +48,7 @@ describe('content_editor/components/top_toolbar', () => { ${'ordered-list'} | ${{ contentType: 'orderedList', iconName: 'list-numbered', label: 'Add a numbered list', editorCommand: 'toggleOrderedList' }} ${'code-block'} | ${{ contentType: 'codeBlock', iconName: 'doc-code', label: 'Insert a code block', editorCommand: 'toggleCodeBlock' }} ${'text-styles'} | ${{}} + ${'link'} | ${{}} `('given a $testId toolbar control', ({ testId, controlProps }) => { beforeEach(() => { buildWrapper(); diff --git a/spec/frontend/invite_members/components/group_select_spec.js b/spec/frontend/invite_members/components/group_select_spec.js index 60527845089..2ef8fe07650 100644 --- a/spec/frontend/invite_members/components/group_select_spec.js +++ b/spec/frontend/invite_members/components/group_select_spec.js @@ -1,7 +1,7 @@ import { GlAvatarLabeled, GlDropdown, GlSearchBoxByType } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; import waitForPromises from 'helpers/wait_for_promises'; -import Api from '~/api'; +import * as groupsApi from '~/api/groups_api'; import GroupSelect from '~/invite_members/components/group_select.vue'; const createComponent = () => { @@ -16,7 +16,7 @@ describe('GroupSelect', () => { let wrapper; beforeEach(() => { - jest.spyOn(Api, 'groups').mockResolvedValue(allGroups); + jest.spyOn(groupsApi, 'getGroups').mockResolvedValue(allGroups); wrapper = createComponent(); }); @@ -45,7 +45,7 @@ describe('GroupSelect', () => { let resolveApiRequest; beforeEach(() => { - jest.spyOn(Api, 'groups').mockImplementation( + jest.spyOn(groupsApi, 'getGroups').mockImplementation( () => new Promise((resolve) => { resolveApiRequest = resolve; @@ -58,7 +58,7 @@ describe('GroupSelect', () => { it('calls the API', () => { resolveApiRequest({ data: allGroups }); - expect(Api.groups).toHaveBeenCalledWith(group1.name, { + expect(groupsApi.getGroups).toHaveBeenCalledWith(group1.name, { active: true, exclude_internal: true, }); diff --git a/spec/frontend/issues_list/components/issues_list_app_spec.js b/spec/frontend/issues_list/components/issues_list_app_spec.js index 8a91dca51c2..d78a436c618 100644 --- a/spec/frontend/issues_list/components/issues_list_app_spec.js +++ b/spec/frontend/issues_list/components/issues_list_app_spec.js @@ -440,6 +440,13 @@ describe('IssuesListApp component', () => { }); describe('tokens', () => { + const mockCurrentUser = { + id: 1, + name: 'Administrator', + username: 'root', + avatar_url: 'avatar/url', + }; + describe('when user is signed out', () => { beforeEach(() => { wrapper = mountComponent({ @@ -451,6 +458,8 @@ describe('IssuesListApp component', () => { it('does not render My-Reaction or Confidential tokens', () => { expect(findIssuableList().props('searchTokens')).not.toMatchObject([ + { type: TOKEN_TYPE_AUTHOR, preloadedAuthors: [mockCurrentUser] }, + { type: TOKEN_TYPE_ASSIGNEE, preloadedAuthors: [mockCurrentUser] }, { type: TOKEN_TYPE_MY_REACTION }, { type: TOKEN_TYPE_CONFIDENTIAL }, ]); @@ -506,7 +515,17 @@ describe('IssuesListApp component', () => { }); describe('when all tokens are available', () => { + const originalGon = window.gon; + beforeEach(() => { + window.gon = { + ...originalGon, + current_user_id: mockCurrentUser.id, + current_user_fullname: mockCurrentUser.name, + current_username: mockCurrentUser.username, + current_user_avatar_url: mockCurrentUser.avatar_url, + }; + wrapper = mountComponent({ provide: { isSignedIn: true, @@ -519,8 +538,8 @@ describe('IssuesListApp component', () => { it('renders all tokens', () => { expect(findIssuableList().props('searchTokens')).toMatchObject([ - { type: TOKEN_TYPE_AUTHOR }, - { type: TOKEN_TYPE_ASSIGNEE }, + { type: TOKEN_TYPE_AUTHOR, preloadedAuthors: [mockCurrentUser] }, + { type: TOKEN_TYPE_ASSIGNEE, preloadedAuthors: [mockCurrentUser] }, { type: TOKEN_TYPE_MILESTONE }, { type: TOKEN_TYPE_LABEL }, { type: TOKEN_TYPE_MY_REACTION }, diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/registry_settings_app_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/registry_settings_app_spec.js index a725941f7f6..8266f9bee89 100644 --- a/spec/frontend/packages_and_registries/settings/project/settings/components/registry_settings_app_spec.js +++ b/spec/frontend/packages_and_registries/settings/project/settings/components/registry_settings_app_spec.js @@ -10,6 +10,8 @@ import { UNAVAILABLE_USER_FEATURE_TEXT, } from '~/packages_and_registries/settings/project/constants'; import expirationPolicyQuery from '~/packages_and_registries/settings/project/graphql/queries/get_expiration_policy.query.graphql'; +import CleanupPolicyEnabledAlert from '~/packages_and_registries/shared/components/cleanup_policy_enabled_alert.vue'; +import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue'; import { expirationPolicyPayload, @@ -28,15 +30,19 @@ describe('Registry Settings App', () => { isAdmin: false, adminSettingsPath: 'settingsPath', enableHistoricEntries: false, + helpPagePath: 'helpPagePath', + showCleanupPolicyOnAlert: false, }; const findSettingsComponent = () => wrapper.find(SettingsForm); const findAlert = () => wrapper.find(GlAlert); + const findCleanupAlert = () => wrapper.findComponent(CleanupPolicyEnabledAlert); const mountComponent = (provide = defaultProvidedValues, config) => { wrapper = shallowMount(component, { stubs: { GlSprintf, + SettingsBlock, }, mocks: { $toast: { @@ -66,6 +72,26 @@ describe('Registry Settings App', () => { wrapper.destroy(); }); + describe('cleanup is on alert', () => { + it('exist when showCleanupPolicyOnAlert is true and has the correct props', () => { + mountComponent({ + ...defaultProvidedValues, + showCleanupPolicyOnAlert: true, + }); + + expect(findCleanupAlert().exists()).toBe(true); + expect(findCleanupAlert().props()).toMatchObject({ + projectPath: 'path', + }); + }); + + it('is hidden when showCleanupPolicyOnAlert is false', async () => { + mountComponent(); + + expect(findCleanupAlert().exists()).toBe(false); + }); + }); + describe('isEdited status', () => { it.each` description | apiResponse | workingCopy | result diff --git a/spec/frontend/packages_and_registries/shared/components/__snapshots__/cleanup_policy_enabled_alert_spec.js.snap b/spec/frontend/packages_and_registries/shared/components/__snapshots__/cleanup_policy_enabled_alert_spec.js.snap new file mode 100644 index 00000000000..2cded2ead2e --- /dev/null +++ b/spec/frontend/packages_and_registries/shared/components/__snapshots__/cleanup_policy_enabled_alert_spec.js.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`CleanupPolicyEnabledAlert renders 1`] = ` +<gl-alert-stub + class="gl-mt-2" + dismissible="true" + dismisslabel="Dismiss" + primarybuttonlink="" + primarybuttontext="" + secondarybuttonlink="" + secondarybuttontext="" + title="" + variant="info" +> + <gl-sprintf-stub + message="Cleanup policies are now available for this project. %{linkStart}Click here to get started.%{linkEnd}" + /> +</gl-alert-stub> +`; diff --git a/spec/frontend/packages_and_registries/shared/components/cleanup_policy_enabled_alert_spec.js b/spec/frontend/packages_and_registries/shared/components/cleanup_policy_enabled_alert_spec.js new file mode 100644 index 00000000000..269e087f5ac --- /dev/null +++ b/spec/frontend/packages_and_registries/shared/components/cleanup_policy_enabled_alert_spec.js @@ -0,0 +1,49 @@ +import { GlAlert } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import { nextTick } from 'vue'; +import component from '~/packages_and_registries/shared/components/cleanup_policy_enabled_alert.vue'; +import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; + +describe('CleanupPolicyEnabledAlert', () => { + let wrapper; + + const defaultProps = { + projectPath: 'foo', + cleanupPoliciesSettingsPath: 'label-bar', + }; + + const findAlert = () => wrapper.findComponent(GlAlert); + + const mountComponent = (props) => { + wrapper = shallowMount(component, { + stubs: { + LocalStorageSync, + }, + propsData: { + ...defaultProps, + ...props, + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders', () => { + mountComponent(); + + expect(wrapper.element).toMatchSnapshot(); + }); + + it('when dismissed is not visible', async () => { + mountComponent(); + + expect(findAlert().exists()).toBe(true); + findAlert().vm.$emit('dismiss'); + + await nextTick(); + + expect(findAlert().exists()).toBe(false); + }); +}); diff --git a/spec/frontend/pages/projects/pipeline_schedules/shared/components/interval_pattern_input_spec.js b/spec/frontend/pages/projects/pipeline_schedules/shared/components/interval_pattern_input_spec.js index 27cd0fe34bf..de0d70a07d7 100644 --- a/spec/frontend/pages/projects/pipeline_schedules/shared/components/interval_pattern_input_spec.js +++ b/spec/frontend/pages/projects/pipeline_schedules/shared/components/interval_pattern_input_spec.js @@ -1,3 +1,4 @@ +import { GlIcon } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; import { trimText } from 'helpers/text_helper'; import IntervalPatternInput from '~/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue'; @@ -27,6 +28,7 @@ describe('Interval Pattern Input Component', () => { const findAllLabels = () => wrapper.findAll('label'); const findSelectedRadio = () => wrapper.findAll('input[type="radio"]').wrappers.find((x) => x.element.checked); + const findIcon = () => wrapper.findComponent(GlIcon); const findSelectedRadioKey = () => findSelectedRadio()?.attributes('data-testid'); const selectEveryDayRadio = () => findEveryDayRadio().trigger('click'); const selectEveryWeekRadio = () => findEveryWeekRadio().trigger('click'); @@ -40,6 +42,11 @@ describe('Interval Pattern Input Component', () => { wrapper = mount(IntervalPatternInput, { propsData: { ...props }, + provide: { + glFeatures: { + ciDailyLimitForPipelineSchedules: true, + }, + }, data() { return { randomHour: data?.hour || mockHour, @@ -202,4 +209,24 @@ describe('Interval Pattern Input Component', () => { expect(findSelectedRadioKey()).toBe(customKey); }); }); + + describe('Custom cron syntax quota info', () => { + it('the info message includes 5 minutes', () => { + createWrapper({ dailyLimit: '288' }); + + expect(findIcon().attributes('title')).toContain('5 minutes'); + }); + + it('the info message includes 60 minutes', () => { + createWrapper({ dailyLimit: '24' }); + + expect(findIcon().attributes('title')).toContain('60 minutes'); + }); + + it('the info message icon is not shown when there is no daily limit', () => { + createWrapper(); + + expect(findIcon().exists()).toBe(false); + }); + }); }); diff --git a/spec/frontend/registry/explorer/pages/list_spec.js b/spec/frontend/registry/explorer/pages/list_spec.js index 48acc06792d..b58a53f0af2 100644 --- a/spec/frontend/registry/explorer/pages/list_spec.js +++ b/spec/frontend/registry/explorer/pages/list_spec.js @@ -5,6 +5,7 @@ import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import getContainerRepositoriesQuery from 'shared_queries/container_registry/get_container_repositories.query.graphql'; +import CleanupPolicyEnabledAlert from '~/packages_and_registries/shared/components/cleanup_policy_enabled_alert.vue'; import { FILTERED_SEARCH_TERM } from '~/packages_and_registries/shared/constants'; import DeleteImage from '~/registry/explorer/components/delete_image.vue'; import CliCommands from '~/registry/explorer/components/list_page/cli_commands.vue'; @@ -43,21 +44,22 @@ describe('List Page', () => { let wrapper; let apolloProvider; - const findDeleteModal = () => wrapper.find(GlModal); - const findSkeletonLoader = () => wrapper.find(GlSkeletonLoader); + const findDeleteModal = () => wrapper.findComponent(GlModal); + const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader); - const findEmptyState = () => wrapper.find(GlEmptyState); + const findEmptyState = () => wrapper.findComponent(GlEmptyState); - const findCliCommands = () => wrapper.find(CliCommands); - const findProjectEmptyState = () => wrapper.find(ProjectEmptyState); - const findGroupEmptyState = () => wrapper.find(GroupEmptyState); - const findRegistryHeader = () => wrapper.find(RegistryHeader); + const findCliCommands = () => wrapper.findComponent(CliCommands); + const findProjectEmptyState = () => wrapper.findComponent(ProjectEmptyState); + const findGroupEmptyState = () => wrapper.findComponent(GroupEmptyState); + const findRegistryHeader = () => wrapper.findComponent(RegistryHeader); - const findDeleteAlert = () => wrapper.find(GlAlert); - const findImageList = () => wrapper.find(ImageList); - const findRegistrySearch = () => wrapper.find(RegistrySearch); + const findDeleteAlert = () => wrapper.findComponent(GlAlert); + const findImageList = () => wrapper.findComponent(ImageList); + const findRegistrySearch = () => wrapper.findComponent(RegistrySearch); const findEmptySearchMessage = () => wrapper.find('[data-testid="emptySearch"]'); - const findDeleteImage = () => wrapper.find(DeleteImage); + const findDeleteImage = () => wrapper.findComponent(DeleteImage); + const findCleanupAlert = () => wrapper.findComponent(CleanupPolicyEnabledAlert); const waitForApolloRequestRender = async () => { jest.runOnlyPendingTimers(); @@ -560,4 +562,33 @@ describe('List Page', () => { }, ); }); + + describe('cleanup is on alert', () => { + it('exist when showCleanupPolicyOnAlert is true and has the correct props', async () => { + mountComponent({ + config: { + showCleanupPolicyOnAlert: true, + projectPath: 'foo', + isGroupPage: false, + cleanupPoliciesSettingsPath: 'bar', + }, + }); + + await waitForApolloRequestRender(); + + expect(findCleanupAlert().exists()).toBe(true); + expect(findCleanupAlert().props()).toMatchObject({ + projectPath: 'foo', + cleanupPoliciesSettingsPath: 'bar', + }); + }); + + it('is hidden when showCleanupPolicyOnAlert is false', async () => { + mountComponent(); + + await waitForApolloRequestRender(); + + expect(findCleanupAlert().exists()).toBe(false); + }); + }); }); diff --git a/spec/frontend/security_configuration/components/redesigned_app_spec.js b/spec/frontend/security_configuration/components/redesigned_app_spec.js index d55d8d183ce..7e27a3e1108 100644 --- a/spec/frontend/security_configuration/components/redesigned_app_spec.js +++ b/spec/frontend/security_configuration/components/redesigned_app_spec.js @@ -1,5 +1,6 @@ import { GlTab } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; +import { makeMockUserCalloutDismisser } from 'helpers/mock_user_callout_dismisser'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { SAST_NAME, @@ -15,18 +16,33 @@ import FeatureCard from '~/security_configuration/components/feature_card.vue'; import RedesignedSecurityConfigurationApp, { i18n, } from '~/security_configuration/components/redesigned_app.vue'; +import UpgradeBanner from '~/security_configuration/components/upgrade_banner.vue'; import { REPORT_TYPE_LICENSE_COMPLIANCE, REPORT_TYPE_SAST, } from '~/vue_shared/security_reports/constants'; +const upgradePath = '/upgrade'; + describe('redesigned App component', () => { let wrapper; + let userCalloutDismissSpy; + + const createComponent = ({ shouldShowCallout = true, ...propsData }) => { + userCalloutDismissSpy = jest.fn(); - const createComponent = (propsData) => { wrapper = extendedWrapper( mount(RedesignedSecurityConfigurationApp, { propsData, + provide: { + upgradePath, + }, + stubs: { + UserCalloutDismisser: makeMockUserCalloutDismisser({ + dismiss: userCalloutDismissSpy, + shouldShowCallout, + }), + }, }), ); }; @@ -38,6 +54,7 @@ describe('redesigned App component', () => { const findFeatureCards = () => wrapper.findAllComponents(FeatureCard); const findComplianceViewHistoryLink = () => findByTestId('compliance-view-history-link'); const findSecurityViewHistoryLink = () => findByTestId('security-view-history-link'); + const findUpgradeBanner = () => wrapper.findComponent(UpgradeBanner); const securityFeaturesMock = [ { @@ -112,6 +129,58 @@ describe('redesigned App component', () => { }); }); + describe('upgrade banner', () => { + const makeAvailable = (available) => (feature) => ({ ...feature, available }); + + describe('given at least one unavailable feature', () => { + beforeEach(() => { + createComponent({ + augmentedSecurityFeatures: securityFeaturesMock, + augmentedComplianceFeatures: complianceFeaturesMock.map(makeAvailable(false)), + }); + }); + + it('renders the banner', () => { + expect(findUpgradeBanner().exists()).toBe(true); + }); + + it('calls the dismiss callback when closing the banner', () => { + expect(userCalloutDismissSpy).not.toHaveBeenCalled(); + + findUpgradeBanner().vm.$emit('close'); + + expect(userCalloutDismissSpy).toHaveBeenCalledTimes(1); + }); + }); + + describe('given at least one unavailable feature, but banner is already dismissed', () => { + beforeEach(() => { + createComponent({ + augmentedSecurityFeatures: securityFeaturesMock, + augmentedComplianceFeatures: complianceFeaturesMock.map(makeAvailable(false)), + shouldShowCallout: false, + }); + }); + + it('does not render the banner', () => { + expect(findUpgradeBanner().exists()).toBe(false); + }); + }); + + describe('given all features are available', () => { + beforeEach(() => { + createComponent({ + augmentedSecurityFeatures: securityFeaturesMock.map(makeAvailable(true)), + augmentedComplianceFeatures: complianceFeaturesMock.map(makeAvailable(true)), + }); + }); + + it('does not render the banner', () => { + expect(findUpgradeBanner().exists()).toBe(false); + }); + }); + }); + describe('when given latestPipelinePath props', () => { beforeEach(() => { createComponent({ diff --git a/spec/frontend/security_configuration/components/upgrade_banner_spec.js b/spec/frontend/security_configuration/components/upgrade_banner_spec.js new file mode 100644 index 00000000000..cf7945343af --- /dev/null +++ b/spec/frontend/security_configuration/components/upgrade_banner_spec.js @@ -0,0 +1,60 @@ +import { GlBanner } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import UpgradeBanner from '~/security_configuration/components/upgrade_banner.vue'; + +const upgradePath = '/upgrade'; + +describe('UpgradeBanner component', () => { + let wrapper; + let closeSpy; + + const createComponent = (propsData) => { + closeSpy = jest.fn(); + + wrapper = shallowMountExtended(UpgradeBanner, { + provide: { + upgradePath, + }, + propsData, + listeners: { + close: closeSpy, + }, + }); + }; + + const findGlBanner = () => wrapper.findComponent(GlBanner); + + beforeEach(() => { + createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('passes the expected props to GlBanner', () => { + expect(findGlBanner().props()).toMatchObject({ + title: UpgradeBanner.i18n.title, + buttonText: UpgradeBanner.i18n.buttonText, + buttonLink: upgradePath, + }); + }); + + it('renders the list of benefits', () => { + const wrapperText = wrapper.text(); + + expect(wrapperText).toContain('GitLab Ultimate checks your application'); + expect(wrapperText).toContain('statistics in the merge request'); + expect(wrapperText).toContain('statistics across projects'); + expect(wrapperText).toContain('Runtime security metrics'); + expect(wrapperText).toContain('risk analysis and remediation'); + }); + + it(`re-emits GlBanner's close event`, () => { + expect(closeSpy).not.toHaveBeenCalled(); + + wrapper.findComponent(GlBanner).vm.$emit('close'); + + expect(closeSpy).toHaveBeenCalledTimes(1); + }); +}); diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js index 899f6c78e89..f50eafdbc52 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js @@ -30,6 +30,15 @@ const defaultStubs = { }, }; +const mockPreloadedAuthors = [ + { + id: 13, + name: 'Administrator', + username: 'root', + avatar_url: 'avatar/url', + }, +]; + function createComponent(options = {}) { const { config = mockAuthorToken, @@ -65,13 +74,6 @@ describe('AuthorToken', () => { const getBaseToken = () => wrapper.findComponent(BaseToken); beforeEach(() => { - window.gon = { - ...originalGon, - current_user_id: 13, - current_user_fullname: 'Administrator', - current_username: 'root', - current_user_avatar_url: 'avatar/url', - }; mock = new MockAdapter(axios); }); @@ -133,6 +135,13 @@ describe('AuthorToken', () => { }); describe('template', () => { + const activateTokenValuesList = async () => { + const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment); + const suggestionsSegment = tokenSegments.at(2); + suggestionsSegment.vm.$emit('activate'); + await wrapper.vm.$nextTick(); + }; + it('renders base-token component', () => { wrapper = createComponent({ value: { data: mockAuthors[0].username }, @@ -206,13 +215,11 @@ describe('AuthorToken', () => { const defaultAuthors = DEFAULT_NONE_ANY; wrapper = createComponent({ active: true, - config: { ...mockAuthorToken, defaultAuthors }, + config: { ...mockAuthorToken, defaultAuthors, preloadedAuthors: mockPreloadedAuthors }, stubs: { Portal: true }, }); - const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment); - const suggestionsSegment = tokenSegments.at(2); - suggestionsSegment.vm.$emit('activate'); - await wrapper.vm.$nextTick(); + + await activateTokenValuesList(); const suggestions = wrapper.findAll(GlFilteredSearchSuggestion); @@ -239,13 +246,11 @@ describe('AuthorToken', () => { it('renders `DEFAULT_LABEL_ANY` as default suggestions', async () => { wrapper = createComponent({ active: true, - config: { ...mockAuthorToken }, + config: { ...mockAuthorToken, preloadedAuthors: mockPreloadedAuthors }, stubs: { Portal: true }, }); - const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment); - const suggestionsSegment = tokenSegments.at(2); - suggestionsSegment.vm.$emit('activate'); - await wrapper.vm.$nextTick(); + + await activateTokenValuesList(); const suggestions = wrapper.findAll(GlFilteredSearchSuggestion); @@ -257,7 +262,11 @@ describe('AuthorToken', () => { beforeEach(() => { wrapper = createComponent({ active: true, - config: { ...mockAuthorToken, defaultAuthors: [] }, + config: { + ...mockAuthorToken, + preloadedAuthors: mockPreloadedAuthors, + defaultAuthors: [], + }, stubs: { Portal: true }, }); }); diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js index 0db47f1f189..602864f4fa5 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js @@ -175,6 +175,23 @@ describe('BaseToken', () => { expect(setTokenValueToRecentlyUsed).toHaveBeenCalledWith(mockStorageKey, mockTokenValue); }); + + it('does not add token from preloadedTokenValues', async () => { + const mockTokenValue = { + id: 1, + title: 'Foo', + }; + + wrapper.setProps({ + preloadedTokenValues: [mockTokenValue], + }); + + await wrapper.vm.$nextTick(); + + wrapper.vm.handleTokenValueSelected(mockTokenValue); + + expect(setTokenValueToRecentlyUsed).not.toHaveBeenCalled(); + }); }); }); diff --git a/spec/helpers/nav/top_nav_helper_spec.rb b/spec/helpers/nav/top_nav_helper_spec.rb index 9efa3574d77..d87c751c62f 100644 --- a/spec/helpers/nav/top_nav_helper_spec.rb +++ b/spec/helpers/nav/top_nav_helper_spec.rb @@ -7,6 +7,7 @@ RSpec.describe Nav::TopNavHelper do let_it_be(:user) { build_stubbed(:user) } let_it_be(:admin) { build_stubbed(:user, :admin) } + let_it_be(:external_user) { build_stubbed(:user, :external, can_create_group: false) } let(:current_user) { nil } @@ -305,6 +306,14 @@ RSpec.describe Nav::TopNavHelper do expect(groups_view[:linksSecondary]).to eq(expected_links_secondary) end + context 'with external user' do + let(:current_user) { external_user } + + it 'does not have create group link' do + expect(groups_view[:linksSecondary]).to eq([]) + end + end + context 'with current nav as group' do before do helper.nav('group') diff --git a/spec/helpers/packages_helper_spec.rb b/spec/helpers/packages_helper_spec.rb index dacd386d01c..93d32cb8418 100644 --- a/spec/helpers/packages_helper_spec.rb +++ b/spec/helpers/packages_helper_spec.rb @@ -3,11 +3,13 @@ require 'spec_helper' RSpec.describe PackagesHelper do + using RSpec::Parameterized::TableSyntax + + let_it_be_with_reload(:project) { create(:project) } let_it_be(:base_url) { "#{Gitlab.config.gitlab.url}/api/v4/" } - let_it_be(:project) { create(:project) } - describe 'package_registry_instance_url' do - it 'returns conant instance url when registry_type is conant' do + describe '#package_registry_instance_url' do + it 'returns conan instance url when registry_type is conant' do url = helper.package_registry_instance_url(:conan) expect(url).to eq("#{base_url}packages/conan") @@ -20,7 +22,7 @@ RSpec.describe PackagesHelper do end end - describe 'package_registry_project_url' do + describe '#package_registry_project_url' do it 'returns maven registry url when registry_type is not provided' do url = helper.package_registry_project_url(1) @@ -34,7 +36,7 @@ RSpec.describe PackagesHelper do end end - describe 'pypi_registry_url' do + describe '#pypi_registry_url' do let_it_be(:base_url_with_token) { base_url.sub('://', '://__token__:<your_personal_token>@') } it 'returns the pypi registry url' do @@ -44,7 +46,7 @@ RSpec.describe PackagesHelper do end end - describe 'composer_registry_url' do + describe '#composer_registry_url' do it 'return the composer registry url' do url = helper.composer_registry_url(1) @@ -52,7 +54,7 @@ RSpec.describe PackagesHelper do end end - describe 'composer_config_repository_name' do + describe '#composer_config_repository_name' do let(:host) { Gitlab.config.gitlab.host } let(:group_id) { 1 } @@ -62,4 +64,157 @@ RSpec.describe PackagesHelper do expect(id).to eq("#{host}/#{group_id}") end end + + describe '#show_cleanup_policy_on_alert' do + let_it_be_with_reload(:container_repository) { create(:container_repository) } + + subject { helper.show_cleanup_policy_on_alert(project.reload) } + + where(:com, :config_registry, :project_registry, :historic_entries, :historic_entry, :nil_policy, :container_repositories_exist, :expected_result) do + false | false | false | false | false | false | false | false + false | false | false | false | false | false | true | false + false | false | false | false | false | true | false | false + false | false | false | false | false | true | true | false + false | false | false | false | true | false | false | false + false | false | false | false | true | false | true | false + false | false | false | false | true | true | false | false + false | false | false | false | true | true | true | false + false | false | false | true | false | false | false | false + false | false | false | true | false | false | true | false + false | false | false | true | false | true | false | false + false | false | false | true | false | true | true | false + false | false | false | true | true | false | false | false + false | false | false | true | true | false | true | false + false | false | false | true | true | true | false | false + false | false | false | true | true | true | true | false + false | false | true | false | false | false | false | false + false | false | true | false | false | false | true | false + false | false | true | false | false | true | false | false + false | false | true | false | false | true | true | false + false | false | true | false | true | false | false | false + false | false | true | false | true | false | true | false + false | false | true | false | true | true | false | false + false | false | true | false | true | true | true | false + false | false | true | true | false | false | false | false + false | false | true | true | false | false | true | false + false | false | true | true | false | true | false | false + false | false | true | true | false | true | true | false + false | false | true | true | true | false | false | false + false | false | true | true | true | false | true | false + false | false | true | true | true | true | false | false + false | false | true | true | true | true | true | false + false | true | false | false | false | false | false | false + false | true | false | false | false | false | true | false + false | true | false | false | false | true | false | false + false | true | false | false | false | true | true | false + false | true | false | false | true | false | false | false + false | true | false | false | true | false | true | false + false | true | false | false | true | true | false | false + false | true | false | false | true | true | true | false + false | true | false | true | false | false | false | false + false | true | false | true | false | false | true | false + false | true | false | true | false | true | false | false + false | true | false | true | false | true | true | false + false | true | false | true | true | false | false | false + false | true | false | true | true | false | true | false + false | true | false | true | true | true | false | false + false | true | false | true | true | true | true | false + false | true | true | false | false | false | false | false + false | true | true | false | false | false | true | false + false | true | true | false | false | true | false | false + false | true | true | false | false | true | true | false + false | true | true | false | true | false | false | false + false | true | true | false | true | false | true | false + false | true | true | false | true | true | false | false + false | true | true | false | true | true | true | false + false | true | true | true | false | false | false | false + false | true | true | true | false | false | true | false + false | true | true | true | false | true | false | false + false | true | true | true | false | true | true | false + false | true | true | true | true | false | false | false + false | true | true | true | true | false | true | false + false | true | true | true | true | true | false | false + false | true | true | true | true | true | true | false + true | false | false | false | false | false | false | false + true | false | false | false | false | false | true | false + true | false | false | false | false | true | false | false + true | false | false | false | false | true | true | false + true | false | false | false | true | false | false | false + true | false | false | false | true | false | true | false + true | false | false | false | true | true | false | false + true | false | false | false | true | true | true | false + true | false | false | true | false | false | false | false + true | false | false | true | false | false | true | false + true | false | false | true | false | true | false | false + true | false | false | true | false | true | true | false + true | false | false | true | true | false | false | false + true | false | false | true | true | false | true | false + true | false | false | true | true | true | false | false + true | false | false | true | true | true | true | false + true | false | true | false | false | false | false | false + true | false | true | false | false | false | true | false + true | false | true | false | false | true | false | false + true | false | true | false | false | true | true | false + true | false | true | false | true | false | false | false + true | false | true | false | true | false | true | false + true | false | true | false | true | true | false | false + true | false | true | false | true | true | true | false + true | false | true | true | false | false | false | false + true | false | true | true | false | false | true | false + true | false | true | true | false | true | false | false + true | false | true | true | false | true | true | false + true | false | true | true | true | false | false | false + true | false | true | true | true | false | true | false + true | false | true | true | true | true | false | false + true | false | true | true | true | true | true | false + true | true | false | false | false | false | false | false + true | true | false | false | false | false | true | false + true | true | false | false | false | true | false | false + true | true | false | false | false | true | true | false + true | true | false | false | true | false | false | false + true | true | false | false | true | false | true | false + true | true | false | false | true | true | false | false + true | true | false | false | true | true | true | false + true | true | false | true | false | false | false | false + true | true | false | true | false | false | true | false + true | true | false | true | false | true | false | false + true | true | false | true | false | true | true | false + true | true | false | true | true | false | false | false + true | true | false | true | true | false | true | false + true | true | false | true | true | true | false | false + true | true | false | true | true | true | true | false + true | true | true | false | false | false | false | false + true | true | true | false | false | false | true | false + true | true | true | false | false | true | false | false + true | true | true | false | false | true | true | false + true | true | true | false | true | false | false | false + true | true | true | false | true | false | true | false + true | true | true | false | true | true | false | false + true | true | true | false | true | true | true | true + true | true | true | true | false | false | false | false + true | true | true | true | false | false | true | false + true | true | true | true | false | true | false | false + true | true | true | true | false | true | true | false + true | true | true | true | true | false | false | false + true | true | true | true | true | false | true | false + true | true | true | true | true | true | false | false + true | true | true | true | true | true | true | false + end + + with_them do + before do + allow(Gitlab).to receive(:com?).and_return(com) + stub_config(registry: { enabled: config_registry }) + allow(project).to receive(:container_registry_enabled).and_return(project_registry) + stub_application_setting(container_expiration_policies_enable_historic_entries: historic_entries) + stub_feature_flags(container_expiration_policies_historic_entry: false) + stub_feature_flags(container_expiration_policies_historic_entry: project) if historic_entry + + project.container_expiration_policy.destroy! if nil_policy + container_repository.update!(project_id: project.id) if container_repositories_exist + end + + it { is_expected.to eq(expected_result) } + end + end end diff --git a/spec/lib/gitlab/ci/config/entry/reports_spec.rb b/spec/lib/gitlab/ci/config/entry/reports_spec.rb index 98105ebcd55..d8907f7015b 100644 --- a/spec/lib/gitlab/ci/config/entry/reports_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/reports_spec.rb @@ -41,7 +41,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Reports do :dependency_scanning | 'gl-dependency-scanning-report.json' :container_scanning | 'gl-container-scanning-report.json' :dast | 'gl-dast-report.json' - :license_management | 'gl-license-management-report.json' :license_scanning | 'gl-license-scanning-report.json' :performance | 'performance.json' :browser_performance | 'browser-performance.json' diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index 40720628a89..f0ea07646fb 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -2001,6 +2001,41 @@ RSpec.describe Gitlab::Database::MigrationHelpers do end end + describe '#ensure_batched_background_migration_is_finished' do + let(:configuration) do + { + job_class_name: 'CopyColumnUsingBackgroundMigrationJob', + table_name: :events, + column_name: :id, + job_arguments: [[:id], [:id_convert_to_bigint]] + } + end + + subject(:ensure_batched_background_migration_is_finished) { model.ensure_batched_background_migration_is_finished(**configuration) } + + it 'raises an error when migration exists and is not marked as finished' do + create(:batched_background_migration, configuration.merge(status: :active)) + + expect { ensure_batched_background_migration_is_finished } + .to raise_error "Expected batched background migration for the given configuration to be marked as 'finished', but it is 'active': #{configuration}" + end + + it 'does not raise error when migration exists and is marked as finished' do + create(:batched_background_migration, configuration.merge(status: :finished)) + + expect { ensure_batched_background_migration_is_finished } + .not_to raise_error + end + + it 'logs a warning when migration does not exist' do + expect(Gitlab::AppLogger).to receive(:warn) + .with("Could not find batched background migration for the given configuration: #{configuration}") + + expect { ensure_batched_background_migration_is_finished } + .not_to raise_error + end + end + describe '#index_exists_by_name?' do it 'returns true if an index exists' do ActiveRecord::Base.connection.execute( diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 11c2ecae68a..5bd8fee339d 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -111,10 +111,6 @@ RSpec.describe Ci::Build do describe '.with_downloadable_artifacts' do subject { described_class.with_downloadable_artifacts } - before do - stub_feature_flags(drop_license_management_artifact: false) - end - context 'when job does not have a downloadable artifact' do let!(:job) { create(:ci_build) } @@ -1732,8 +1728,6 @@ RSpec.describe Ci::Build do subject { build.erase_erasable_artifacts! } before do - stub_feature_flags(drop_license_management_artifact: false) - Ci::JobArtifact.file_types.keys.each do |file_type| create(:ci_job_artifact, job: build, file_type: file_type, file_format: Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS[file_type.to_sym]) end diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb index 3c4769764d5..582639b105e 100644 --- a/spec/models/ci/job_artifact_spec.rb +++ b/spec/models/ci/job_artifact_spec.rb @@ -328,35 +328,9 @@ RSpec.describe Ci::JobArtifact do end end - describe 'validates if file format is supported' do - subject { artifact } - - let(:artifact) { build(:ci_job_artifact, file_type: :license_management, file_format: :raw) } - - context 'when license_management is supported' do - before do - stub_feature_flags(drop_license_management_artifact: false) - end - - it { is_expected.to be_valid } - end - - context 'when license_management is not supported' do - before do - stub_feature_flags(drop_license_management_artifact: true) - end - - it { is_expected.not_to be_valid } - end - end - describe 'validates file format' do subject { artifact } - before do - stub_feature_flags(drop_license_management_artifact: false) - end - described_class::TYPE_AND_FORMAT_PAIRS.except(:trace).each do |file_type, file_format| context "when #{file_type} type with #{file_format} format" do let(:artifact) { build(:ci_job_artifact, file_type: file_type, file_format: file_format) } diff --git a/spec/models/ci/pipeline_schedule_spec.rb b/spec/models/ci/pipeline_schedule_spec.rb index 33baba30fd8..cf73460bf1e 100644 --- a/spec/models/ci/pipeline_schedule_spec.rb +++ b/spec/models/ci/pipeline_schedule_spec.rb @@ -122,6 +122,9 @@ RSpec.describe Ci::PipelineSchedule do '*/5 * * * *' | '0 * * * *' | (1.day.in_minutes / 10).to_i | false | Time.zone.local(2021, 5, 27, 11, 0) | Time.zone.local(2021, 5, 27, 12, 5) '*/5 * * * *' | '0 * * * *' | (1.day.in_minutes / 1.hour.in_minutes).to_i | true | Time.zone.local(2021, 5, 27, 11, 0) | Time.zone.local(2021, 5, 27, 12, 0) '*/5 * * * *' | '0 * * * *' | (1.day.in_minutes / 2.hours.in_minutes).to_i | true | Time.zone.local(2021, 5, 27, 11, 0) | Time.zone.local(2021, 5, 27, 12, 5) + '*/5 * * * *' | '0 1 * * *' | (1.day.in_minutes / 1.hour.in_minutes).to_i | true | Time.zone.local(2021, 5, 27, 1, 0) | Time.zone.local(2021, 5, 28, 1, 0) + '*/5 * * * *' | '0 1 * * *' | (1.day.in_minutes / 1.hour.in_minutes).to_i | true | Time.zone.local(2021, 5, 27, 1, 0) | Time.zone.local(2021, 5, 28, 1, 0) + '*/5 * * * *' | '0 1 1 * *' | (1.day.in_minutes / 1.hour.in_minutes).to_i | true | Time.zone.local(2021, 5, 1, 1, 0) | Time.zone.local(2021, 6, 1, 1, 0) end with_them do @@ -198,4 +201,26 @@ RSpec.describe Ci::PipelineSchedule do it { is_expected.to contain_exactly(*pipeline_schedule_variables.map(&:to_runner_variable)) } end + + describe '#daily_limit' do + let(:pipeline_schedule) { build(:ci_pipeline_schedule) } + + subject(:daily_limit) { pipeline_schedule.daily_limit } + + context 'when there is no limit' do + before do + create(:plan_limits, :default_plan, ci_daily_pipeline_schedule_triggers: 0) + end + + it { is_expected.to be_nil } + end + + context 'when there is a limit' do + before do + create(:plan_limits, :default_plan, ci_daily_pipeline_schedule_triggers: 144) + end + + it { is_expected.to eq(144) } + end + end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index c3c80ea3a2a..94b4c1901b8 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -4906,7 +4906,6 @@ RSpec.describe MergeRequest, factory_default: :keep do subject { merge_request.enabled_reports[report_type] } before do - stub_feature_flags(drop_license_management_artifact: false) stub_licensed_features({ feature => true }) end diff --git a/spec/policies/global_policy_spec.rb b/spec/policies/global_policy_spec.rb index bbbc5d08c07..9e995366c17 100644 --- a/spec/policies/global_policy_spec.rb +++ b/spec/policies/global_policy_spec.rb @@ -565,4 +565,34 @@ RSpec.describe GlobalPolicy do it { is_expected.not_to be_allowed(:log_in) } end end + + describe 'update_runners_registration_token' do + context 'when anonymous' do + let(:current_user) { nil } + + it { is_expected.not_to be_allowed(:update_runners_registration_token) } + end + + context 'regular user' do + it { is_expected.not_to be_allowed(:update_runners_registration_token) } + end + + context 'when external' do + let(:current_user) { build(:user, :external) } + + it { is_expected.not_to be_allowed(:update_runners_registration_token) } + end + + context 'admin' do + let(:current_user) { create(:admin) } + + context 'when admin mode is enabled', :enable_admin_mode do + it { is_expected.to be_allowed(:update_runners_registration_token) } + end + + context 'when admin mode is disabled' do + it { is_expected.to be_disallowed(:update_runners_registration_token) } + end + end + end end diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb index ee87a2da189..9fac5521aa6 100644 --- a/spec/policies/group_policy_spec.rb +++ b/spec/policies/group_policy_spec.rb @@ -923,4 +923,54 @@ RSpec.describe GroupPolicy do it { expect(described_class.new(current_user, subgroup)).to be_allowed(:read_label) } end end + + describe 'update_runners_registration_token' do + context 'admin' do + let(:current_user) { admin } + + context 'when admin mode is enabled', :enable_admin_mode do + it { is_expected.to be_allowed(:update_runners_registration_token) } + end + + context 'when admin mode is disabled' do + it { is_expected.to be_disallowed(:update_runners_registration_token) } + end + end + + context 'with owner' do + let(:current_user) { owner } + + it { is_expected.to be_allowed(:update_runners_registration_token) } + end + + context 'with maintainer' do + let(:current_user) { maintainer } + + it { is_expected.to be_allowed(:update_runners_registration_token) } + end + + context 'with reporter' do + let(:current_user) { reporter } + + it { is_expected.to be_disallowed(:update_runners_registration_token) } + end + + context 'with guest' do + let(:current_user) { guest } + + it { is_expected.to be_disallowed(:update_runners_registration_token) } + end + + context 'with non member' do + let(:current_user) { create(:user) } + + it { is_expected.to be_disallowed(:update_runners_registration_token) } + end + + context 'with anonymous' do + let(:current_user) { nil } + + it { is_expected.to be_disallowed(:update_runners_registration_token) } + end + end end diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 2b4501a71a5..d0fe0cca8a1 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -1595,4 +1595,40 @@ RSpec.describe ProjectPolicy do end end end + + describe 'update_runners_registration_token' do + context 'when anonymous' do + let(:current_user) { anonymous } + + it { is_expected.not_to be_allowed(:update_runners_registration_token) } + end + + context 'admin' do + let(:current_user) { create(:admin) } + + context 'when admin mode is enabled', :enable_admin_mode do + it { is_expected.to be_allowed(:update_runners_registration_token) } + end + + context 'when admin mode is disabled' do + it { is_expected.to be_disallowed(:update_runners_registration_token) } + end + end + + %w(guest reporter developer).each do |role| + context role do + let(:current_user) { send(role) } + + it { is_expected.to be_disallowed(:update_runners_registration_token) } + end + end + + %w(maintainer owner).each do |role| + context role do + let(:current_user) { send(role) } + + it { is_expected.to be_allowed(:update_runners_registration_token) } + end + end + end end diff --git a/spec/requests/api/graphql/mutations/ci/runners_registration_token/reset_spec.rb b/spec/requests/api/graphql/mutations/ci/runners_registration_token/reset_spec.rb new file mode 100644 index 00000000000..07b05ead651 --- /dev/null +++ b/spec/requests/api/graphql/mutations/ci/runners_registration_token/reset_spec.rb @@ -0,0 +1,122 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'RunnersRegistrationTokenReset' do + include GraphqlHelpers + + let(:mutation) { graphql_mutation(:runners_registration_token_reset, input) } + let(:mutation_response) { graphql_mutation_response(:runners_registration_token_reset) } + + subject { post_graphql_mutation(mutation, current_user: user) } + + shared_examples 'unauthorized' do + it 'returns an error' do + subject + + expect(graphql_errors).not_to be_empty + expect(graphql_errors).to include(a_hash_including('message' => "The resource that you are attempting to access does not exist or you don't have permission to perform this action")) + expect(mutation_response).to be_nil + end + end + + shared_context 'when unauthorized' do |scope| + context 'when unauthorized' do + let_it_be(:user) { create(:user) } + + context "when not a #{scope} member" do + it_behaves_like 'unauthorized' + end + + context "with a non-admin #{scope} member" do + before do + target.add_developer(user) + end + + it_behaves_like 'unauthorized' + end + end + end + + shared_context 'when authorized' do |scope| + it 'resets runner registration token' do + expect { subject }.to change { get_token } + expect(response).to have_gitlab_http_status(:success) + + expect(mutation_response).not_to be_nil + expect(mutation_response['errors']).to be_empty + expect(mutation_response['token']).not_to be_empty + expect(mutation_response['token']).to eq(get_token) + end + + context 'when malformed id is provided' do + let(:input) { { type: "#{scope.upcase}_TYPE", id: 'some string' } } + + it 'returns errors' do + expect { subject }.not_to change { get_token } + + expect(graphql_errors).not_to be_empty + expect(mutation_response).to be_nil + end + end + end + + context 'applied to project' do + let_it_be(:project) { create_default(:project) } + + let(:input) { { type: 'PROJECT_TYPE', id: project.to_global_id.to_s } } + + include_context 'when unauthorized', 'project' do + let(:target) { project } + end + + include_context 'when authorized', 'project' do + let_it_be(:user) { project.owner } + + def get_token + project.reload.runners_token + end + end + end + + context 'applied to group' do + let_it_be(:group) { create_default(:group) } + + let(:input) { { type: 'GROUP_TYPE', id: group.to_global_id.to_s } } + + include_context 'when unauthorized', 'group' do + let(:target) { group } + end + + include_context 'when authorized', 'group' do + let_it_be(:user) { create_default(:group_member, :maintainer, user: create(:user), group: group ).user } + + def get_token + group.reload.runners_token + end + end + end + + context 'applied to instance' do + before do + ApplicationSetting.create_from_defaults + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') + end + + let(:input) { { type: 'INSTANCE_TYPE' } } + + context 'when unauthorized' do + let(:user) { create(:user) } + + it_behaves_like 'unauthorized' + end + + include_context 'when authorized', 'instance' do + let_it_be(:user) { create(:user, :admin) } + + def get_token + ApplicationSetting.current_without_cache.runners_registration_token + end + end + end +end diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb index 880adc80b24..c71bec31984 100644 --- a/spec/services/ci/retry_build_service_spec.rb +++ b/spec/services/ci/retry_build_service_spec.rb @@ -40,7 +40,7 @@ RSpec.describe Ci::RetryBuildService do job_artifacts_metadata job_artifacts_trace job_artifacts_junit job_artifacts_sast job_artifacts_secret_detection job_artifacts_dependency_scanning job_artifacts_container_scanning job_artifacts_dast - job_artifacts_license_management job_artifacts_license_scanning + job_artifacts_license_scanning job_artifacts_performance job_artifacts_browser_performance job_artifacts_load_performance job_artifacts_lsif job_artifacts_terraform job_artifacts_cluster_applications job_artifacts_codequality job_artifacts_metrics scheduled_at @@ -74,9 +74,6 @@ RSpec.describe Ci::RetryBuildService do end before_all do - # Test correctly behaviour of deprecated artifact because it can be still in use - stub_feature_flags(drop_license_management_artifact: false) - # Make sure that build has both `stage_id` and `stage` because FactoryBot # can reset one of the fields when assigning another. We plan to deprecate # and remove legacy `stage` column in the future. diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb index 9465fe7f0d6..5f53d6f34d8 100644 --- a/spec/services/web_hook_service_spec.rb +++ b/spec/services/web_hook_service_spec.rb @@ -375,15 +375,18 @@ RSpec.describe WebHookService do it 'does not queue a worker and logs an error' do expect(WebHookWorker).not_to receive(:perform_async) - payload = { - message: 'Webhook rate limit exceeded', - hook_id: project_hook.id, - hook_type: 'ProjectHook', - hook_name: 'push_hooks' - } - - expect(Gitlab::AuthLogger).to receive(:error).with(payload) - expect(Gitlab::AppLogger).to receive(:error).with(payload) + expect(Gitlab::AuthLogger).to receive(:error).with( + include( + message: 'Webhook rate limit exceeded', + hook_id: project_hook.id, + hook_type: 'ProjectHook', + hook_name: 'push_hooks', + "correlation_id" => kind_of(String), + "meta.project" => project.full_path, + "meta.related_class" => 'ProjectHook', + "meta.root_namespace" => project.root_namespace.full_path + ) + ) service_instance.async_execute end @@ -403,7 +406,6 @@ RSpec.describe WebHookService do it 'stops queueing workers and logs errors' do expect(Gitlab::AuthLogger).to receive(:error).twice - expect(Gitlab::AppLogger).to receive(:error).twice 2.times { service_instance.async_execute } end diff --git a/spec/workers/expire_pipeline_cache_worker_spec.rb b/spec/workers/expire_pipeline_cache_worker_spec.rb index de42eeeab75..6a1a95b8052 100644 --- a/spec/workers/expire_pipeline_cache_worker_spec.rb +++ b/spec/workers/expire_pipeline_cache_worker_spec.rb @@ -42,8 +42,15 @@ RSpec.describe ExpirePipelineCacheWorker do subject.perform(617748) end - it_behaves_like 'an idempotent worker' do - let(:job_args) { [pipeline.id] } + skip "with https://gitlab.com/gitlab-org/gitlab/-/issues/325291 resolved" do + it_behaves_like 'an idempotent worker' do + let(:job_args) { [pipeline.id] } + end end + + it_behaves_like 'worker with data consistency', + described_class, + feature_flag: :load_balancing_for_expire_pipeline_cache_worker, + data_consistency: :delayed end end diff --git a/spec/workers/pipeline_process_worker_spec.rb b/spec/workers/pipeline_process_worker_spec.rb index 0c1db3ccc5a..f8140d11f2e 100644 --- a/spec/workers/pipeline_process_worker_spec.rb +++ b/spec/workers/pipeline_process_worker_spec.rb @@ -3,10 +3,44 @@ require 'spec_helper' RSpec.describe PipelineProcessWorker do + let_it_be(:pipeline) { create(:ci_pipeline) } + + include_examples 'an idempotent worker' do + let(:pipeline) { create(:ci_pipeline, :created) } + let(:job_args) { [pipeline.id] } + + before do + create(:ci_build, :created, pipeline: pipeline) + end + + it 'processes the pipeline' do + expect(pipeline.status).to eq('created') + expect(pipeline.processables.pluck(:status)).to contain_exactly('created') + + subject + + expect(pipeline.reload.status).to eq('pending') + expect(pipeline.processables.pluck(:status)).to contain_exactly('pending') + + subject + + expect(pipeline.reload.status).to eq('pending') + expect(pipeline.processables.pluck(:status)).to contain_exactly('pending') + end + end + + context 'when the FF ci_idempotent_pipeline_process_worker is disabled' do + before do + stub_feature_flags(ci_idempotent_pipeline_process_worker: false) + end + + it 'is not deduplicated' do + expect(described_class).not_to be_deduplication_enabled + end + end + describe '#perform' do context 'when pipeline exists' do - let(:pipeline) { create(:ci_pipeline) } - it 'processes pipeline' do expect_any_instance_of(Ci::ProcessPipelineService).to receive(:execute) @@ -16,14 +50,9 @@ RSpec.describe PipelineProcessWorker do context 'when pipeline does not exist' do it 'does not raise exception' do - expect { described_class.new.perform(123) } + expect { described_class.new.perform(non_existing_record_id) } .not_to raise_error end end - - it_behaves_like 'worker with data consistency', - described_class, - feature_flag: :load_balancing_for_pipeline_process_worker, - data_consistency: :delayed end end |