Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
4e98c75b8c
commit
8b2a413032
|
|
@ -1,12 +0,0 @@
|
|||
---
|
||||
# Cop supports --autocorrect.
|
||||
Lint/NonAtomicFileOperation:
|
||||
Exclude:
|
||||
- 'lib/gitlab/ci/trace.rb'
|
||||
- 'lib/gitlab/database/migrations/test_batched_background_runner.rb'
|
||||
- 'lib/gitlab/gpg.rb'
|
||||
- 'lib/gitlab/import_export/after_export_strategies/base_after_export_strategy.rb'
|
||||
- 'lib/gitlab/import_export/recursive_merge_folders.rb'
|
||||
- 'lib/gitlab/memory/upload_and_cleanup_reports.rb'
|
||||
- 'lib/tasks/gitlab/update_templates.rake'
|
||||
- 'lib/tasks/tanuki_emoji.rake'
|
||||
|
|
@ -87,7 +87,7 @@ export default {
|
|||
},
|
||||
dropzoneAllowList: ['.csv'],
|
||||
docsLink: helpPagePath('user/project/import/_index', {
|
||||
anchor: 'reassign-contributions-and-memberships',
|
||||
anchor: 'request-reassignment-by-using-a-csv-file',
|
||||
}),
|
||||
i18n: {
|
||||
description: s__(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import { GlAlert, GlLink, GlModal, GlSprintf } from '@gitlab/ui';
|
||||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
import { __, s__, sprintf } from '~/locale';
|
||||
import autopopulateAllowlistMutation from '../graphql/mutations/autopopulate_allowlist.mutation.graphql';
|
||||
|
||||
|
|
@ -116,6 +117,9 @@ export default {
|
|||
this.$emit('hide');
|
||||
},
|
||||
},
|
||||
compactionAlgorithmHelpPage: helpPagePath('ci/jobs/ci_job_token', {
|
||||
anchor: 'auto-populate-a-projects-allowlist',
|
||||
}),
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
@ -129,6 +133,7 @@ export default {
|
|||
@primary.prevent="autopopulateAllowlist"
|
||||
@secondary="hideModal"
|
||||
@canceled="hideModal"
|
||||
@hidden="hideModal"
|
||||
>
|
||||
<gl-alert v-if="errorMessage" variant="danger" class="gl-mb-3" :dismissible="false">
|
||||
{{ errorMessage }}
|
||||
|
|
@ -138,8 +143,6 @@ export default {
|
|||
{{ authLogExceedsLimitMessage }}
|
||||
</gl-alert>
|
||||
<p data-testid="modal-description">
|
||||
<!-- TODO: Update documentation link -->
|
||||
<!-- See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181294 -->
|
||||
<gl-sprintf
|
||||
:message="
|
||||
s__(
|
||||
|
|
@ -148,7 +151,9 @@ export default {
|
|||
"
|
||||
>
|
||||
<template #link="{ content }">
|
||||
<gl-link href="/" target="_blank">{{ content }}</gl-link>
|
||||
<gl-link :href="$options.compactionAlgorithmHelpPage" target="_blank">{{
|
||||
content
|
||||
}}</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {
|
|||
GlAlert,
|
||||
GlButton,
|
||||
GlCollapsibleListbox,
|
||||
GlDisclosureDropdown,
|
||||
GlIcon,
|
||||
GlLink,
|
||||
GlLoadingIcon,
|
||||
|
|
@ -24,13 +25,16 @@ import inboundGetCIJobTokenScopeQuery from '../graphql/queries/inbound_get_ci_jo
|
|||
import inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery from '../graphql/queries/inbound_get_groups_and_projects_with_ci_job_token_scope.query.graphql';
|
||||
import getCiJobTokenScopeAllowlistQuery from '../graphql/queries/get_ci_job_token_scope_allowlist.query.graphql';
|
||||
import getAuthLogCountQuery from '../graphql/queries/get_auth_log_count.query.graphql';
|
||||
import removeAutopopulatedEntriesMutation from '../graphql/mutations/remove_autopopulated_entries.mutation.graphql';
|
||||
import {
|
||||
JOB_TOKEN_FORM_ADD_GROUP_OR_PROJECT,
|
||||
JOB_TOKEN_FORM_AUTOPOPULATE_AUTH_LOG,
|
||||
JOB_TOKEN_REMOVE_AUTOPOPULATED_ENTRIES_MODAL,
|
||||
} from '../constants';
|
||||
import TokenAccessTable from './token_access_table.vue';
|
||||
import NamespaceForm from './namespace_form.vue';
|
||||
import AutopopulateAllowlistModal from './autopopulate_allowlist_modal.vue';
|
||||
import RemoveAutopopulatedEntriesModal from './remove_autopopulated_entries_modal.vue';
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
|
|
@ -55,6 +59,7 @@ export default {
|
|||
'CICD|Are you sure you want to remove %{namespace} from the job token allowlist?',
|
||||
),
|
||||
removeNamespaceModalActionText: s__('CICD|Remove group or project'),
|
||||
removeAutopopulatedEntries: s__('CICD|Remove all auto-added allowlist entries'),
|
||||
},
|
||||
inboundJobTokenScopeOptions: [
|
||||
{
|
||||
|
|
@ -81,11 +86,13 @@ export default {
|
|||
GlAlert,
|
||||
GlButton,
|
||||
GlCollapsibleListbox,
|
||||
GlDisclosureDropdown,
|
||||
GlIcon,
|
||||
GlLink,
|
||||
GlLoadingIcon,
|
||||
GlSprintf,
|
||||
CrudComponent,
|
||||
RemoveAutopopulatedEntriesModal,
|
||||
TokenAccessTable,
|
||||
GlFormRadioGroup,
|
||||
NamespaceForm,
|
||||
|
|
@ -170,9 +177,11 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
authLogCount: 0,
|
||||
allowlistLoadingMessage: '',
|
||||
inboundJobTokenScopeEnabled: null,
|
||||
isUpdating: false,
|
||||
isUpdatingJobTokenScope: false,
|
||||
groupsAndProjectsWithAccess: { groups: [], projects: [] },
|
||||
autopopulationErrorMessage: null,
|
||||
projectName: '',
|
||||
namespaceToEdit: null,
|
||||
namespaceToRemove: null,
|
||||
|
|
@ -183,6 +192,12 @@ export default {
|
|||
authLogExceedsLimit() {
|
||||
return this.projectCount + this.groupCount + this.authLogCount > this.projectAllowlistLimit;
|
||||
},
|
||||
isAllowlistLoading() {
|
||||
return (
|
||||
this.$apollo.queries.groupsAndProjectsWithAccess.loading ||
|
||||
this.allowlistLoadingMessage.length > 0
|
||||
);
|
||||
},
|
||||
isJobTokenPoliciesEnabled() {
|
||||
return this.glFeatures.addPoliciesToCiJobToken;
|
||||
},
|
||||
|
|
@ -198,6 +213,17 @@ export default {
|
|||
canAutopopulateAuthLog() {
|
||||
return this.glFeatures.authenticationLogsMigrationForAllowlist;
|
||||
},
|
||||
disclosureDropdownOptions() {
|
||||
return [
|
||||
{
|
||||
text: this.$options.i18n.removeAutopopulatedEntries,
|
||||
variant: 'danger',
|
||||
action: () => {
|
||||
this.selectedAction = JOB_TOKEN_REMOVE_AUTOPOPULATED_ENTRIES_MODAL;
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
groupCount() {
|
||||
return this.groupsAndProjectsWithAccess.groups.length;
|
||||
},
|
||||
|
|
@ -210,14 +236,14 @@ export default {
|
|||
projectCountTooltip() {
|
||||
return n__('%d project has access', '%d projects have access', this.projectCount);
|
||||
},
|
||||
isAllowlistLoading() {
|
||||
return this.$apollo.queries.groupsAndProjectsWithAccess.loading;
|
||||
},
|
||||
removeNamespaceModalTitle() {
|
||||
return sprintf(this.$options.i18n.removeNamespaceModalTitle, {
|
||||
namespace: this.namespaceToRemove?.fullPath,
|
||||
});
|
||||
},
|
||||
showRemoveAutopopulatedEntriesModal() {
|
||||
return this.selectedAction === JOB_TOKEN_REMOVE_AUTOPOPULATED_ENTRIES_MODAL;
|
||||
},
|
||||
showAutopopulateModal() {
|
||||
return this.selectedAction === JOB_TOKEN_FORM_AUTOPOPULATE_AUTH_LOG;
|
||||
},
|
||||
|
|
@ -238,7 +264,7 @@ export default {
|
|||
}));
|
||||
},
|
||||
async updateCIJobTokenScope() {
|
||||
this.isUpdating = true;
|
||||
this.isUpdatingJobTokenScope = true;
|
||||
|
||||
try {
|
||||
const {
|
||||
|
|
@ -268,7 +294,7 @@ export default {
|
|||
this.inboundJobTokenScopeEnabled = !this.inboundJobTokenScopeEnabled;
|
||||
createAlert({ message: error.message });
|
||||
} finally {
|
||||
this.isUpdating = false;
|
||||
this.isUpdatingJobTokenScope = false;
|
||||
}
|
||||
},
|
||||
async removeItem() {
|
||||
|
|
@ -291,6 +317,42 @@ export default {
|
|||
this.refetchGroupsAndProjects();
|
||||
return Promise.resolve();
|
||||
},
|
||||
async removeAutopopulatedEntries() {
|
||||
this.hideSelectedAction();
|
||||
this.autopopulationErrorMessage = null;
|
||||
this.allowlistLoadingMessage = s__(
|
||||
'CICD|Removing auto-added allowlist entries. Please wait while the action completes.',
|
||||
);
|
||||
|
||||
try {
|
||||
const {
|
||||
data: {
|
||||
ciJobTokenScopeClearAllowlistAutopopulations: { errors },
|
||||
},
|
||||
} = await this.$apollo.mutate({
|
||||
mutation: removeAutopopulatedEntriesMutation,
|
||||
variables: {
|
||||
projectPath: this.fullPath,
|
||||
},
|
||||
});
|
||||
|
||||
if (errors.length) {
|
||||
this.autopopulationErrorMessage = errors[0].message;
|
||||
return;
|
||||
}
|
||||
|
||||
this.refetchAllowlist();
|
||||
this.$toast.show(
|
||||
s__('CICD|Authentication log entries were successfully removed from the allowlist.'),
|
||||
);
|
||||
} catch (error) {
|
||||
this.autopopulationErrorMessage = s__(
|
||||
'CICD|An error occurred while removing the auto-added log entries. Please try again.',
|
||||
);
|
||||
} finally {
|
||||
this.allowlistLoadingMessage = '';
|
||||
}
|
||||
},
|
||||
refetchAllowlist() {
|
||||
this.$apollo.queries.groupsAndProjectsWithAccess.refetch();
|
||||
this.hideSelectedAction();
|
||||
|
|
@ -328,117 +390,133 @@ export default {
|
|||
@hide="hideSelectedAction"
|
||||
@refetch-allowlist="refetchAllowlist"
|
||||
/>
|
||||
<gl-loading-icon v-if="$apollo.queries.inboundJobTokenScopeEnabled.loading" size="md" />
|
||||
<template v-else>
|
||||
<div class="gl-font-bold">
|
||||
{{ $options.i18n.radioGroupTitle }}
|
||||
</div>
|
||||
<div class="gl-mb-3">
|
||||
<gl-sprintf :message="$options.i18n.radioGroupDescription">
|
||||
<template #link="{ content }">
|
||||
<gl-link :href="ciJobTokenHelpPage" class="inline-link" target="_blank">{{
|
||||
content
|
||||
}}</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</div>
|
||||
<gl-form-radio-group
|
||||
v-if="!enforceAllowlist"
|
||||
v-model="inboundJobTokenScopeEnabled"
|
||||
:options="$options.inboundJobTokenScopeOptions"
|
||||
stacked
|
||||
/>
|
||||
<gl-alert
|
||||
v-if="!inboundJobTokenScopeEnabled && !enforceAllowlist"
|
||||
variant="warning"
|
||||
class="gl-my-3"
|
||||
:dismissible="false"
|
||||
:show-icon="false"
|
||||
>
|
||||
{{ $options.i18n.settingDisabledMessage }}
|
||||
</gl-alert>
|
||||
|
||||
<gl-button
|
||||
v-if="!enforceAllowlist"
|
||||
variant="confirm"
|
||||
class="gl-mt-3"
|
||||
data-testid="save-ci-job-token-scope-changes-btn"
|
||||
:loading="isUpdating"
|
||||
@click="updateCIJobTokenScope"
|
||||
>
|
||||
{{ $options.i18n.saveButtonTitle }}
|
||||
</gl-button>
|
||||
|
||||
<crud-component
|
||||
:title="$options.i18n.cardHeaderTitle"
|
||||
:description="$options.i18n.cardHeaderDescription"
|
||||
:toggle-text="!canAutopopulateAuthLog ? $options.i18n.addGroupOrProject : undefined"
|
||||
class="gl-mt-5"
|
||||
@hideForm="hideSelectedAction"
|
||||
>
|
||||
<template v-if="canAutopopulateAuthLog" #actions="{ showForm }">
|
||||
<gl-collapsible-listbox
|
||||
v-model="selectedAction"
|
||||
:items="$options.crudFormActions"
|
||||
:toggle-text="$options.i18n.add"
|
||||
data-testid="form-selector"
|
||||
size="small"
|
||||
@select="selectAction($event, showForm)"
|
||||
/>
|
||||
<remove-autopopulated-entries-modal
|
||||
:show-modal="showRemoveAutopopulatedEntriesModal"
|
||||
@hide="hideSelectedAction"
|
||||
@remove-entries="removeAutopopulatedEntries"
|
||||
/>
|
||||
<div class="gl-font-bold">
|
||||
{{ $options.i18n.radioGroupTitle }}
|
||||
</div>
|
||||
<div class="gl-mb-3">
|
||||
<gl-sprintf :message="$options.i18n.radioGroupDescription">
|
||||
<template #link="{ content }">
|
||||
<gl-link :href="ciJobTokenHelpPage" class="inline-link" target="_blank">{{
|
||||
content
|
||||
}}</gl-link>
|
||||
</template>
|
||||
<template #count>
|
||||
<gl-loading-icon v-if="isAllowlistLoading" data-testid="count-loading-icon" />
|
||||
<template v-else>
|
||||
<span
|
||||
v-gl-tooltip.d0="groupCountTooltip"
|
||||
class="gl-cursor-default"
|
||||
data-testid="group-count"
|
||||
>
|
||||
<gl-icon name="group" /> {{ groupCount }}
|
||||
</span>
|
||||
<span
|
||||
v-gl-tooltip.d0="projectCountTooltip"
|
||||
class="gl-ml-2 gl-cursor-default"
|
||||
data-testid="project-count"
|
||||
>
|
||||
<gl-icon name="project" /> {{ projectCount }}
|
||||
</span>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<template #form="{ hideForm }">
|
||||
<namespace-form
|
||||
:namespace="namespaceToEdit"
|
||||
@saved="refetchGroupsAndProjects"
|
||||
@close="hideForm"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #default="{ showForm }">
|
||||
<token-access-table
|
||||
:items="allowlist"
|
||||
:loading="isAllowlistLoading"
|
||||
:show-policies="isJobTokenPoliciesEnabled"
|
||||
@editItem="showNamespaceForm($event, showForm)"
|
||||
@removeItem="namespaceToRemove = $event"
|
||||
/>
|
||||
|
||||
<confirm-action-modal
|
||||
v-if="namespaceToRemove"
|
||||
modal-id="inbound-token-access-remove-confirm-modal"
|
||||
:title="removeNamespaceModalTitle"
|
||||
:action-fn="removeItem"
|
||||
:action-text="$options.i18n.removeNamespaceModalActionText"
|
||||
@close="namespaceToRemove = null"
|
||||
</gl-sprintf>
|
||||
</div>
|
||||
<gl-form-radio-group
|
||||
v-if="!enforceAllowlist"
|
||||
v-model="inboundJobTokenScopeEnabled"
|
||||
:options="$options.inboundJobTokenScopeOptions"
|
||||
stacked
|
||||
/>
|
||||
<gl-alert
|
||||
v-if="!inboundJobTokenScopeEnabled && !enforceAllowlist"
|
||||
variant="warning"
|
||||
class="gl-my-3"
|
||||
:dismissible="false"
|
||||
:show-icon="false"
|
||||
>
|
||||
{{ $options.i18n.settingDisabledMessage }}
|
||||
</gl-alert>
|
||||
<gl-button
|
||||
v-if="!enforceAllowlist"
|
||||
variant="confirm"
|
||||
class="gl-mt-3"
|
||||
data-testid="save-ci-job-token-scope-changes-btn"
|
||||
:loading="isUpdatingJobTokenScope"
|
||||
@click="updateCIJobTokenScope"
|
||||
>
|
||||
{{ $options.i18n.saveButtonTitle }}
|
||||
</gl-button>
|
||||
<gl-alert
|
||||
v-if="autopopulationErrorMessage"
|
||||
variant="danger"
|
||||
class="gl-my-5"
|
||||
:dismissible="false"
|
||||
data-testid="autopopulation-alert"
|
||||
>
|
||||
{{ autopopulationErrorMessage }}
|
||||
</gl-alert>
|
||||
<crud-component
|
||||
:title="$options.i18n.cardHeaderTitle"
|
||||
:description="$options.i18n.cardHeaderDescription"
|
||||
:toggle-text="!canAutopopulateAuthLog ? $options.i18n.addGroupOrProject : undefined"
|
||||
class="gl-mt-5"
|
||||
@hideForm="hideSelectedAction"
|
||||
>
|
||||
<template v-if="canAutopopulateAuthLog" #actions="{ showForm }">
|
||||
<gl-collapsible-listbox
|
||||
v-model="selectedAction"
|
||||
:items="$options.crudFormActions"
|
||||
:toggle-text="$options.i18n.add"
|
||||
data-testid="form-selector"
|
||||
size="small"
|
||||
@select="selectAction($event, showForm)"
|
||||
/>
|
||||
<gl-disclosure-dropdown
|
||||
category="tertiary"
|
||||
icon="ellipsis_v"
|
||||
no-caret
|
||||
:items="disclosureDropdownOptions"
|
||||
/>
|
||||
</template>
|
||||
<template #count>
|
||||
<gl-loading-icon v-if="isAllowlistLoading" data-testid="count-loading-icon" />
|
||||
<template v-else>
|
||||
<span
|
||||
v-gl-tooltip.d0="groupCountTooltip"
|
||||
class="gl-cursor-default"
|
||||
data-testid="group-count"
|
||||
>
|
||||
<gl-sprintf :message="$options.i18n.removeNamespaceModalText">
|
||||
<template #namespace>
|
||||
<code>{{ namespaceToRemove.fullPath }}</code>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</confirm-action-modal>
|
||||
<gl-icon name="group" /> {{ groupCount }}
|
||||
</span>
|
||||
<span
|
||||
v-gl-tooltip.d0="projectCountTooltip"
|
||||
class="gl-ml-2 gl-cursor-default"
|
||||
data-testid="project-count"
|
||||
>
|
||||
<gl-icon name="project" /> {{ projectCount }}
|
||||
</span>
|
||||
</template>
|
||||
</crud-component>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<template #form="{ hideForm }">
|
||||
<namespace-form
|
||||
:namespace="namespaceToEdit"
|
||||
@saved="refetchGroupsAndProjects"
|
||||
@close="hideForm"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #default="{ showForm }">
|
||||
<token-access-table
|
||||
:items="allowlist"
|
||||
:loading="isAllowlistLoading"
|
||||
:loading-message="allowlistLoadingMessage"
|
||||
:show-policies="isJobTokenPoliciesEnabled"
|
||||
@editItem="showNamespaceForm($event, showForm)"
|
||||
@removeItem="namespaceToRemove = $event"
|
||||
/>
|
||||
|
||||
<confirm-action-modal
|
||||
v-if="namespaceToRemove"
|
||||
modal-id="inbound-token-access-remove-confirm-modal"
|
||||
:title="removeNamespaceModalTitle"
|
||||
:action-fn="removeItem"
|
||||
:action-text="$options.i18n.removeNamespaceModalActionText"
|
||||
@close="namespaceToRemove = null"
|
||||
>
|
||||
<gl-sprintf :message="$options.i18n.removeNamespaceModalText">
|
||||
<template #namespace>
|
||||
<code>{{ namespaceToRemove.fullPath }}</code>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</confirm-action-modal>
|
||||
</template>
|
||||
</crud-component>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
<script>
|
||||
import { GlModal } from '@gitlab/ui';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
export default {
|
||||
name: 'RemoveAutopopulatedEntriesModal',
|
||||
components: {
|
||||
GlModal,
|
||||
},
|
||||
inject: ['fullPath'],
|
||||
props: {
|
||||
showModal: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
apollo: {},
|
||||
computed: {
|
||||
modalOptions() {
|
||||
return {
|
||||
actionPrimary: {
|
||||
text: __('Remove entries'),
|
||||
attributes: {
|
||||
variant: 'danger',
|
||||
},
|
||||
},
|
||||
actionSecondary: {
|
||||
text: __('Cancel'),
|
||||
attributes: {
|
||||
variant: 'default',
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hideModal() {
|
||||
this.$emit('hide');
|
||||
},
|
||||
removeEntries() {
|
||||
this.$emit('remove-entries');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-modal
|
||||
modal-id="remove-autopopulated-allowlist-entries-modal"
|
||||
:visible="showModal"
|
||||
:title="s__('CICD|Remove all auto-added allowlist entries')"
|
||||
:action-primary="modalOptions.actionPrimary"
|
||||
:action-secondary="modalOptions.actionSecondary"
|
||||
@primary.prevent="removeEntries"
|
||||
@secondary="hideModal"
|
||||
@canceled="hideModal"
|
||||
@hidden="hideModal"
|
||||
>
|
||||
<p>
|
||||
{{
|
||||
s__(
|
||||
'CICD|This action removes all groups and projects that were auto-added from the authentication log.',
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<p>
|
||||
{{
|
||||
s__('CICD|Removing these entries could cause authentication failures or disrupt pipelines.')
|
||||
}}
|
||||
</p>
|
||||
</gl-modal>
|
||||
</template>
|
||||
|
|
@ -40,6 +40,11 @@ export default {
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
loadingMessage: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
// This can be removed after outbound_token_access.vue is removed, which is a deprecated feature. We need to hide
|
||||
// policies for that component, but show them on inbound_token_access.vue.
|
||||
showPolicies: {
|
||||
|
|
@ -95,6 +100,13 @@ export default {
|
|||
<gl-table :items="items" :fields="fields" :busy="loading" class="gl-mb-0" stacked="md">
|
||||
<template #table-busy>
|
||||
<gl-loading-icon size="md" />
|
||||
<p
|
||||
v-if="loadingMessage.length > 0"
|
||||
class="gl-mt-5 gl-text-center"
|
||||
data-testid="loading-message"
|
||||
>
|
||||
{{ loadingMessage }}
|
||||
</p>
|
||||
</template>
|
||||
<template #cell(fullPath)="{ item }">
|
||||
<div class="gl-inline-flex gl-items-center">
|
||||
|
|
|
|||
|
|
@ -148,3 +148,5 @@ export const JOB_TOKEN_POLICIES = keyBy(
|
|||
|
||||
export const JOB_TOKEN_FORM_ADD_GROUP_OR_PROJECT = 'JOB_TOKEN_FORM_ADD_GROUP_OR_PROJECT';
|
||||
export const JOB_TOKEN_FORM_AUTOPOPULATE_AUTH_LOG = 'JOB_TOKEN_FORM_AUTOPOPULATE_AUTH_LOG';
|
||||
export const JOB_TOKEN_REMOVE_AUTOPOPULATED_ENTRIES_MODAL =
|
||||
'JOB_TOKEN_REMOVE_AUTOPOPULATED_ENTRIES_MODAL';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
mutation CiJobTokenScopeClearAllowlistAutopopulations($projectPath: ID!) {
|
||||
ciJobTokenScopeClearAllowlistAutopopulations(input: { projectPath: $projectPath }) {
|
||||
status
|
||||
errors
|
||||
}
|
||||
}
|
||||
|
|
@ -65,6 +65,7 @@ query getWorkItems(
|
|||
webPath
|
||||
}
|
||||
closedAt
|
||||
userDiscussionsCount
|
||||
confidential
|
||||
createdAt
|
||||
iid
|
||||
|
|
@ -126,6 +127,7 @@ query getWorkItems(
|
|||
webPath
|
||||
}
|
||||
closedAt
|
||||
userDiscussionsCount
|
||||
confidential
|
||||
createdAt
|
||||
iid
|
||||
|
|
|
|||
|
|
@ -386,3 +386,7 @@ input[type='search'] {
|
|||
}
|
||||
}
|
||||
/* stylelint-enable property-no-vendor-prefix */
|
||||
|
||||
.description.term p:last-child {
|
||||
@apply gl-m-0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@
|
|||
- position = index + 1
|
||||
|
||||
.search-result-row
|
||||
= link_to project_milestone_path(milestone.project, milestone), class: 'gl-font-bold gl-text-default', data: { event_tracking: 'click_search_result', event_label: @scope, event_value: position } do
|
||||
= link_to project_milestone_path(milestone.project, milestone), class: 'gl-font-bold', data: { event_tracking: 'click_search_result', event_label: @scope, event_value: position } do
|
||||
%span.term.str-truncated= simple_search_highlight_and_truncate(milestone.title, @search_term)
|
||||
|
||||
- if milestone.project_milestone?
|
||||
.gl-mt-2= gl_badge_tag milestone.project.full_name, { variant: :muted }, { class: 'gl-whitespace-normal gl-text-left' }
|
||||
.gl-mt-2.gl-text-subtle.gl-whitespace-normal.gl-text-left.gl-text-sm= milestone.project.full_name
|
||||
|
||||
- if milestone.description.present?
|
||||
.description.term
|
||||
|
|
|
|||
|
|
@ -6,4 +6,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/513796
|
|||
milestone: '17.9'
|
||||
group: group::package registry
|
||||
type: beta
|
||||
default_enabled: false
|
||||
default_enabled: true
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: use_typhoeus_elasticsearch_adapter
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/76879
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/348607
|
||||
milestone: '14.7'
|
||||
type: development
|
||||
group: group::global search
|
||||
default_enabled: false
|
||||
|
|
@ -115,7 +115,7 @@ This section is for links to information elsewhere in the GitLab documentation.
|
|||
- Including [troubleshooting](../postgresql/replication_and_failover_troubleshooting.md)
|
||||
`gitlab-ctl patroni check-leader` and PgBouncer errors.
|
||||
|
||||
- [Developer database documentation](../../development/feature_development.md#database-guides),
|
||||
- [Developer database documentation](../../development/database/_index.md),
|
||||
some of which is absolutely not for production use. Including:
|
||||
- Understanding EXPLAIN plans.
|
||||
|
||||
|
|
|
|||
|
|
@ -944,3 +944,60 @@ The opposite configuration (`docker:24.0.5-dind` service and Docker Engine on th
|
|||
19.06.x or older) works without problems. For the best strategy, you should to frequently test and update
|
||||
job environment versions to the newest. This brings new features, improved security and - for this specific
|
||||
case - makes the upgrade on the underlying Docker Engine on the runner's host transparent for the job.
|
||||
|
||||
### Error: `failed to verify certificate: x509: certificate signed by unknown authority`
|
||||
|
||||
This error can appear when Docker commands like `docker build` or `docker pull` are executed in a Docker-in-Docker
|
||||
environment where custom or private certificates are used (for example, Zscaler certificates):
|
||||
|
||||
```plaintext
|
||||
error pulling image configuration: download failed after attempts=6: tls: failed to verify certificate: x509: certificate signed by unknown authority
|
||||
```
|
||||
|
||||
This error occurs because Docker commands in a Docker-in-Docker environment
|
||||
use two separate containers:
|
||||
|
||||
- The **build container** runs the Docker client (`/usr/bin/docker`) and executes your job's script commands.
|
||||
- The **service container** (often named `svc`) runs the Docker daemon that processes most Docker commands.
|
||||
|
||||
When your organization uses custom certificates, both containers need these certificates.
|
||||
Without proper certificate configuration in both containers, Docker operations that connect to external
|
||||
registries or services will fail with certificate errors.
|
||||
|
||||
To resolve this issue:
|
||||
|
||||
1. Store your root certificate as a [CI/CD variable](../variables/_index.md#define-a-cicd-variable-in-the-ui) named `CA_CERTIFICATE`.
|
||||
The certificate should be in this format:
|
||||
|
||||
```plaintext
|
||||
-----BEGIN CERTIFICATE-----
|
||||
(certificate content)
|
||||
-----END CERTIFICATE-----
|
||||
```
|
||||
|
||||
1. Configure your pipeline to install the certificate in the service container before starting the Docker daemon. For example:
|
||||
|
||||
```yaml
|
||||
image_build:
|
||||
stage: build
|
||||
image:
|
||||
name: docker:19.03
|
||||
variables:
|
||||
DOCKER_HOST: tcp://localhost:2375
|
||||
DOCKER_TLS_CERTDIR: ""
|
||||
CA_CERTIFICATE: "$CA_CERTIFICATE"
|
||||
services:
|
||||
- name: docker:19.03-dind
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
echo "$CA_CERTIFICATE" > /usr/local/share/ca-certificates/custom-ca.crt && \
|
||||
update-ca-certificates && \
|
||||
dockerd-entrypoint.sh || exit
|
||||
script:
|
||||
- docker info
|
||||
- docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD $DOCKER_REGISTRY
|
||||
- docker build -t "${DOCKER_REGISTRY}/my-app:${CI_COMMIT_REF_NAME}" .
|
||||
- docker push "${DOCKER_REGISTRY}/my-app:${CI_COMMIT_REF_NAME}"
|
||||
```
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/ee/development/development_processes.html#development-guidelines-review.
|
||||
---
|
||||
|
||||
|
||||
# Data Retention Guidelines for Feature Development
|
||||
|
||||
## Overview
|
||||
|
||||
Data retention is a critical aspect of feature development at GitLab. As we build and maintain features, we must consider the lifecycle of the data we collect and store. This document outlines the guidelines for incorporating data retention considerations into feature development from the outset.
|
||||
|
||||
## Why data retention matters
|
||||
|
||||
- **System performance**: Time-based data organization enables better query optimization and efficient data access patterns, leading to faster response times and improved system scalability.
|
||||
- **Infrastructure cost**: Strategic storage management through data lifecycle policies reduces infrastructure costs for primary storage, backups, and disaster recovery systems.
|
||||
- **Engineering efficiency**: Designing features with data retention in mind from the start makes development faster and more reliable by establishing clear data lifecycles, reducing technical debt and faster data migrations.
|
||||
|
||||
## Guidelines for feature development
|
||||
|
||||
### 1. Early planning
|
||||
|
||||
When designing new features, consider data retention requirements during the initial planning phase:
|
||||
|
||||
- Document the types of data being persisted. Is this user-facing data?
|
||||
Is it generated internally to make processing more efficient?
|
||||
Is it derived/cache data?
|
||||
- Identify the business purpose and required retention period for each data type.
|
||||
- Define the product justification and customer usage pattern of older data.
|
||||
How do people interact with older data as opposed to newer data?
|
||||
How does the value change over time?
|
||||
- Consider regulatory requirements that might affect data retention (such as Personally Identifiable Information).
|
||||
- Plan for data removal or archival mechanisms.
|
||||
|
||||
### 2. Design for data lifecycle
|
||||
|
||||
Features should be designed with the understanding that data is not permanent:
|
||||
|
||||
- Avoid assumptions about infinite data availability.
|
||||
- Implement graceful handling of missing or archived data.
|
||||
- Design user interfaces to clearly communicate data availability periods.
|
||||
- Design data structures for longer-term storage that is optimized to be viewed in a longer-term context.
|
||||
- Consider implementing "time to live" (TTL) mechanisms where appropriate, especially for derived/cache data
|
||||
that can be gracefully reproduced on-demand.
|
||||
|
||||
### 3. Documentation recommendations
|
||||
|
||||
Each feature implementation must include:
|
||||
|
||||
- Clear documentation of data retention periods (on GitLab.com and default values, if any)
|
||||
and business reasoning/justification
|
||||
- Description of data removal/archival mechanisms.
|
||||
- Impact analysis of data removal on dependent features.
|
||||
|
||||
## Implementation checklist
|
||||
|
||||
Before submitting a merge request for a new feature:
|
||||
|
||||
- [ ] Document data retention requirements.
|
||||
- [ ] Design data models with data removal in mind.
|
||||
- [ ] Implement data removal/archival mechanisms.
|
||||
- [ ] Test feature behavior with missing/archived data.
|
||||
- [ ] Include retention periods in user documentation.
|
||||
- [ ] Consider impact on dependent features.
|
||||
- [ ] Consider impact on backups/restores and export/import.
|
||||
- [ ] Consider impact on replication (eg Geo).
|
||||
|
||||
## Related links
|
||||
|
||||
- [Large tables limitations](../development/database/large_tables_limitations.md)
|
||||
|
|
@ -122,3 +122,4 @@ Disadvantages
|
|||
- [Database size limits](https://handbook.gitlab.com/handbook/engineering/architecture/design-documents/database_size_limits/#solutions)
|
||||
- [Adding database indexes](adding_database_indexes.md)
|
||||
- [Database layout and access patterns](layout_and_access_patterns.md#data-model-trade-offs)
|
||||
- [Data retention guidelines for feature development](../data_retention_policies.md)
|
||||
|
|
|
|||
|
|
@ -137,9 +137,11 @@ The following integration guides are internal. Some integrations require access
|
|||
- [JSON guidelines](json.md) for how to handle JSON in a performant manner.
|
||||
- [GraphQL API optimizations](api_graphql_styleguide.md#optimizations) for how to optimize GraphQL code.
|
||||
|
||||
## Database guides
|
||||
## Data stores guides
|
||||
|
||||
See [database guidelines](database/_index.md).
|
||||
- [Database guidelines](database/_index.md).
|
||||
- [Data retention policies](data_retention_policies.md)
|
||||
- [Gitaly guidelines](gitaly.md)
|
||||
|
||||
## Testing guides
|
||||
|
||||
|
|
|
|||
|
|
@ -65,6 +65,21 @@ To enable [exact code search](../../user/search/exact_code_search.md) in GitLab:
|
|||
1. Select the **Enable indexing for exact code search** and **Enable exact code search** checkboxes.
|
||||
1. Select **Save changes**.
|
||||
|
||||
## Check indexing status
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must have administrator access to the instance.
|
||||
|
||||
Indexing performance depends on the CPU and memory limits on the Zoekt indexer nodes.
|
||||
To check indexing status, in the Rails console, run the following command:
|
||||
|
||||
```ruby
|
||||
Search::Zoekt::Index.group(:state).count
|
||||
Search::Zoekt::Repository.group(:state).count
|
||||
Search::Zoekt::Task.group(:state).count
|
||||
```
|
||||
|
||||
## Delete offline nodes automatically
|
||||
|
||||
Prerequisites:
|
||||
|
|
@ -97,6 +112,13 @@ To index all root namespaces automatically:
|
|||
1. Select the **Index root namespaces automatically** checkbox.
|
||||
1. Select **Save changes**.
|
||||
|
||||
When you enable this setting, GitLab creates indexing tasks for all projects in:
|
||||
|
||||
- All groups and subgroups
|
||||
- Any new root namespace
|
||||
|
||||
After a project is indexed, GitLab creates only incremental indexing when a repository change is detected.
|
||||
|
||||
When you disable this setting:
|
||||
|
||||
- Existing root namespaces remain indexed.
|
||||
|
|
|
|||
|
|
@ -35,10 +35,12 @@ If there are any problems, you can:
|
|||
|
||||
Before migrating by using direct transfer, see the following prerequisites.
|
||||
|
||||
### Network
|
||||
### Network and storage space
|
||||
|
||||
- The network connection between instances or GitLab.com must support HTTPS.
|
||||
- Firewalls must not block the connection between the source and destination GitLab instances.
|
||||
- The source and destination GitLab instances must have enough free space in the `/tmp` directory
|
||||
to create and extract archives of transferred projects and groups.
|
||||
|
||||
### Versions
|
||||
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ To disable diff previews for all projects in a group:
|
|||
|
||||
{{< history >}}
|
||||
|
||||
- Notifications to inherited group members [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/463016) in GitLab 17.7 [with a flag](../../administration/feature_flags.md) named `pat_expiry_inherited_members_notification`. Disabled by default.
|
||||
- Notifications to inherited group members [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/463016) in GitLab 17.7 [with a flag](../../administration/feature_flags.md) named `extended_expiry_webhook_execution_setting`. Disabled by default.
|
||||
|
||||
{{< /history >}}
|
||||
|
||||
|
|
|
|||
|
|
@ -351,7 +351,7 @@ are then redirected to sign in through the identity provider.
|
|||
GitLab.com uses the SAML **NameID** to identify users. The **NameID** is:
|
||||
|
||||
- A required field in the SAML response.
|
||||
- Case sensitive.
|
||||
- Case-insensitive.
|
||||
|
||||
The **NameID** must:
|
||||
|
||||
|
|
|
|||
|
|
@ -274,10 +274,13 @@ Users with the Owner role for a top-level group can reassign contributions and m
|
|||
from placeholder users to existing active (non-bot) users.
|
||||
On the destination instance, users with the Owner role for a top-level group can:
|
||||
|
||||
- Request users to accept reassignment of contributions and membership [in the UI](#request-reassignment-in-ui).
|
||||
The reassignment process starts only after the selected user [accepts the reassignment request](#accept-contribution-reassignment),
|
||||
which is sent to them by email.
|
||||
- Choose not to reassign contributions and memberships, and [keep them with placeholder users](#keep-as-placeholder).
|
||||
- Request users to review reassignment of contributions and memberships [in the UI](#request-reassignment-in-ui)
|
||||
or [through a CSV file](#request-reassignment-by-using-a-csv-file).
|
||||
For a large number of placeholder users, you should use a CSV file.
|
||||
In both cases, users receive a request by email to accept or reject the reassignment.
|
||||
The reassignment starts only after the selected user
|
||||
[accepts the reassignment request](#accept-contribution-reassignment).
|
||||
- Choose not to reassign contributions and memberships and [keep them assigned to placeholder users](#keep-as-placeholder).
|
||||
|
||||
All the contributions initially assigned to a single placeholder user can only be reassigned to a single active regular
|
||||
user on the destination instance. The contributions assigned to a single placeholder user cannot be split among multiple
|
||||
|
|
@ -353,6 +356,56 @@ Contributions of only one placeholder user can be reassigned to an active non-bo
|
|||
|
||||
Before a user accepts the reassignment, you can [cancel the request](#cancel-reassignment-request).
|
||||
|
||||
#### Request reassignment by using a CSV file
|
||||
|
||||
{{< history >}}
|
||||
|
||||
- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/455901) in GitLab 17.10 [with a flag](../../../administration/feature_flags.md) named `importer_user_mapping_reassignment_csv`. Disabled by default.
|
||||
|
||||
{{< /history >}}
|
||||
|
||||
{{< alert type="flag" >}}
|
||||
|
||||
The availability of this feature is controlled by a feature flag.
|
||||
For more information, see the history.
|
||||
This feature is available for testing, but not ready for production use.
|
||||
|
||||
{{< /alert >}}
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must have the Owner role for the group.
|
||||
|
||||
For a large number of placeholder users, you might want to
|
||||
reassign contributions and memberships by using a CSV file.
|
||||
You can download a prefilled CSV template with the following information.
|
||||
For example:
|
||||
|
||||
| Source host | Import type | Source user identifier | Source user name | Source username |
|
||||
|----------------------|-------------|------------------------|------------------|-----------------|
|
||||
| `gitlab.example.com` | `gitlab` | `alice` | `Alice Coder` | `a.coer` |
|
||||
|
||||
Do not update **Source host**, **Import type**, or **Source user identifier**.
|
||||
This information locates the corresponding database record
|
||||
after you've uploaded the completed CSV file.
|
||||
**Source user name** and **Source username** identify the source user
|
||||
and are not used after you've uploaded the CSV file.
|
||||
|
||||
To request reassignment of contributions and memberships by using a CSV file:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your group.
|
||||
1. Select **Manage > Members**.
|
||||
1. Select the **Placeholders** tab.
|
||||
1. Select **Reassign with CSV**.
|
||||
1. Download the prefilled CSV template.
|
||||
1. In **GitLab username** or **GitLab public email**, enter the username or email address
|
||||
of the GitLab user on the destination instance.
|
||||
You can use only public email addresses for reassignment.
|
||||
1. Upload the completed CSV file.
|
||||
1. Select **Reassign**.
|
||||
|
||||
Users receive an email to review and accept any contributions reassigned to them.
|
||||
|
||||
#### Keep as placeholder
|
||||
|
||||
You might not want to reassign contributions and memberships to users on the destination instance. For example, you
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ To set this default:
|
|||
|
||||
{{< history >}}
|
||||
|
||||
- [Added](https://gitlab.com/gitlab-org/gitlab/-/issues/463016) 60 day and 30 days triggers to project and group access tokens webhooks in GitLab 17.9 [with a flag](../../../administration/feature_flags.md) named `pat_expiry_inherited_members_notification`. Disabled by default.
|
||||
- [Added](https://gitlab.com/gitlab-org/gitlab/-/issues/463016) 60 day and 30 days triggers to project and group access tokens webhooks in GitLab 17.9 [with a flag](../../../administration/feature_flags.md) named `extended_expiry_webhook_execution_setting`. Disabled by default.
|
||||
|
||||
{{< /history >}}
|
||||
|
||||
|
|
|
|||
|
|
@ -112,57 +112,64 @@ For more information, see [`image_pull_secrets`](settings.md#image_pull_secrets)
|
|||
|
||||
{{< /history >}}
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Ensure the container images used in the devfile support [arbitrary user IDs](_index.md#arbitrary-user-ids).
|
||||
Sudo access for a workspace does not mean that the container image used
|
||||
in a [devfile](_index.md#devfile) can run with a user ID of `0`.
|
||||
|
||||
A development environment often requires sudo permissions to
|
||||
install, configure, and use dependencies during runtime.
|
||||
You can configure secure sudo access for a workspace with:
|
||||
Development environments often require sudo permissions to install, configure, and use dependencies
|
||||
during runtime. You can configure sudo access for a workspace with:
|
||||
|
||||
- [Sysbox](#with-sysbox)
|
||||
- [Kata Containers](#with-kata-containers)
|
||||
- [User namespaces](#with-user-namespaces)
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Your container images must support [arbitrary user IDs](_index.md#arbitrary-user-ids).
|
||||
Even with sudo access configured, container images used in a [devfile](_index.md#devfile)
|
||||
cannot run with a user ID of `0`.
|
||||
|
||||
### With Sysbox
|
||||
|
||||
[Sysbox](https://github.com/nestybox/sysbox) is a container runtime that improves container isolation
|
||||
and enables containers to run the same workloads as virtual machines.
|
||||
|
||||
To configure sudo access for a workspace with Sysbox:
|
||||
To configure sudo access with Sysbox:
|
||||
|
||||
1. In the Kubernetes cluster, [install Sysbox](https://github.com/nestybox/sysbox#installation).
|
||||
1. In the GitLab agent for workspaces:
|
||||
- Set [`default_runtime_class`](settings.md#default_runtime_class) to the runtime class
|
||||
of Sysbox (for example, `sysbox-runc`).
|
||||
- Set [`allow_privilege_escalation`](settings.md#allow_privilege_escalation) to `true`.
|
||||
- Set [`annotations`](settings.md#annotations) to `{"io.kubernetes.cri-o.userns-mode": "auto:size=65536"}`.
|
||||
1. In your Kubernetes cluster, [install Sysbox](https://github.com/nestybox/sysbox#installation).
|
||||
1. Configure the GitLab agent for workspaces:
|
||||
|
||||
- Set the default runtime class. In [`default_runtime_class`](settings.md#default_runtime_class),
|
||||
enter the runtime class for Sysbox. For example, `sysbox-runc`.
|
||||
- Enable privilege escalation.
|
||||
Set [`allow_privilege_escalation`](settings.md#allow_privilege_escalation) to `true`.
|
||||
- Configure the annotations required by Sysbox. Set [`annotations`](settings.md#annotations) to
|
||||
`{"io.kubernetes.cri-o.userns-mode": "auto:size=65536"}`.
|
||||
|
||||
### With Kata Containers
|
||||
|
||||
[Kata Containers](https://github.com/kata-containers/kata-containers) is a standard implementation of lightweight
|
||||
virtual machines that perform like containers but provide the workload isolation and security of virtual machines.
|
||||
[Kata Containers](https://github.com/kata-containers/kata-containers) is a standard implementation
|
||||
of lightweight virtual machines that perform like containers but provide the workload isolation and
|
||||
security of virtual machines.
|
||||
|
||||
To configure sudo access for a workspace with Kata Containers:
|
||||
To configure sudo access with Kata Containers:
|
||||
|
||||
1. In the Kubernetes cluster, [install Kata Containers](https://github.com/kata-containers/kata-containers/tree/main/docs/install).
|
||||
1. In the GitLab agent for workspaces:
|
||||
- Set [`default_runtime_class`](settings.md#default_runtime_class) to one of the runtime classes
|
||||
of Kata Containers (for example, `kata-qemu`).
|
||||
- Set [`allow_privilege_escalation`](settings.md#allow_privilege_escalation) to `true`.
|
||||
1. In your Kubernetes cluster, [install Kata Containers](https://github.com/kata-containers/kata-containers/tree/main/docs/install).
|
||||
1. Configure the GitLab agent for workspaces:
|
||||
|
||||
- Set the default runtime class. In [`default_runtime_class`](settings.md#default_runtime_class),
|
||||
enter the runtime class for Kata Containers. For example, `kata-qemu`.
|
||||
- Enable privilege escalation.
|
||||
Set [`allow_privilege_escalation`](settings.md#allow_privilege_escalation) to `true`.
|
||||
|
||||
### With user namespaces
|
||||
|
||||
[User namespaces](https://kubernetes.io/docs/concepts/workloads/pods/user-namespaces/) isolate the user
|
||||
running inside the container from the user on the host.
|
||||
[User namespaces](https://kubernetes.io/docs/concepts/workloads/pods/user-namespaces/) isolate
|
||||
container users from host users.
|
||||
|
||||
To configure sudo access for a workspace with user namespaces:
|
||||
To configure sudo access with user namespaces:
|
||||
|
||||
1. In the Kubernetes cluster, [configure user namespaces](https://kubernetes.io/blog/2024/04/22/userns-beta/).
|
||||
1. In the GitLab agent for workspaces, set [`use_kubernetes_user_namespaces`](settings.md#use_kubernetes_user_namespaces)
|
||||
and [`allow_privilege_escalation`](settings.md#allow_privilege_escalation) to `true`.
|
||||
1. In your Kubernetes cluster, [configure user namespaces](https://kubernetes.io/blog/2024/04/22/userns-beta/).
|
||||
1. Configure the GitLab agent for workspaces:
|
||||
|
||||
- Set [`use_kubernetes_user_namespaces`](settings.md#use_kubernetes_user_namespaces) to `true`.
|
||||
- Set [`allow_privilege_escalation`](settings.md#allow_privilege_escalation) to `true`.
|
||||
|
||||
## Build and run containers in a workspace
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ module ActiveContext
|
|||
|
||||
def elasticsearch_config
|
||||
{
|
||||
adapter: :net_http,
|
||||
adapter: :typhoeus,
|
||||
urls: options[:url],
|
||||
transport_options: {
|
||||
request: {
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ module ActiveContext
|
|||
|
||||
def opensearch_config
|
||||
{
|
||||
adapter: :net_http,
|
||||
adapter: :typhoeus,
|
||||
urls: options[:url],
|
||||
transport_options: {
|
||||
request: {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ RSpec.describe ActiveContext::Databases::Elasticsearch::Client do
|
|||
|
||||
it 'includes all expected keys with correct values' do
|
||||
expect(elasticsearch_config).to include(
|
||||
adapter: :net_http,
|
||||
adapter: :typhoeus,
|
||||
urls: 'http://localhost:9200',
|
||||
transport_options: {
|
||||
request: {
|
||||
|
|
|
|||
|
|
@ -32,6 +32,21 @@ RSpec.describe ActiveContext::Databases::Opensearch::Client do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#opensearch_config' do
|
||||
it 'returns correct configuration hash' do
|
||||
config = client.send(:opensearch_config)
|
||||
|
||||
expect(config).to include(
|
||||
urls: options[:url],
|
||||
randomize_hosts: true
|
||||
)
|
||||
expect(config[:transport_options][:request]).to include(
|
||||
timeout: options[:client_request_timeout],
|
||||
open_timeout: described_class::OPEN_TIMEOUT
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#aws_credentials' do
|
||||
context 'when static credentials are provided' do
|
||||
let(:options) do
|
||||
|
|
|
|||
|
|
@ -245,9 +245,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def ensure_directory
|
||||
unless Dir.exist?(default_directory)
|
||||
FileUtils.mkdir_p(default_directory)
|
||||
end
|
||||
FileUtils.mkdir_p(default_directory)
|
||||
end
|
||||
|
||||
def current_path
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ module Gitlab
|
|||
def export_migration_details(migration_name, attributes)
|
||||
directory = result_dir.join(migration_name)
|
||||
|
||||
FileUtils.mkdir_p(directory) unless Dir.exist?(directory)
|
||||
FileUtils.mkdir_p(directory)
|
||||
|
||||
File.write(directory.join(MIGRATION_DETAILS_FILE_NAME), attributes.to_json)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ module Gitlab
|
|||
# 15 tries will never complete within the maximum time with exponential
|
||||
# backoff. So our limit is the runtime, not the number of tries.
|
||||
Retriable.retriable(max_elapsed_time: cleanup_time, base_interval: 0.1, tries: 15) do
|
||||
FileUtils.remove_entry(tmp_dir) if File.exist?(tmp_dir)
|
||||
FileUtils.rm_rf(tmp_dir)
|
||||
end
|
||||
rescue StandardError => e
|
||||
raise CleanupError, e
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def ensure_lock_files_path!
|
||||
FileUtils.mkdir_p(lock_files_path) unless Dir.exist?(lock_files_path)
|
||||
FileUtils.mkdir_p(lock_files_path)
|
||||
end
|
||||
|
||||
def lock_files_path
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ module Gitlab
|
|||
next if Gitlab::Utils::FileInfo.linked?(source_child)
|
||||
|
||||
if File.directory?(source_child)
|
||||
FileUtils.mkdir_p(target_child, mode: DEFAULT_DIR_MODE) unless File.exist?(target_child)
|
||||
FileUtils.mkdir_p(target_child, mode: DEFAULT_DIR_MODE)
|
||||
recursive_merge(source_child, target_child)
|
||||
else
|
||||
FileUtils.mv(source_child, target_child)
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def cleanup!(path)
|
||||
File.unlink(path) if File.exist?(path)
|
||||
FileUtils.rm_f(path)
|
||||
rescue Errno::ENOENT
|
||||
# Path does not exist: Ignore. We already check `File.exist?`. Rescue to be extra safe.
|
||||
end
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ namespace :gitlab do
|
|||
end
|
||||
|
||||
def clone_repository(url, directory)
|
||||
FileUtils.rm_rf(directory) if Dir.exist?(directory)
|
||||
FileUtils.rm_rf(directory)
|
||||
|
||||
system("git clone #{url} --depth=1 --branch=master #{directory}")
|
||||
end
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ namespace :tanuki_emoji do
|
|||
puts "Importing emojis into: #{emoji_dir} ..."
|
||||
|
||||
# Re-create the assets folder and copy emojis renaming them to use name instead of unicode hex
|
||||
FileUtils.rm_rf(emoji_dir) if Dir.exist?(emoji_dir)
|
||||
FileUtils.rm_rf(emoji_dir)
|
||||
FileUtils.mkdir_p(emoji_dir, mode: 0700)
|
||||
|
||||
TanukiEmoji.index.all.find_each do |emoji|
|
||||
|
|
|
|||
|
|
@ -9078,6 +9078,9 @@ msgstr ""
|
|||
msgid "Batch size"
|
||||
msgstr ""
|
||||
|
||||
msgid "Batch size of namespaces for initial indexing"
|
||||
msgstr ""
|
||||
|
||||
msgid "Batched Job|Background migrations"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -11240,6 +11243,9 @@ msgstr ""
|
|||
msgid "CICD|An error occurred while adding the authentication log entries. Please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "CICD|An error occurred while removing the auto-added log entries. Please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "CICD|Are you sure you want to remove %{namespace} from the job token allowlist?"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -11255,6 +11261,9 @@ msgstr ""
|
|||
msgid "CICD|Authentication log entries were successfully added to the allowlist."
|
||||
msgstr ""
|
||||
|
||||
msgid "CICD|Authentication log entries were successfully removed from the allowlist."
|
||||
msgstr ""
|
||||
|
||||
msgid "CICD|Authorized groups and projects"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -11354,9 +11363,18 @@ msgstr ""
|
|||
msgid "CICD|Prevent CI/CD job tokens from this project from being used to access other projects unless the other project is added to the allowlist. It is a security risk to disable this feature, because unauthorized projects might attempt to retrieve an active token and access the API. %{linkStart}Learn more%{linkEnd}."
|
||||
msgstr ""
|
||||
|
||||
msgid "CICD|Remove all auto-added allowlist entries"
|
||||
msgstr ""
|
||||
|
||||
msgid "CICD|Remove group or project"
|
||||
msgstr ""
|
||||
|
||||
msgid "CICD|Removing auto-added allowlist entries. Please wait while the action completes."
|
||||
msgstr ""
|
||||
|
||||
msgid "CICD|Removing these entries could cause authentication failures or disrupt pipelines."
|
||||
msgstr ""
|
||||
|
||||
msgid "CICD|Select the groups and projects authorized to use a CI/CD job token to authenticate requests to this project. %{linkStart}Learn more%{linkEnd}."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -11387,6 +11405,9 @@ msgstr ""
|
|||
msgid "CICD|There was a problem fetching authorization logs count."
|
||||
msgstr ""
|
||||
|
||||
msgid "CICD|This action removes all groups and projects that were auto-added from the authentication log."
|
||||
msgstr ""
|
||||
|
||||
msgid "CICD|Unprotected branches will not have access to the cache from protected branches."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -47964,6 +47985,9 @@ msgstr ""
|
|||
msgid "Remove email participants"
|
||||
msgstr ""
|
||||
|
||||
msgid "Remove entries"
|
||||
msgstr ""
|
||||
|
||||
msgid "Remove favicon"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@
|
|||
"@gitlab/fonts": "^1.3.0",
|
||||
"@gitlab/query-language-rust": "0.4.0",
|
||||
"@gitlab/svgs": "3.123.0",
|
||||
"@gitlab/ui": "108.4.1",
|
||||
"@gitlab/ui": "108.6.0",
|
||||
"@gitlab/vue-router-vue3": "npm:vue-router@4.5.0",
|
||||
"@gitlab/vuex-vue3": "npm:vuex@4.1.0",
|
||||
"@gitlab/web-ide": "^0.0.1-dev-20250211142744",
|
||||
|
|
|
|||
|
|
@ -278,6 +278,7 @@ class ApplicationSettingsAnalysis
|
|||
vertex_ai_project
|
||||
web_ide_oauth_application_id
|
||||
zoekt_cpu_to_tasks_ratio
|
||||
zoekt_rollout_batch_size
|
||||
zoekt_indexing_enabled
|
||||
zoekt_search_enabled
|
||||
zoekt_settings
|
||||
|
|
|
|||
|
|
@ -87,12 +87,29 @@ describe('AutopopulateAllowlistModal component', () => {
|
|||
);
|
||||
});
|
||||
|
||||
// TODO: Test for help link
|
||||
// See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181294
|
||||
it('renders help link', () => {
|
||||
expect(findLink().text()).toBe('What is the compaction algorithm?');
|
||||
expect(findLink().attributes('href')).toBe(
|
||||
'/help/ci/jobs/ci_job_token#auto-populate-a-projects-allowlist',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it.each`
|
||||
modalEvent | emittedEvent
|
||||
${'canceled'} | ${'hide'}
|
||||
${'hidden'} | ${'hide'}
|
||||
${'secondary'} | ${'hide'}
|
||||
`(
|
||||
'emits the $emittedEvent event when $modalEvent event is triggered',
|
||||
({ modalEvent, emittedEvent }) => {
|
||||
expect(wrapper.emitted(emittedEvent)).toBeUndefined();
|
||||
|
||||
findModal().vm.$emit(modalEvent);
|
||||
|
||||
expect(wrapper.emitted(emittedEvent)).toHaveLength(1);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('when mutation is running', () => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
import { GlAlert, GlLoadingIcon, GlFormRadioGroup } from '@gitlab/ui';
|
||||
import {
|
||||
GlAlert,
|
||||
GlDisclosureDropdown,
|
||||
GlDisclosureDropdownItem,
|
||||
GlFormRadioGroup,
|
||||
} from '@gitlab/ui';
|
||||
import Vue, { nextTick } from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
|
|
@ -12,6 +17,7 @@ import {
|
|||
} from '~/token_access/constants';
|
||||
import AutopopulateAllowlistModal from '~/token_access/components/autopopulate_allowlist_modal.vue';
|
||||
import NamespaceForm from '~/token_access/components/namespace_form.vue';
|
||||
import RemoveAutopopulatedEntriesModal from '~/token_access/components/remove_autopopulated_entries_modal.vue';
|
||||
import inboundRemoveGroupCIJobTokenScopeMutation from '~/token_access/graphql/mutations/inbound_remove_group_ci_job_token_scope.mutation.graphql';
|
||||
import inboundRemoveProjectCIJobTokenScopeMutation from '~/token_access/graphql/mutations/inbound_remove_project_ci_job_token_scope.mutation.graphql';
|
||||
import inboundUpdateCIJobTokenScopeMutation from '~/token_access/graphql/mutations/inbound_update_ci_job_token_scope.mutation.graphql';
|
||||
|
|
@ -19,6 +25,7 @@ import inboundGetCIJobTokenScopeQuery from '~/token_access/graphql/queries/inbou
|
|||
import inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery from '~/token_access/graphql/queries/inbound_get_groups_and_projects_with_ci_job_token_scope.query.graphql';
|
||||
import getAuthLogCountQuery from '~/token_access/graphql/queries/get_auth_log_count.query.graphql';
|
||||
import getCiJobTokenScopeAllowlistQuery from '~/token_access/graphql/queries/get_ci_job_token_scope_allowlist.query.graphql';
|
||||
import removeAutopopulatedEntriesMutation from '~/token_access/graphql/mutations/remove_autopopulated_entries.mutation.graphql';
|
||||
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||
import ConfirmActionModal from '~/vue_shared/components/confirm_action_modal.vue';
|
||||
import TokenAccessTable from '~/token_access/components/token_access_table.vue';
|
||||
|
|
@ -31,6 +38,7 @@ import {
|
|||
inboundRemoveNamespaceSuccess,
|
||||
inboundUpdateScopeSuccessResponse,
|
||||
mockAuthLogsCountResponse,
|
||||
mockRemoveAutopopulatedEntriesResponse,
|
||||
} from './mock_data';
|
||||
|
||||
const projectPath = 'root/my-repo';
|
||||
|
|
@ -63,13 +71,22 @@ describe('TokenAccess component', () => {
|
|||
const inboundUpdateScopeSuccessResponseHandler = jest
|
||||
.fn()
|
||||
.mockResolvedValue(inboundUpdateScopeSuccessResponse);
|
||||
const removeAutopopulatedEntriesMutationHandler = jest
|
||||
.fn()
|
||||
.mockResolvedValue(mockRemoveAutopopulatedEntriesResponse());
|
||||
const removeAutopopulatedEntriesMutationErrorHandler = jest
|
||||
.fn()
|
||||
.mockResolvedValue(mockRemoveAutopopulatedEntriesResponse({ errorMessage: message }));
|
||||
const failureHandler = jest.fn().mockRejectedValue(error);
|
||||
const mockToastShow = jest.fn();
|
||||
|
||||
const findAutopopulateAllowlistModal = () => wrapper.findComponent(AutopopulateAllowlistModal);
|
||||
const findAutopopulationAlert = () => wrapper.findByTestId('autopopulation-alert');
|
||||
const findAllowlistOptions = () => wrapper.findComponent(GlDisclosureDropdown);
|
||||
const findAllowlistOption = (index) =>
|
||||
wrapper.findAllComponents(GlDisclosureDropdownItem).at(index).find('button');
|
||||
const findFormSelector = () => wrapper.findByTestId('form-selector');
|
||||
const findRadioGroup = () => wrapper.findComponent(GlFormRadioGroup);
|
||||
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
|
||||
const findToggleFormBtn = () => wrapper.findByTestId('crud-form-toggle');
|
||||
const findTokenDisabledAlert = () => wrapper.findComponent(GlAlert);
|
||||
const findNamespaceForm = () => wrapper.findComponent(NamespaceForm);
|
||||
|
|
@ -78,6 +95,8 @@ describe('TokenAccess component', () => {
|
|||
const findGroupCount = () => wrapper.findByTestId('group-count');
|
||||
const findProjectCount = () => wrapper.findByTestId('project-count');
|
||||
const findConfirmActionModal = () => wrapper.findComponent(ConfirmActionModal);
|
||||
const findRemoveAutopopulatedEntriesModal = () =>
|
||||
wrapper.findComponent(RemoveAutopopulatedEntriesModal);
|
||||
const findTokenAccessTable = () => wrapper.findComponent(TokenAccessTable);
|
||||
|
||||
const createComponent = (
|
||||
|
|
@ -88,6 +107,7 @@ describe('TokenAccess component', () => {
|
|||
enforceAllowlist = false,
|
||||
projectAllowlistLimit = 2,
|
||||
stubs = {},
|
||||
isLoading = false,
|
||||
} = {},
|
||||
) => {
|
||||
wrapper = shallowMountExtended(InboundTokenAccess, {
|
||||
|
|
@ -110,24 +130,30 @@ describe('TokenAccess component', () => {
|
|||
},
|
||||
});
|
||||
|
||||
return waitForPromises();
|
||||
if (!isLoading) {
|
||||
return waitForPromises();
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
describe('loading state', () => {
|
||||
it('shows loading state while waiting on query to resolve', async () => {
|
||||
createComponent([
|
||||
[inboundGetCIJobTokenScopeQuery, inboundJobTokenScopeEnabledResponseHandler],
|
||||
createComponent(
|
||||
[
|
||||
inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery,
|
||||
inboundGroupsAndProjectsWithScopeResponseHandler,
|
||||
[inboundGetCIJobTokenScopeQuery, inboundJobTokenScopeEnabledResponseHandler],
|
||||
[
|
||||
inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery,
|
||||
inboundGroupsAndProjectsWithScopeResponseHandler,
|
||||
],
|
||||
],
|
||||
]);
|
||||
{ isLoading: true },
|
||||
);
|
||||
|
||||
expect(findLoadingIcon().exists()).toBe(true);
|
||||
await nextTick();
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findLoadingIcon().exists()).toBe(false);
|
||||
expect(findTokenAccessTable().props('loading')).toBe(true);
|
||||
expect(findTokenAccessTable().props('loadingMessage')).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -448,8 +474,12 @@ describe('TokenAccess component', () => {
|
|||
inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery,
|
||||
inboundGroupsAndProjectsWithScopeResponseHandler,
|
||||
],
|
||||
[removeAutopopulatedEntriesMutation, removeAutopopulatedEntriesMutationHandler],
|
||||
],
|
||||
{ authenticationLogsMigrationForAllowlist: true, stubs: { CrudComponent } },
|
||||
{
|
||||
authenticationLogsMigrationForAllowlist: true,
|
||||
stubs: { CrudComponent, GlDisclosureDropdown, GlDisclosureDropdownItem },
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
|
|
@ -496,6 +526,132 @@ describe('TokenAccess component', () => {
|
|||
expect(findFormSelector().props('selected')).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove autopopulated entries', () => {
|
||||
const triggerRemoveEntries = () => {
|
||||
findAllowlistOption(0).trigger('click');
|
||||
findRemoveAutopopulatedEntriesModal().vm.$emit('remove-entries');
|
||||
};
|
||||
|
||||
it('additional actions are available in the disclosure dropdown', () => {
|
||||
expect(findAllowlistOptions().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('"Remove only entries auto-added" renders the remove autopopulated entries modal', async () => {
|
||||
expect(findRemoveAutopopulatedEntriesModal().props('showModal')).toBe(false);
|
||||
|
||||
findAllowlistOption(0).trigger('click');
|
||||
await nextTick();
|
||||
|
||||
expect(findRemoveAutopopulatedEntriesModal().props('showModal')).toBe(true);
|
||||
});
|
||||
|
||||
it('shows loading state while remove autopopulated entries mutation is processing', async () => {
|
||||
expect(findCountLoadingIcon().exists()).toBe(false);
|
||||
expect(findTokenAccessTable().props('loading')).toBe(false);
|
||||
|
||||
triggerRemoveEntries();
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(findCountLoadingIcon().exists()).toBe(true);
|
||||
expect(findTokenAccessTable().props('loading')).toBe(true);
|
||||
expect(findTokenAccessTable().props('loadingMessage')).toBe(
|
||||
'Removing auto-added allowlist entries. Please wait while the action completes.',
|
||||
);
|
||||
});
|
||||
|
||||
it('calls the remove autopopulated entries mutation and refetches allowlist', async () => {
|
||||
expect(removeAutopopulatedEntriesMutationHandler).toHaveBeenCalledTimes(0);
|
||||
expect(inboundGroupsAndProjectsWithScopeResponseHandler).toHaveBeenCalledTimes(1);
|
||||
|
||||
triggerRemoveEntries();
|
||||
await waitForPromises();
|
||||
await nextTick();
|
||||
|
||||
expect(removeAutopopulatedEntriesMutationHandler).toHaveBeenCalledTimes(1);
|
||||
expect(inboundGroupsAndProjectsWithScopeResponseHandler).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('shows toast message when mutation is successful', async () => {
|
||||
triggerRemoveEntries();
|
||||
await waitForPromises();
|
||||
await nextTick();
|
||||
|
||||
expect(mockToastShow).toHaveBeenCalledWith(
|
||||
'Authentication log entries were successfully removed from the allowlist.',
|
||||
);
|
||||
});
|
||||
|
||||
it('shows error alert when mutation returns an error', async () => {
|
||||
createComponent(
|
||||
[
|
||||
[inboundGetCIJobTokenScopeQuery, inboundJobTokenScopeEnabledResponseHandler],
|
||||
[
|
||||
inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery,
|
||||
inboundGroupsAndProjectsWithScopeResponseHandler,
|
||||
],
|
||||
[removeAutopopulatedEntriesMutation, removeAutopopulatedEntriesMutationErrorHandler],
|
||||
],
|
||||
{
|
||||
authenticationLogsMigrationForAllowlist: true,
|
||||
stubs: { CrudComponent, GlDisclosureDropdown, GlDisclosureDropdownItem },
|
||||
},
|
||||
);
|
||||
|
||||
expect(findAutopopulationAlert().exists()).toBe(false);
|
||||
|
||||
triggerRemoveEntries();
|
||||
await waitForPromises();
|
||||
await nextTick();
|
||||
|
||||
expect(findAutopopulationAlert().text()).toBe('An error occurred');
|
||||
});
|
||||
|
||||
it('shows error alert when mutation fails', async () => {
|
||||
createComponent(
|
||||
[
|
||||
[inboundGetCIJobTokenScopeQuery, inboundJobTokenScopeEnabledResponseHandler],
|
||||
[
|
||||
inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery,
|
||||
inboundGroupsAndProjectsWithScopeResponseHandler,
|
||||
],
|
||||
[removeAutopopulatedEntriesMutation, failureHandler],
|
||||
],
|
||||
{
|
||||
authenticationLogsMigrationForAllowlist: true,
|
||||
stubs: { CrudComponent, GlDisclosureDropdown, GlDisclosureDropdownItem },
|
||||
},
|
||||
);
|
||||
|
||||
expect(findAutopopulationAlert().exists()).toBe(false);
|
||||
|
||||
triggerRemoveEntries();
|
||||
await waitForPromises();
|
||||
await nextTick();
|
||||
|
||||
expect(findAutopopulationAlert().text()).toBe(
|
||||
'An error occurred while removing the auto-added log entries. Please try again.',
|
||||
);
|
||||
});
|
||||
|
||||
it('modal can be re-opened again after it closes', async () => {
|
||||
findAllowlistOption(0).trigger('click');
|
||||
await nextTick();
|
||||
|
||||
expect(findRemoveAutopopulatedEntriesModal().props('showModal')).toBe(true);
|
||||
|
||||
findRemoveAutopopulatedEntriesModal().vm.$emit('hide');
|
||||
await nextTick();
|
||||
|
||||
expect(findRemoveAutopopulatedEntriesModal().props('showModal')).toBe(false);
|
||||
|
||||
findAllowlistOption(0).trigger('click');
|
||||
await nextTick();
|
||||
|
||||
expect(findRemoveAutopopulatedEntriesModal().props('showModal')).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe.each`
|
||||
|
|
|
|||
|
|
@ -323,3 +323,13 @@ export const mockAutopopulateAllowlistError = {
|
|||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const mockRemoveAutopopulatedEntriesResponse = ({ errorMessage } = {}) => ({
|
||||
data: {
|
||||
ciJobTokenScopeClearAllowlistAutopopulations: {
|
||||
status: 'complete',
|
||||
errors: errorMessage ? [{ message: errorMessage }] : [],
|
||||
__typename: 'CiJobTokenScopeClearAllowlistAutopopulationsPayload',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
import { GlModal } from '@gitlab/ui';
|
||||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import RemoveAutopopulatedEntriesModal from '~/token_access/components/remove_autopopulated_entries_modal.vue';
|
||||
|
||||
const projectName = 'My project';
|
||||
const fullPath = 'root/my-repo';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
describe('RemoveAutopopulatedEntriesModal component', () => {
|
||||
let wrapper;
|
||||
|
||||
const findModal = () => wrapper.findComponent(GlModal);
|
||||
|
||||
const createComponent = ({ props } = {}) => {
|
||||
wrapper = shallowMountExtended(RemoveAutopopulatedEntriesModal, {
|
||||
provide: {
|
||||
fullPath,
|
||||
},
|
||||
propsData: {
|
||||
projectName,
|
||||
showModal: true,
|
||||
...props,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
describe('template', () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it.each`
|
||||
modalEvent | emittedEvent
|
||||
${'canceled'} | ${'hide'}
|
||||
${'hidden'} | ${'hide'}
|
||||
${'secondary'} | ${'hide'}
|
||||
`(
|
||||
'emits the $emittedEvent event when $modalEvent event is triggered',
|
||||
({ modalEvent, emittedEvent }) => {
|
||||
expect(wrapper.emitted(emittedEvent)).toBeUndefined();
|
||||
|
||||
findModal().vm.$emit(modalEvent);
|
||||
|
||||
expect(wrapper.emitted(emittedEvent)).toHaveLength(1);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('when clicking on the primary button', () => {
|
||||
it('emits the remove-entries event', () => {
|
||||
createComponent();
|
||||
|
||||
expect(wrapper.emitted('remove-entries')).toBeUndefined();
|
||||
|
||||
findModal().vm.$emit('primary', { preventDefault: jest.fn() });
|
||||
|
||||
expect(wrapper.emitted('remove-entries')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -22,6 +22,7 @@ describe('Token access table', () => {
|
|||
const findName = () => wrapper.findByTestId('token-access-name');
|
||||
const findPolicies = () => findAllTableRows().at(0).findAll('td').at(1);
|
||||
const findAutopopulatedIcon = () => wrapper.findByTestId('autopopulated-icon');
|
||||
const findLoadingMessage = () => wrapper.findByTestId('loading-message');
|
||||
|
||||
describe.each`
|
||||
type | items
|
||||
|
|
@ -86,6 +87,17 @@ describe('Token access table', () => {
|
|||
createComponent({ items: mockGroups, loading: true });
|
||||
|
||||
expect(findTable().findComponent(GlLoadingIcon).props('size')).toBe('md');
|
||||
expect(findLoadingMessage().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('shows loading message when available', () => {
|
||||
createComponent({
|
||||
items: mockGroups,
|
||||
loading: true,
|
||||
loadingMessage: 'Removing auto-populated entries...',
|
||||
});
|
||||
|
||||
expect(findLoadingMessage().text()).toBe('Removing auto-populated entries...');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -5347,6 +5347,7 @@ export const groupWorkItemsQueryResponse = {
|
|||
title: 'a group level work item',
|
||||
updatedAt: '',
|
||||
webUrl: 'web/url',
|
||||
userDiscussionsCount: 0,
|
||||
widgets: [
|
||||
{
|
||||
__typename: 'WorkItemWidgetAssignees',
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ RSpec.describe Gitlab::Gpg do
|
|||
it 'does not fail if the homedir was deleted while running' do
|
||||
expect do
|
||||
described_class.using_tmp_keychain do
|
||||
FileUtils.remove_entry(described_class.current_home_dir)
|
||||
FileUtils.rm_rf(described_class.current_home_dir)
|
||||
end
|
||||
end.not_to raise_error
|
||||
end
|
||||
|
|
@ -204,8 +204,8 @@ RSpec.describe Gitlab::Gpg do
|
|||
end
|
||||
|
||||
before do
|
||||
# Stub all the other calls for `remove_entry`
|
||||
allow(FileUtils).to receive(:remove_entry).with(any_args).and_call_original
|
||||
# Stub all the other calls for `rm_rf`
|
||||
allow(FileUtils).to receive(:rm_rf).with(any_args).and_call_original
|
||||
end
|
||||
|
||||
it "tries for #{seconds} or 15 times" do
|
||||
|
|
@ -216,15 +216,15 @@ RSpec.describe Gitlab::Gpg do
|
|||
|
||||
it 'tries at least 2 times to remove the tmp dir before raising', :aggregate_failures do
|
||||
expect(Retriable).to receive(:sleep).at_least(:twice)
|
||||
expect(FileUtils).to receive(:remove_entry).with(tmp_dir).at_least(:twice).and_raise('Deletion failed')
|
||||
expect(FileUtils).to receive(:rm_rf).with(tmp_dir).at_least(:twice).and_raise('Deletion failed')
|
||||
|
||||
expect { described_class.using_tmp_keychain {} }.to raise_error(described_class::CleanupError)
|
||||
end
|
||||
|
||||
it 'does not attempt multiple times when the deletion succeeds' do
|
||||
expect(Retriable).to receive(:sleep).once
|
||||
expect(FileUtils).to receive(:remove_entry).with(tmp_dir).once.and_raise('Deletion failed')
|
||||
expect(FileUtils).to receive(:remove_entry).with(tmp_dir).and_call_original
|
||||
expect(FileUtils).to receive(:rm_rf).with(tmp_dir).once.and_raise('Deletion failed')
|
||||
expect(FileUtils).to receive(:rm_rf).with(tmp_dir).and_call_original
|
||||
|
||||
expect { described_class.using_tmp_keychain {} }.not_to raise_error
|
||||
|
||||
|
|
|
|||
|
|
@ -2,38 +2,68 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'gitlab:update_project_templates rake task', :silence_stdout, feature_category: :importers do
|
||||
let!(:tmpdir) { Dir.mktmpdir }
|
||||
let(:template) { Gitlab::ProjectTemplate.find(:rails) }
|
||||
|
||||
RSpec.describe 'gitlab:update_templates rake task', :silence_stdout, feature_category: :importers do
|
||||
before do
|
||||
Rake.application.rake_require 'tasks/gitlab/update_templates'
|
||||
admin = create(:admin)
|
||||
create(:key, user: admin)
|
||||
end
|
||||
|
||||
allow(Gitlab::ProjectTemplate)
|
||||
.to receive(:archive_directory)
|
||||
.and_return(Pathname.new(tmpdir))
|
||||
describe '.update_project_templates' do
|
||||
let!(:tmpdir) { Dir.mktmpdir }
|
||||
let(:template) { Gitlab::ProjectTemplate.find(:rails) }
|
||||
|
||||
# Gitlab::HTTP resolves the domain to an IP prior to WebMock taking effect, hence the wildcard
|
||||
stub_request(:get, %r{^https://.*/api/v4/projects/#{template.uri_encoded_project_path}/repository/commits\?page=1&per_page=1})
|
||||
.to_return(
|
||||
status: 200,
|
||||
body: [{ id: '67812735b83cb42710f22dc98d73d42c8bf4d907' }].to_json,
|
||||
headers: { 'Content-Type' => 'application/json' }
|
||||
before do
|
||||
admin = create(:admin)
|
||||
create(:key, user: admin)
|
||||
|
||||
allow(Gitlab::ProjectTemplate)
|
||||
.to receive(:archive_directory)
|
||||
.and_return(Pathname.new(tmpdir))
|
||||
|
||||
# Gitlab::HTTP resolves the domain to an IP prior to WebMock taking effect, hence the wildcard
|
||||
stub_request(:get, %r{^https://.*/api/v4/projects/#{template.uri_encoded_project_path}/repository/commits\?page=1&per_page=1})
|
||||
.to_return(
|
||||
status: 200,
|
||||
body: [{ id: '67812735b83cb42710f22dc98d73d42c8bf4d907' }].to_json,
|
||||
headers: { 'Content-Type' => 'application/json' }
|
||||
)
|
||||
end
|
||||
|
||||
after do
|
||||
FileUtils.rm_rf(tmpdir)
|
||||
end
|
||||
|
||||
it 'updates valid project templates' do
|
||||
expect(Gitlab::TaskHelpers).to receive(:run_command!).with(anything).exactly(6).times.and_call_original
|
||||
expect(Gitlab::TaskHelpers).to receive(:run_command!).with(%w[git push -u origin master])
|
||||
|
||||
expect { run_rake_task('gitlab:update_project_templates', [template.name]) }
|
||||
.to change { Dir.entries(tmpdir) }
|
||||
.by(["#{template.name}.tar.gz"])
|
||||
end
|
||||
end
|
||||
|
||||
describe '#clone_repository' do
|
||||
let(:repo_url) { "https://test.com/example/text.git" }
|
||||
let(:template) do
|
||||
Struct.new(:repo_url, :cleanup_regex).new(
|
||||
repo_url,
|
||||
/(\.{1,2}|LICENSE|Global|\.test)\z/
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
after do
|
||||
FileUtils.rm_rf(tmpdir)
|
||||
end
|
||||
let(:dir) { Rails.root.join('vendor/text').to_s }
|
||||
let(:kernel_system_call_params) { "git clone #{repo_url} --depth=1 --branch=master #{dir}" }
|
||||
|
||||
it 'updates valid project templates' do
|
||||
expect(Gitlab::TaskHelpers).to receive(:run_command!).with(anything).exactly(6).times.and_call_original
|
||||
expect(Gitlab::TaskHelpers).to receive(:run_command!).with(%w[git push -u origin master])
|
||||
before do
|
||||
stub_const('TEMPLATE_DATA', [template])
|
||||
allow(main_object).to receive(:system).with(kernel_system_call_params).and_return(false)
|
||||
allow(FileUtils).to receive(:rm_rf).and_call_original
|
||||
end
|
||||
|
||||
expect { run_rake_task('gitlab:update_project_templates', [template.name]) }
|
||||
.to change { Dir.entries(tmpdir) }
|
||||
.by(["#{template.name}.tar.gz"])
|
||||
it 'calls FileUtils.rm_rf to remove directory' do
|
||||
expect(FileUtils).to receive(:rm_rf).with(dir)
|
||||
|
||||
run_rake_task('gitlab:update_templates')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1436,10 +1436,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.123.0.tgz#1fa3b1a709755ff7c8ef67e18c0442101655ebf0"
|
||||
integrity sha512-yjVn+utOTIKk8d9JlvGo6EgJ4TQ+CKpe3RddflAqtsQqQuL/2MlVdtaUePybxYzWIaumFuh5LouQ6BrWyw1niQ==
|
||||
|
||||
"@gitlab/ui@108.4.1":
|
||||
version "108.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-108.4.1.tgz#d45dbcc3a9f568694f53eebb795544157ddbf44f"
|
||||
integrity sha512-o3mcR/AQ5tkKd3mMClMvsJIHhLWzU6n1L0m3bp4Yr4zaNtRG3c8gEGF7MR6pMhlnyb5r/FQk2wigxHaUuOSblQ==
|
||||
"@gitlab/ui@108.6.0":
|
||||
version "108.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-108.6.0.tgz#55c87776b1f247250dc33ab7921e27c415675842"
|
||||
integrity sha512-azX2X0Qzs9Jt1ZruGlFWa9JAJuCO8Bix+uWMMWYqCQYCk4HLOT2t7jI0Kt8vUTwweA/IsmT6VPID2aKxFeJR0g==
|
||||
dependencies:
|
||||
"@floating-ui/dom" "1.4.3"
|
||||
echarts "^5.3.2"
|
||||
|
|
|
|||
Loading…
Reference in New Issue