diff --git a/.rubocop_todo/migration/background_migration_record.yml b/.rubocop_todo/migration/background_migration_record.yml index af93c19f11f..cb297d4fded 100644 --- a/.rubocop_todo/migration/background_migration_record.yml +++ b/.rubocop_todo/migration/background_migration_record.yml @@ -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' diff --git a/.rubocop_todo/migration/batched_migration_base_class.yml b/.rubocop_todo/migration/batched_migration_base_class.yml index 732e01ec9c0..e1e8e643d46 100644 --- a/.rubocop_todo/migration/batched_migration_base_class.yml +++ b/.rubocop_todo/migration/batched_migration_base_class.yml @@ -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' diff --git a/app/assets/javascripts/projects/pipelines/charts/components/pipeline_duration_chart.vue b/app/assets/javascripts/projects/pipelines/charts/components/pipeline_duration_chart.vue index 0f2f310e3de..2bb50cc9432 100644 --- a/app/assets/javascripts/projects/pipelines/charts/components/pipeline_duration_chart.vue +++ b/app/assets/javascripts/projects/pipelines/charts/components/pipeline_duration_chart.vue @@ -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" - /> + > + + + diff --git a/app/assets/javascripts/projects/pipelines/charts/components/pipeline_status_chart.vue b/app/assets/javascripts/projects/pipelines/charts/components/pipeline_status_chart.vue index 940f3b8924d..2c09558f87d 100644 --- a/app/assets/javascripts/projects/pipelines/charts/components/pipeline_status_chart.vue +++ b/app/assets/javascripts/projects/pipelines/charts/components/pipeline_status_chart.vue @@ -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], }; @@ -68,6 +77,10 @@ export default { :group-by="groupBy" :bars="bars" :include-legend-avg-max="false" - /> + > + + diff --git a/app/assets/javascripts/token_access/components/autopopulate_allowlist_modal.vue b/app/assets/javascripts/token_access/components/autopopulate_allowlist_modal.vue index 3b6df8c2e5e..87077cbf949 100644 --- a/app/assets/javascripts/token_access/components/autopopulate_allowlist_modal.vue +++ b/app/assets/javascripts/token_access/components/autopopulate_allowlist_modal.vue @@ -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', }), }; @@ -135,9 +93,6 @@ export default { @canceled="hideModal" @hidden="hideModal" > - - {{ errorMessage }} -
{{ authLogExceedsLimitMessage }} @@ -163,13 +118,16 @@ export default { +

@@ -182,7 +140,7 @@ export default {

{{ 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.', ) }}

diff --git a/app/assets/javascripts/token_access/components/inbound_token_access.vue b/app/assets/javascripts/token_access/components/inbound_token_access.vue index 3f81fbc7a7b..dbc9d0be3ce 100644 --- a/app/assets/javascripts/token_access/components/inbound_token_access.vue +++ b/app/assets/javascripts/token_access/components/inbound_token_access.vue @@ -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" /> { - 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 = !( diff --git a/app/controllers/graphql_controller.rb b/app/controllers/graphql_controller.rb index 358a67194f8..fe13a3dfd99 100644 --- a/app/controllers/graphql_controller.rb +++ b/app/controllers/graphql_controller.rb @@ -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 diff --git a/app/helpers/ci/runners_helper.rb b/app/helpers/ci/runners_helper.rb index 333b30b1157..e61231bc878 100644 --- a/app/helpers/ci/runners_helper.rb +++ b/app/helpers/ci/runners_helper.rb @@ -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, diff --git a/app/workers/gitlab/export/prune_project_export_jobs_worker.rb b/app/workers/gitlab/export/prune_project_export_jobs_worker.rb index daaa5528c07..6e10afd9ef5 100644 --- a/app/workers/gitlab/export/prune_project_export_jobs_worker.rb +++ b/app/workers/gitlab/export/prune_project_export_jobs_worker.rb @@ -11,6 +11,7 @@ module Gitlab include CronjobQueue # rubocop:enable Scalability/CronWorkerContext + deduplicate :until_executed feature_category :importers data_consistency :sticky idempotent! diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 8ab0c1e5449..1bd211f10dc 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -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 * * *' diff --git a/data/deprecations/17-9-enable-gitlab-advanced-sast-by-default.yml b/data/deprecations/17-9-enable-gitlab-advanced-sast-by-default.yml index 69f3c8592af..8f4ca6c6fa6 100644 --- a/data/deprecations/17-9-enable-gitlab-advanced-sast-by-default.yml +++ b/data/deprecations/17-9-enable-gitlab-advanced-sast-by-default.yml @@ -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. diff --git a/doc/ci/jobs/ci_job_token.md b/doc/ci/jobs/ci_job_token.md index 8334faf055a..be519eea0f2 100644 --- a/doc/ci/jobs/ci_job_token.md +++ b/doc/ci/jobs/ci_job_token.md @@ -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. diff --git a/doc/development/advanced_search.md b/doc/development/advanced_search.md index 0b1259c563f..4ebfa6d7e25 100644 --- a/doc/development/advanced_search.md +++ b/doc/development/advanced_search.md @@ -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 --- diff --git a/doc/development/database/add_foreign_key_to_existing_column.md b/doc/development/database/add_foreign_key_to_existing_column.md index 0b26b65e45d..3f6f0f9219e 100644 --- a/doc/development/database/add_foreign_key_to_existing_column.md +++ b/doc/development/database/add_foreign_key_to_existing_column.md @@ -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 --- diff --git a/doc/development/database/adding_database_indexes.md b/doc/development/database/adding_database_indexes.md index 412d880fb38..2f56b3b4d5f 100644 --- a/doc/development/database/adding_database_indexes.md +++ b/doc/development/database/adding_database_indexes.md @@ -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 --- diff --git a/doc/development/database/avoiding_downtime_in_migrations.md b/doc/development/database/avoiding_downtime_in_migrations.md index c0428aeb128..2e10c3f5493 100644 --- a/doc/development/database/avoiding_downtime_in_migrations.md +++ b/doc/development/database/avoiding_downtime_in_migrations.md @@ -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 --- diff --git a/doc/development/database/batching_best_practices.md b/doc/development/database/batching_best_practices.md index e124ea7a79c..c1c979f152f 100644 --- a/doc/development/database/batching_best_practices.md +++ b/doc/development/database/batching_best_practices.md @@ -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 --- diff --git a/doc/development/database/clickhouse/_index.md b/doc/development/database/clickhouse/_index.md index 9466dc02c08..07351dd34b2 100644 --- a/doc/development/database/clickhouse/_index.md +++ b/doc/development/database/clickhouse/_index.md @@ -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 --- diff --git a/doc/development/database/clickhouse/clickhouse_within_gitlab.md b/doc/development/database/clickhouse/clickhouse_within_gitlab.md index dfe18d390ef..9883b4abfa8 100644 --- a/doc/development/database/clickhouse/clickhouse_within_gitlab.md +++ b/doc/development/database/clickhouse/clickhouse_within_gitlab.md @@ -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 --- diff --git a/doc/development/database/clickhouse/gitlab_activity_data.md b/doc/development/database/clickhouse/gitlab_activity_data.md index 4c7df3704f4..3ca3388965f 100644 --- a/doc/development/database/clickhouse/gitlab_activity_data.md +++ b/doc/development/database/clickhouse/gitlab_activity_data.md @@ -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 --- diff --git a/doc/development/database/clickhouse/merge_request_analytics.md b/doc/development/database/clickhouse/merge_request_analytics.md index 2ff361138d2..b446bb9064c 100644 --- a/doc/development/database/clickhouse/merge_request_analytics.md +++ b/doc/development/database/clickhouse/merge_request_analytics.md @@ -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 --- diff --git a/doc/development/database/clickhouse/optimization.md b/doc/development/database/clickhouse/optimization.md index 737f254e1b5..baa96d7e6bd 100644 --- a/doc/development/database/clickhouse/optimization.md +++ b/doc/development/database/clickhouse/optimization.md @@ -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 --- diff --git a/doc/development/database/clickhouse/tiered_storage.md b/doc/development/database/clickhouse/tiered_storage.md index 071e9d155f6..e5992ab3823 100644 --- a/doc/development/database/clickhouse/tiered_storage.md +++ b/doc/development/database/clickhouse/tiered_storage.md @@ -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 --- diff --git a/doc/development/database/database_dictionary.md b/doc/development/database/database_dictionary.md index 6f99519429c..f4ece7d4572 100644 --- a/doc/development/database/database_dictionary.md +++ b/doc/development/database/database_dictionary.md @@ -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 --- diff --git a/doc/development/database/database_lab_pgai.md b/doc/development/database/database_lab_pgai.md index 4d9fd22ad0c..a5fc545a717 100644 --- a/doc/development/database/database_lab_pgai.md +++ b/doc/development/database/database_lab_pgai.md @@ -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 --- diff --git a/doc/development/database/database_reviewer_guidelines.md b/doc/development/database/database_reviewer_guidelines.md index a6a3ea47a02..27c7bc96e0f 100644 --- a/doc/development/database/database_reviewer_guidelines.md +++ b/doc/development/database/database_reviewer_guidelines.md @@ -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 --- diff --git a/doc/development/database/db_dump.md b/doc/development/database/db_dump.md index 10147dde2d2..d738c903c63 100644 --- a/doc/development/database/db_dump.md +++ b/doc/development/database/db_dump.md @@ -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 --- diff --git a/doc/development/database/deleting_migrations.md b/doc/development/database/deleting_migrations.md index bb24ade7df1..d80ca0eb9e6 100644 --- a/doc/development/database/deleting_migrations.md +++ b/doc/development/database/deleting_migrations.md @@ -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 --- diff --git a/doc/development/database/efficient_in_operator_queries.md b/doc/development/database/efficient_in_operator_queries.md index baabdfdc62c..55356255478 100644 --- a/doc/development/database/efficient_in_operator_queries.md +++ b/doc/development/database/efficient_in_operator_queries.md @@ -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 --- diff --git a/doc/development/database/filtering_by_label.md b/doc/development/database/filtering_by_label.md index 764e273be49..11c0dde7f78 100644 --- a/doc/development/database/filtering_by_label.md +++ b/doc/development/database/filtering_by_label.md @@ -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 --- diff --git a/doc/development/database/foreign_keys.md b/doc/development/database/foreign_keys.md index 0e7ca32b6de..5d6deafbf78 100644 --- a/doc/development/database/foreign_keys.md +++ b/doc/development/database/foreign_keys.md @@ -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 --- diff --git a/doc/development/database/keyset_pagination.md b/doc/development/database/keyset_pagination.md index 80585e9111b..0b1926c432c 100644 --- a/doc/development/database/keyset_pagination.md +++ b/doc/development/database/keyset_pagination.md @@ -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 --- diff --git a/doc/development/database/load_balancing.md b/doc/development/database/load_balancing.md index 8f04184b9f6..a53609c930e 100644 --- a/doc/development/database/load_balancing.md +++ b/doc/development/database/load_balancing.md @@ -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 --- diff --git a/doc/development/database/post_deployment_migrations.md b/doc/development/database/post_deployment_migrations.md index dfcb17c1054..d0839fc949f 100644 --- a/doc/development/database/post_deployment_migrations.md +++ b/doc/development/database/post_deployment_migrations.md @@ -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 --- diff --git a/doc/development/database/rename_database_tables.md b/doc/development/database/rename_database_tables.md index 00d6f6a76d1..c0d153e004a 100644 --- a/doc/development/database/rename_database_tables.md +++ b/doc/development/database/rename_database_tables.md @@ -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 --- diff --git a/doc/development/database/swapping_tables.md b/doc/development/database/swapping_tables.md index c174a6ff512..ef86ba4c412 100644 --- a/doc/development/database/swapping_tables.md +++ b/doc/development/database/swapping_tables.md @@ -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 --- diff --git a/doc/development/database/verifying_database_capabilities.md b/doc/development/database/verifying_database_capabilities.md index 23fc43d5645..eb2b16e3589 100644 --- a/doc/development/database/verifying_database_capabilities.md +++ b/doc/development/database/verifying_database_capabilities.md @@ -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 --- diff --git a/doc/development/development_seed_files.md b/doc/development/development_seed_files.md index 93ce389ac80..ac379949ec8 100644 --- a/doc/development/development_seed_files.md +++ b/doc/development/development_seed_files.md @@ -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 --- diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md index 0adb9a5205d..4c60e60448f 100644 --- a/doc/development/ee_features.md +++ b/doc/development/ee_features.md @@ -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 --- diff --git a/doc/development/issuable-like-models.md b/doc/development/issuable-like-models.md index 02577b1a76f..94d71c295c5 100644 --- a/doc/development/issuable-like-models.md +++ b/doc/development/issuable-like-models.md @@ -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 --- diff --git a/doc/development/language_server/_index.md b/doc/development/language_server/_index.md index d915d80554b..75e38fdb442 100644 --- a/doc/development/language_server/_index.md +++ b/doc/development/language_server/_index.md @@ -11,4 +11,4 @@ This document was moved to [another location](../../editor_extensions/language_s - + diff --git a/doc/development/merge_request_concepts/_index.md b/doc/development/merge_request_concepts/_index.md index f563f2e0f97..eb9019329c8 100644 --- a/doc/development/merge_request_concepts/_index.md +++ b/doc/development/merge_request_concepts/_index.md @@ -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 --- diff --git a/doc/development/merge_request_concepts/approval_rules.md b/doc/development/merge_request_concepts/approval_rules.md index 9662151c36a..20ff8a30380 100644 --- a/doc/development/merge_request_concepts/approval_rules.md +++ b/doc/development/merge_request_concepts/approval_rules.md @@ -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 --- diff --git a/doc/development/merge_request_concepts/diffs/_index.md b/doc/development/merge_request_concepts/diffs/_index.md index 1951ac9ed03..03e82d8f14b 100644 --- a/doc/development/merge_request_concepts/diffs/_index.md +++ b/doc/development/merge_request_concepts/diffs/_index.md @@ -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 --- diff --git a/doc/development/merge_request_concepts/diffs/development.md b/doc/development/merge_request_concepts/diffs/development.md index 4788ebc6f86..0f72ad1184e 100644 --- a/doc/development/merge_request_concepts/diffs/development.md +++ b/doc/development/merge_request_concepts/diffs/development.md @@ -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 --- diff --git a/doc/development/merge_request_concepts/diffs/frontend.md b/doc/development/merge_request_concepts/diffs/frontend.md index b5465636686..6365e59214e 100644 --- a/doc/development/merge_request_concepts/diffs/frontend.md +++ b/doc/development/merge_request_concepts/diffs/frontend.md @@ -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 --- diff --git a/doc/development/merge_request_concepts/mergeability_framework.md b/doc/development/merge_request_concepts/mergeability_framework.md index 444c90acc4d..b8f0aea06a1 100644 --- a/doc/development/merge_request_concepts/mergeability_framework.md +++ b/doc/development/merge_request_concepts/mergeability_framework.md @@ -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 --- diff --git a/doc/development/merge_request_concepts/performance.md b/doc/development/merge_request_concepts/performance.md index 6b3cdd79d7c..ff1b9b89870 100644 --- a/doc/development/merge_request_concepts/performance.md +++ b/doc/development/merge_request_concepts/performance.md @@ -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 --- diff --git a/doc/development/merge_request_concepts/rate_limits.md b/doc/development/merge_request_concepts/rate_limits.md index 85444ab256a..61d7d58eb10 100644 --- a/doc/development/merge_request_concepts/rate_limits.md +++ b/doc/development/merge_request_concepts/rate_limits.md @@ -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 --- diff --git a/doc/development/product_qualified_lead_guide/_index.md b/doc/development/product_qualified_lead_guide/_index.md index 053948d2f8d..e050c91c600 100644 --- a/doc/development/product_qualified_lead_guide/_index.md +++ b/doc/development/product_qualified_lead_guide/_index.md @@ -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 --- diff --git a/doc/development/rubocop_development_guide.md b/doc/development/rubocop_development_guide.md index 77192d3d0e0..c0caaea42df 100644 --- a/doc/development/rubocop_development_guide.md +++ b/doc/development/rubocop_development_guide.md @@ -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 --- diff --git a/doc/update/deprecations.md b/doc/update/deprecations.md index 98f0049938f..32f417deca0 100644 --- a/doc/update/deprecations.md +++ b/doc/update/deprecations.md @@ -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.
diff --git a/doc/user/img/markdown_logo.png b/doc/user/img/markdown_logo_v17_11.png similarity index 100% rename from doc/user/img/markdown_logo.png rename to doc/user/img/markdown_logo_v17_11.png diff --git a/doc/user/markdown.md b/doc/user/markdown.md index 9fb8253e88c..30b3bf1474e 100644 --- a/doc/user/markdown.md +++ b/doc/user/markdown.md @@ -1169,7 +1169,7 @@ Do not change it back to a markdown codeblocks. --> @@ -1182,18 +1182,18 @@ Inline-style: -
![alt text](img/markdown_logo.png "Title Text")
+
![alt text](img/markdown_logo_v17_11.png "Title Text")
 
-![alt text](img/markdown_logo.png "Title Text") +![alt text](img/markdown_logo_v17_11.png "Title Text") Reference-style:
![alt text1][logo]
-[logo]: img/markdown_logo.png "Title Text"
+[logo]: img/markdown_logo_v17_11.png "Title Text"
 
-![alt text](img/markdown_logo.png "Title Text") +![alt text](img/markdown_logo_v17_11.png "Title Text") @@ -1230,12 +1230,12 @@ The value must an integer with a unit of either `px` (default) or `%`. For example ```markdown -![alt text](img/markdown_logo.png "Title Text"){width=100 height=100px} +![alt text](img/markdown_logo_v17_11.png "Title Text"){width=100 height=100px} -![alt text](img/markdown_logo.png "Title Text"){width=75%} +![alt text](img/markdown_logo_v17_11.png "Title Text"){width=75%} ``` -![alt text](img/markdown_logo.png "Title Text"){width=100 height=100px} +![alt text](img/markdown_logo_v17_11.png "Title Text"){width=100 height=100px} You can also use the `img` HTML tag instead of Markdown and set its `height` and `width` parameters. diff --git a/gems/gitlab-backup-cli/.gitlab-ci.yml b/gems/gitlab-backup-cli/.gitlab-ci.yml index 01d2a5fadc7..61c9091d582 100644 --- a/gems/gitlab-backup-cli/.gitlab-ci.yml +++ b/gems/gitlab-backup-cli/.gitlab-ci.yml @@ -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 diff --git a/gems/gitlab-backup-cli/.rubocop.yml b/gems/gitlab-backup-cli/.rubocop.yml index b67751b139c..11e619c2b04 100644 --- a/gems/gitlab-backup-cli/.rubocop.yml +++ b/gems/gitlab-backup-cli/.rubocop.yml @@ -11,3 +11,6 @@ Rails/Exit: RSpec/MultipleMemoizedHelpers: Max: 25 AllowSubject: true + +Rails/RakeEnvironment: + Enabled: false diff --git a/gems/gitlab-backup-cli/Rakefile b/gems/gitlab-backup-cli/Rakefile index cca71754493..bc60e76f5c6 100644 --- a/gems/gitlab-backup-cli/Rakefile +++ b/gems/gitlab-backup-cli/Rakefile @@ -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 diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/errors.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/errors.rb index 045230a07ea..45d2f68e598 100644 --- a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/errors.rb +++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/errors.rb @@ -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' diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/errors/database_cleanup_error.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/errors/database_cleanup_error.rb new file mode 100644 index 00000000000..bb4e32751e1 --- /dev/null +++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/errors/database_cleanup_error.rb @@ -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 diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/database.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/database.rb index 79b3371cca9..aaa5340911f 100644 --- a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/database.rb +++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/database.rb @@ -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:) diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/utils.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/utils.rb index 5c8293602e4..0234f17cd68 100644 --- a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/utils.rb +++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/utils.rb @@ -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 diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/utils/rake.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/utils/rake.rb new file mode 100644 index 00000000000..3c532077d1e --- /dev/null +++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/utils/rake.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +module Gitlab + module Backup + module Cli + module Utils + class Rake + # @return [Array] 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] *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] array of commands to be used by Shellout + def rake_command + %w[bundle exec rake] + tasks + end + end + end + end + end +end diff --git a/gems/gitlab-backup-cli/spec/fixtures/gitlab_fake/Gemfile b/gems/gitlab-backup-cli/spec/fixtures/gitlab_fake/Gemfile new file mode 100644 index 00000000000..65055a249d8 --- /dev/null +++ b/gems/gitlab-backup-cli/spec/fixtures/gitlab_fake/Gemfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gem 'rake', '~> 13.0' diff --git a/gems/gitlab-backup-cli/spec/fixtures/gitlab_fake/Gemfile.lock b/gems/gitlab-backup-cli/spec/fixtures/gitlab_fake/Gemfile.lock new file mode 100644 index 00000000000..54a0646b769 --- /dev/null +++ b/gems/gitlab-backup-cli/spec/fixtures/gitlab_fake/Gemfile.lock @@ -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 diff --git a/gems/gitlab-backup-cli/spec/fixtures/gitlab_fake/Rakefile b/gems/gitlab-backup-cli/spec/fixtures/gitlab_fake/Rakefile new file mode 100644 index 00000000000..3a0d01cd939 --- /dev/null +++ b/gems/gitlab-backup-cli/spec/fixtures/gitlab_fake/Rakefile @@ -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 diff --git a/gems/gitlab-backup-cli/spec/gitlab/backup/cli/errors/database_cleanup_error_spec.rb b/gems/gitlab-backup-cli/spec/gitlab/backup/cli/errors/database_cleanup_error_spec.rb new file mode 100644 index 00000000000..6f1947f11a4 --- /dev/null +++ b/gems/gitlab-backup-cli/spec/gitlab/backup/cli/errors/database_cleanup_error_spec.rb @@ -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 diff --git a/gems/gitlab-backup-cli/spec/gitlab/backup/cli/services/database_spec.rb b/gems/gitlab-backup-cli/spec/gitlab/backup/cli/services/database_spec.rb index e4e6626a703..7e34c304182 100644 --- a/gems/gitlab-backup-cli/spec/gitlab/backup/cli/services/database_spec.rb +++ b/gems/gitlab-backup-cli/spec/gitlab/backup/cli/services/database_spec.rb @@ -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) } diff --git a/gems/gitlab-backup-cli/spec/gitlab/backup/cli/services/postgres_spec.rb b/gems/gitlab-backup-cli/spec/gitlab/backup/cli/services/postgres_spec.rb index 788f0afcdb6..05cce6361d1 100644 --- a/gems/gitlab-backup-cli/spec/gitlab/backup/cli/services/postgres_spec.rb +++ b/gems/gitlab-backup-cli/spec/gitlab/backup/cli/services/postgres_spec.rb @@ -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 diff --git a/gems/gitlab-backup-cli/spec/gitlab/backup/cli/targets/database_spec.rb b/gems/gitlab-backup-cli/spec/gitlab/backup/cli/targets/database_spec.rb index e4a0963aa3d..821e845bb4a 100644 --- a/gems/gitlab-backup-cli/spec/gitlab/backup/cli/targets/database_spec.rb +++ b/gems/gitlab-backup-cli/spec/gitlab/backup/cli/targets/database_spec.rb @@ -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) diff --git a/gems/gitlab-backup-cli/spec/gitlab/backup/cli/utils/rake_spec.rb b/gems/gitlab-backup-cli/spec/gitlab/backup/cli/utils/rake_spec.rb new file mode 100644 index 00000000000..32b0f297fe0 --- /dev/null +++ b/gems/gitlab-backup-cli/spec/gitlab/backup/cli/utils/rake_spec.rb @@ -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 diff --git a/gems/gitlab-backup-cli/spec/support/helpers.rb b/gems/gitlab-backup-cli/spec/support/helpers.rb index a16ab4a4e8b..2be73c5bd3f 100644 --- a/gems/gitlab-backup-cli/spec/support/helpers.rb +++ b/gems/gitlab-backup-cli/spec/support/helpers.rb @@ -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 diff --git a/gems/gitlab-backup-cli/spec/support/test_context.rb b/gems/gitlab-backup-cli/spec/support/test_context.rb index c49f803bb4f..406d91fd5c9 100644 --- a/gems/gitlab-backup-cli/spec/support/test_context.rb +++ b/gems/gitlab-backup-cli/spec/support/test_context.rb @@ -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 diff --git a/lib/gitlab/background_migration/fix_first_mentioned_in_commit_at.rb b/lib/gitlab/background_migration/fix_first_mentioned_in_commit_at.rb deleted file mode 100644 index 987decd19bc..00000000000 --- a/lib/gitlab/background_migration/fix_first_mentioned_in_commit_at.rb +++ /dev/null @@ -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 diff --git a/lib/gitlab/github_import/markdown_text.rb b/lib/gitlab/github_import/markdown_text.rb index 96f73f48800..029e189a59a 100644 --- a/lib/gitlab/github_import/markdown_text.rb +++ b/lib/gitlab/github_import/markdown_text.rb @@ -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? diff --git a/locale/gitlab.pot b/locale/gitlab.pot index d852766897b..85fbdbb4d21 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -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 "" diff --git a/spec/controllers/graphql_controller_spec.rb b/spec/controllers/graphql_controller_spec.rb index ba170f05fc7..973a4fae376 100644 --- a/spec/controllers/graphql_controller_spec.rb +++ b/spec/controllers/graphql_controller_spec.rb @@ -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 diff --git a/spec/controllers/help_controller_spec.rb b/spec/controllers/help_controller_spec.rb index ada4e006286..66dd279309e 100644 --- a/spec/controllers/help_controller_spec.rb +++ b/spec/controllers/help_controller_spec.rb @@ -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 diff --git a/spec/frontend/projects/pipelines/charts/components/pipeline_duration_chart_spec.js b/spec/frontend/projects/pipelines/charts/components/pipeline_duration_chart_spec.js index 8cf45b256ec..ba556c6f458 100644 --- a/spec/frontend/projects/pipelines/charts/components/pipeline_duration_chart_spec.js +++ b/spec/frontend/projects/pipelines/charts/components/pipeline_duration_chart_spec.js @@ -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: `
+ + - + +
`, + }), + }, + }); + + expect(findLineChart().text()).toMatchInterpolatedText(expectedTooltip); + }); + }); }); diff --git a/spec/frontend/projects/pipelines/charts/components/pipeline_status_chart_spec.js b/spec/frontend/projects/pipelines/charts/components/pipeline_status_chart_spec.js index e762374cd3c..c649aaa60aa 100644 --- a/spec/frontend/projects/pipelines/charts/components/pipeline_status_chart_spec.js +++ b/spec/frontend/projects/pipelines/charts/components/pipeline_status_chart_spec.js @@ -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: `
+ +
`, + }), + }, + }); + + expect(findStackedColumnChart().text()).toMatchInterpolatedText(expectedTooltip); + }); + }); }); diff --git a/spec/frontend/token_access/autopopulate_allowlist_modal_spec.js b/spec/frontend/token_access/autopopulate_allowlist_modal_spec.js index e0eac4f7236..6e460c96ccb 100644 --- a/spec/frontend/token_access/autopopulate_allowlist_modal_spec.js +++ b/spec/frontend/token_access/autopopulate_allowlist_modal_spec.js @@ -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); }); }); }); diff --git a/spec/frontend/token_access/inbound_token_access_spec.js b/spec/frontend/token_access/inbound_token_access_spec.js index b3c9aabcc8c..bf9207b3611 100644 --- a/spec/frontend/token_access/inbound_token_access_spec.js +++ b/spec/frontend/token_access/inbound_token_access_spec.js @@ -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` diff --git a/spec/frontend/token_access/mock_data.js b/spec/frontend/token_access/mock_data.js index b7fd38afb6a..d1e107b2265 100644 --- a/spec/frontend/token_access/mock_data.js +++ b/spec/frontend/token_access/mock_data.js @@ -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: { diff --git a/spec/helpers/ci/runners_helper_spec.rb b/spec/helpers/ci/runners_helper_spec.rb index aabb67c4dae..308f7b02951 100644 --- a/spec/helpers/ci/runners_helper_spec.rb +++ b/spec/helpers/ci/runners_helper_spec.rb @@ -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) } diff --git a/spec/lib/gitlab/github_import/markdown_text_spec.rb b/spec/lib/gitlab/github_import/markdown_text_spec.rb index dfa6276ebe2..cd7fbe240ad 100644 --- a/spec/lib/gitlab/github_import/markdown_text_spec.rb +++ b/spec/lib/gitlab/github_import/markdown_text_spec.rb @@ -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 diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index 181e2e9c311..f24ec315ad3 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -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 diff --git a/workhorse/_support/lint_last_known_acceptable.txt b/workhorse/_support/lint_last_known_acceptable.txt index 473c5e2f176..27be97b2e4e 100644 --- a/workhorse/_support/lint_last_known_acceptable.txt +++ b/workhorse/_support/lint_last_known_acceptable.txt @@ -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) diff --git a/workhorse/internal/git/archive.go b/workhorse/internal/git/archive.go index 0a7ed8a0e0c..9ac362ae2c2 100644 --- a/workhorse/internal/git/archive.go +++ b/workhorse/internal/git/archive.go @@ -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, }