Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
1079a0ed1e
commit
ffbcfe8ea9
|
|
@ -10,7 +10,6 @@ Migration/BackgroundMigrationRecord:
|
|||
- 'lib/gitlab/background_migration/backfill_draft_status_on_merge_requests.rb'
|
||||
- 'lib/gitlab/background_migration/backfill_project_repositories.rb'
|
||||
- 'lib/gitlab/background_migration/backfill_topics_title.rb'
|
||||
- 'lib/gitlab/background_migration/fix_first_mentioned_in_commit_at.rb'
|
||||
- 'lib/gitlab/background_migration/fix_projects_without_prometheus_service.rb'
|
||||
- 'lib/gitlab/background_migration/migrate_null_private_profile_to_false.rb'
|
||||
- 'lib/gitlab/background_migration/populate_latest_pipeline_ids.rb'
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ Migration/BatchedMigrationBaseClass:
|
|||
- 'lib/gitlab/background_migration/backfill_snippet_repositories.rb'
|
||||
- 'lib/gitlab/background_migration/backfill_topics_title.rb'
|
||||
- 'lib/gitlab/background_migration/create_security_setting.rb'
|
||||
- 'lib/gitlab/background_migration/fix_first_mentioned_in_commit_at.rb'
|
||||
- 'lib/gitlab/background_migration/fix_projects_without_project_feature.rb'
|
||||
- 'lib/gitlab/background_migration/fix_projects_without_prometheus_service.rb'
|
||||
- 'lib/gitlab/background_migration/legacy_upload_mover.rb'
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import { GlLineChart } from '@gitlab/ui/dist/charts';
|
||||
import { s__ } from '~/locale';
|
||||
import { stringifyTime, parseSeconds } from '~/lib/utils/datetime/date_format_utility';
|
||||
import { localeDateFormat } from '~/lib/utils/datetime/locale_dateformat';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
|
@ -35,6 +37,17 @@ export default {
|
|||
return durationSeries;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
formatDate(isoDateStr) {
|
||||
if (isoDateStr) {
|
||||
return localeDateFormat.asDate.format(new Date(isoDateStr));
|
||||
}
|
||||
return '';
|
||||
},
|
||||
formatDuration(seconds) {
|
||||
return stringifyTime(parseSeconds(seconds, { daysPerWeek: 7, hoursPerDay: 24 }));
|
||||
},
|
||||
},
|
||||
lineChartOptions: {
|
||||
yAxis: {
|
||||
name: s__('Pipeline|Seconds'),
|
||||
|
|
@ -55,6 +68,13 @@ export default {
|
|||
:data="data"
|
||||
:option="$options.lineChartOptions"
|
||||
:include-legend-avg-max="false"
|
||||
/>
|
||||
>
|
||||
<template #tooltip-title="{ params }">
|
||||
<template v-if="params && params.value">{{ formatDate(params.value) }}</template>
|
||||
</template>
|
||||
<template #tooltip-value="{ value }">
|
||||
{{ formatDuration(value) }}
|
||||
</template>
|
||||
</gl-line-chart>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
DATA_VIZ_BLUE_500,
|
||||
} from '@gitlab/ui/src/tokens/build/js/tokens';
|
||||
import { s__ } from '~/locale';
|
||||
import { localeDateFormat } from '~/lib/utils/datetime/locale_dateformat';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
|
@ -51,6 +52,14 @@ export default {
|
|||
return this.data.bars;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
formatDate(isoDateStr) {
|
||||
if (isoDateStr) {
|
||||
return localeDateFormat.asDate.format(new Date(isoDateStr));
|
||||
}
|
||||
return '';
|
||||
},
|
||||
},
|
||||
palette: [DATA_VIZ_GREEN_500, DATA_VIZ_MAGENTA_600, DATA_VIZ_BLUE_500],
|
||||
};
|
||||
</script>
|
||||
|
|
@ -68,6 +77,10 @@ export default {
|
|||
:group-by="groupBy"
|
||||
:bars="bars"
|
||||
:include-legend-avg-max="false"
|
||||
/>
|
||||
>
|
||||
<template #tooltip-title="{ params }">
|
||||
<template v-if="params">{{ formatDate(params.value) }}</template>
|
||||
</template>
|
||||
</gl-stacked-column-chart>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
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';
|
||||
|
||||
export default {
|
||||
name: 'AutopopulateAllowlistModal',
|
||||
|
|
@ -33,12 +32,6 @@ export default {
|
|||
},
|
||||
},
|
||||
apollo: {},
|
||||
data() {
|
||||
return {
|
||||
errorMessage: false,
|
||||
isAutopopulating: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
authLogExceedsLimitMessage() {
|
||||
return sprintf(
|
||||
|
|
@ -56,14 +49,12 @@ export default {
|
|||
text: __('Add entries'),
|
||||
attributes: {
|
||||
variant: 'confirm',
|
||||
loading: this.isAutopopulating,
|
||||
},
|
||||
},
|
||||
actionSecondary: {
|
||||
text: __('Cancel'),
|
||||
attributes: {
|
||||
variant: 'default',
|
||||
disabled: this.isAutopopulating,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -77,48 +68,15 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
async autopopulateAllowlist() {
|
||||
this.isAutopopulating = true;
|
||||
this.errorMessage = null;
|
||||
|
||||
try {
|
||||
const {
|
||||
data: {
|
||||
ciJobTokenScopeAutopopulateAllowlist: { errors },
|
||||
},
|
||||
} = await this.$apollo.mutate({
|
||||
mutation: autopopulateAllowlistMutation,
|
||||
variables: {
|
||||
projectPath: this.fullPath,
|
||||
},
|
||||
});
|
||||
|
||||
if (errors.length) {
|
||||
throw new Error(errors[0]);
|
||||
}
|
||||
|
||||
this.$emit('refetch-allowlist');
|
||||
this.hideModal();
|
||||
this.$toast.show(
|
||||
s__('CICD|Authentication log entries were successfully added to the allowlist.'),
|
||||
);
|
||||
} catch (error) {
|
||||
this.errorMessage =
|
||||
error?.message ||
|
||||
s__(
|
||||
'CICD|An error occurred while adding the authentication log entries. Please try again.',
|
||||
);
|
||||
} finally {
|
||||
this.isAutopopulating = false;
|
||||
}
|
||||
autopopulateAllowlist() {
|
||||
this.$emit('autopopulate-allowlist');
|
||||
},
|
||||
hideModal() {
|
||||
this.errorMessage = null;
|
||||
this.$emit('hide');
|
||||
},
|
||||
},
|
||||
compactionAlgorithmHelpPage: helpPagePath('ci/jobs/ci_job_token', {
|
||||
anchor: 'auto-populate-a-projects-allowlist',
|
||||
anchor: 'allowlist-compaction',
|
||||
}),
|
||||
};
|
||||
</script>
|
||||
|
|
@ -135,9 +93,6 @@ export default {
|
|||
@canceled="hideModal"
|
||||
@hidden="hideModal"
|
||||
>
|
||||
<gl-alert v-if="errorMessage" variant="danger" class="gl-mb-3" :dismissible="false">
|
||||
{{ errorMessage }}
|
||||
</gl-alert>
|
||||
<div v-if="authLogExceedsLimit">
|
||||
<gl-alert variant="warning" class="gl-mb-3" :dismissible="false">
|
||||
{{ authLogExceedsLimitMessage }}
|
||||
|
|
@ -163,13 +118,16 @@ export default {
|
|||
<gl-sprintf
|
||||
:message="
|
||||
s__(
|
||||
`CICD|You're about to add all entries from the authentication log to the allowlist for %{projectName}. Duplicate entries will be ignored.`,
|
||||
`CICD|You're about to add all entries from the authentication log to the allowlist for %{projectName}. This will also update the Job Token setting to %{codeStart}This project and any groups and projects in the allowlist%{codeEnd}, if not already set. Duplicate entries will be ignored.`,
|
||||
)
|
||||
"
|
||||
>
|
||||
<template #projectName>
|
||||
<b>{{ projectName }}</b>
|
||||
</template>
|
||||
<template #code="{ content }">
|
||||
<code>{{ content }}</code>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</p>
|
||||
<p>
|
||||
|
|
@ -182,7 +140,7 @@ export default {
|
|||
<p>
|
||||
{{
|
||||
s__(
|
||||
'CICD|The process to add entries could take a moment to complete with large logs or allowlists.',
|
||||
'CICD|The process might take a moment to complete for large authentication logs or allowlists.',
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import inboundRemoveGroupCIJobTokenScopeMutation from '../graphql/mutations/inbo
|
|||
import inboundUpdateCIJobTokenScopeMutation from '../graphql/mutations/inbound_update_ci_job_token_scope.mutation.graphql';
|
||||
import inboundGetCIJobTokenScopeQuery from '../graphql/queries/inbound_get_ci_job_token_scope.query.graphql';
|
||||
import inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery from '../graphql/queries/inbound_get_groups_and_projects_with_ci_job_token_scope.query.graphql';
|
||||
import autopopulateAllowlistMutation from '../graphql/mutations/autopopulate_allowlist.mutation.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';
|
||||
|
|
@ -71,16 +72,6 @@ export default {
|
|||
text: s__('CICD|Only this project and any groups and projects in the allowlist'),
|
||||
},
|
||||
],
|
||||
crudFormActions: [
|
||||
{
|
||||
text: __('Group or project'),
|
||||
value: JOB_TOKEN_FORM_ADD_GROUP_OR_PROJECT,
|
||||
},
|
||||
{
|
||||
text: __('All projects in authentication log'),
|
||||
value: JOB_TOKEN_FORM_AUTOPOPULATE_AUTH_LOG,
|
||||
},
|
||||
],
|
||||
components: {
|
||||
AutopopulateAllowlistModal,
|
||||
GlAlert,
|
||||
|
|
@ -206,6 +197,23 @@ export default {
|
|||
anchor: 'control-job-token-access-to-your-project',
|
||||
});
|
||||
},
|
||||
crudFormActions() {
|
||||
const actions = [
|
||||
{
|
||||
text: __('Group or project'),
|
||||
value: JOB_TOKEN_FORM_ADD_GROUP_OR_PROJECT,
|
||||
},
|
||||
];
|
||||
|
||||
if (this.authLogCount > 0) {
|
||||
actions.push({
|
||||
text: __('All projects in authentication log'),
|
||||
value: JOB_TOKEN_FORM_AUTOPOPULATE_AUTH_LOG,
|
||||
});
|
||||
}
|
||||
|
||||
return actions;
|
||||
},
|
||||
allowlist() {
|
||||
const { groups, projects } = this.groupsAndProjectsWithAccess;
|
||||
return [...groups, ...projects];
|
||||
|
|
@ -224,6 +232,9 @@ export default {
|
|||
},
|
||||
];
|
||||
},
|
||||
hasAutoPopulatedEntries() {
|
||||
return this.allowlist.filter((entry) => entry.autopopulated).length > 0;
|
||||
},
|
||||
groupCount() {
|
||||
return this.groupsAndProjectsWithAccess.groups.length;
|
||||
},
|
||||
|
|
@ -317,6 +328,43 @@ export default {
|
|||
this.refetchGroupsAndProjects();
|
||||
return Promise.resolve();
|
||||
},
|
||||
async autopopulateAllowlist() {
|
||||
this.hideSelectedAction();
|
||||
this.autopopulationErrorMessage = null;
|
||||
this.allowlistLoadingMessage = s__(
|
||||
'CICD|Auto-populating allowlist entries. Please wait while the action completes.',
|
||||
);
|
||||
|
||||
try {
|
||||
const {
|
||||
data: {
|
||||
ciJobTokenScopeAutopopulateAllowlist: { errors },
|
||||
},
|
||||
} = await this.$apollo.mutate({
|
||||
mutation: autopopulateAllowlistMutation,
|
||||
variables: {
|
||||
projectPath: this.fullPath,
|
||||
},
|
||||
});
|
||||
|
||||
if (errors.length) {
|
||||
this.autopopulationErrorMessage = errors[0].message;
|
||||
return;
|
||||
}
|
||||
|
||||
this.$apollo.queries.inboundJobTokenScopeEnabled.refetch();
|
||||
this.refetchAllowlist();
|
||||
this.$toast.show(
|
||||
s__('CICD|Authentication log entries were successfully added to the allowlist.'),
|
||||
);
|
||||
} catch {
|
||||
this.autopopulationErrorMessage = s__(
|
||||
'CICD|An error occurred while adding the authentication log entries. Please try again.',
|
||||
);
|
||||
} finally {
|
||||
this.allowlistLoadingMessage = '';
|
||||
}
|
||||
},
|
||||
async removeAutopopulatedEntries() {
|
||||
this.hideSelectedAction();
|
||||
this.autopopulationErrorMessage = null;
|
||||
|
|
@ -388,7 +436,7 @@ export default {
|
|||
:project-name="projectName"
|
||||
:show-modal="showAutopopulateModal"
|
||||
@hide="hideSelectedAction"
|
||||
@refetch-allowlist="refetchAllowlist"
|
||||
@autopopulate-allowlist="autopopulateAllowlist"
|
||||
/>
|
||||
<remove-autopopulated-entries-modal
|
||||
:show-modal="showRemoveAutopopulatedEntriesModal"
|
||||
|
|
@ -451,13 +499,14 @@ export default {
|
|||
<template v-if="canAutopopulateAuthLog" #actions="{ showForm }">
|
||||
<gl-collapsible-listbox
|
||||
v-model="selectedAction"
|
||||
:items="$options.crudFormActions"
|
||||
:items="crudFormActions"
|
||||
:toggle-text="$options.i18n.add"
|
||||
data-testid="form-selector"
|
||||
size="small"
|
||||
@select="selectAction($event, showForm)"
|
||||
/>
|
||||
<gl-disclosure-dropdown
|
||||
v-if="hasAutoPopulatedEntries"
|
||||
category="tertiary"
|
||||
icon="ellipsis_v"
|
||||
no-caret
|
||||
|
|
|
|||
|
|
@ -302,8 +302,8 @@ export default {
|
|||
cancelToken: this.refCancelToken.token,
|
||||
})
|
||||
.then(({ data }) => {
|
||||
const branches = data.Branches;
|
||||
const tags = data.Tags;
|
||||
const branches = data?.Branches || [];
|
||||
const tags = data?.Tags || [];
|
||||
|
||||
if (target === WORK_ITEM_CREATE_ENTITY_MODAL_TARGET_SOURCE) {
|
||||
this.invalidSource = !(
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ class GraphqlController < ApplicationController
|
|||
private
|
||||
|
||||
def check_dpop!
|
||||
return unless !!sessionless_user? # DPoP is only enforced on token-based authentication
|
||||
return unless current_user && Feature.enabled?(:dpop_authentication, current_user)
|
||||
|
||||
token = extract_personal_access_token
|
||||
|
|
|
|||
|
|
@ -81,6 +81,15 @@ module Ci
|
|||
})
|
||||
end
|
||||
|
||||
def admin_runners_fleet_dashboard_data
|
||||
{
|
||||
admin_runners_path: admin_runners_path,
|
||||
new_runner_path: new_admin_runner_path,
|
||||
clickhouse_ci_analytics_available: ::Gitlab::ClickHouse.configured?.to_s,
|
||||
can_admin_runners: current_user.can_admin_all_resources?.to_s
|
||||
}
|
||||
end
|
||||
|
||||
def group_shared_runners_settings_data(group)
|
||||
data = {
|
||||
group_id: group.id,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ module Gitlab
|
|||
include CronjobQueue
|
||||
# rubocop:enable Scalability/CronWorkerContext
|
||||
|
||||
deduplicate :until_executed
|
||||
feature_category :importers
|
||||
data_consistency :sticky
|
||||
idempotent!
|
||||
|
|
|
|||
|
|
@ -558,7 +558,7 @@ Settings.cron_jobs['prune_old_events_worker'] ||= {}
|
|||
Settings.cron_jobs['prune_old_events_worker']['cron'] ||= '0 */6 * * *'
|
||||
Settings.cron_jobs['prune_old_events_worker']['job_class'] = 'PruneOldEventsWorker'
|
||||
Settings.cron_jobs['gitlab_export_prune_project_export_jobs_worker'] ||= {}
|
||||
Settings.cron_jobs['gitlab_export_prune_project_export_jobs_worker']['cron'] ||= '30 3 * * *'
|
||||
Settings.cron_jobs['gitlab_export_prune_project_export_jobs_worker']['cron'] ||= '30 * * * *'
|
||||
Settings.cron_jobs['gitlab_export_prune_project_export_jobs_worker']['job_class'] = 'Gitlab::Export::PruneProjectExportJobsWorker'
|
||||
Settings.cron_jobs['trending_projects_worker'] ||= {}
|
||||
Settings.cron_jobs['trending_projects_worker']['cron'] = '0 1 * * *'
|
||||
|
|
|
|||
|
|
@ -20,3 +20,4 @@
|
|||
|
||||
Because it scans your project in more detail, Advanced SAST may take more time to scan your project.
|
||||
If needed, you can [disable GitLab Advanced SAST](https://docs.gitlab.com/user/application_security/sast/gitlab_advanced_sast#disable-gitlab-advanced-sast-scanning) by setting the CI/CD variable `GITLAB_ADVANCED_SAST_ENABLED` to `false`.
|
||||
You can set this variable in your project, group, or policy now to prevent Advanced SAST from being enabled by default in GitLab 18.0.
|
||||
|
|
|
|||
|
|
@ -163,6 +163,12 @@ it [compacts the allowlist](#allowlist-compaction) to stay under the 200 entry l
|
|||
|
||||
#### With the UI
|
||||
|
||||
{{< history >}}
|
||||
|
||||
- Introduced in [GitLab 17.10](https://gitlab.com/gitlab-org/gitlab/-/issues/498125). [Deployed behind the `:authentication_logs_migration_for_allowlist` feature flag](../../user/feature_flags.md), disabled by default.
|
||||
|
||||
{{< /history >}}
|
||||
|
||||
To auto-populate the allowlist through the UI:
|
||||
|
||||
1. On the left sidebar, select **Search or go** to and find your project.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
stage: Foundations
|
||||
group: Global Search
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
title: Advanced search development guidelines
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
stage: Data Access
|
||||
group: Database Frameworks
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
title: Add a foreign key constraint to an existing column
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
stage: Data Access
|
||||
group: Database Frameworks
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
title: Adding Database Indexes
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
stage: Data Access
|
||||
group: Database Frameworks
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
title: Avoiding downtime in migrations
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
stage: Data Access
|
||||
group: Database Frameworks
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
title: Batching best practices
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
title: Introduction to ClickHouse use and table design
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
title: ClickHouse within GitLab
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
title: Store GitLab activity data in ClickHouse
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
title: Merge request analytics with ClickHouse
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
title: Optimizing query execution
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
title: Tiered Storages in ClickHouse
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
stage: Data Access
|
||||
group: Database Frameworks
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
title: Database Dictionary
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
stage: Data Access
|
||||
group: Database Frameworks
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
title: Database Lab access using the `pgai` Ruby gem
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
stage: Data Access
|
||||
group: Database Frameworks
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
title: Database Reviewer Guidelines
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
stage: Data Access
|
||||
group: Database Frameworks
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
title: Importing a database dump into a staging environment
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
stage: Data Access
|
||||
group: Database Frameworks
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
title: Delete existing migrations
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
stage: Data Access
|
||||
group: Database
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
title: Efficient `IN` operator queries
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
stage: Plan
|
||||
group: Project Management
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
title: Filtering by label
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
stage: Data Access
|
||||
group: Database Frameworks
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
title: Foreign keys and associations
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
stage: Data Access
|
||||
group: Database Frameworks
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
title: Keyset pagination
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
stage: Data Access
|
||||
group: Database Frameworks
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
title: Database load balancing
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
stage: Data Access
|
||||
group: Database Frameworks
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
title: Post Deployment Migrations
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
stage: Data Access
|
||||
group: Database Frameworks
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
title: Rename table without downtime
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
stage: Data Access
|
||||
group: Database Frameworks
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
title: Swapping Tables
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
stage: Data Access
|
||||
group: Database Frameworks
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
title: Verifying Database Capabilities
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
title: Development seed files
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
title: Guidelines for implementing Enterprise Edition features
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
stage: Plan
|
||||
group: Project Management
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
title: Issuable-like Rails models utilities
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -11,4 +11,4 @@ This document was moved to [another location](../../editor_extensions/language_s
|
|||
<!-- This redirect file can be deleted after <2025-02-11>. -->
|
||||
<!-- Redirects that point to other docs in the same project expire in three months. -->
|
||||
<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. -->
|
||||
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
|
||||
<!-- Before deletion, see: https://docs.gitlab.com/development/documentation/redirects/ -->
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
stage: Create
|
||||
group: Code Review
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
description: Developer information explaining terminology and features used in merge requests.
|
||||
title: Merge request concepts
|
||||
---
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
stage: Create
|
||||
group: Code Review
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
description: Developer documentation explaining the design and workflow of merge request approval rules.
|
||||
title: Approval rules development guidelines
|
||||
---
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
stage: Create
|
||||
group: Code Review
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
description: Developer documentation for how diffs are generated and rendered in GitLab.
|
||||
title: Working with diffs
|
||||
---
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
stage: Create
|
||||
group: Code Review
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
description: Developer documentation for the backend design and flow of merge request diffs.
|
||||
title: Merge request diffs development guide
|
||||
---
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
stage: Create
|
||||
group: Code Review
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
description: Developer documentation explaining how the different parts of the Vue-based frontend diffs are generated.
|
||||
title: Merge request diffs frontend overview
|
||||
---
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
stage: Create
|
||||
group: Code Review
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
description: Developer information explaining the process to add a new mergeability check
|
||||
title: Mergeability framework
|
||||
---
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
title: Merge Request Performance Guidelines
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
stage: Create
|
||||
group: Source Code
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
title: Application and rate limit guidelines
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
stage: Growth
|
||||
group: Acquisition
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
title: Product Qualified Lead (PQL) development guidelines
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
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.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review.
|
||||
title: RuboCop rule development guidelines
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1007,6 +1007,7 @@ An automated process migrates results from previous scanners after the first sca
|
|||
|
||||
Because it scans your project in more detail, Advanced SAST may take more time to scan your project.
|
||||
If needed, you can [disable GitLab Advanced SAST](https://docs.gitlab.com/user/application_security/sast/gitlab_advanced_sast#disable-gitlab-advanced-sast-scanning) by setting the CI/CD variable `GITLAB_ADVANCED_SAST_ENABLED` to `false`.
|
||||
You can set this variable in your project, group, or policy now to prevent Advanced SAST from being enabled by default in GitLab 18.0.
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.5 KiB |
|
|
@ -1169,7 +1169,7 @@ Do not change it back to a markdown codeblocks.
|
|||
-->
|
||||
|
||||
<!--
|
||||
DO NOT change the name of markdown_logo.png. This file is used for a test in
|
||||
DO NOT change the name of markdown_logo_v17_11.png. This file is used for a test in
|
||||
spec/controllers/help_controller_spec.rb.
|
||||
-->
|
||||
|
||||
|
|
@ -1182,18 +1182,18 @@ Inline-style:
|
|||
|
||||
<!-- markdownlint-disable proper-names -->
|
||||
|
||||
<pre class="highlight"><code>
|
||||
<pre class="highlight"><code>
|
||||
</code></pre>
|
||||
|
||||

|
||||

|
||||
|
||||
Reference-style:
|
||||
|
||||
<pre class="highlight"><code>![alt text1][logo]
|
||||
[logo]: img/markdown_logo.png "Title Text"
|
||||
[logo]: img/markdown_logo_v17_11.png "Title Text"
|
||||
</code></pre>
|
||||
|
||||

|
||||

|
||||
|
||||
<!-- markdownlint-enable proper-names -->
|
||||
|
||||
|
|
@ -1230,12 +1230,12 @@ The value must an integer with a unit of either `px` (default) or `%`.
|
|||
For example
|
||||
|
||||
```markdown
|
||||
{width=100 height=100px}
|
||||
{width=100 height=100px}
|
||||
|
||||
{width=75%}
|
||||
{width=75%}
|
||||
```
|
||||
|
||||
{width=100 height=100px}
|
||||
{width=100 height=100px}
|
||||
|
||||
You can also use the `img` HTML tag instead of Markdown and set its `height` and
|
||||
`width` parameters.
|
||||
|
|
|
|||
|
|
@ -17,13 +17,18 @@ rspec:
|
|||
- name: postgres:${POSTGRES_VERSION}
|
||||
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
||||
before_script:
|
||||
- apt update && apt install -y postgresql-client
|
||||
- psql -h postgres -U $POSTGRES_USER -c 'create database gitlabhq_test;'
|
||||
- psql -h postgres -U $POSTGRES_USER -c 'create database gitlabhq_ci_test;'
|
||||
- cp gems/gitlab-backup-cli/spec/fixtures/config/database.yml config/
|
||||
- "sed -i \"s/username: postgres$/username: $POSTGRES_USER/g\" config/database.yml"
|
||||
- "sed -i \"s/password:\\s*$/password: $POSTGRES_PASSWORD/g\" config/database.yml"
|
||||
- "sed -i \"s/host: localhost$/host: postgres/g\" config/database.yml"
|
||||
- apt update && apt install -y postgresql-client
|
||||
- psql -h postgres -U $POSTGRES_USER -c 'create database gitlabhq_test;'
|
||||
- psql -h postgres -U $POSTGRES_USER -c 'create database gitlabhq_ci_test;'
|
||||
- |
|
||||
cd gems/gitlab-backup-cli/spec/fixtures/gitlab_fake &&
|
||||
[ -n "$BUNDLE_GEMFILE" ] && mv Gemfile ${BUNDLE_GEMFILE} && mv Gemfile.lock ${BUNDLE_GEMFILE}.lock
|
||||
- bundle install --retry=3
|
||||
- cd -
|
||||
- !reference [.default, before_script]
|
||||
script:
|
||||
- RAILS_ENV=test bundle exec rspec
|
||||
|
|
|
|||
|
|
@ -11,3 +11,6 @@ Rails/Exit:
|
|||
RSpec/MultipleMemoizedHelpers:
|
||||
Max: 25
|
||||
AllowSubject: true
|
||||
|
||||
Rails/RakeEnvironment:
|
||||
Enabled: false
|
||||
|
|
|
|||
|
|
@ -10,3 +10,7 @@ require "rubocop/rake_task"
|
|||
RuboCop::RakeTask.new
|
||||
|
||||
task default: %i[spec rubocop]
|
||||
|
||||
task :version do |_|
|
||||
puts Gitlab::Backup::Cli::VERSION
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ module Gitlab
|
|||
module Cli
|
||||
module Errors
|
||||
autoload :DatabaseBackupError, 'gitlab/backup/cli/errors/database_backup_error'
|
||||
autoload :DatabaseCleanupError, 'gitlab/backup/cli/errors/database_cleanup_error'
|
||||
autoload :DatabaseConfigMissingError, 'gitlab/backup/cli/errors/database_config_missing_error'
|
||||
autoload :DatabaseMissingConnectionError, 'gitlab/backup/cli/errors/database_missing_connection_error'
|
||||
autoload :FileBackupError, 'gitlab/backup/cli/errors/file_backup_error'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Backup
|
||||
module Cli
|
||||
module Errors
|
||||
class DatabaseCleanupError < StandardError
|
||||
attr_reader :task, :path, :error
|
||||
|
||||
def initialize(task:, path:, error:)
|
||||
@task = task
|
||||
@path = path
|
||||
@error = error
|
||||
|
||||
super(build_message)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_message
|
||||
"Failed to cleanup GitLab databases \n" \
|
||||
"Running the following rake task: '#{task}' (from: #{path}) failed:\n" \
|
||||
"#{error}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -18,6 +18,10 @@ module Gitlab
|
|||
].freeze
|
||||
IGNORED_ERRORS_REGEXP = Regexp.union(IGNORED_ERRORS).freeze
|
||||
|
||||
# Rake task used to drop all tables from GitLab databases
|
||||
# This task is executed before restoring data
|
||||
DROP_TABLES_TASK = "gitlab:db:drop_tables"
|
||||
|
||||
attr_reader :errors
|
||||
|
||||
def initialize(context)
|
||||
|
|
@ -66,6 +70,10 @@ module Gitlab
|
|||
def restore(source)
|
||||
databases = Gitlab::Backup::Cli::Services::Postgres.new(context)
|
||||
|
||||
# Drop all tables Load the schema to ensure we don't have any newer tables
|
||||
# hanging out from a failed upgrade
|
||||
drop_tables!
|
||||
|
||||
databases.each do |db|
|
||||
database_name = db.configuration.name
|
||||
pg_database_name = db.configuration.database
|
||||
|
|
@ -89,10 +97,6 @@ module Gitlab
|
|||
next
|
||||
end
|
||||
|
||||
# Drop all tables Load the schema to ensure we don't have any newer tables
|
||||
# hanging out from a failed upgrade
|
||||
drop_tables(db)
|
||||
|
||||
Gitlab::Backup::Cli::Output.info "Restoring PostgreSQL database #{pg_database_name} ... "
|
||||
|
||||
status = restore_tables(database: db, filepath: db_file_name)
|
||||
|
|
@ -151,18 +155,22 @@ module Gitlab
|
|||
Gitlab::Backup::Cli::Output.print_tag(status ? :success : :failure)
|
||||
end
|
||||
|
||||
def drop_tables(database)
|
||||
pg_database_name = database.configuration.database
|
||||
Gitlab::Backup::Cli::Output.print_info "Cleaning the '#{pg_database_name}' database ... "
|
||||
def drop_tables!
|
||||
Gitlab::Backup::Cli::Output.print_info "Cleaning existing databases ... "
|
||||
|
||||
if Rake::Task.task_defined? "gitlab:db:drop_tables:#{database.configuration.name}"
|
||||
Rake::Task["gitlab:db:drop_tables:#{database.configuration.name}"].invoke
|
||||
else
|
||||
# In single database (single or two connections)
|
||||
Rake::Task["gitlab:db:drop_tables"].invoke
|
||||
gitlab_path = context.gitlab_basepath
|
||||
|
||||
# Drop existing tables from configured databases before restoring from a backup
|
||||
rake = Utils::Rake.new(DROP_TABLES_TASK, chdir: gitlab_path).execute
|
||||
|
||||
unless rake.success?
|
||||
Gitlab::Backup::Cli::Output.print_tag(:failure)
|
||||
|
||||
raise Errors::DatabaseCleanupError.new(task: DROP_TABLES_TASK, path: gitlab_path, error: rake.stderr)
|
||||
end
|
||||
|
||||
Gitlab::Backup::Cli::Output.print_tag(:success)
|
||||
Gitlab::Backup::Cli::Output.info(rake.output) unless rake.output.empty?
|
||||
end
|
||||
|
||||
def restore_tables(database:, filepath:)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ module Gitlab
|
|||
module Utils
|
||||
autoload :Compression, 'gitlab/backup/cli/utils/compression'
|
||||
autoload :PgDump, 'gitlab/backup/cli/utils/pg_dump'
|
||||
autoload :Rake, 'gitlab/backup/cli/utils/rake'
|
||||
autoload :Tar, 'gitlab/backup/cli/utils/tar'
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Backup
|
||||
module Cli
|
||||
module Utils
|
||||
class Rake
|
||||
# @return [Array<String>] a list of tasks to be executed
|
||||
attr_reader :tasks
|
||||
|
||||
# @return [String|Pathname] a path where rake tasks are run from
|
||||
attr_reader :chdir
|
||||
|
||||
# @param [Array<String>] *tasks a list of tasks to be executed
|
||||
# @param [String|Pathname] chdir a path where rake tasks are run from
|
||||
def initialize(*tasks, chdir: Gitlab::Backup::Cli.root)
|
||||
@tasks = tasks
|
||||
@chdir = chdir
|
||||
end
|
||||
|
||||
# @return [self]
|
||||
def execute
|
||||
Bundler.with_original_env do
|
||||
@result = Shell::Command.new(*rake_command, chdir: chdir).capture
|
||||
end
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
# Return whether the execution was a success or not
|
||||
#
|
||||
# @return [Boolean] whether the execution was a success
|
||||
def success?
|
||||
@result&.status&.success? || false
|
||||
end
|
||||
|
||||
# Return the captured rake output
|
||||
#
|
||||
# @return [String] stdout content
|
||||
def output
|
||||
@result&.stdout || ''
|
||||
end
|
||||
|
||||
# Return the captured error content
|
||||
#
|
||||
# @return [String] stdout content
|
||||
def stderr
|
||||
@result&.stderr || ''
|
||||
end
|
||||
|
||||
# Return the captured execution duration
|
||||
#
|
||||
# @return [Float] execution duration
|
||||
def duration
|
||||
@result&.duration || 0.0
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Return a list of commands necessary to execute `rake`
|
||||
#
|
||||
# @return [Array<String (frozen)>] array of commands to be used by Shellout
|
||||
def rake_command
|
||||
%w[bundle exec rake] + tasks
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
source "https://rubygems.org"
|
||||
|
||||
gem 'rake', '~> 13.0'
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
rake (13.2.1)
|
||||
|
||||
PLATFORMS
|
||||
aarch64-linux
|
||||
arm64-darwin
|
||||
ruby
|
||||
x86-linux
|
||||
x86_64-darwin
|
||||
x86_64-linux
|
||||
|
||||
DEPENDENCIES
|
||||
rake (~> 13.0)
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.22
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
namespace :gitlab do
|
||||
namespace :db do
|
||||
task :drop_tables do |_|
|
||||
exit 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
task :current_pwd do |_|
|
||||
puts Dir.getwd
|
||||
end
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Gitlab::Backup::Cli::Errors::DatabaseCleanupError do
|
||||
let(:task) { 'gitlab:task' }
|
||||
let(:path) { fixtures_path }
|
||||
let(:error) { 'error message from task execution' }
|
||||
|
||||
subject(:database_error) { described_class.new(task: task, path: path, error: error) }
|
||||
|
||||
describe '#initialize' do
|
||||
it 'sets task, path and error attributes' do
|
||||
expect(database_error.path).to eq(path)
|
||||
expect(database_error.task).to eq(task)
|
||||
expect(database_error.error).to eq(error)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,16 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Gitlab::Backup::Cli::Services::Database do
|
||||
let(:database_yml) { YAML.load_file(fixtures_path.join('config/database.yml'), aliases: true) }
|
||||
let(:context) { build_test_context }
|
||||
let(:connection) { database.send(:connection) }
|
||||
let(:mocked_configuration) do
|
||||
database_yml = YAML.load_file(fixtures_path.join('config/database.yml'), aliases: true)
|
||||
ActiveRecord::DatabaseConfigurations.new(database_yml).configs_for(env_name: 'test', include_hidden: false).first
|
||||
end
|
||||
|
||||
let(:test_configuration) do
|
||||
Gitlab::Backup::Cli::Services::Postgres.new(build_test_context).send(:database_configurations).first
|
||||
Gitlab::Backup::Cli::Services::Postgres.new(context).send(:database_configurations).first
|
||||
end
|
||||
|
||||
let(:connection) { database.send(:connection) }
|
||||
after do
|
||||
context.cleanup!
|
||||
end
|
||||
|
||||
context 'with mocked configuration' do
|
||||
subject(:database) { described_class.new(mocked_configuration) }
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@ RSpec.describe Gitlab::Backup::Cli::Services::Postgres do
|
|||
|
||||
subject(:postgres) { described_class.new(context) }
|
||||
|
||||
after do
|
||||
context.cleanup!
|
||||
end
|
||||
|
||||
describe '#entries' do
|
||||
context 'with missing database configuration' do
|
||||
it 'raises an error' do
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@ RSpec.describe Gitlab::Backup::Cli::Targets::Database do
|
|||
let(:database) { described_class.new(context) }
|
||||
let(:pipeline_success) { instance_double(Gitlab::Backup::Cli::Shell::Pipeline::Result, success?: true) }
|
||||
|
||||
after do
|
||||
context.cleanup!
|
||||
end
|
||||
|
||||
describe '#dump', :silence_output do
|
||||
let(:destination) { Pathname(Dir.mktmpdir('database-target', temp_path)) }
|
||||
|
||||
|
|
@ -17,7 +21,7 @@ RSpec.describe Gitlab::Backup::Cli::Targets::Database do
|
|||
it 'creates the destination directory' do
|
||||
mock_database_dump!
|
||||
|
||||
expect(FileUtils).to receive(:mkdir_p).with(destination)
|
||||
expect(destination).to be_directory
|
||||
|
||||
database.dump(destination)
|
||||
end
|
||||
|
|
@ -99,18 +103,16 @@ RSpec.describe Gitlab::Backup::Cli::Targets::Database do
|
|||
pipeline_success
|
||||
)
|
||||
|
||||
mock_databases_collection('main') do |db|
|
||||
mock_databases_collection('main') do |_|
|
||||
FileUtils.touch(source.join('database.sql.gz'))
|
||||
|
||||
expect(database).to receive(:drop_tables).with(db)
|
||||
end
|
||||
|
||||
expect(database).to receive(:drop_tables!)
|
||||
|
||||
database.restore(source)
|
||||
end
|
||||
|
||||
it 'restores the database' do
|
||||
allow(database).to receive(:drop_tables)
|
||||
|
||||
mock_databases_collection('main') do |db|
|
||||
filepath = source.join('database.sql.gz')
|
||||
FileUtils.touch(filepath)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,119 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Gitlab::Backup::Cli::Utils::Rake do
|
||||
subject(:rake) { described_class.new('version') }
|
||||
|
||||
describe '#execute' do
|
||||
it 'clears out bundler environment' do
|
||||
expect(Bundler).to receive(:with_original_env).and_yield
|
||||
|
||||
rake.execute
|
||||
end
|
||||
|
||||
it 'runs rake using bundle exec' do
|
||||
expect_next_instance_of(Gitlab::Backup::Cli::Shell::Command) do |shell|
|
||||
expect(shell.cmd_args).to start_with(%w[bundle exec rake])
|
||||
end
|
||||
|
||||
rake.execute
|
||||
end
|
||||
|
||||
it 'runs rake command with the defined tasks' do
|
||||
expect_next_instance_of(Gitlab::Backup::Cli::Shell::Command) do |shell|
|
||||
expect(shell.cmd_args).to end_with(%w[version])
|
||||
end
|
||||
|
||||
rake.execute
|
||||
|
||||
expect(rake.success?).to eq(true)
|
||||
end
|
||||
|
||||
context 'when chdir is set' do
|
||||
let(:tmpdir) { Dir.mktmpdir }
|
||||
|
||||
after do
|
||||
FileUtils.rm_rf(tmpdir)
|
||||
end
|
||||
|
||||
subject(:rake) { described_class.new('current_pwd', chdir: tmpdir) }
|
||||
|
||||
it 'runs rake in the provided chdir directory' do
|
||||
expect_next_instance_of(Gitlab::Backup::Cli::Shell::Command) do |shell|
|
||||
expect(shell.chdir).to eq(tmpdir)
|
||||
end
|
||||
|
||||
FileUtils.cp_r(fixtures_path.join('gitlab_fake').glob('*'), tmpdir)
|
||||
|
||||
rake.execute
|
||||
|
||||
expect(rake.success?).to eq(true)
|
||||
expect(rake.output).to match(/#{tmpdir}/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#success?' do
|
||||
subject(:rake) { described_class.new('--version') } # valid command that has no side-effect
|
||||
|
||||
context 'with a successful rake execution' do
|
||||
it 'returns true' do
|
||||
rake.execute
|
||||
|
||||
expect(rake.success?).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a failed rake execution', :hide_output do
|
||||
subject(:invalid_rake) { described_class.new('--invalid') } # valid command that has no side-effect
|
||||
|
||||
it 'returns false when a previous execution failed' do
|
||||
invalid_rake.execute
|
||||
|
||||
expect(invalid_rake.duration).to be > 0.0
|
||||
expect(invalid_rake.success?).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns false when no execution was done before' do
|
||||
expect(rake.success?).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
describe '#output' do
|
||||
it 'returns the output from running a rake task' do
|
||||
rake.execute
|
||||
|
||||
expect(rake.output).to match(Gitlab::Backup::Cli::VERSION)
|
||||
end
|
||||
|
||||
it 'returns an empty string when the task has not been run' do
|
||||
expect(rake.output).to eq('')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#stderr' do
|
||||
subject(:invalid_rake) { described_class.new('--invalid') } # valid command that has no side-effect
|
||||
|
||||
it 'returns the content from stderr when available' do
|
||||
invalid_rake.execute
|
||||
|
||||
expect(invalid_rake.stderr).to match('invalid option: --invalid')
|
||||
end
|
||||
|
||||
it 'returns an empty string when the task has not been run' do
|
||||
expect(invalid_rake.stderr).to eq('')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#duration' do
|
||||
it 'returns a duration time' do
|
||||
rake.execute
|
||||
|
||||
expect(rake.duration).to be > 0.0
|
||||
end
|
||||
|
||||
it 'returns 0.0 when the task has not been run' do
|
||||
expect(rake.duration).to eq(0.0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -34,7 +34,16 @@ module GitlabBackupHelpers
|
|||
end
|
||||
|
||||
def build_test_context
|
||||
TestContext.new
|
||||
TestContext.new.tap do |context|
|
||||
# config/database.yml
|
||||
db = context.gitlab_original_basepath.join('config/database.yml')
|
||||
test_db = context.gitlab_basepath.join('config/database.yml')
|
||||
FileUtils.mkdir_p(File.dirname(test_db))
|
||||
FileUtils.copy(db, test_db)
|
||||
|
||||
# Mocked Rakefile and Gemfile
|
||||
FileUtils.cp_r(fixtures_path.join('gitlab_fake').glob('*'), context.gitlab_basepath)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -2,11 +2,22 @@
|
|||
|
||||
class TestContext < Gitlab::Backup::Cli::Context::SourceContext
|
||||
def gitlab_basepath
|
||||
test_helpers.spec_path.join('../../..')
|
||||
@gitlab_basepath ||= Pathname(Dir.mktmpdir('gitlab', test_helpers.temp_path))
|
||||
end
|
||||
|
||||
def backup_basedir
|
||||
test_helpers.temp_path.join('backups')
|
||||
gitlab_basepath.join('backups')
|
||||
end
|
||||
|
||||
def gitlab_original_basepath
|
||||
test_helpers.spec_path.join('../../..')
|
||||
end
|
||||
|
||||
# Deletes the temporary folders
|
||||
def cleanup!
|
||||
dir_permissions = (File.stat(gitlab_basepath).mode & 0o777).to_s(8) # retrieve permissions in octal format)
|
||||
|
||||
FileUtils.rm_rf(gitlab_basepath) if dir_permissions == "700" # ensure it's a temporary dir before deleting
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
|||
|
|
@ -1,97 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module BackgroundMigration
|
||||
# Class that fixes the incorrectly set authored_date within
|
||||
# issue_metrics table
|
||||
class FixFirstMentionedInCommitAt
|
||||
SUB_BATCH_SIZE = 500
|
||||
|
||||
class TmpIssueMetrics < ActiveRecord::Base
|
||||
include EachBatch
|
||||
|
||||
self.table_name = 'issue_metrics'
|
||||
|
||||
def self.from_2020
|
||||
where(first_mentioned_in_commit_at_condition)
|
||||
end
|
||||
|
||||
def self.first_mentioned_in_commit_at_condition
|
||||
if columns_hash['first_mentioned_in_commit_at'].sql_type == 'timestamp without time zone'
|
||||
'EXTRACT(YEAR FROM first_mentioned_in_commit_at) > 2019'
|
||||
else
|
||||
"EXTRACT(YEAR FROM first_mentioned_in_commit_at at time zone 'UTC') > 2019"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def perform(start_id, end_id)
|
||||
scope(start_id, end_id).each_batch(of: SUB_BATCH_SIZE, column: :issue_id) do |sub_batch|
|
||||
first, last = sub_batch.pick(Arel.sql('min(issue_id), max(issue_id)'))
|
||||
|
||||
# The query need to be reconstructed because .each_batch modifies the default scope
|
||||
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/330510
|
||||
inner_query = TmpIssueMetrics
|
||||
.unscoped
|
||||
.merge(scope(first, last))
|
||||
.from("issue_metrics, #{lateral_query}")
|
||||
.select('issue_metrics.issue_id', 'first_authored_date.authored_date')
|
||||
.where('issue_metrics.first_mentioned_in_commit_at > first_authored_date.authored_date')
|
||||
|
||||
TmpIssueMetrics.connection.execute <<~UPDATE_METRICS
|
||||
WITH cte AS MATERIALIZED (
|
||||
#{inner_query.to_sql}
|
||||
)
|
||||
UPDATE issue_metrics
|
||||
SET
|
||||
first_mentioned_in_commit_at = cte.authored_date
|
||||
FROM
|
||||
cte
|
||||
WHERE
|
||||
cte.issue_id = issue_metrics.issue_id
|
||||
UPDATE_METRICS
|
||||
end
|
||||
|
||||
mark_job_as_succeeded(start_id, end_id)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def mark_job_as_succeeded(*arguments)
|
||||
Gitlab::Database::BackgroundMigrationJob.mark_all_as_succeeded(
|
||||
'FixFirstMentionedInCommitAt',
|
||||
arguments
|
||||
)
|
||||
end
|
||||
|
||||
def scope(start_id, end_id)
|
||||
TmpIssueMetrics.from_2020.where(issue_id: start_id..end_id)
|
||||
end
|
||||
|
||||
def lateral_query
|
||||
<<~SQL
|
||||
LATERAL (
|
||||
SELECT MIN(first_authored_date.authored_date) as authored_date
|
||||
FROM merge_requests_closing_issues,
|
||||
LATERAL (
|
||||
SELECT id
|
||||
FROM merge_request_diffs
|
||||
WHERE merge_request_id = merge_requests_closing_issues.merge_request_id
|
||||
ORDER BY id DESC
|
||||
LIMIT 1
|
||||
) last_diff_id,
|
||||
LATERAL (
|
||||
SELECT authored_date
|
||||
FROM merge_request_diff_commits
|
||||
WHERE
|
||||
merge_request_diff_id = last_diff_id.id
|
||||
ORDER BY relative_order DESC
|
||||
LIMIT 1
|
||||
) first_authored_date
|
||||
WHERE merge_requests_closing_issues.issue_id = issue_metrics.issue_id
|
||||
) first_authored_date
|
||||
SQL
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -60,6 +60,8 @@ module Gitlab
|
|||
end
|
||||
|
||||
def to_s
|
||||
return if text.blank?
|
||||
|
||||
# Gitlab::EncodingHelper#clean remove `null` chars from the string
|
||||
text = clean(format)
|
||||
text = convert_ref_links(text, project) if project.present?
|
||||
|
|
|
|||
|
|
@ -11251,6 +11251,9 @@ msgstr ""
|
|||
msgid "CICD|Auto DevOps"
|
||||
msgstr ""
|
||||
|
||||
msgid "CICD|Auto-populating allowlist entries. Please wait while the action completes."
|
||||
msgstr ""
|
||||
|
||||
msgid "CICD|Automatic deployment to staging, manual deployment to production"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -11371,7 +11374,7 @@ msgstr ""
|
|||
msgid "CICD|The allowlist can contain a maximum of %{projectAllowlistLimit} groups and projects."
|
||||
msgstr ""
|
||||
|
||||
msgid "CICD|The process to add entries could take a moment to complete with large logs or allowlists."
|
||||
msgid "CICD|The process might take a moment to complete for large authentication logs or allowlists."
|
||||
msgstr ""
|
||||
|
||||
msgid "CICD|There are several CI/CD limits in place."
|
||||
|
|
@ -11398,7 +11401,7 @@ msgstr ""
|
|||
msgid "CICD|When enabled, all projects must use their allowlist to control CI/CD job token access between projects. The option to allow access from all groups and projects is hidden. %{link_start}Learn More.%{link_end}"
|
||||
msgstr ""
|
||||
|
||||
msgid "CICD|You're about to add all entries from the authentication log to the allowlist for %{projectName}. Duplicate entries will be ignored."
|
||||
msgid "CICD|You're about to add all entries from the authentication log to the allowlist for %{projectName}. This will also update the Job Token setting to %{codeStart}This project and any groups and projects in the allowlist%{codeEnd}, if not already set. Duplicate entries will be ignored."
|
||||
msgstr ""
|
||||
|
||||
msgid "CICD|group enabled"
|
||||
|
|
@ -21671,6 +21674,18 @@ msgstr ""
|
|||
msgid "DuoChat|Give feedback"
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoChat|How do I change my password in GitLab?"
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoChat|How do I clone a repository?"
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoChat|How do I create a template?"
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoChat|How do I fork a project?"
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoChat|How to use GitLab"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -29280,24 +29295,12 @@ msgstr ""
|
|||
msgid "How can I make my variables more secure?"
|
||||
msgstr ""
|
||||
|
||||
msgid "How do I change my password in GitLab?"
|
||||
msgstr ""
|
||||
|
||||
msgid "How do I clone a repository?"
|
||||
msgstr ""
|
||||
|
||||
msgid "How do I configure Akismet?"
|
||||
msgstr ""
|
||||
|
||||
msgid "How do I configure this integration?"
|
||||
msgstr ""
|
||||
|
||||
msgid "How do I create a template?"
|
||||
msgstr ""
|
||||
|
||||
msgid "How do I fork a project?"
|
||||
msgstr ""
|
||||
|
||||
msgid "How do I generate it?"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -531,77 +531,6 @@ RSpec.describe GraphqlController, feature_category: :integrations do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'DPoP authentication' do
|
||||
context 'when :dpop_authentication FF is disabled' do
|
||||
let(:user) { create(:user, last_activity_on: last_activity_on) }
|
||||
let(:personal_access_token) { create(:personal_access_token, user: user, scopes: [:api]) }
|
||||
|
||||
it 'does not check for DPoP token' do
|
||||
stub_feature_flags(dpop_authentication: false)
|
||||
|
||||
post :execute, params: { access_token: personal_access_token.token }
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when :dpop_authentication FF is enabled' do
|
||||
before do
|
||||
stub_feature_flags(dpop_authentication: true)
|
||||
end
|
||||
|
||||
context 'when DPoP is disabled for the user' do
|
||||
let(:user) { create(:user, last_activity_on: last_activity_on) }
|
||||
let(:personal_access_token) { create(:personal_access_token, user: user, scopes: [:api]) }
|
||||
|
||||
it 'does not check for DPoP token' do
|
||||
post :execute, params: { access_token: personal_access_token.token }
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when DPoP is enabled for the user' do
|
||||
let_it_be(:user) { create(:user, last_activity_on: last_activity_on, dpop_enabled: true) }
|
||||
let_it_be(:personal_access_token) { create(:personal_access_token, user: user, scopes: [:api]) }
|
||||
let_it_be(:oauth_token) { create(:oauth_access_token, user: user, scopes: [:api]) }
|
||||
let_it_be(:dpop_proof) { generate_dpop_proof_for(user) }
|
||||
|
||||
context 'when API is called with an OAuth token' do
|
||||
it 'does not invoke DPoP' do
|
||||
request.headers["Authorization"] = "Bearer #{oauth_token.plaintext_token}"
|
||||
post :execute
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a missing DPoP token' do
|
||||
it 'returns 401' do
|
||||
post :execute, params: { access_token: personal_access_token.token }
|
||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||
expect(json_response["errors"][0]["message"]).to eq("DPoP validation error: DPoP header is missing")
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a valid DPoP token' do
|
||||
it 'returns 200' do
|
||||
request.headers["dpop"] = dpop_proof.proof
|
||||
post :execute, params: { access_token: personal_access_token.token }
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a malformed DPoP token' do
|
||||
it 'returns 401' do
|
||||
request.headers["dpop"] = "invalid"
|
||||
post :execute, params: { access_token: personal_access_token.token } # -- We need the entire error message
|
||||
expect(json_response["errors"][0]["message"])
|
||||
.to eq("DPoP validation error: Malformed JWT, unable to decode. Not enough or too many segments")
|
||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is not logged in' do
|
||||
it 'returns 200' do
|
||||
post :execute
|
||||
|
|
@ -782,6 +711,88 @@ RSpec.describe GraphqlController, feature_category: :integrations do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'DPoP authentication' do
|
||||
context 'when :dpop_authentication FF is disabled' do
|
||||
let(:user) { create(:user, last_activity_on: last_activity_on) }
|
||||
let(:personal_access_token) { create(:personal_access_token, user: user, scopes: [:api]) }
|
||||
|
||||
it 'does not check for DPoP token' do
|
||||
stub_feature_flags(dpop_authentication: false)
|
||||
|
||||
post :execute, params: { access_token: personal_access_token.token }
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when :dpop_authentication FF is enabled' do
|
||||
before do
|
||||
stub_feature_flags(dpop_authentication: true)
|
||||
end
|
||||
|
||||
context 'when DPoP is disabled for the user' do
|
||||
let(:user) { create(:user, last_activity_on: last_activity_on) }
|
||||
let(:personal_access_token) { create(:personal_access_token, user: user, scopes: [:api]) }
|
||||
|
||||
it 'does not check for DPoP token' do
|
||||
post :execute, params: { access_token: personal_access_token.token }
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when DPoP is enabled for the user' do
|
||||
let_it_be(:user) { create(:user, last_activity_on: last_activity_on, dpop_enabled: true) }
|
||||
let_it_be(:personal_access_token) { create(:personal_access_token, user: user, scopes: [:api]) }
|
||||
let_it_be(:oauth_token) { create(:oauth_access_token, user: user, scopes: [:api]) }
|
||||
let_it_be(:dpop_proof) { generate_dpop_proof_for(user) }
|
||||
|
||||
context 'when cookie-based authentication is used' do
|
||||
it 'does not invoke DPoP' do
|
||||
sign_in(user)
|
||||
expect(controller).not_to receive(:extract_personal_access_token)
|
||||
|
||||
post :execute
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when API is called with an OAuth token' do
|
||||
it 'does not invoke DPoP' do
|
||||
request.headers["Authorization"] = "Bearer #{oauth_token.plaintext_token}"
|
||||
post :execute
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a missing DPoP token' do
|
||||
it 'returns 401' do
|
||||
post :execute, params: { access_token: personal_access_token.token }
|
||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||
expect(json_response["errors"][0]["message"]).to eq("DPoP validation error: DPoP header is missing")
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a valid DPoP token' do
|
||||
it 'returns 200' do
|
||||
request.headers["dpop"] = dpop_proof.proof
|
||||
post :execute, params: { access_token: personal_access_token.token }
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a malformed DPoP token' do
|
||||
it 'returns 401' do
|
||||
request.headers["dpop"] = "invalid"
|
||||
post :execute, params: { access_token: personal_access_token.token } # -- We need the entire error message
|
||||
expect(json_response["errors"][0]["message"])
|
||||
.to eq("DPoP validation error: Malformed JWT, unable to decode. Not enough or too many segments")
|
||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Admin Mode' do
|
||||
|
|
|
|||
|
|
@ -349,7 +349,7 @@ RSpec.describe HelpController do
|
|||
context 'for image formats' do
|
||||
context 'when requested file exists' do
|
||||
it 'renders the raw file' do
|
||||
get :show, params: { path: 'user/img/markdown_logo' }, format: :png
|
||||
get :show, params: { path: 'user/img/markdown_logo_v17_11' }, format: :png
|
||||
|
||||
aggregate_failures do
|
||||
expect(response).to be_successful
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { GlLoadingIcon } from '@gitlab/ui';
|
|||
import { GlLineChart } from '@gitlab/ui/dist/charts';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import PipelineDurationChart from '~/projects/pipelines/charts/components/pipeline_duration_chart.vue';
|
||||
import { stubComponent } from 'helpers/stub_component';
|
||||
|
||||
describe('PipelineDurationChart', () => {
|
||||
let wrapper;
|
||||
|
|
@ -9,11 +10,12 @@ describe('PipelineDurationChart', () => {
|
|||
const findLineChart = () => wrapper.findComponent(GlLineChart);
|
||||
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
|
||||
|
||||
const createComponent = ({ props } = {}) => {
|
||||
const createComponent = ({ props, ...options } = {}) => {
|
||||
wrapper = shallowMount(PipelineDurationChart, {
|
||||
propsData: {
|
||||
...props,
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -75,4 +77,31 @@ describe('PipelineDurationChart', () => {
|
|||
},
|
||||
]);
|
||||
});
|
||||
|
||||
describe('formats tooltip', () => {
|
||||
const oneMinute = 60;
|
||||
const oneHour = 3600;
|
||||
const oneDay = oneHour * 24;
|
||||
|
||||
it.each`
|
||||
date | value | expectedTooltip
|
||||
${'2021-12-01'} | ${oneMinute} | ${'Dec 1, 2021 - 1m'}
|
||||
${'2022-12-15'} | ${oneHour + oneMinute} | ${'Dec 15, 2022 - 1h 1m'}
|
||||
${'2023-12-31'} | ${oneDay + oneHour + oneMinute} | ${'Dec 31, 2023 - 1d 1h 1m'}
|
||||
`('$expectedTooltip', ({ date, value, expectedTooltip }) => {
|
||||
createComponent({
|
||||
stubs: {
|
||||
GlLineChart: stubComponent(GlLineChart, {
|
||||
template: `<div>
|
||||
<slot name="tooltip-title" :params="{ value: '${date}' }"></slot>
|
||||
-
|
||||
<slot name="tooltip-value" :value="${value}"></slot>
|
||||
</div>`,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
expect(findLineChart().text()).toMatchInterpolatedText(expectedTooltip);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { GlLoadingIcon } from '@gitlab/ui';
|
|||
import { GlStackedColumnChart } from '@gitlab/ui/dist/charts';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import PipelineStatusChart from '~/projects/pipelines/charts/components/pipeline_status_chart.vue';
|
||||
import { stubComponent } from 'helpers/stub_component';
|
||||
|
||||
describe('PipelineStatusChart', () => {
|
||||
let wrapper;
|
||||
|
|
@ -9,11 +10,12 @@ describe('PipelineStatusChart', () => {
|
|||
const findStackedColumnChart = () => wrapper.findComponent(GlStackedColumnChart);
|
||||
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
|
||||
|
||||
const createComponent = ({ props } = {}) => {
|
||||
const createComponent = ({ props, ...options } = {}) => {
|
||||
wrapper = shallowMount(PipelineStatusChart, {
|
||||
propsData: {
|
||||
...props,
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -62,4 +64,25 @@ describe('PipelineStatusChart', () => {
|
|||
{ data: [30, 31], name: 'Other' },
|
||||
]);
|
||||
});
|
||||
|
||||
describe('formats tooltip', () => {
|
||||
it.each`
|
||||
date | expectedTooltip
|
||||
${'2021-12-01'} | ${'Dec 1, 2021'}
|
||||
${'2022-12-15'} | ${'Dec 15, 2022'}
|
||||
${'2023-12-31'} | ${'Dec 31, 2023'}
|
||||
`('$expectedTooltip', ({ date, expectedTooltip }) => {
|
||||
createComponent({
|
||||
stubs: {
|
||||
GlStackedColumnChart: stubComponent(GlStackedColumnChart, {
|
||||
template: `<div>
|
||||
<slot name="tooltip-title" :params="{ value: '${date}' }"></slot>
|
||||
</div>`,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
expect(findStackedColumnChart().text()).toMatchInterpolatedText(expectedTooltip);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,44 +1,22 @@
|
|||
import { GlAlert, GlLink, GlModal, GlSprintf } from '@gitlab/ui';
|
||||
import Vue, { nextTick } from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { createMockDirective } from 'helpers/vue_mock_directive';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import AutopopulateAllowlistMutation from '~/token_access/graphql/mutations/autopopulate_allowlist.mutation.graphql';
|
||||
import AutopopulateAllowlistModal from '~/token_access/components/autopopulate_allowlist_modal.vue';
|
||||
import { mockAutopopulateAllowlistResponse, mockAutopopulateAllowlistError } from './mock_data';
|
||||
|
||||
const projectName = 'My project';
|
||||
const fullPath = 'root/my-repo';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
const mockToastShow = jest.fn();
|
||||
|
||||
describe('AutopopulateAllowlistModal component', () => {
|
||||
let wrapper;
|
||||
let mockApollo;
|
||||
let mockAutopopulateMutation;
|
||||
|
||||
const findAlert = () => wrapper.findComponent(GlAlert);
|
||||
const findModal = () => wrapper.findComponent(GlModal);
|
||||
const findLink = () => wrapper.findComponent(GlLink);
|
||||
|
||||
const createComponent = ({ props } = {}) => {
|
||||
const handlers = [[AutopopulateAllowlistMutation, mockAutopopulateMutation]];
|
||||
mockApollo = createMockApollo(handlers);
|
||||
|
||||
wrapper = shallowMountExtended(AutopopulateAllowlistModal, {
|
||||
apolloProvider: mockApollo,
|
||||
provide: {
|
||||
fullPath,
|
||||
},
|
||||
mocks: {
|
||||
$toast: { show: mockToastShow },
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: createMockDirective('gl-tooltip'),
|
||||
},
|
||||
propsData: {
|
||||
authLogExceedsLimit: false,
|
||||
projectAllowlistLimit: 4,
|
||||
|
|
@ -52,10 +30,6 @@ describe('AutopopulateAllowlistModal component', () => {
|
|||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockAutopopulateMutation = jest.fn();
|
||||
});
|
||||
|
||||
describe('template', () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
|
|
@ -90,7 +64,7 @@ describe('AutopopulateAllowlistModal component', () => {
|
|||
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',
|
||||
'/help/ci/jobs/ci_job_token#allowlist-compaction',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -112,66 +86,15 @@ describe('AutopopulateAllowlistModal component', () => {
|
|||
);
|
||||
});
|
||||
|
||||
describe('when mutation is running', () => {
|
||||
beforeEach(() => {
|
||||
mockAutopopulateMutation.mockResolvedValue(mockAutopopulateAllowlistResponse);
|
||||
describe('when clicking on the primary button', () => {
|
||||
it('emits the remove-entries event', () => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('shows loading state for confirm button and disables cancel button', async () => {
|
||||
expect(findModal().props('actionPrimary').attributes).toMatchObject({ loading: false });
|
||||
expect(findModal().props('actionSecondary').attributes).toMatchObject({ disabled: false });
|
||||
expect(wrapper.emitted('autopopulate-allowlist')).toBeUndefined();
|
||||
|
||||
findModal().vm.$emit('primary', { preventDefault: jest.fn() });
|
||||
await nextTick();
|
||||
|
||||
expect(findModal().props('actionPrimary').attributes).toMatchObject({ loading: true });
|
||||
expect(findModal().props('actionSecondary').attributes).toMatchObject({ disabled: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe('when mutation is successful', () => {
|
||||
beforeEach(async () => {
|
||||
mockAutopopulateMutation.mockResolvedValue(mockAutopopulateAllowlistResponse);
|
||||
|
||||
createComponent();
|
||||
findModal().vm.$emit('primary', { preventDefault: jest.fn() });
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('calls the mutation', () => {
|
||||
expect(mockAutopopulateMutation).toHaveBeenCalledWith({ projectPath: fullPath });
|
||||
});
|
||||
|
||||
it('shows toast message', () => {
|
||||
expect(mockToastShow).toHaveBeenCalledWith(
|
||||
'Authentication log entries were successfully added to the allowlist.',
|
||||
);
|
||||
});
|
||||
|
||||
it('emits events for refetching data and hiding modal', () => {
|
||||
expect(wrapper.emitted('refetch-allowlist')).toHaveLength(1);
|
||||
expect(wrapper.emitted('hide')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when mutation fails', () => {
|
||||
beforeEach(async () => {
|
||||
createComponent();
|
||||
findModal().vm.$emit('primary', { preventDefault: jest.fn() });
|
||||
await waitForPromises();
|
||||
|
||||
mockAutopopulateMutation.mockResolvedValue(mockAutopopulateAllowlistError);
|
||||
});
|
||||
|
||||
it('renders alert', () => {
|
||||
expect(findAlert().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('does not render toast message or emit events', () => {
|
||||
expect(mockToastShow).not.toHaveBeenCalledWith();
|
||||
expect(wrapper.emitted('refetch-allowlist')).toBeUndefined();
|
||||
expect(wrapper.emitted('hide')).toBeUndefined();
|
||||
expect(wrapper.emitted('autopopulate-allowlist')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import {
|
|||
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 autopopulateAllowlistMutation from '~/token_access/graphql/mutations/autopopulate_allowlist.mutation.graphql';
|
||||
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';
|
||||
|
|
@ -38,6 +39,7 @@ import {
|
|||
inboundRemoveNamespaceSuccess,
|
||||
inboundUpdateScopeSuccessResponse,
|
||||
mockAuthLogsCountResponse,
|
||||
mockAutopopulateAllowlistResponse,
|
||||
mockRemoveAutopopulatedEntriesResponse,
|
||||
} from './mock_data';
|
||||
|
||||
|
|
@ -53,6 +55,13 @@ describe('TokenAccess component', () => {
|
|||
let wrapper;
|
||||
|
||||
const authLogCountResponseHandler = jest.fn().mockResolvedValue(mockAuthLogsCountResponse(4));
|
||||
const authLogZeroCountResponseHandler = jest.fn().mockResolvedValue(mockAuthLogsCountResponse(0));
|
||||
const autopopulateAllowlistResponseHandler = jest
|
||||
.fn()
|
||||
.mockResolvedValue(mockAutopopulateAllowlistResponse());
|
||||
const autopopulateAllowlistResponseErrorHandler = jest
|
||||
.fn()
|
||||
.mockResolvedValue(mockAutopopulateAllowlistResponse({ errorMessage: message }));
|
||||
const inboundJobTokenScopeEnabledResponseHandler = jest
|
||||
.fn()
|
||||
.mockResolvedValue(inboundJobTokenScopeEnabledResponse);
|
||||
|
|
@ -61,7 +70,10 @@ describe('TokenAccess component', () => {
|
|||
.mockResolvedValue(inboundJobTokenScopeDisabledResponse);
|
||||
const inboundGroupsAndProjectsWithScopeResponseHandler = jest
|
||||
.fn()
|
||||
.mockResolvedValue(inboundGroupsAndProjectsWithScopeResponse);
|
||||
.mockResolvedValue(inboundGroupsAndProjectsWithScopeResponse(true));
|
||||
const inboundGroupsAndProjectsWithoutAutopopulatedEntriesResponseHandler = jest
|
||||
.fn()
|
||||
.mockResolvedValue(inboundGroupsAndProjectsWithScopeResponse(false));
|
||||
const inboundRemoveGroupSuccessHandler = jest
|
||||
.fn()
|
||||
.mockResolvedValue(inboundRemoveNamespaceSuccess);
|
||||
|
|
@ -474,7 +486,9 @@ describe('TokenAccess component', () => {
|
|||
inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery,
|
||||
inboundGroupsAndProjectsWithScopeResponseHandler,
|
||||
],
|
||||
[autopopulateAllowlistMutation, autopopulateAllowlistResponseHandler],
|
||||
[removeAutopopulatedEntriesMutation, removeAutopopulatedEntriesMutationHandler],
|
||||
[getAuthLogCountQuery, authLogCountResponseHandler],
|
||||
],
|
||||
{
|
||||
authenticationLogsMigrationForAllowlist: true,
|
||||
|
|
@ -515,15 +529,112 @@ describe('TokenAccess component', () => {
|
|||
expect(findFormSelector().props('selected')).toBe(null);
|
||||
});
|
||||
|
||||
it('refetches allowlist when autopopulate mutation is successful', async () => {
|
||||
expect(inboundGroupsAndProjectsWithScopeResponseHandler).toHaveBeenCalledTimes(1);
|
||||
it('shows loading state while autopopulating entries', async () => {
|
||||
expect(findCountLoadingIcon().exists()).toBe(false);
|
||||
expect(findTokenAccessTable().props('loading')).toBe(false);
|
||||
|
||||
findFormSelector().vm.$emit('select', JOB_TOKEN_FORM_AUTOPOPULATE_AUTH_LOG);
|
||||
findAutopopulateAllowlistModal().vm.$emit('refetch-allowlist');
|
||||
findAutopopulateAllowlistModal().vm.$emit('autopopulate-allowlist');
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(findCountLoadingIcon().exists()).toBe(true);
|
||||
expect(findTokenAccessTable().props('loading')).toBe(true);
|
||||
expect(findTokenAccessTable().props('loadingMessage')).toBe(
|
||||
'Auto-populating allowlist entries. Please wait while the action completes.',
|
||||
);
|
||||
});
|
||||
|
||||
it('resets loading state after autopopulating entries', async () => {
|
||||
findFormSelector().vm.$emit('select', JOB_TOKEN_FORM_AUTOPOPULATE_AUTH_LOG);
|
||||
findAutopopulateAllowlistModal().vm.$emit('autopopulate-allowlist');
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(findTokenAccessTable().props('loadingMessage')).toBe(
|
||||
'Auto-populating allowlist entries. Please wait while the action completes.',
|
||||
);
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findCountLoadingIcon().exists()).toBe(false);
|
||||
expect(findTokenAccessTable().props('loading')).toBe(false);
|
||||
expect(findTokenAccessTable().props('loadingMessage')).toBe('');
|
||||
});
|
||||
|
||||
it('calls the autopopulate allowlist mutation and refetches allowlist and job token setting', async () => {
|
||||
expect(autopopulateAllowlistResponseHandler).toHaveBeenCalledTimes(0);
|
||||
expect(inboundGroupsAndProjectsWithScopeResponseHandler).toHaveBeenCalledTimes(1);
|
||||
expect(inboundJobTokenScopeEnabledResponseHandler).toHaveBeenCalledTimes(1);
|
||||
|
||||
findFormSelector().vm.$emit('select', JOB_TOKEN_FORM_AUTOPOPULATE_AUTH_LOG);
|
||||
findAutopopulateAllowlistModal().vm.$emit('autopopulate-allowlist');
|
||||
await waitForPromises();
|
||||
await nextTick();
|
||||
|
||||
expect(autopopulateAllowlistResponseHandler).toHaveBeenCalledTimes(1);
|
||||
expect(inboundGroupsAndProjectsWithScopeResponseHandler).toHaveBeenCalledTimes(2);
|
||||
expect(findFormSelector().props('selected')).toBe(null);
|
||||
expect(inboundJobTokenScopeEnabledResponseHandler).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('shows error alert when mutation returns an error', async () => {
|
||||
createComponent(
|
||||
[
|
||||
[inboundGetCIJobTokenScopeQuery, inboundJobTokenScopeEnabledResponseHandler],
|
||||
[
|
||||
inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery,
|
||||
inboundGroupsAndProjectsWithScopeResponseHandler,
|
||||
],
|
||||
[autopopulateAllowlistMutation, autopopulateAllowlistResponseErrorHandler],
|
||||
[getAuthLogCountQuery, authLogCountResponseHandler],
|
||||
],
|
||||
{
|
||||
authenticationLogsMigrationForAllowlist: true,
|
||||
stubs: { CrudComponent, GlDisclosureDropdown, GlDisclosureDropdownItem },
|
||||
},
|
||||
);
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findAutopopulationAlert().exists()).toBe(false);
|
||||
|
||||
findFormSelector().vm.$emit('select', JOB_TOKEN_FORM_AUTOPOPULATE_AUTH_LOG);
|
||||
findAutopopulateAllowlistModal().vm.$emit('autopopulate-allowlist');
|
||||
await waitForPromises();
|
||||
await nextTick();
|
||||
|
||||
expect(findAutopopulationAlert().text()).toBe('An error occurred');
|
||||
});
|
||||
|
||||
it('shows error alert when mutation fails', async () => {
|
||||
createComponent(
|
||||
[
|
||||
[inboundGetCIJobTokenScopeQuery, inboundJobTokenScopeEnabledResponseHandler],
|
||||
[
|
||||
inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery,
|
||||
inboundGroupsAndProjectsWithScopeResponseHandler,
|
||||
],
|
||||
[autopopulateAllowlistMutation, failureHandler],
|
||||
[getAuthLogCountQuery, authLogCountResponseHandler],
|
||||
],
|
||||
{
|
||||
authenticationLogsMigrationForAllowlist: true,
|
||||
stubs: { CrudComponent, GlDisclosureDropdown, GlDisclosureDropdownItem },
|
||||
},
|
||||
);
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findAutopopulationAlert().exists()).toBe(false);
|
||||
|
||||
findFormSelector().vm.$emit('select', JOB_TOKEN_FORM_AUTOPOPULATE_AUTH_LOG);
|
||||
findAutopopulateAllowlistModal().vm.$emit('autopopulate-allowlist');
|
||||
await waitForPromises();
|
||||
await nextTick();
|
||||
|
||||
expect(findAutopopulationAlert().text()).toBe(
|
||||
'An error occurred while adding the authentication log entries. Please try again.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -561,6 +672,21 @@ describe('TokenAccess component', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('resets loading state after removing autopopulated entries', async () => {
|
||||
triggerRemoveEntries();
|
||||
await nextTick();
|
||||
|
||||
expect(findTokenAccessTable().props('loadingMessage')).toBe(
|
||||
'Removing auto-added allowlist entries. Please wait while the action completes.',
|
||||
);
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findCountLoadingIcon().exists()).toBe(false);
|
||||
expect(findTokenAccessTable().props('loading')).toBe(false);
|
||||
expect(findTokenAccessTable().props('loadingMessage')).toBe('');
|
||||
});
|
||||
|
||||
it('calls the remove autopopulated entries mutation and refetches allowlist', async () => {
|
||||
expect(removeAutopopulatedEntriesMutationHandler).toHaveBeenCalledTimes(0);
|
||||
expect(inboundGroupsAndProjectsWithScopeResponseHandler).toHaveBeenCalledTimes(1);
|
||||
|
|
@ -592,6 +718,7 @@ describe('TokenAccess component', () => {
|
|||
inboundGroupsAndProjectsWithScopeResponseHandler,
|
||||
],
|
||||
[removeAutopopulatedEntriesMutation, removeAutopopulatedEntriesMutationErrorHandler],
|
||||
[getAuthLogCountQuery, authLogCountResponseHandler],
|
||||
],
|
||||
{
|
||||
authenticationLogsMigrationForAllowlist: true,
|
||||
|
|
@ -599,6 +726,8 @@ describe('TokenAccess component', () => {
|
|||
},
|
||||
);
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findAutopopulationAlert().exists()).toBe(false);
|
||||
|
||||
triggerRemoveEntries();
|
||||
|
|
@ -617,6 +746,7 @@ describe('TokenAccess component', () => {
|
|||
inboundGroupsAndProjectsWithScopeResponseHandler,
|
||||
],
|
||||
[removeAutopopulatedEntriesMutation, failureHandler],
|
||||
[getAuthLogCountQuery, authLogCountResponseHandler],
|
||||
],
|
||||
{
|
||||
authenticationLogsMigrationForAllowlist: true,
|
||||
|
|
@ -624,6 +754,8 @@ describe('TokenAccess component', () => {
|
|||
},
|
||||
);
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findAutopopulationAlert().exists()).toBe(false);
|
||||
|
||||
triggerRemoveEntries();
|
||||
|
|
@ -652,6 +784,39 @@ describe('TokenAccess component', () => {
|
|||
expect(findRemoveAutopopulatedEntriesModal().props('showModal')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('allowlist actions', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent(
|
||||
[
|
||||
[inboundGetCIJobTokenScopeQuery, inboundJobTokenScopeEnabledResponseHandler],
|
||||
[
|
||||
inboundGetGroupsAndProjectsWithCIJobTokenScopeQuery,
|
||||
inboundGroupsAndProjectsWithoutAutopopulatedEntriesResponseHandler,
|
||||
],
|
||||
[getAuthLogCountQuery, authLogZeroCountResponseHandler],
|
||||
],
|
||||
{
|
||||
authenticationLogsMigrationForAllowlist: true,
|
||||
stubs: { CrudComponent, GlDisclosureDropdown, GlDisclosureDropdownItem },
|
||||
},
|
||||
);
|
||||
await nextTick();
|
||||
});
|
||||
|
||||
it('hides add auth log entries option if auth log count is zero', () => {
|
||||
expect(findFormSelector().props('items')).toMatchObject([
|
||||
{
|
||||
text: 'Group or project',
|
||||
value: 'JOB_TOKEN_FORM_ADD_GROUP_OR_PROJECT',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('hides remove auth log entries option if there are no autopopulated entries', () => {
|
||||
expect(findAllowlistOptions().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe.each`
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ export const inboundJobTokenScopeDisabledResponse = {
|
|||
},
|
||||
};
|
||||
|
||||
export const inboundGroupsAndProjectsWithScopeResponse = {
|
||||
export const inboundGroupsAndProjectsWithScopeResponse = (hasAutopopulatedEntries = true) => ({
|
||||
data: {
|
||||
project: {
|
||||
__typename: 'Project',
|
||||
|
|
@ -197,12 +197,14 @@ export const inboundGroupsAndProjectsWithScopeResponse = {
|
|||
},
|
||||
],
|
||||
},
|
||||
groupAllowlistAutopopulatedIds: ['gid://gitlab/Group/45'],
|
||||
inboundAllowlistAutopopulatedIds: ['gid://gitlab/Project/23'],
|
||||
groupAllowlistAutopopulatedIds: hasAutopopulatedEntries ? ['gid://gitlab/Group/45'] : [],
|
||||
inboundAllowlistAutopopulatedIds: hasAutopopulatedEntries
|
||||
? ['gid://gitlab/Project/23']
|
||||
: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const getSaveNamespaceHandler = (error) =>
|
||||
jest.fn().mockResolvedValue({
|
||||
|
|
@ -301,28 +303,15 @@ export const mockAuthLogsResponse = (hasNextPage = false) => ({
|
|||
},
|
||||
});
|
||||
|
||||
export const mockAutopopulateAllowlistResponse = {
|
||||
export const mockAutopopulateAllowlistResponse = ({ errorMessage } = {}) => ({
|
||||
data: {
|
||||
ciJobTokenScopeAutopopulateAllowlist: {
|
||||
status: 'complete',
|
||||
errors: [],
|
||||
errors: errorMessage ? [{ message: errorMessage }] : [],
|
||||
__typename: 'CiJobTokenScopeAutopopulateAllowlistPayload',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const mockAutopopulateAllowlistError = {
|
||||
data: {
|
||||
ciJobTokenScopeAutopopulateAllowlist: {
|
||||
errors: [
|
||||
{
|
||||
message: 'An error occurred',
|
||||
},
|
||||
],
|
||||
__typename: 'CiJobTokenScopeAutopopulateAllowlistPayload',
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const mockRemoveAutopopulatedEntriesResponse = ({ errorMessage } = {}) => ({
|
||||
data: {
|
||||
|
|
|
|||
|
|
@ -78,6 +78,39 @@ RSpec.describe Ci::RunnersHelper, feature_category: :fleet_visibility do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#admin_runners_fleet_dashboard_data', :enable_admin_mode do
|
||||
let_it_be(:user) { admin_user }
|
||||
|
||||
subject(:data) { helper.admin_runners_fleet_dashboard_data }
|
||||
|
||||
it 'returns correct data' do
|
||||
expect(data).to include(
|
||||
admin_runners_path: '/admin/runners',
|
||||
new_runner_path: '/admin/runners/new',
|
||||
clickhouse_ci_analytics_available: 'false',
|
||||
can_admin_runners: 'true'
|
||||
)
|
||||
end
|
||||
|
||||
context 'when ClickHouse is configured' do
|
||||
before do
|
||||
allow(Gitlab::ClickHouse).to receive(:configured?).and_return(true)
|
||||
end
|
||||
|
||||
it 'returns the correct data' do
|
||||
expect(data).to include(clickhouse_ci_analytics_available: 'true')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when current user is not an admin' do
|
||||
let_it_be(:user) { non_admin_user }
|
||||
|
||||
it 'returns the correct data' do
|
||||
expect(data).to include(can_admin_runners: 'false')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#group_shared_runners_settings_data' do
|
||||
let_it_be(:parent) { create(:group) }
|
||||
let_it_be(:group) { create(:group, parent: parent, shared_runners_enabled: false) }
|
||||
|
|
|
|||
|
|
@ -124,13 +124,6 @@ RSpec.describe Gitlab::GithubImport::MarkdownText, feature_category: :importers
|
|||
expect(text.to_s).to eq('Hello')
|
||||
end
|
||||
|
||||
it 'returns empty text when it receives nil' do
|
||||
author = double(:author, login: nil)
|
||||
text = described_class.new(nil, author, true)
|
||||
|
||||
expect(text.to_s).to eq('')
|
||||
end
|
||||
|
||||
it 'returns the text with an extra header when the author was not found' do
|
||||
author = double(:author, login: 'Alice')
|
||||
text = described_class.new('Hello', author)
|
||||
|
|
@ -150,11 +143,28 @@ RSpec.describe Gitlab::GithubImport::MarkdownText, feature_category: :importers
|
|||
let(:text) { "I said to @sam_allen\0 the code" }
|
||||
let(:instance) { described_class.new(text, project:) }
|
||||
|
||||
subject(:format) { instance.to_s }
|
||||
|
||||
it 'calls wrap_mentions_in_backticks and convert_ref_links method as a cleaning step' do
|
||||
expect(instance).to receive(:wrap_mentions_in_backticks)
|
||||
expect(instance).to receive(:convert_ref_links)
|
||||
|
||||
instance.to_s
|
||||
format
|
||||
end
|
||||
|
||||
context "when the text is blank?" do
|
||||
let(:text) { nil }
|
||||
|
||||
it "skips formatting" do
|
||||
expect(instance).not_to receive(:wrap_mentions_in_backticks)
|
||||
expect(instance).not_to receive(:convert_ref_links)
|
||||
|
||||
format
|
||||
end
|
||||
|
||||
it "returns nil as response" do
|
||||
expect(format).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -115,8 +115,8 @@ RSpec.describe HelpController, "routing" do
|
|||
path = '/help/user/markdown.md'
|
||||
expect(get(path)).to route_to('help#show', path: 'user/markdown', format: 'md')
|
||||
|
||||
path = '/help/user/markdown/markdown_logo.png'
|
||||
expect(get(path)).to route_to('help#show', path: 'user/markdown/markdown_logo', format: 'png')
|
||||
path = '/help/user/markdown/markdown_logo_v17_11.png'
|
||||
expect(get(path)).to route_to('help#show', path: 'user/markdown/markdown_logo_v17_11', format: 'png')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -9,12 +9,7 @@ internal/config/config.go:247:18: G204: Subprocess launched with variable (gosec
|
|||
internal/config/config.go:339:8: G101: Potential hardcoded credentials (gosec)
|
||||
internal/dependencyproxy/dependencyproxy.go:121: Function 'Inject' is too long (61 > 60) (funlen)
|
||||
internal/dependencyproxy/dependencyproxy_test.go:514: internal/dependencyproxy/dependencyproxy_test.go:514: Line contains TODO/BUG/FIXME/NOTE/OPTIMIZE/HACK: "note that the timeout duration here is s..." (godox)
|
||||
internal/git/archive.go:39:2: var-naming: struct field CommitId should be CommitID (revive)
|
||||
internal/git/archive.go:49:2: exported: exported var SendArchive should have comment or be unexported (revive)
|
||||
internal/git/archive.go:66: Function 'Inject' has too many statements (49 > 40) (funlen)
|
||||
internal/git/archive.go:90:29: Error return value of `cachedArchive.Close` is not checked (errcheck)
|
||||
internal/git/archive.go:116:23: Error return value of `tempFile.Close` is not checked (errcheck)
|
||||
internal/git/archive.go:117:18: Error return value of `os.Remove` is not checked (errcheck)
|
||||
internal/git/archive.go:67: Function 'Inject' has too many statements (55 > 40) (funlen)
|
||||
internal/git/blob.go:21:5: exported: exported var SendBlob should have comment or be unexported (revive)
|
||||
internal/git/diff.go:1: 1-47 lines are duplicate of `internal/git/format-patch.go:1-48` (dupl)
|
||||
internal/git/diff.go:22:5: exported: exported var SendDiff should have comment or be unexported (revive)
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ type archive struct {
|
|||
type archiveParams struct {
|
||||
ArchivePath string
|
||||
ArchivePrefix string
|
||||
CommitId string
|
||||
CommitID string
|
||||
GitalyServer api.GitalyServer
|
||||
GitalyRepository gitalypb.Repository
|
||||
DisableCache bool
|
||||
|
|
@ -46,6 +46,7 @@ type archiveParams struct {
|
|||
}
|
||||
|
||||
var (
|
||||
// SendArchive sends a Git archive to the client, retrieving from the local disk cache if available.
|
||||
SendArchive = newArchive("git-archive:")
|
||||
gitArchiveCache = promauto.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
|
|
@ -87,7 +88,12 @@ func (a *archive) Inject(w http.ResponseWriter, r *http.Request, sendData string
|
|||
|
||||
cachedArchive, err := os.Open(params.ArchivePath)
|
||||
if err == nil {
|
||||
defer cachedArchive.Close()
|
||||
defer func() {
|
||||
err = cachedArchive.Close()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("SendArchive: failed to close cached archive")
|
||||
}
|
||||
}()
|
||||
gitArchiveCache.WithLabelValues("hit").Inc()
|
||||
setArchiveHeaders(w, format, archiveFilename)
|
||||
// Even if somebody deleted the cachedArchive from disk since we opened
|
||||
|
|
@ -113,8 +119,15 @@ func (a *archive) Inject(w http.ResponseWriter, r *http.Request, sendData string
|
|||
fail.Request(w, r, fmt.Errorf("SendArchive: create tempfile: %v", err))
|
||||
return
|
||||
}
|
||||
defer tempFile.Close()
|
||||
defer os.Remove(tempFile.Name())
|
||||
defer func() {
|
||||
// Ignore error, this may have already been closed with finalizeCachedArchive
|
||||
_ = tempFile.Close()
|
||||
|
||||
err = os.Remove(tempFile.Name())
|
||||
if err != nil {
|
||||
log.WithError(err).Error("SendArchive: failed to remove tempfile")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
var archiveReader io.Reader
|
||||
|
|
@ -175,7 +188,7 @@ func handleArchiveWithGitaly(r *http.Request, params *archiveParams, format gita
|
|||
} else {
|
||||
request = &gitalypb.GetArchiveRequest{
|
||||
Repository: ¶ms.GitalyRepository,
|
||||
CommitId: params.CommitId,
|
||||
CommitId: params.CommitID,
|
||||
Prefix: params.ArchivePrefix,
|
||||
Format: format,
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue