Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-08-18 13:50:51 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-08-18 13:50:51 +0300
commitdb384e6b19af03b4c3c82a5760d83a3fd79f7982 (patch)
tree34beaef37df5f47ccbcf5729d7583aae093cffa0 /lib
parent54fd7b1bad233e3944434da91d257fa7f63c3996 (diff)
Add latest changes from gitlab-org/gitlab@16-3-stable-eev16.3.0-rc42
Diffstat (limited to 'lib')
-rw-r--r--lib/api/admin/broadcast_messages.rb127
-rw-r--r--lib/api/api.rb26
-rw-r--r--lib/api/api_guard.rb2
-rw-r--r--lib/api/broadcast_messages.rb122
-rw-r--r--lib/api/ci/pipeline_schedules.rb46
-rw-r--r--lib/api/ci/pipelines.rb4
-rw-r--r--lib/api/concerns/packages/npm_endpoints.rb9
-rw-r--r--lib/api/concerns/packages/nuget/private_endpoints.rb3
-rw-r--r--lib/api/concerns/packages/nuget/public_endpoints.rb45
-rw-r--r--lib/api/draft_notes.rb39
-rw-r--r--lib/api/entities/batched_background_migration.rb1
-rw-r--r--lib/api/entities/broadcast_message.rb10
-rw-r--r--lib/api/entities/ci/pipeline_basic_with_metadata.rb3
-rw-r--r--lib/api/entities/ci/pipeline_with_metadata.rb3
-rw-r--r--lib/api/entities/commit_status.rb1
-rw-r--r--lib/api/entities/group.rb1
-rw-r--r--lib/api/entities/group_detail.rb2
-rw-r--r--lib/api/entities/merge_request_basic.rb2
-rw-r--r--lib/api/entities/notification_setting.rb6
-rw-r--r--lib/api/entities/nuget/metadatum.rb6
-rw-r--r--lib/api/entities/project.rb5
-rw-r--r--lib/api/entities/snippet.rb2
-rw-r--r--lib/api/entities/system/broadcast_message.rb13
-rw-r--r--lib/api/groups.rb4
-rw-r--r--lib/api/helpers.rb49
-rw-r--r--lib/api/helpers/groups_helpers.rb13
-rw-r--r--lib/api/helpers/integrations_helpers.rb9
-rw-r--r--lib/api/helpers/internal_helpers.rb52
-rw-r--r--lib/api/helpers/label_helpers.rb4
-rw-r--r--lib/api/helpers/projects_helpers.rb8
-rw-r--r--lib/api/helpers/snippets_helpers.rb5
-rw-r--r--lib/api/internal/base.rb49
-rw-r--r--lib/api/merge_requests.rb3
-rw-r--r--lib/api/metrics/dashboard/annotations.rb18
-rw-r--r--lib/api/metrics/user_starred_dashboards.rb21
-rw-r--r--lib/api/ml/mlflow/api_helpers.rb2
-rw-r--r--lib/api/ml/mlflow/entrypoint.rb1
-rw-r--r--lib/api/ml_model_packages.rb2
-rw-r--r--lib/api/nuget_project_packages.rb94
-rw-r--r--lib/api/project_packages.rb4
-rw-r--r--lib/api/projects.rb16
-rw-r--r--lib/api/repositories.rb9
-rw-r--r--lib/api/settings.rb14
-rw-r--r--lib/api/snippets.rb63
-rw-r--r--lib/api/time_tracking_endpoints.rb13
-rw-r--r--lib/atlassian/jira_connect/serializers/deployment_entity.rb2
-rw-r--r--lib/backup/gitaly_backup.rb72
-rw-r--r--lib/backup/manager.rb10
-rw-r--r--lib/banzai/filter/issuable_reference_expansion_filter.rb9
-rw-r--r--lib/banzai/filter/references/project_reference_filter.rb1
-rw-r--r--lib/banzai/filter/references/reference_cache.rb4
-rw-r--r--lib/bulk_imports/common/graphql/get_members_query.rb10
-rw-r--r--lib/bulk_imports/file_downloads/validations.rb2
-rw-r--r--lib/bulk_imports/visibility_level.rb15
-rw-r--r--lib/click_house/bind_index_manager.rb15
-rw-r--r--lib/click_house/query_builder.rb137
-rw-r--r--lib/click_house/redactor.rb53
-rw-r--r--lib/container_registry/gitlab_api_client.rb5
-rw-r--r--lib/csv_builder.rb130
-rw-r--r--lib/csv_builders/single_batch.rb11
-rw-r--r--lib/csv_builders/stream.rb17
-rw-r--r--lib/extracts_ref.rb2
-rw-r--r--lib/generators/batched_background_migration/templates/batched_background_migration_job.template2
-rw-r--r--lib/generators/batched_background_migration/templates/ee_batched_background_migration_job.template2
-rw-r--r--lib/generators/gitlab/analytics/internal_events_generator.rb12
-rw-r--r--lib/generators/gitlab/usage_metric_definition_generator.rb4
-rw-r--r--lib/gitlab/action_cable/request_store_callbacks.rb2
-rw-r--r--lib/gitlab/alert_management/payload.rb17
-rw-r--r--lib/gitlab/alert_management/payload/managed_prometheus.rb46
-rw-r--r--lib/gitlab/audit/auditor.rb8
-rw-r--r--lib/gitlab/auth.rb6
-rw-r--r--lib/gitlab/auth/auth_finders.rb6
-rw-r--r--lib/gitlab/auth/saml/auth_hash.rb4
-rw-r--r--lib/gitlab/auth/two_factor_auth_verifier.rb13
-rw-r--r--lib/gitlab/authorized_keys.rb2
-rw-r--r--lib/gitlab/background_migration/backfill_default_branch_protection_namespace_setting.rb131
-rw-r--r--lib/gitlab/background_migration/backfill_dismissal_reason_in_vulnerability_reads.rb19
-rw-r--r--lib/gitlab/background_migration/backfill_missing_vulnerability_dismissal_details.rb17
-rw-r--r--lib/gitlab/background_migration/backfill_namespace_id_for_project_route.rb1
-rw-r--r--lib/gitlab/background_migration/backfill_project_statistics_storage_size_without_pipeline_artifacts_size_job.rb95
-rw-r--r--lib/gitlab/background_migration/cleanup_orphaned_routes.rb34
-rw-r--r--lib/gitlab/background_migration/delete_orphaned_transferred_project_approval_rules.rb14
-rw-r--r--lib/gitlab/background_migration/fix_allow_descendants_override_disabled_shared_runners.rb22
-rw-r--r--lib/gitlab/background_migration/nullify_creator_id_column_of_orphaned_projects.rb8
-rw-r--r--lib/gitlab/background_migration/populate_projects_star_count.rb31
-rw-r--r--lib/gitlab/blame.rb4
-rw-r--r--lib/gitlab/checks/branch_check.rb2
-rw-r--r--lib/gitlab/checks/diff_check.rb28
-rw-r--r--lib/gitlab/checks/file_size_check/allow_existing_oversized_blobs.rb38
-rw-r--r--lib/gitlab/checks/file_size_check/hook_environment_aware_any_oversized_blobs.rb54
-rw-r--r--lib/gitlab/checks/global_file_size_check.rb30
-rw-r--r--lib/gitlab/ci/build/artifacts/metadata.rb6
-rw-r--r--lib/gitlab/ci/build/artifacts/metadata/entry.rb2
-rw-r--r--lib/gitlab/ci/config/entry/include/rules.rb9
-rw-r--r--lib/gitlab/ci/config/entry/include/rules/rule.rb9
-rw-r--r--lib/gitlab/ci/config/entry/need.rb20
-rw-r--r--lib/gitlab/ci/config/entry/needs.rb12
-rw-r--r--lib/gitlab/ci/config/entry/reports.rb3
-rw-r--r--lib/gitlab/ci/config/external/context.rb10
-rw-r--r--lib/gitlab/ci/config/external/file/base.rb7
-rw-r--r--lib/gitlab/ci/config/external/file/component.rb4
-rw-r--r--lib/gitlab/ci/config/external/mapper.rb1
-rw-r--r--lib/gitlab/ci/config/external/mapper/matcher.rb11
-rw-r--r--lib/gitlab/ci/config/external/mapper/verifier.rb15
-rw-r--r--lib/gitlab/ci/config/external/rules.rb20
-rw-r--r--lib/gitlab/ci/config/header/input.rb5
-rw-r--r--lib/gitlab/ci/config/interpolation/access.rb60
-rw-r--r--lib/gitlab/ci/config/interpolation/block.rb77
-rw-r--r--lib/gitlab/ci/config/interpolation/config.rb129
-rw-r--r--lib/gitlab/ci/config/interpolation/context.rb78
-rw-r--r--lib/gitlab/ci/config/interpolation/functions/base.rb52
-rw-r--r--lib/gitlab/ci/config/interpolation/functions/truncate.rb40
-rw-r--r--lib/gitlab/ci/config/interpolation/functions_stack.rb71
-rw-r--r--lib/gitlab/ci/config/interpolation/inputs.rb78
-rw-r--r--lib/gitlab/ci/config/interpolation/inputs/base_input.rb96
-rw-r--r--lib/gitlab/ci/config/interpolation/inputs/boolean_input.rb25
-rw-r--r--lib/gitlab/ci/config/interpolation/inputs/number_input.rb25
-rw-r--r--lib/gitlab/ci/config/interpolation/inputs/string_input.rb33
-rw-r--r--lib/gitlab/ci/config/interpolation/interpolator.rb (renamed from lib/gitlab/ci/config/yaml/interpolator.rb)34
-rw-r--r--lib/gitlab/ci/config/interpolation/template.rb69
-rw-r--r--lib/gitlab/ci/config/normalizer.rb4
-rw-r--r--lib/gitlab/ci/config/yaml.rb4
-rw-r--r--lib/gitlab/ci/config/yaml/loader.rb11
-rw-r--r--lib/gitlab/ci/config/yaml/result.rb7
-rw-r--r--lib/gitlab/ci/input/arguments/base.rb62
-rw-r--r--lib/gitlab/ci/input/arguments/default.rb48
-rw-r--r--lib/gitlab/ci/input/arguments/options.rb55
-rw-r--r--lib/gitlab/ci/input/arguments/required.rb55
-rw-r--r--lib/gitlab/ci/input/arguments/unknown.rb31
-rw-r--r--lib/gitlab/ci/input/inputs.rb73
-rw-r--r--lib/gitlab/ci/interpolation/access.rb60
-rw-r--r--lib/gitlab/ci/interpolation/block.rb48
-rw-r--r--lib/gitlab/ci/interpolation/config.rb124
-rw-r--r--lib/gitlab/ci/interpolation/context.rb76
-rw-r--r--lib/gitlab/ci/interpolation/template.rb67
-rw-r--r--lib/gitlab/ci/jwt_v2.rb24
-rw-r--r--lib/gitlab/ci/jwt_v2/claim_mapper.rb28
-rw-r--r--lib/gitlab/ci/jwt_v2/claim_mapper/repository.rb31
-rw-r--r--lib/gitlab/ci/pipeline/chain/ensure_environments.rb2
-rw-r--r--lib/gitlab/ci/project_config/remote.rb2
-rw-r--r--lib/gitlab/ci/project_config/repository.rb2
-rw-r--r--lib/gitlab/ci/queue/metrics.rb10
-rw-r--r--lib/gitlab/ci/reports/sbom/component.rb38
-rw-r--r--lib/gitlab/ci/status/bridge/factory.rb1
-rw-r--r--lib/gitlab/ci/status/bridge/waiting_for_approval.rb18
-rw-r--r--lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Jobs/Build.latest.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml4
-rw-r--r--lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Jobs/Deploy.latest.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Jobs/License-Scanning.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Security/BAS.latest.gitlab-ci.yml3
-rw-r--r--lib/gitlab/ci/variables/builder/pipeline.rb1
-rw-r--r--lib/gitlab/ci/variables/downstream/expandable_variable_generator.rb35
-rw-r--r--lib/gitlab/ci/variables/downstream/generator.rb4
-rw-r--r--lib/gitlab/config/entry/validators.rb11
-rw-r--r--lib/gitlab/content_security_policy/config_loader.rb312
-rw-r--r--lib/gitlab/data_builder/build.rb21
-rw-r--r--lib/gitlab/data_builder/issuable.rb4
-rw-r--r--lib/gitlab/database.rb17
-rw-r--r--lib/gitlab/database/batch_counter.rb6
-rw-r--r--lib/gitlab/database/bump_sequences.rb68
-rw-r--r--lib/gitlab/database/ci_builds_partitioning.rb224
-rw-r--r--lib/gitlab/database/health_status.rb3
-rw-r--r--lib/gitlab/database/health_status/indicators/patroni_apdex.rb73
-rw-r--r--lib/gitlab/database/health_status/indicators/prometheus_alert_indicator.rb113
-rw-r--r--lib/gitlab/database/health_status/indicators/wal_rate.rb29
-rw-r--r--lib/gitlab/database/migration_helpers.rb13
-rw-r--r--lib/gitlab/database/migration_helpers/convert_to_bigint.rb14
-rw-r--r--lib/gitlab/database/migrations/batched_background_migration_helpers.rb2
-rw-r--r--lib/gitlab/database/migrations/squasher.rb72
-rw-r--r--lib/gitlab/database/postgres_constraint.rb2
-rw-r--r--lib/gitlab/database/postgres_foreign_key.rb6
-rw-r--r--lib/gitlab/database/postgres_index.rb2
-rw-r--r--lib/gitlab/database/postgres_partition.rb4
-rw-r--r--lib/gitlab/database/postgres_partitioned_table.rb2
-rw-r--r--lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin.rb2
-rw-r--r--lib/gitlab/database/query_analyzers/query_recorder.rb53
-rw-r--r--lib/gitlab/database/reindexing.rb1
-rw-r--r--lib/gitlab/database/reindexing/reindex_concurrently.rb2
-rw-r--r--lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb2
-rw-r--r--lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb3
-rw-r--r--lib/gitlab/database/schema_validation/schema_inconsistency.rb17
-rw-r--r--lib/gitlab/database/tables_sorted_by_foreign_keys.rb10
-rw-r--r--lib/gitlab/database_importers/work_items/base_type_importer.rb50
-rw-r--r--lib/gitlab/database_importers/work_items/hierarchy_restrictions_importer.rb7
-rw-r--r--lib/gitlab/dependency_linker/base_linker.rb2
-rw-r--r--lib/gitlab/dependency_linker/cargo_toml_linker.rb12
-rw-r--r--lib/gitlab/dependency_linker/composer_json_linker.rb2
-rw-r--r--lib/gitlab/diff/parser.rb2
-rw-r--r--lib/gitlab/discussions_diff/highlight_cache.rb6
-rw-r--r--lib/gitlab/email/reply_parser.rb2
-rw-r--r--lib/gitlab/etag_caching/router/graphql.rb2
-rw-r--r--lib/gitlab/etag_caching/store.rb12
-rw-r--r--lib/gitlab/exclusive_lease.rb95
-rw-r--r--lib/gitlab/git.rb2
-rw-r--r--lib/gitlab/git/blame.rb18
-rw-r--r--lib/gitlab/git/commit.rb19
-rw-r--r--lib/gitlab/git/diff_tree.rb30
-rw-r--r--lib/gitlab/git/gitmodules_parser.rb2
-rw-r--r--lib/gitlab/git/object_pool.rb11
-rw-r--r--lib/gitlab/git/repository.rb72
-rw-r--r--lib/gitlab/git/rugged_impl/tree.rb2
-rw-r--r--lib/gitlab/git/tree.rb17
-rw-r--r--lib/gitlab/git_access.rb2
-rw-r--r--lib/gitlab/gitaly_client/blob_service.rb26
-rw-r--r--lib/gitlab/gitaly_client/commit_service.rb38
-rw-r--r--lib/gitlab/gitaly_client/conflicts_service.rb8
-rw-r--r--lib/gitlab/gitaly_client/operation_service.rb20
-rw-r--r--lib/gitlab/gitaly_client/repository_service.rb12
-rw-r--r--lib/gitlab/github_import.rb2
-rw-r--r--lib/gitlab/gon_helper.rb3
-rw-r--r--lib/gitlab/graphql/loaders/full_path_model_loader.rb5
-rw-r--r--lib/gitlab/hook_data/issue_builder.rb4
-rw-r--r--lib/gitlab/hook_data/merge_request_builder.rb2
-rw-r--r--lib/gitlab/i18n.rb20
-rw-r--r--lib/gitlab/import_export/command_line_util.rb6
-rw-r--r--lib/gitlab/import_export/decompressed_archive_size_validator.rb5
-rw-r--r--lib/gitlab/import_export/file_importer.rb8
-rw-r--r--lib/gitlab/import_export/project/base_task.rb2
-rw-r--r--lib/gitlab/import_export/project/export_task.rb2
-rw-r--r--lib/gitlab/import_export/project/import_export.yml2
-rw-r--r--lib/gitlab/import_export/project/import_task.rb2
-rw-r--r--lib/gitlab/internal_events/event_definitions.rb4
-rw-r--r--lib/gitlab/jwt_authenticatable.rb4
-rw-r--r--lib/gitlab/kas.rb20
-rw-r--r--lib/gitlab/markdown_cache/redis/store.rb8
-rw-r--r--lib/gitlab/merge_requests/message_generator.rb1
-rw-r--r--lib/gitlab/metrics/dashboard/defaults.rb13
-rw-r--r--lib/gitlab/metrics/dashboard/finder.rb95
-rw-r--r--lib/gitlab/metrics/dashboard/importer.rb41
-rw-r--r--lib/gitlab/metrics/dashboard/importers/prometheus_metrics.rb76
-rw-r--r--lib/gitlab/metrics/dashboard/service_selector.rb54
-rw-r--r--lib/gitlab/metrics/dashboard/stages/base_stage.rb2
-rw-r--r--lib/gitlab/metrics/dashboard/stages/cluster_endpoint_inserter.rb38
-rw-r--r--lib/gitlab/metrics/dashboard/stages/common_metrics_inserter.rb23
-rw-r--r--lib/gitlab/metrics/dashboard/stages/custom_dashboard_metrics_inserter.rb24
-rw-r--r--lib/gitlab/metrics/dashboard/stages/custom_metrics_details_inserter.rb40
-rw-r--r--lib/gitlab/metrics/dashboard/stages/custom_metrics_inserter.rb109
-rw-r--r--lib/gitlab/metrics/dashboard/stages/grafana_formatter.rb151
-rw-r--r--lib/gitlab/metrics/dashboard/stages/metric_endpoint_inserter.rb56
-rw-r--r--lib/gitlab/metrics/dashboard/stages/panel_ids_inserter.rb61
-rw-r--r--lib/gitlab/metrics/dashboard/stages/track_panel_type.rb27
-rw-r--r--lib/gitlab/metrics/dashboard/stages/variable_endpoint_inserter.rb34
-rw-r--r--lib/gitlab/metrics/dashboard/transformers/yml/v1/prometheus_metrics.rb54
-rw-r--r--lib/gitlab/metrics/dashboard/validator.rb30
-rw-r--r--lib/gitlab/metrics/dashboard/validator/client.rb56
-rw-r--r--lib/gitlab/metrics/dashboard/validator/custom_formats.rb23
-rw-r--r--lib/gitlab/metrics/dashboard/validator/errors.rb60
-rw-r--r--lib/gitlab/metrics/dashboard/validator/post_schema_validator.rb52
-rw-r--r--lib/gitlab/metrics/dashboard/validator/schemas/axis.json14
-rw-r--r--lib/gitlab/metrics/dashboard/validator/schemas/dashboard.json18
-rw-r--r--lib/gitlab/metrics/dashboard/validator/schemas/link.json12
-rw-r--r--lib/gitlab/metrics/dashboard/validator/schemas/metric.json16
-rw-r--r--lib/gitlab/metrics/dashboard/validator/schemas/panel.json24
-rw-r--r--lib/gitlab/metrics/dashboard/validator/schemas/panel_group.json12
-rw-r--r--lib/gitlab/metrics/dashboard/validator/schemas/templating.json7
-rw-r--r--lib/gitlab/metrics/global_search_slis.rb3
-rw-r--r--lib/gitlab/metrics/samplers/threads_sampler.rb2
-rw-r--r--lib/gitlab/middleware/sidekiq_web_static.rb2
-rw-r--r--lib/gitlab/middleware/static.rb2
-rw-r--r--lib/gitlab/null_request_store.rb41
-rw-r--r--lib/gitlab/pages/url_builder.rb3
-rw-r--r--lib/gitlab/pages/virtual_host_finder.rb1
-rw-r--r--lib/gitlab/patch/command_loader.rb19
-rw-r--r--lib/gitlab/patch/node_loader.rb12
-rw-r--r--lib/gitlab/patch/redis_cache_store.rb6
-rw-r--r--lib/gitlab/patch/sidekiq_poller.rb2
-rw-r--r--lib/gitlab/patch/slot_loader.rb19
-rw-r--r--lib/gitlab/path_traversal.rb2
-rw-r--r--lib/gitlab/profiler.rb4
-rw-r--r--lib/gitlab/project_search_results.rb1
-rw-r--r--lib/gitlab/quick_actions/issue_and_merge_request_actions.rb18
-rw-r--r--lib/gitlab/quick_actions/relate_actions.rb2
-rw-r--r--lib/gitlab/quick_actions/work_item_actions.rb16
-rw-r--r--lib/gitlab/rack_attack.rb2
-rw-r--r--lib/gitlab/redis.rb2
-rw-r--r--lib/gitlab/redis/cache.rb19
-rw-r--r--lib/gitlab/redis/cluster_shared_state.rb (renamed from lib/gitlab/redis/cluster_cache.rb)4
-rw-r--r--lib/gitlab/redis/etag_cache.rb22
-rw-r--r--lib/gitlab/redis/feature_flag.rb1
-rw-r--r--lib/gitlab/redis/repository_cache.rb1
-rw-r--r--lib/gitlab/regex.rb323
-rw-r--r--lib/gitlab/regex/bulk_imports.rb40
-rw-r--r--lib/gitlab/regex/merge_requests.rb19
-rw-r--r--lib/gitlab/regex/packages.rb273
-rw-r--r--lib/gitlab/repository_size_checker.rb2
-rw-r--r--lib/gitlab/repository_size_error_message.rb32
-rw-r--r--lib/gitlab/safe_request_store.rb46
-rw-r--r--lib/gitlab/search_results.rb39
-rw-r--r--lib/gitlab/sidekiq_logging/logs_jobs.rb3
-rw-r--r--lib/gitlab/sidekiq_logging/pause_control_logger.rb31
-rw-r--r--lib/gitlab/sidekiq_logging/structured_logger.rb2
-rw-r--r--lib/gitlab/sidekiq_middleware.rb2
-rw-r--r--lib/gitlab/sidekiq_middleware/pause_control.rb20
-rw-r--r--lib/gitlab/sidekiq_middleware/pause_control/client.rb13
-rw-r--r--lib/gitlab/sidekiq_middleware/pause_control/pause_control_service.rb118
-rw-r--r--lib/gitlab/sidekiq_middleware/pause_control/server.rb13
-rw-r--r--lib/gitlab/sidekiq_middleware/pause_control/strategies/base.rb64
-rw-r--r--lib/gitlab/sidekiq_middleware/pause_control/strategies/none.rb17
-rw-r--r--lib/gitlab/sidekiq_middleware/pause_control/strategies/zoekt.rb16
-rw-r--r--lib/gitlab/sidekiq_middleware/pause_control/strategy_handler.rb35
-rw-r--r--lib/gitlab/sidekiq_middleware/pause_control/workers_map.rb26
-rw-r--r--lib/gitlab/sidekiq_middleware/request_store_middleware.rb4
-rw-r--r--lib/gitlab/sidekiq_middleware/server_metrics.rb59
-rw-r--r--lib/gitlab/sidekiq_middleware/skip_jobs.rb4
-rw-r--r--lib/gitlab/url_blocker.rb6
-rw-r--r--lib/gitlab/usage/metric.rb4
-rw-r--r--lib/gitlab/usage/metrics/instrumentations/aggregated_metric.rb4
-rw-r--r--lib/gitlab/usage/metrics/instrumentations/batched_background_migration_failed_jobs_metric.rb34
-rw-r--r--lib/gitlab/usage/metrics/instrumentations/count_imported_projects_metric.rb2
-rw-r--r--lib/gitlab/usage/metrics/instrumentations/database_metric.rb8
-rw-r--r--lib/gitlab/usage/metrics/instrumentations/generic_metric.rb4
-rw-r--r--lib/gitlab/usage/metrics/instrumentations/numbers_metric.rb4
-rw-r--r--lib/gitlab/usage/metrics/instrumentations/redis_hll_metric.rb4
-rw-r--r--lib/gitlab/usage/metrics/instrumentations/redis_metric.rb4
-rw-r--r--lib/gitlab/usage/metrics/instrumentations/work_items_activity_aggregated_metric.rb13
-rw-r--r--lib/gitlab/usage/metrics/name_suggestion.rb216
-rw-r--r--lib/gitlab/usage/metrics/names_suggestions/generator.rb57
-rw-r--r--lib/gitlab/usage/metrics/names_suggestions/relation_parsers/having_constraints.rb31
-rw-r--r--lib/gitlab/usage/metrics/names_suggestions/relation_parsers/joins.rb74
-rw-r--r--lib/gitlab/usage/metrics/names_suggestions/relation_parsers/where_constraints.rb31
-rw-r--r--lib/gitlab/usage_data_counters/ci_template_unique_counter.rb1
-rw-r--r--lib/gitlab/usage_data_counters/editor_unique_counter.rb28
-rw-r--r--lib/gitlab/usage_data_counters/hll_redis_counter.rb79
-rw-r--r--lib/gitlab/usage_data_counters/ipynb_diff_activity_counter.rb1
-rw-r--r--lib/gitlab/usage_data_counters/known_events/analytics.yml26
-rw-r--r--lib/gitlab/usage_data_counters/known_events/ci_templates.yml311
-rw-r--r--lib/gitlab/usage_data_counters/known_events/ci_users.yml4
-rw-r--r--lib/gitlab/usage_data_counters/known_events/code_review_events.yml235
-rw-r--r--lib/gitlab/usage_data_counters/known_events/common.yml167
-rw-r--r--lib/gitlab/usage_data_counters/known_events/container_registry_events.yml11
-rw-r--r--lib/gitlab/usage_data_counters/known_events/ecosystem.yml24
-rw-r--r--lib/gitlab/usage_data_counters/known_events/error_tracking.yml5
-rw-r--r--lib/gitlab/usage_data_counters/known_events/importer_events.yml13
-rw-r--r--lib/gitlab/usage_data_counters/known_events/integrations.yml18
-rw-r--r--lib/gitlab/usage_data_counters/known_events/kubernetes_agent.yml22
-rw-r--r--lib/gitlab/usage_data_counters/known_events/package_events.yml49
-rw-r--r--lib/gitlab/usage_data_counters/known_events/product_analytics.yml8
-rw-r--r--lib/gitlab/usage_data_counters/known_events/quickactions.yml139
-rw-r--r--lib/gitlab/usage_data_counters/known_events/work_items.yml21
-rw-r--r--lib/gitlab/usage_data_counters/known_events/workspaces.yml5
-rw-r--r--lib/gitlab/usage_data_counters/neovim_plugin_activity_unique_counter.rb29
-rw-r--r--lib/gitlab/usage_data_counters/visual_studio_extension_activity_unique_counter.rb29
-rw-r--r--lib/gitlab/usage_data_counters/work_item_activity_unique_counter.rb2
-rw-r--r--lib/gitlab/utils/markdown.rb2
-rw-r--r--lib/gitlab/web_hooks/logger.rb11
-rw-r--r--lib/gitlab/with_request_store.rb25
-rw-r--r--lib/gitlab/x509/signature.rb26
-rw-r--r--lib/peek/views/click_house.rb50
-rw-r--r--lib/product_analytics/settings.rb15
-rw-r--r--lib/sbom/package_url/encoder.rb11
-rw-r--r--lib/sidebars/groups/menus/packages_registries_menu.rb2
-rw-r--r--lib/sidebars/groups/super_sidebar_menus/deploy_menu.rb3
-rw-r--r--lib/sidebars/groups/super_sidebar_menus/operations_menu.rb1
-rw-r--r--lib/sidebars/menu.rb4
-rw-r--r--lib/sidebars/projects/menus/settings_menu.rb2
-rw-r--r--lib/slack_markdown_sanitizer.rb4
-rw-r--r--lib/system_check/app/ruby_version_check.rb2
-rw-r--r--lib/tasks/gems.rake14
-rw-r--r--lib/tasks/gitlab/audit_event_types/audit_event_types.rake28
-rw-r--r--lib/tasks/gitlab/audit_event_types/check_docs_task.rb34
-rw-r--r--lib/tasks/gitlab/audit_event_types/compile_docs_task.rb22
-rw-r--r--lib/tasks/gitlab/db/cells/bump_cell_sequences.rake25
-rw-r--r--lib/tasks/gitlab/db/decomposition/rollback/bump_ci_sequences.rake49
-rw-r--r--lib/tasks/gitlab/db/migration_squash.rake28
-rw-r--r--lib/tasks/gitlab/info.rake13
-rw-r--r--lib/tasks/gitlab/tw/codeowners.rake22
-rw-r--r--lib/tasks/gitlab/user_management.rake15
371 files changed, 4812 insertions, 5628 deletions
diff --git a/lib/api/admin/broadcast_messages.rb b/lib/api/admin/broadcast_messages.rb
new file mode 100644
index 00000000000..f199f3ce842
--- /dev/null
+++ b/lib/api/admin/broadcast_messages.rb
@@ -0,0 +1,127 @@
+# frozen_string_literal: true
+
+module API
+ module Admin
+ class BroadcastMessages < ::API::Base
+ include PaginationParams
+
+ feature_category :onboarding
+ urgency :low
+
+ resource :broadcast_messages do
+ helpers do
+ def find_message
+ System::BroadcastMessage.find(params[:id])
+ end
+ end
+
+ desc 'Get all broadcast messages' do
+ detail 'This feature was introduced in GitLab 8.12.'
+ success Entities::System::BroadcastMessage
+ end
+ params do
+ use :pagination
+ end
+ get do
+ messages = System::BroadcastMessage.all.order_id_desc
+
+ present paginate(messages), with: Entities::System::BroadcastMessage
+ end
+
+ desc 'Create a broadcast message' do
+ detail 'This feature was introduced in GitLab 8.12.'
+ success Entities::System::BroadcastMessage
+ end
+ params do
+ requires :message, type: String, desc: 'Message to display'
+ optional :starts_at, type: DateTime, desc: 'Starting time', default: -> { Time.zone.now }
+ optional :ends_at, type: DateTime, desc: 'Ending time', default: -> { 1.hour.from_now }
+ optional :color, type: String, desc: 'Background color'
+ optional :font, type: String, desc: 'Foreground color'
+ optional :target_access_levels,
+ type: Array[Integer],
+ coerce_with: Validations::Types::CommaSeparatedToIntegerArray.coerce,
+ values: System::BroadcastMessage::ALLOWED_TARGET_ACCESS_LEVELS,
+ desc: 'Target user roles'
+ optional :target_path, type: String, desc: 'Target path'
+ optional :broadcast_type, type: String, values: System::BroadcastMessage.broadcast_types.keys, desc: 'Broadcast type. Defaults to banner', default: -> {
+ 'banner'
+ }
+ optional :dismissable, type: Boolean, desc: 'Is dismissable'
+ end
+ post do
+ authenticated_as_admin!
+
+ message = System::BroadcastMessage.create(declared_params(include_missing: false))
+
+ if message.persisted?
+ present message, with: Entities::System::BroadcastMessage
+ else
+ render_validation_error!(message)
+ end
+ end
+
+ desc 'Get a specific broadcast message' do
+ detail 'This feature was introduced in GitLab 8.12.'
+ success Entities::System::BroadcastMessage
+ end
+ params do
+ requires :id, type: Integer, desc: 'Broadcast message ID'
+ end
+ get ':id' do
+ message = find_message
+
+ present message, with: Entities::System::BroadcastMessage
+ end
+
+ desc 'Update a broadcast message' do
+ detail 'This feature was introduced in GitLab 8.12.'
+ success Entities::System::BroadcastMessage
+ end
+ params do
+ requires :id, type: Integer, desc: 'Broadcast message ID'
+ optional :message, type: String, desc: 'Message to display'
+ optional :starts_at, type: DateTime, desc: 'Starting time'
+ optional :ends_at, type: DateTime, desc: 'Ending time'
+ optional :color, type: String, desc: 'Background color'
+ optional :font, type: String, desc: 'Foreground color'
+ optional :target_access_levels,
+ type: Array[Integer],
+ coerce_with: Validations::Types::CommaSeparatedToIntegerArray.coerce,
+ values: System::BroadcastMessage::ALLOWED_TARGET_ACCESS_LEVELS,
+ desc: 'Target user roles'
+ optional :target_path, type: String, desc: 'Target path'
+ optional :broadcast_type, type: String, values: System::BroadcastMessage.broadcast_types.keys,
+ desc: 'Broadcast Type'
+ optional :dismissable, type: Boolean, desc: 'Is dismissable'
+ end
+ put ':id' do
+ authenticated_as_admin!
+
+ message = find_message
+
+ if message.update(declared_params(include_missing: false))
+ present message, with: Entities::System::BroadcastMessage
+ else
+ render_validation_error!(message)
+ end
+ end
+
+ desc 'Delete a broadcast message' do
+ detail 'This feature was introduced in GitLab 8.12.'
+ success Entities::System::BroadcastMessage
+ end
+ params do
+ requires :id, type: Integer, desc: 'Broadcast message ID'
+ end
+ delete ':id' do
+ authenticated_as_admin!
+
+ message = find_message
+
+ destroy_conditionally!(message)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 7da5f21b21f..8ebd7f83acb 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -15,6 +15,18 @@ module API
LOG_FORMATTER = Gitlab::GrapeLogging::Formatters::LogrageWithTimestamp.new
LOGGER = Logger.new(LOG_FILENAME)
+ class MovedPermanentlyError < StandardError
+ MSG_PREFIX = 'This resource has been moved permanently to'
+
+ attr_reader :location_url
+
+ def initialize(location_url)
+ @location_url = location_url
+
+ super("#{MSG_PREFIX} #{location_url}")
+ end
+ end
+
insert_before Grape::Middleware::Error,
GrapeLogging::Middleware::RequestLogger,
logger: LOGGER,
@@ -95,6 +107,14 @@ module API
end
after do
+ Gitlab::UsageDataCounters::VisualStudioExtensionActivityUniqueCounter.track_api_request_when_trackable(user_agent: request&.user_agent, user: @current_user)
+ end
+
+ after do
+ Gitlab::UsageDataCounters::NeovimPluginActivityUniqueCounter.track_api_request_when_trackable(user_agent: request&.user_agent, user: @current_user)
+ end
+
+ after do
Gitlab::UsageDataCounters::GitLabCliActivityUniqueCounter.track_api_request_when_trackable(user_agent: request&.user_agent, user: @current_user)
end
@@ -134,6 +154,10 @@ module API
error! e.message, e.status, e.headers
end
+ rescue_from MovedPermanentlyError do |e|
+ rack_response(e.message, 301, { 'Location' => e.location_url })
+ end
+
rescue_from Gitlab::Auth::TooManyIps do |e|
rack_response({ 'message' => '403 Forbidden' }.to_json, 403)
end
@@ -180,6 +204,7 @@ module API
# Keep in alphabetical order
mount ::API::AccessRequests
mount ::API::Admin::BatchedBackgroundMigrations
+ mount ::API::Admin::BroadcastMessages
mount ::API::Admin::Ci::Variables
mount ::API::Admin::Dictionary
mount ::API::Admin::InstanceClusters
@@ -191,7 +216,6 @@ module API
mount ::API::Avatar
mount ::API::Badges
mount ::API::Branches
- mount ::API::BroadcastMessages
mount ::API::BulkImports
mount ::API::Ci::JobArtifacts
mount ::API::Groups
diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb
index 0aee0c70203..7033856a42e 100644
--- a/lib/api/api_guard.rb
+++ b/lib/api/api_guard.rb
@@ -128,7 +128,7 @@ module API
end
def two_factor_required_but_not_setup?(user)
- verifier = Gitlab::Auth::TwoFactorAuthVerifier.new(user)
+ verifier = Gitlab::Auth::TwoFactorAuthVerifier.new(user, request)
if verifier.two_factor_authentication_required? && verifier.current_user_needs_to_setup_two_factor?
verifier.two_factor_grace_period_expired?
diff --git a/lib/api/broadcast_messages.rb b/lib/api/broadcast_messages.rb
deleted file mode 100644
index 6af7c3b4804..00000000000
--- a/lib/api/broadcast_messages.rb
+++ /dev/null
@@ -1,122 +0,0 @@
-# frozen_string_literal: true
-
-module API
- class BroadcastMessages < ::API::Base
- include PaginationParams
-
- feature_category :onboarding
- urgency :low
-
- resource :broadcast_messages do
- helpers do
- def find_message
- BroadcastMessage.find(params[:id])
- end
- end
-
- desc 'Get all broadcast messages' do
- detail 'This feature was introduced in GitLab 8.12.'
- success Entities::BroadcastMessage
- end
- params do
- use :pagination
- end
- get do
- messages = BroadcastMessage.all.order_id_desc
-
- present paginate(messages), with: Entities::BroadcastMessage
- end
-
- desc 'Create a broadcast message' do
- detail 'This feature was introduced in GitLab 8.12.'
- success Entities::BroadcastMessage
- end
- params do
- requires :message, type: String, desc: 'Message to display'
- optional :starts_at, type: DateTime, desc: 'Starting time', default: -> { Time.zone.now }
- optional :ends_at, type: DateTime, desc: 'Ending time', default: -> { 1.hour.from_now }
- optional :color, type: String, desc: 'Background color'
- optional :font, type: String, desc: 'Foreground color'
- optional :target_access_levels,
- type: Array[Integer],
- coerce_with: Validations::Types::CommaSeparatedToIntegerArray.coerce,
- values: BroadcastMessage::ALLOWED_TARGET_ACCESS_LEVELS,
- desc: 'Target user roles'
- optional :target_path, type: String, desc: 'Target path'
- optional :broadcast_type, type: String, values: BroadcastMessage.broadcast_types.keys, desc: 'Broadcast type. Defaults to banner', default: -> { 'banner' }
- optional :dismissable, type: Boolean, desc: 'Is dismissable'
- end
- post do
- authenticated_as_admin!
-
- message = BroadcastMessage.create(declared_params(include_missing: false))
-
- if message.persisted?
- present message, with: Entities::BroadcastMessage
- else
- render_validation_error!(message)
- end
- end
-
- desc 'Get a specific broadcast message' do
- detail 'This feature was introduced in GitLab 8.12.'
- success Entities::BroadcastMessage
- end
- params do
- requires :id, type: Integer, desc: 'Broadcast message ID'
- end
- get ':id' do
- message = find_message
-
- present message, with: Entities::BroadcastMessage
- end
-
- desc 'Update a broadcast message' do
- detail 'This feature was introduced in GitLab 8.12.'
- success Entities::BroadcastMessage
- end
- params do
- requires :id, type: Integer, desc: 'Broadcast message ID'
- optional :message, type: String, desc: 'Message to display'
- optional :starts_at, type: DateTime, desc: 'Starting time'
- optional :ends_at, type: DateTime, desc: 'Ending time'
- optional :color, type: String, desc: 'Background color'
- optional :font, type: String, desc: 'Foreground color'
- optional :target_access_levels,
- type: Array[Integer],
- coerce_with: Validations::Types::CommaSeparatedToIntegerArray.coerce,
- values: BroadcastMessage::ALLOWED_TARGET_ACCESS_LEVELS,
- desc: 'Target user roles'
- optional :target_path, type: String, desc: 'Target path'
- optional :broadcast_type, type: String, values: BroadcastMessage.broadcast_types.keys, desc: 'Broadcast Type'
- optional :dismissable, type: Boolean, desc: 'Is dismissable'
- end
- put ':id' do
- authenticated_as_admin!
-
- message = find_message
-
- if message.update(declared_params(include_missing: false))
- present message, with: Entities::BroadcastMessage
- else
- render_validation_error!(message)
- end
- end
-
- desc 'Delete a broadcast message' do
- detail 'This feature was introduced in GitLab 8.12.'
- success Entities::BroadcastMessage
- end
- params do
- requires :id, type: Integer, desc: 'Broadcast message ID'
- end
- delete ':id' do
- authenticated_as_admin!
-
- message = find_message
-
- destroy_conditionally!(message)
- end
- end
- end
-end
diff --git a/lib/api/ci/pipeline_schedules.rb b/lib/api/ci/pipeline_schedules.rb
index 1606d5ba649..1087c734f98 100644
--- a/lib/api/ci/pipeline_schedules.rb
+++ b/lib/api/ci/pipeline_schedules.rb
@@ -90,28 +90,16 @@ module API
post ':id/pipeline_schedules' do
authorize! :create_pipeline_schedule, user_project
- if ::Feature.enabled?(:ci_refactoring_pipeline_schedule_create_service, @project)
- response = ::Ci::PipelineSchedules::CreateService
- .new(user_project, current_user, declared_params(include_missing: false))
- .execute
+ response = ::Ci::PipelineSchedules::CreateService
+ .new(user_project, current_user, declared_params(include_missing: false))
+ .execute
- pipeline_schedule = response.payload
+ pipeline_schedule = response.payload
- if response.success?
- present pipeline_schedule, with: Entities::Ci::PipelineScheduleDetails
- else
- render_validation_error!(pipeline_schedule)
- end
+ if response.success?
+ present pipeline_schedule, with: Entities::Ci::PipelineScheduleDetails
else
- pipeline_schedule = ::Ci::CreatePipelineScheduleService
- .new(user_project, current_user, declared_params(include_missing: false))
- .execute
-
- if pipeline_schedule.persisted?
- present pipeline_schedule, with: Entities::Ci::PipelineScheduleDetails
- else
- render_validation_error!(pipeline_schedule)
- end
+ render_validation_error!(pipeline_schedule)
end
end
@@ -135,22 +123,14 @@ module API
put ':id/pipeline_schedules/:pipeline_schedule_id' do
authorize! :update_pipeline_schedule, pipeline_schedule
- if ::Feature.enabled?(:ci_refactoring_pipeline_schedule_create_service, @project)
- response = ::Ci::PipelineSchedules::UpdateService
- .new(pipeline_schedule, current_user, declared_params(include_missing: false))
- .execute
+ response = ::Ci::PipelineSchedules::UpdateService
+ .new(pipeline_schedule, current_user, declared_params(include_missing: false))
+ .execute
- if response.success?
- present pipeline_schedule, with: Entities::Ci::PipelineScheduleDetails
- else
- render_validation_error!(pipeline_schedule)
- end
+ if response.success?
+ present pipeline_schedule, with: Entities::Ci::PipelineScheduleDetails
else
- if pipeline_schedule.update(declared_params(include_missing: false)) # rubocop:disable Style/IfInsideElse
- present pipeline_schedule, with: Entities::Ci::PipelineScheduleDetails
- else
- render_validation_error!(pipeline_schedule)
- end
+ render_validation_error!(pipeline_schedule)
end
end
diff --git a/lib/api/ci/pipelines.rb b/lib/api/ci/pipelines.rb
index 809a9bd781b..bd5c04f401b 100644
--- a/lib/api/ci/pipelines.rb
+++ b/lib/api/ci/pipelines.rb
@@ -76,10 +76,8 @@ module API
authorize! :read_pipeline, user_project
authorize! :read_build, user_project
- params.delete(:name) unless ::Feature.enabled?(:pipeline_name_in_api, user_project)
-
pipelines = ::Ci::PipelinesFinder.new(user_project, current_user, params).execute
- pipelines = pipelines.preload_pipeline_metadata if ::Feature.enabled?(:pipeline_name_in_api, user_project)
+ pipelines = pipelines.preload_pipeline_metadata
present paginate(pipelines), with: Entities::Ci::PipelineBasicWithMetadata, project: user_project
end
diff --git a/lib/api/concerns/packages/npm_endpoints.rb b/lib/api/concerns/packages/npm_endpoints.rb
index ec20440f013..a045a3d4828 100644
--- a/lib/api/concerns/packages/npm_endpoints.rb
+++ b/lib/api/concerns/packages/npm_endpoints.rb
@@ -96,9 +96,8 @@ module API
track_package_event(:list_tags, :npm, project: project, namespace: project.namespace)
- metadata = generate_metadata_service(packages).execute(only_dist_tags: true)
- present ::Packages::Npm::PackagePresenter.new(metadata),
- with: ::API::Entities::NpmPackageTag
+ metadata = generate_metadata_service(packages).execute(only_dist_tags: true).payload
+ present metadata, with: ::API::Entities::NpmPackageTag
end
params do
@@ -229,8 +228,8 @@ module API
enqueue_sync_metadata_cache_worker(project, package_name)
end
- present ::Packages::Npm::PackagePresenter.new(generate_metadata_service(packages).execute),
- with: ::API::Entities::NpmPackage
+ metadata = generate_metadata_service(packages).execute.payload
+ present metadata, with: ::API::Entities::NpmPackage
end
end
diff --git a/lib/api/concerns/packages/nuget/private_endpoints.rb b/lib/api/concerns/packages/nuget/private_endpoints.rb
index 20c02f0a285..a166a7294f4 100644
--- a/lib/api/concerns/packages/nuget/private_endpoints.rb
+++ b/lib/api/concerns/packages/nuget/private_endpoints.rb
@@ -43,7 +43,8 @@ module API
current_user,
project_or_group,
package_name: package_name,
- package_version: package_version
+ package_version: package_version,
+ client_version: headers['X-Nuget-Client-Version']
)
end
diff --git a/lib/api/concerns/packages/nuget/public_endpoints.rb b/lib/api/concerns/packages/nuget/public_endpoints.rb
index 37b503212d9..b0c9177f452 100644
--- a/lib/api/concerns/packages/nuget/public_endpoints.rb
+++ b/lib/api/concerns/packages/nuget/public_endpoints.rb
@@ -16,7 +16,7 @@ module API
included do
# https://docs.microsoft.com/en-us/nuget/api/service-index
- desc 'The NuGet Service Index' do
+ desc 'The NuGet V3 Feed Service Index' do
detail 'This feature was introduced in GitLab 12.6'
success code: 200, model: ::API::Entities::Nuget::ServiceIndex
failure [
@@ -34,6 +34,49 @@ module API
present ::Packages::Nuget::ServiceIndexPresenter.new(project_or_group_without_auth),
with: ::API::Entities::Nuget::ServiceIndex
end
+
+ desc 'The NuGet V2 Feed Service Index' do
+ detail 'This feature was introduced in GitLab 16.2'
+ success code: 200
+ failure [
+ { code: 404, message: 'Not Found' }
+ ]
+ tags %w[nuget_packages]
+ end
+ namespace '/v2' do
+ get format: :xml, urgency: :low do
+ env['api.format'] = :xml
+ content_type 'application/xml; charset=utf-8'
+ # needed to allow browser default inline styles in xml response
+ header 'Content-Security-Policy', "nonce-#{SecureRandom.base64(16)}"
+
+ track_package_event(
+ 'cli_metadata',
+ :nuget,
+ **snowplow_gitlab_standard_context_without_auth.merge(category: 'API::NugetPackages', feed: 'v2')
+ )
+
+ present ::Packages::Nuget::V2::ServiceIndexPresenter
+ .new(project_or_group_without_auth)
+ .xml
+ end
+
+ # https://www.nuget.org/api/v2/$metadata
+ desc 'The NuGet V2 Feed Package $metadata endpoint' do
+ detail 'This feature was introduced in GitLab 16.3'
+ success code: 200
+ tags %w[nuget_packages]
+ end
+
+ get '$metadata', format: :xml, urgency: :low do
+ env['api.format'] = :xml
+ content_type 'application/xml; charset=utf-8'
+ # needed to allow browser default inline styles in xml response
+ header 'Content-Security-Policy', "nonce-#{SecureRandom.base64(16)}"
+
+ present ::Packages::Nuget::V2::MetadataIndexPresenter.new.xml
+ end
+ end
end
end
end
diff --git a/lib/api/draft_notes.rb b/lib/api/draft_notes.rb
index df9e060e592..6fadc68233d 100644
--- a/lib/api/draft_notes.rb
+++ b/lib/api/draft_notes.rb
@@ -45,9 +45,42 @@ module API
access_denied! unless can?(current_user, :admin_note, draft_note)
end
+ params :positional do
+ optional :position, type: Hash do
+ requires :base_sha, type: String, desc: 'Base commit SHA in the source branch'
+ requires :start_sha, type: String, desc: 'SHA referencing commit in target branch'
+ requires :head_sha, type: String, desc: 'SHA referencing HEAD of this merge request'
+ requires :position_type, type: String, desc: 'Type of the position reference', values: %w[text image]
+ optional :new_path, type: String, desc: 'File path after change'
+ optional :new_line, type: Integer, desc: 'Line number after change'
+ optional :old_path, type: String, desc: 'File path before change'
+ optional :old_line, type: Integer, desc: 'Line number before change'
+ optional :width, type: Integer, desc: 'Width of the image'
+ optional :height, type: Integer, desc: 'Height of the image'
+ optional :x, type: Integer, desc: 'X coordinate in the image'
+ optional :y, type: Integer, desc: 'Y coordinate in the image'
+
+ optional :line_range, type: Hash, desc: 'Multi-line start and end' do
+ optional :start, type: Hash do
+ optional :line_code, type: String, desc: 'Start line code for multi-line note'
+ optional :type, type: String, desc: 'Start line type for multi-line note'
+ optional :old_line, type: String, desc: 'Start old_line line number'
+ optional :new_line, type: String, desc: 'Start new_line line number'
+ end
+ optional :end, type: Hash do
+ optional :line_code, type: String, desc: 'End line code for multi-line note'
+ optional :type, type: String, desc: 'End line type for multi-line note'
+ optional :old_line, type: String, desc: 'End old_line line number'
+ optional :new_line, type: String, desc: 'End new_line line number'
+ end
+ end
+ end
+ end
+
def draft_note_params
{
note: params[:note],
+ position: params[:position],
commit_id: params[:commit_id] == 'undefined' ? nil : params[:commit_id],
resolve_discussion: params[:resolve_discussion] || false
}
@@ -104,9 +137,10 @@ module API
requires :id, type: String, desc: "The ID of a project."
requires :merge_request_iid, type: Integer, desc: "The ID of a merge request."
requires :note, type: String, desc: 'The content of a note.'
- optional :in_reply_to_discussion_id, type: Integer, desc: 'The ID of a discussion the draft note replies to.'
+ optional :in_reply_to_discussion_id, type: String, desc: 'The ID of a discussion the draft note replies to.'
optional :commit_id, type: String, desc: 'The sha of a commit to associate the draft note to.'
optional :resolve_discussion, type: Boolean, desc: 'The associated discussion should be resolved.'
+ use :positional
end
post ":id/merge_requests/:merge_request_iid/draft_notes", feature_category: :code_review_workflow do
authorize_create_note!(params: params)
@@ -135,6 +169,7 @@ module API
requires :merge_request_iid, type: Integer, desc: "The ID of a merge request."
requires :draft_note_id, type: Integer, desc: "The ID of a draft note"
optional :note, type: String, allow_blank: false, desc: 'The content of a note.'
+ use :positional
end
put ":id/merge_requests/:merge_request_iid/draft_notes/:draft_note_id", feature_category: :code_review_workflow do
bad_request!('Missing params to modify') unless params[:note].present?
@@ -144,7 +179,7 @@ module API
if draft_note
authorize_admin_draft!(draft_note)
- draft_note.update!(note: params[:note])
+ draft_note.update!(note: params[:note], position: params[:position])
present draft_note, with: Entities::DraftNote
else
not_found!("Draft Note")
diff --git a/lib/api/entities/batched_background_migration.rb b/lib/api/entities/batched_background_migration.rb
index 08e4681e0aa..65e9de4b2bd 100644
--- a/lib/api/entities/batched_background_migration.rb
+++ b/lib/api/entities/batched_background_migration.rb
@@ -6,6 +6,7 @@ module API
expose :id, documentation: { type: :string, example: "1234" }
expose :job_class_name, documentation: { type: :string, example: "CopyColumnUsingBackgroundMigrationJob" }
expose :table_name, documentation: { type: :string, example: "events" }
+ expose :column_name, documentation: { type: :string, example: "id" }
expose :status_name, as: :status, override: true, documentation: { type: :string, example: "active" }
expose :progress, documentation: { type: :float, example: 50 }
expose :created_at, documentation: { type: :dateTime, example: "2022-11-28T16:26:39+02:00" }
diff --git a/lib/api/entities/broadcast_message.rb b/lib/api/entities/broadcast_message.rb
deleted file mode 100644
index 5a31d64fd86..00000000000
--- a/lib/api/entities/broadcast_message.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-# frozen_string_literal: true
-
-module API
- module Entities
- class BroadcastMessage < Grape::Entity
- expose :id, :message, :starts_at, :ends_at, :color, :font, :target_access_levels, :target_path, :broadcast_type, :dismissable
- expose :active?, as: :active
- end
- end
-end
diff --git a/lib/api/entities/ci/pipeline_basic_with_metadata.rb b/lib/api/entities/ci/pipeline_basic_with_metadata.rb
index 4eeba3aec41..a352da05b2d 100644
--- a/lib/api/entities/ci/pipeline_basic_with_metadata.rb
+++ b/lib/api/entities/ci/pipeline_basic_with_metadata.rb
@@ -5,8 +5,7 @@ module API
module Ci
class PipelineBasicWithMetadata < PipelineBasic
expose :name,
- documentation: { type: 'string', example: 'Build pipeline' },
- if: ->(pipeline, _) { ::Feature.enabled?(:pipeline_name_in_api, pipeline.project) }
+ documentation: { type: 'string', example: 'Build pipeline' }
end
end
end
diff --git a/lib/api/entities/ci/pipeline_with_metadata.rb b/lib/api/entities/ci/pipeline_with_metadata.rb
index a8b1d81a053..31604f33fc1 100644
--- a/lib/api/entities/ci/pipeline_with_metadata.rb
+++ b/lib/api/entities/ci/pipeline_with_metadata.rb
@@ -5,8 +5,7 @@ module API
module Ci
class PipelineWithMetadata < Pipeline
expose :name,
- documentation: { type: 'string', example: 'Build pipeline' },
- if: ->(pipeline, _) { ::Feature.enabled?(:pipeline_name_in_api, pipeline.project) }
+ documentation: { type: 'string', example: 'Build pipeline' }
end
end
end
diff --git a/lib/api/entities/commit_status.rb b/lib/api/entities/commit_status.rb
index df6a41895ff..14ec3ba461b 100644
--- a/lib/api/entities/commit_status.rb
+++ b/lib/api/entities/commit_status.rb
@@ -18,6 +18,7 @@ module API
expose :finished_at, documentation: { type: 'dateTime', example: '2016-01-21T08:40:25.832Z' }
expose :allow_failure, documentation: { type: 'boolean', example: false }
expose :coverage, documentation: { type: 'number', format: 'float', example: 98.29 }
+ expose :pipeline_id, documentation: { type: 'integer', example: 101 }
expose :author, using: Entities::UserBasic
end
diff --git a/lib/api/entities/group.rb b/lib/api/entities/group.rb
index 9296617dac9..d18a29ce4d4 100644
--- a/lib/api/entities/group.rb
+++ b/lib/api/entities/group.rb
@@ -14,6 +14,7 @@ module API
expose :mentions_disabled
expose :lfs_enabled?, as: :lfs_enabled
expose :default_branch_protection
+ expose :default_branch_protection_defaults
expose :avatar_url do |group, options|
group.avatar_url(only_path: false)
end
diff --git a/lib/api/entities/group_detail.rb b/lib/api/entities/group_detail.rb
index 7b05984421a..f3d64315203 100644
--- a/lib/api/entities/group_detail.rb
+++ b/lib/api/entities/group_detail.rb
@@ -16,7 +16,7 @@ module API
projects = GroupProjectsFinder.new(
group: group,
current_user: options[:current_user],
- options: { only_owned: true, limit: projects_limit }
+ options: { exclude_shared: true, limit: projects_limit }
).execute
Entities::Project.prepare_relation(projects, options)
diff --git a/lib/api/entities/merge_request_basic.rb b/lib/api/entities/merge_request_basic.rb
index adff7f87cd3..56519e2bf08 100644
--- a/lib/api/entities/merge_request_basic.rb
+++ b/lib/api/entities/merge_request_basic.rb
@@ -107,8 +107,6 @@ module API
end
def can_check_mergeability?(project)
- return true if ::Feature.disabled?(:restrict_merge_status_recheck, project)
-
Ability.allowed?(options[:current_user], :update_merge_request, project)
end
end
diff --git a/lib/api/entities/notification_setting.rb b/lib/api/entities/notification_setting.rb
index cdff4f2f5c5..aa6112b4402 100644
--- a/lib/api/entities/notification_setting.rb
+++ b/lib/api/entities/notification_setting.rb
@@ -4,9 +4,9 @@ module API
module Entities
class NotificationSetting < Grape::Entity
expose :level
- expose :events, if: ->(notification_setting, _) { notification_setting.custom? } do
- ::NotificationSetting.email_events.each do |event|
- expose event
+ expose :events, if: ->(notification_setting, _) { notification_setting.custom? } do |setting|
+ setting.email_events.index_with do |event_name|
+ setting[event_name]
end
end
end
diff --git a/lib/api/entities/nuget/metadatum.rb b/lib/api/entities/nuget/metadatum.rb
index c316dfce740..1df57f8243d 100644
--- a/lib/api/entities/nuget/metadatum.rb
+++ b/lib/api/entities/nuget/metadatum.rb
@@ -7,8 +7,10 @@ module API
expose :authors, documentation: { type: 'string', example: 'Authors' } do |metadatum|
metadatum[:authors] || ''
end
- expose :description, as: :summary, documentation: { type: 'string', example: 'Description' } do |metadatum|
- metadatum[:description] || ''
+ with_options documentation: { type: 'string', example: 'Description' } do
+ set_default = ->(metadatum) { metadatum[:description] || '' }
+ expose :description, &set_default
+ expose :description, as: :summary, &set_default
end
expose :project_url, as: :projectUrl, expose_nil: false, documentation: { type: 'string', example: 'http://sandbox.com/project' }
expose :license_url, as: :licenseUrl, expose_nil: false, documentation: { type: 'string', example: 'http://sandbox.com/license' }
diff --git a/lib/api/entities/project.rb b/lib/api/entities/project.rb
index 61feacd6586..0f947c85633 100644
--- a/lib/api/entities/project.rb
+++ b/lib/api/entities/project.rb
@@ -85,7 +85,9 @@ module API
expose(:infrastructure_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :infrastructure) }
expose(:monitor_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :monitor) }
- expose :emails_disabled, documentation: { type: 'boolean' }
+ expose(:emails_disabled, documentation: { type: 'boolean' }) { |project, options| project.emails_disabled? }
+ expose :emails_enabled, documentation: { type: 'boolean' }
+
expose :shared_runners_enabled, documentation: { type: 'boolean' }
expose :lfs_enabled?, as: :lfs_enabled, documentation: { type: 'boolean' }
expose :creator_id, documentation: { type: 'integer', example: 1 }
@@ -110,6 +112,7 @@ module API
# CI/CD Settings
expose :ci_default_git_depth, documentation: { type: 'integer', example: 20 }
expose :ci_forward_deployment_enabled, documentation: { type: 'boolean' }
+ expose :ci_forward_deployment_rollback_allowed, documentation: { type: 'boolean' }
expose(:ci_job_token_scope_enabled, documentation: { type: 'boolean' }) { |p, _| p.ci_outbound_job_token_scope_enabled? }
expose :ci_separated_caches, documentation: { type: 'boolean' }
expose :ci_allow_fork_pipelines_to_run_in_parent_project, documentation: { type: 'boolean' }
diff --git a/lib/api/entities/snippet.rb b/lib/api/entities/snippet.rb
index 709566944ed..ee652225ba0 100644
--- a/lib/api/entities/snippet.rb
+++ b/lib/api/entities/snippet.rb
@@ -26,3 +26,5 @@ module API
end
end
end
+
+API::Entities::Snippet.prepend_mod_with('API::Entities::Snippet', with_descendants: true)
diff --git a/lib/api/entities/system/broadcast_message.rb b/lib/api/entities/system/broadcast_message.rb
new file mode 100644
index 00000000000..9a31095baf1
--- /dev/null
+++ b/lib/api/entities/system/broadcast_message.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module System
+ class BroadcastMessage < Grape::Entity
+ expose :id, :message, :starts_at, :ends_at, :color, :font, :target_access_levels, :target_path,
+ :broadcast_type, :dismissable
+ expose :active?, as: :active
+ end
+ end
+ end
+end
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 1a2314d41f0..2efdfe109f7 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -26,6 +26,7 @@ module API
optional :sort, type: String, values: %w[asc desc], default: 'asc', desc: 'Sort by asc (ascending) or desc (descending)'
optional :min_access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'Minimum access level of authenticated user'
optional :top_level_only, type: Boolean, desc: 'Only include top level groups'
+ use :optional_group_list_params_ee
use :pagination
end
@@ -36,6 +37,7 @@ module API
:custom_attributes,
:owned, :min_access_level,
:include_parent_descendants,
+ :repository_storage,
:search
)
@@ -322,7 +324,7 @@ module API
# TODO: Set higher urgency after resolving https://gitlab.com/gitlab-org/gitlab/-/issues/211498
get ":id/projects", feature_category: :groups_and_projects, urgency: :low do
finder_options = {
- only_owned: !params[:with_shared],
+ exclude_shared: !params[:with_shared],
include_subgroups: params[:include_subgroups],
include_ancestor_groups: params[:include_ancestor_groups]
}
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index b616f1b35b3..b7f21bd6c22 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -147,7 +147,7 @@ module API
if id.is_a?(Integer) || id =~ INTEGER_ID_REGEX
projects.find_by(id: id)
elsif id.include?("/")
- projects.find_by_full_path(id)
+ projects.find_by_full_path(id, follow_redirects: Feature.enabled?(:api_redirect_moved_projects))
end
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -157,10 +157,23 @@ module API
return forbidden! unless authorized_project_scope?(project)
- return project if can?(current_user, :read_project, project)
- return unauthorized! if authenticate_non_public?
+ unless can?(current_user, read_project_ability, project)
+ return unauthorized! if authenticate_non_public?
+
+ return not_found!('Project')
+ end
- not_found!('Project')
+ if project_moved?(id, project)
+ return not_allowed!('Non GET methods are not allowed for moved projects') unless request.get?
+
+ return redirect!(url_with_project_id(project))
+ end
+
+ project
+ end
+
+ def read_project_ability
+ :read_project
end
def authorized_project_scope?(project)
@@ -438,6 +451,13 @@ module API
order_options
end
+ # An error is raised to interrupt user's request and redirect them to the right route.
+ # The error! helper behaves similarly, but it cannot be used because it formats the
+ # response message.
+ def redirect!(location_url)
+ raise ::API::API::MovedPermanentlyError, location_url
+ end
+
# error helpers
def forbidden!(reason = nil)
@@ -737,6 +757,9 @@ module API
@initial_current_user = Gitlab::Auth::UniqueIpsLimiter.limit_user! { find_current_user! }
rescue Gitlab::Auth::UnauthorizedError
unauthorized!
+
+ # Explicitly return `nil`, otherwise an instance of `Rack::Response` is returned when reporting an error
+ nil
end
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
@@ -837,6 +860,24 @@ module API
def sanitize_id_param(id)
id.present? ? id.to_i : nil
end
+
+ def project_moved?(id, project)
+ return false unless Feature.enabled?(:api_redirect_moved_projects)
+ return false unless id.is_a?(String) && id.include?('/')
+ return false if project.blank? || id == project.full_path
+ return false unless params[:id] == id
+
+ true
+ end
+
+ def url_with_project_id(project)
+ new_params = params.merge(id: project.id.to_s).transform_values { |v| v.is_a?(String) ? CGI.escape(v) : v }
+ new_path = GrapePathHelpers::DecoratedRoute.new(route).path_segments_with_values(new_params).join('/')
+
+ Rack::Request.new(env).tap do |r|
+ r.path_info = "/#{new_path}"
+ end.url
+ end
end
end
diff --git a/lib/api/helpers/groups_helpers.rb b/lib/api/helpers/groups_helpers.rb
index 74c8b582fde..f7802938d8b 100644
--- a/lib/api/helpers/groups_helpers.rb
+++ b/lib/api/helpers/groups_helpers.rb
@@ -23,6 +23,16 @@ module API
optional :lfs_enabled, type: Boolean, desc: 'Enable/disable LFS for the projects in this group'
optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access'
optional :default_branch_protection, type: Integer, values: ::Gitlab::Access.protection_values, desc: 'Determine if developers can push to default branch'
+ optional :default_branch_protection_defaults, type: Hash, desc: 'Determine if developers can push to default branch' do
+ optional :allowed_to_push, type: Array, desc: 'An array of access levels allowed to push' do
+ requires :access_level, type: Integer, values: [::Gitlab::Access::DEVELOPER, ::Gitlab::Access::MAINTAINER], desc: 'A valid access level'
+ end
+ optional :allow_force_push, type: Boolean, desc: 'Allow force push for all users with push access.'
+ optional :allowed_to_merge, type: Array, desc: 'An array of access levels allowed to merge' do
+ requires :access_level, type: Integer, values: [::Gitlab::Access::DEVELOPER, ::Gitlab::Access::MAINTAINER], desc: 'A valid access level'
+ end
+ optional :developer_can_initial_push, type: Boolean, desc: 'Allow developers to initial push'
+ end
optional :shared_runners_setting, type: String, values: ::Namespace::SHARED_RUNNERS_SETTINGS, desc: 'Enable/disable shared runners for the group and its subgroups and projects'
end
@@ -44,6 +54,9 @@ module API
params :optional_projects_params_ee do
end
+ params :optional_group_list_params_ee do
+ end
+
params :optional_projects_params do
use :optional_projects_params_ee
end
diff --git a/lib/api/helpers/integrations_helpers.rb b/lib/api/helpers/integrations_helpers.rb
index 09dd69ef03b..53117af8648 100644
--- a/lib/api/helpers/integrations_helpers.rb
+++ b/lib/api/helpers/integrations_helpers.rb
@@ -453,7 +453,8 @@ module API
desc: 'Branches for which notifications are to be sent'
},
chat_notification_flags,
- chat_notification_events
+ chat_notification_events,
+ chat_notification_channels
].flatten,
'drone-ci' => [
{
@@ -527,6 +528,12 @@ module API
name: :service_account_key_file_name,
type: String,
desc: 'The filename of the Google Play service account key'
+ },
+ {
+ required: false,
+ name: :google_play_protected_refs,
+ type: ::Grape::API::Boolean,
+ desc: 'Only enable for protected refs'
}
],
'hangouts-chat' => [
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index 0a6b288e3f8..f66f899c98b 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -5,6 +5,8 @@ module API
module InternalHelpers
attr_reader :redirected_path
+ UNKNOWN_CHECK_RESULT_ERROR = 'Unknown check result'
+
delegate :wiki?, to: :repo_type
def actor
@@ -28,6 +30,35 @@ module API
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
+ def access_check_result
+ with_admin_mode_bypass!(actor.user&.id) do
+ access_check!(actor, params)
+ end
+ rescue Gitlab::GitAccess::ForbiddenError => e
+ # The return code needs to be 401. If we return 403
+ # the custom message we return won't be shown to the user
+ # and, instead, the default message 'GitLab: API is not accessible'
+ # will be displayed
+ response_with_status(code: 401, success: false, message: e.message)
+ rescue Gitlab::GitAccess::TimeoutError => e
+ response_with_status(code: 503, success: false, message: e.message)
+ rescue Gitlab::GitAccess::NotFoundError => e
+ response_with_status(code: 404, success: false, message: e.message)
+ end
+
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ def access_check!(actor, params)
+ access_checker = access_checker_for(actor, params[:protocol])
+ access_checker.check(params[:action], params[:changes]).tap do |result|
+ break result if @project || !repo_type.project?
+
+ # If we have created a project directly from a git push
+ # we have to assign its value to both @project and @container
+ @project = @container = access_checker.container
+ end
+ end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
+
def access_checker_for(actor, protocol)
access_checker_klass.new(actor.key_or_user, container, protocol,
authentication_abilities: ssh_authentication_abilities,
@@ -73,6 +104,25 @@ module API
false
end
+ def response_with_status(code: 200, success: true, message: nil, **extra_options)
+ status code
+ { status: success, message: message }.merge(extra_options).compact
+ end
+
+ def unsuccessful_response?(response)
+ response.is_a?(Hash) && response[:status] == false
+ end
+
+ def with_admin_mode_bypass!(actor_id, &block)
+ return yield unless Gitlab::CurrentSettings.admin_mode
+
+ Gitlab::Auth::CurrentUserMode.bypass_session!(actor_id, &block)
+ end
+
+ def send_git_audit_streaming_event(msg)
+ # Defined in EE
+ end
+
private
def repository_path
@@ -138,3 +188,5 @@ module API
end
end
end
+
+API::Helpers::InternalHelpers.prepend_mod
diff --git a/lib/api/helpers/label_helpers.rb b/lib/api/helpers/label_helpers.rb
index b3ba962666f..e27278f5681 100644
--- a/lib/api/helpers/label_helpers.rb
+++ b/lib/api/helpers/label_helpers.rb
@@ -107,7 +107,9 @@ module API
authorize! :admin_label, label
- destroy_conditionally!(label)
+ return if destroy_conditionally!(label)
+
+ render_api_error!('Label is locked and was not removed', 400)
end
def promote_label(parent)
diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb
index 642963768f8..699d3f360d9 100644
--- a/lib/api/helpers/projects_helpers.rb
+++ b/lib/api/helpers/projects_helpers.rb
@@ -40,7 +40,8 @@ module API
optional :infrastructure_access_level, type: String, values: %w(disabled private enabled), desc: 'Infrastructure access level. One of `disabled`, `private` or `enabled`'
optional :monitor_access_level, type: String, values: %w(disabled private enabled), desc: 'Monitor access level. One of `disabled`, `private` or `enabled`'
- optional :emails_disabled, type: Boolean, desc: 'Disable email notifications'
+ optional :emails_disabled, type: Boolean, desc: 'Deprecated: Use emails_enabled instead.'
+ optional :emails_enabled, type: Boolean, desc: 'Enable email notifications'
optional :show_default_award_emojis, type: Boolean, desc: 'Show default award emojis'
optional :show_diff_preview_in_email, type: Boolean, desc: 'Include the code diff preview in merge request notification emails'
optional :warn_about_potentially_unwanted_characters, type: Boolean, desc: 'Warn about Potentially Unwanted Characters'
@@ -102,6 +103,7 @@ module API
optional :ci_default_git_depth, type: Integer, desc: 'Default number of revisions for shallow cloning'
optional :keep_latest_artifact, type: Boolean, desc: 'Indicates if the latest artifact should be kept for this project.'
optional :ci_forward_deployment_enabled, type: Boolean, desc: 'Prevent older deployment jobs that are still pending'
+ optional :ci_forward_deployment_rollback_allowed, type: Boolean, desc: 'Allow job retries for rollback deployments'
optional :ci_allow_fork_pipelines_to_run_in_parent_project, type: Boolean, desc: 'Allow fork merge request pipelines to run in parent project'
optional :ci_separated_caches, type: Boolean, desc: 'Enable or disable separated caches based on branch protection.'
optional :restrict_user_defined_variables, type: Boolean, desc: 'Restrict use of user-defined variables when triggering a pipeline'
@@ -139,12 +141,14 @@ module API
:ci_default_git_depth,
:ci_allow_fork_pipelines_to_run_in_parent_project,
:ci_forward_deployment_enabled,
+ :ci_forward_deployment_rollback_allowed,
:ci_separated_caches,
:container_registry_access_level,
:container_expiration_policy_attributes,
:default_branch,
:description,
- :emails_disabled,
+ :emails_disabled, # deprecated
+ :emails_enabled,
:forking_access_level,
:issues_access_level,
:lfs_enabled,
diff --git a/lib/api/helpers/snippets_helpers.rb b/lib/api/helpers/snippets_helpers.rb
index fe20fb3cbe2..241e92e9d10 100644
--- a/lib/api/helpers/snippets_helpers.rb
+++ b/lib/api/helpers/snippets_helpers.rb
@@ -46,6 +46,9 @@ module API
at_least_one_of :content, :description, :files, :file_name, :title, :visibility
end
+ params :optional_list_params_ee do # rubocop:disable Lint/EmptyBlock
+ end
+
def content_for(snippet)
if snippet.empty_repo?
env['api.format'] = :txt
@@ -96,3 +99,5 @@ module API
end
end
end
+
+API::Helpers::SnippetsHelpers.prepend_mod_with('API::Helpers::SnippetsHelpers')
diff --git a/lib/api/internal/base.rb b/lib/api/internal/base.rb
index 2a5ff257718..f9dc888fbeb 100644
--- a/lib/api/internal/base.rb
+++ b/lib/api/internal/base.rb
@@ -21,18 +21,11 @@ module API
helpers ::API::Helpers::InternalHelpers
- UNKNOWN_CHECK_RESULT_ERROR = 'Unknown check result'
-
VALID_PAT_SCOPES = Set.new(
Gitlab::Auth::API_SCOPES + Gitlab::Auth::REPOSITORY_SCOPES + Gitlab::Auth::REGISTRY_SCOPES
).freeze
helpers do
- def response_with_status(code: 200, success: true, message: nil, **extra_options)
- status code
- { status: success, message: message }.merge(extra_options).compact
- end
-
def lfs_authentication_url(container)
# This is a separate method so that EE can alter its behaviour more
# easily.
@@ -58,21 +51,8 @@ module API
actor.update_last_used_at!
- check_result = begin
- with_admin_mode_bypass!(actor.user&.id) do
- access_check!(actor, params)
- end
- rescue Gitlab::GitAccess::ForbiddenError => e
- # The return code needs to be 401. If we return 403
- # the custom message we return won't be shown to the user
- # and, instead, the default message 'GitLab: API is not accessible'
- # will be displayed
- return response_with_status(code: 401, success: false, message: e.message)
- rescue Gitlab::GitAccess::TimeoutError => e
- return response_with_status(code: 503, success: false, message: e.message)
- rescue Gitlab::GitAccess::NotFoundError => e
- return response_with_status(code: 404, success: false, message: e.message)
- end
+ check_result = access_check_result
+ return check_result if unsuccessful_response?(check_result)
log_user_activity(actor.user)
@@ -103,26 +83,11 @@ module API
when ::Gitlab::GitAccessResult::CustomAction
response_with_status(code: 300, payload: check_result.payload, gl_console_messages: check_result.console_messages)
else
- response_with_status(code: 500, success: false, message: UNKNOWN_CHECK_RESULT_ERROR)
+ response_with_status(code: 500, success: false, message: ::API::Helpers::InternalHelpers::UNKNOWN_CHECK_RESULT_ERROR)
end
end
# rubocop: enable Metrics/AbcSize
- def send_git_audit_streaming_event(msg)
- # Defined in EE
- end
-
- def access_check!(actor, params)
- access_checker = access_checker_for(actor, params[:protocol])
- access_checker.check(params[:action], params[:changes]).tap do |result|
- break result if @project || !repo_type.project?
-
- # If we have created a project directly from a git push
- # we have to assign its value to both @project and @container
- @project = @container = access_checker.container
- end
- end
-
def validate_actor(actor)
return 'Could not find the given key' unless actor.key
@@ -136,14 +101,6 @@ module API
def two_factor_push_otp_check
{ success: false, message: 'Feature is not available' }
end
-
- def with_admin_mode_bypass!(actor_id)
- return yield unless Gitlab::CurrentSettings.admin_mode
-
- Gitlab::Auth::CurrentUserMode.bypass_session!(actor_id) do
- yield
- end
- end
end
namespace 'internal' do
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index ff9d0e2c371..03b9ee03b46 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -118,8 +118,7 @@ module API
end
def recheck_mergeability_of(merge_requests:)
- return if ::Feature.enabled?(:restrict_merge_status_recheck, user_project) &&
- !can?(current_user, :update_merge_request, user_project)
+ return unless can?(current_user, :update_merge_request, user_project)
merge_requests.each { |mr| mr.check_mergeability(async: true) }
end
diff --git a/lib/api/metrics/dashboard/annotations.rb b/lib/api/metrics/dashboard/annotations.rb
index 6edf4783159..ace772842c0 100644
--- a/lib/api/metrics/dashboard/annotations.rb
+++ b/lib/api/metrics/dashboard/annotations.rb
@@ -40,23 +40,7 @@ module API
end
post ':id/metrics_dashboard/annotations' do
- not_found! if Feature.enabled?(:remove_monitor_metrics)
-
- annotations_source_object = annotations_source[:class].find(params[:id])
-
- forbidden! unless can?(current_user, :admin_metrics_dashboard_annotation, annotations_source_object)
-
- create_service_params = declared(params).merge(
- annotations_source[:create_service_param_key] => annotations_source_object
- )
-
- result = ::Metrics::Dashboard::Annotations::CreateService.new(current_user, create_service_params).execute
-
- if result[:status] == :success
- present result[:annotation], with: Entities::Metrics::Dashboard::Annotation
- else
- error!(result, 400)
- end
+ not_found!
end
end
end
diff --git a/lib/api/metrics/user_starred_dashboards.rb b/lib/api/metrics/user_starred_dashboards.rb
index b7fba2b6459..eeb1efb9001 100644
--- a/lib/api/metrics/user_starred_dashboards.rb
+++ b/lib/api/metrics/user_starred_dashboards.rb
@@ -25,15 +25,7 @@ module API
end
post ':id/metrics/user_starred_dashboards' do
- not_found! if Feature.enabled?(:remove_monitor_metrics)
-
- result = ::Metrics::UsersStarredDashboards::CreateService.new(current_user, user_project, params[:dashboard_path]).execute
-
- if result.success?
- present result.payload, with: Entities::Metrics::UserStarredDashboard
- else
- error!({ errors: result.message }, 400)
- end
+ not_found!
end
desc 'Remove a star from a dashboard' do
@@ -52,16 +44,7 @@ module API
end
delete ':id/metrics/user_starred_dashboards' do
- not_found! if Feature.enabled?(:remove_monitor_metrics)
-
- result = ::Metrics::UsersStarredDashboards::DeleteService.new(current_user, user_project, params[:dashboard_path]).execute
-
- if result.success?
- status :ok
- result.payload
- else
- status :bad_request
- end
+ not_found!
end
end
end
diff --git a/lib/api/ml/mlflow/api_helpers.rb b/lib/api/ml/mlflow/api_helpers.rb
index 7f4a895235c..66689d8e0ca 100644
--- a/lib/api/ml/mlflow/api_helpers.rb
+++ b/lib/api/ml/mlflow/api_helpers.rb
@@ -46,7 +46,7 @@ module API
)
path = path.delete_suffix('/package_version')
- "#{request.base_url}#{path}"
+ expose_url(path)
end
end
end
diff --git a/lib/api/ml/mlflow/entrypoint.rb b/lib/api/ml/mlflow/entrypoint.rb
index 048234eccd1..7948949dac6 100644
--- a/lib/api/ml/mlflow/entrypoint.rb
+++ b/lib/api/ml/mlflow/entrypoint.rb
@@ -10,6 +10,7 @@ module API
# The first part of the url is the namespace, the second part of the URL is what the MLFlow client calls
MLFLOW_API_PREFIX = ':id/ml/mlflow/api/2.0/mlflow/'
+ helpers ::API::Helpers::RelatedResourcesHelpers
helpers ::API::Ml::Mlflow::ApiHelpers
allow_access_with_scope :api
diff --git a/lib/api/ml_model_packages.rb b/lib/api/ml_model_packages.rb
index 35d231d9fe1..8a7a8fc9525 100644
--- a/lib/api/ml_model_packages.rb
+++ b/lib/api/ml_model_packages.rb
@@ -50,7 +50,7 @@ module API
requires :model_name, type: String, desc: 'Model name', regexp: Gitlab::Regex.ml_model_name_regex,
file_path: true
requires :model_version, type: String, desc: 'Model version',
- regexp: Gitlab::Regex.ml_model_version_regex
+ regexp: Gitlab::Regex.semver_regex
requires :file_name, type: String, desc: 'Package file name',
regexp: Gitlab::Regex.ml_model_file_name_regex, file_path: true
optional :status, type: String, values: ALLOWED_STATUSES, desc: 'Package status'
diff --git a/lib/api/nuget_project_packages.rb b/lib/api/nuget_project_packages.rb
index 2716d6f0b64..bff645700f5 100644
--- a/lib/api/nuget_project_packages.rb
+++ b/lib/api/nuget_project_packages.rb
@@ -98,9 +98,30 @@ module API
created!
end
+ def publish_package(symbol_package: false)
+ upload_nuget_package_file(symbol_package: symbol_package) do |package|
+ track_package_event(
+ symbol_package ? 'push_symbol_package' : 'push_package',
+ :nuget,
+ **{ category: 'API::NugetPackages',
+ project: package.project,
+ namespace: package.project.namespace }.tap { |args| args[:feed] = 'v2' if request.path.include?('nuget/v2') }
+ )
+ end
+ rescue ObjectStorage::RemoteStoreError => e
+ Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:file_name], project_id: project_or_group.id })
+
+ forbidden!
+ end
+
def required_permission
:read_package
end
+
+ def format_filename(package)
+ return "#{params[:package_filename]}.#{params[:format]}" if Feature.disabled?(:nuget_normalized_version, project_or_group) || package.version == params[:package_version]
+ return "#{params[:package_filename].sub(params[:package_version], package.version)}.#{params[:format]}" if package.normalized_nuget_version == params[:package_version]
+ end
end
params do
@@ -159,8 +180,9 @@ module API
requires :package_filename, type: String, desc: 'The NuGet package filename', regexp: API::NO_SLASH_URL_PART_REGEX, documentation: { example: 'mynugetpkg.1.3.0.17.nupkg' }
end
get '*package_version/*package_filename', format: [:nupkg, :snupkg], urgency: :low do
- filename = "#{params[:package_filename]}.#{params[:format]}"
- package_file = ::Packages::PackageFileFinder.new(find_package(params[:package_name], params[:package_version]), filename, with_file_name_like: true)
+ package = find_package(params[:package_name], params[:package_version])
+ filename = format_filename(package)
+ package_file = ::Packages::PackageFileFinder.new(package, filename, with_file_name_like: true)
.execute
not_found!('Package') unless package_file
@@ -179,7 +201,7 @@ module API
end
end
- # To support an additional authentication option for download endpoints,
+ # To support an additional authentication option for publish endpoints,
# we redefine the `authenticate_with` method by combining the previous
# authentication option with the new one.
authenticate_with do |accept|
@@ -191,7 +213,7 @@ module API
namespace '/nuget' do
# https://docs.microsoft.com/en-us/nuget/api/package-publish-resource
- desc 'The NuGet Package Publish endpoint' do
+ desc 'The NuGet V3 Feed Package Publish endpoint' do
detail 'This feature was introduced in GitLab 12.6'
success code: 201
failure [
@@ -207,19 +229,7 @@ module API
use :file_params
end
put urgency: :low do
- upload_nuget_package_file do |package|
- track_package_event(
- 'push_package',
- :nuget,
- category: 'API::NugetPackages',
- project: package.project,
- namespace: package.project.namespace
- )
- end
- rescue ObjectStorage::RemoteStoreError => e
- Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:file_name], project_id: project_or_group.id })
-
- forbidden!
+ publish_package
end
desc 'The NuGet Package Authorize endpoint' do
@@ -252,19 +262,7 @@ module API
use :file_params
end
put 'symbolpackage', urgency: :low do
- upload_nuget_package_file(symbol_package: true) do |package|
- track_package_event(
- 'push_symbol_package',
- :nuget,
- category: 'API::NugetPackages',
- project: package.project,
- namespace: package.project.namespace
- )
- end
- rescue ObjectStorage::RemoteStoreError => e
- Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:file_name], project_id: project_or_group.id })
-
- forbidden!
+ publish_package(symbol_package: true)
end
desc 'The NuGet Symbol Package Authorize endpoint' do
@@ -280,6 +278,42 @@ module API
put 'symbolpackage/authorize', urgency: :low do
authorize_nuget_upload
end
+
+ namespace '/v2' do
+ desc 'The NuGet V2 Feed Package Publish endpoint' do
+ detail 'This feature was introduced in GitLab 16.2'
+ success code: 201
+ failure [
+ { code: 400, message: 'Bad Request' },
+ { code: 401, message: 'Unauthorized' },
+ { code: 403, message: 'Forbidden' },
+ { code: 404, message: 'Not Found' }
+ ]
+ tags %w[nuget_packages]
+ end
+
+ params do
+ use :file_params
+ end
+ put do
+ publish_package
+ end
+
+ desc 'The NuGet V2 Feed Package Authorize endpoint' do
+ detail 'This feature was introduced in GitLab 16.2'
+ success code: 200
+ failure [
+ { code: 401, message: 'Unauthorized' },
+ { code: 403, message: 'Forbidden' },
+ { code: 404, message: 'Not Found' }
+ ]
+ tags %w[nuget_packages]
+ end
+
+ put 'authorize', urgency: :low do
+ authorize_nuget_upload
+ end
+ end
end
end
end
diff --git a/lib/api/project_packages.rb b/lib/api/project_packages.rb
index 2aa6858e41d..6b2ba41f013 100644
--- a/lib/api/project_packages.rb
+++ b/lib/api/project_packages.rb
@@ -5,8 +5,6 @@ module API
include Gitlab::Utils::StrongMemoize
include PaginationParams
- PIPELINE_COLUMNS = %i[id iid project_id sha ref status source created_at updated_at user_id].freeze
-
before do
authorize_packages_access!(user_project)
end
@@ -110,7 +108,7 @@ module API
package.build_infos.without_empty_pipelines,
paginator_params: { per_page: declared_params[:per_page], cursor: declared_params[:cursor] }
) do |results|
- ::Ci::Pipeline.id_in(results.map(&:pipeline_id)).select(PIPELINE_COLUMNS).order_id_desc
+ ::Packages::PipelinesFinder.new(results.map(&:pipeline_id)).execute
end
present pipelines, with: ::API::Entities::Package::Pipeline, user: current_user
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 468f284f136..f6a2ce0f829 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -220,6 +220,8 @@ module API
def translate_params_for_compatibility(params)
params[:builds_enabled] = params.delete(:jobs_enabled) if params.key?(:jobs_enabled)
+ params[:emails_enabled] = !params.delete(:emails_disabled) if params.key?(:emails_disabled)
+
params
end
@@ -792,10 +794,12 @@ module API
desc 'Import members from another project' do
detail 'This feature was introduced in GitLab 14.2'
- success code: 201
+ success code: 200
failure [
{ code: 403, message: 'Unauthenticated' },
- { code: 404, message: 'Not found' }
+ { code: 403, message: 'Forbidden - Project' },
+ { code: 404, message: 'Project Not Found' },
+ { code: 422, message: 'Import failed' }
]
tags %w[projects]
end
@@ -812,10 +816,12 @@ module API
result = ::Members::ImportProjectTeamService.new(current_user, params).execute
- if result
- { status: result, message: 'Successfully imported' }
+ if result.success?
+ { status: result.status }
+ elsif result.reason == :unprocessable_entity
+ render_api_error!(result.message, result.reason)
else
- render_api_error!('Import failed', :unprocessable_entity)
+ { status: result.status, message: result.message, total_members_count: result.payload[:total_members_count] }
end
end
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index 295d1d5ab16..4131f41743f 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -95,6 +95,10 @@ module API
params
]
end
+
+ def rescue_not_found?
+ Feature.disabled?(:handle_structured_gitaly_errors)
+ end
end
desc 'Get a project repository tree' do
@@ -123,13 +127,16 @@ module API
end
end
get ':id/repository/tree', urgency: :low do
- tree_finder = ::Repositories::TreeFinder.new(user_project, declared_params(include_missing: false))
+ tree_finder = ::Repositories::TreeFinder.new(user_project, declared_params(include_missing: false).merge(rescue_not_found: rescue_not_found?))
not_found!("Tree") unless tree_finder.commit_exists?
tree = Gitlab::Pagination::GitalyKeysetPager.new(self, user_project).paginate(tree_finder)
present tree, with: Entities::TreeObject
+
+ rescue Gitlab::Git::Index::IndexError => e
+ not_found!(e.message)
end
desc 'Get raw blob contents from the repository'
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index b12ca48829b..e2dc78fe84a 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -49,6 +49,16 @@ module API
optional :default_ci_config_path, type: String, desc: 'The instance default CI/CD configuration file and path for new projects'
optional :default_project_creation, type: Integer, values: ::Gitlab::Access.project_creation_values, desc: 'Determine if developers can create projects in the group'
optional :default_branch_protection, type: Integer, values: ::Gitlab::Access.protection_values, desc: 'Determine if developers can push to default branch'
+ optional :default_branch_protection_defaults, type: Hash, desc: 'Determine if developers can push to default branch' do
+ optional :allowed_to_push, type: Array, desc: 'An array of access levels allowed to push' do
+ requires :access_level, type: Integer, values: [::Gitlab::Access::DEVELOPER, ::Gitlab::Access::MAINTAINER], desc: 'A valid access level'
+ end
+ optional :allow_force_push, type: Boolean, desc: 'Allow force push for all users with push access.'
+ optional :allowed_to_merge, type: Array, desc: 'An array of access levels allowed to merge' do
+ requires :access_level, type: Integer, values: [::Gitlab::Access::DEVELOPER, ::Gitlab::Access::MAINTAINER], desc: 'A valid access level'
+ end
+ optional :developer_can_initial_push, type: Boolean, desc: 'Allow developers to initial push'
+ end
optional :default_group_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default group visibility'
optional :default_project_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default project visibility'
optional :default_projects_limit, type: Integer, desc: 'The maximum number of personal projects'
@@ -105,6 +115,8 @@ module API
optional :max_attachment_size, type: Integer, desc: 'Maximum attachment size in MB'
optional :max_export_size, type: Integer, desc: 'Maximum export size in MB'
optional :max_import_size, type: Integer, desc: 'Maximum import size in MB'
+ optional :max_import_remote_file_size, type: Integer, desc: 'Maximum remote file size in MB for imports from external object storages'
+ optional :max_decompressed_archive_size, type: Integer, desc: 'Maximum decompressed size in MB'
optional :max_pages_size, type: Integer, desc: 'Maximum size of pages in MB'
optional :max_pages_custom_domains_per_project, type: Integer, desc: 'Maximum number of GitLab Pages custom domains per project'
optional :max_terraform_state_size_bytes, type: Integer, desc: "Maximum size in bytes of the Terraform state file. Set this to 0 for unlimited file size."
@@ -200,6 +212,7 @@ module API
optional :jira_connect_application_key, type: String, desc: "Application ID of the OAuth application that should be used to authenticate with the GitLab for Jira Cloud app"
optional :jira_connect_proxy_url, type: String, desc: "URL of the GitLab instance that should be used as a proxy for the GitLab for Jira Cloud app"
optional :bulk_import_enabled, type: Boolean, desc: 'Enable migrating GitLab groups and projects by direct transfer'
+ optional :bulk_import_max_download_file, type: Integer, desc: 'Maximum download file size in MB when importing from source GitLab instances by direct transfer'
optional :allow_runner_registration_token, type: Boolean, desc: 'Allow registering runners using a registration token'
optional :ci_max_includes, type: Integer, desc: 'Maximum number of includes per pipeline'
optional :security_policy_global_group_approvers_enabled, type: Boolean, desc: 'Query scan result policy approval groups globally'
@@ -210,6 +223,7 @@ module API
requires :slack_app_signing_secret, type: String, desc: 'The signing secret of the GitLab for Slack app. Used for authenticating API requests from the app'
requires :slack_app_verification_token, type: String, desc: 'The verification token of the GitLab for Slack app. This method of authentication is deprecated by Slack and used only for authenticating slash commands from the app'
end
+ optional :namespace_aggregation_schedule_lease_duration_in_seconds, type: Integer, desc: 'Maximum duration (in seconds) between refreshes of namespace statistics (Default: 300)'
Gitlab::SSHPublicKey.supported_types.each do |type|
optional :"#{type}_key_restriction",
diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb
index 77872e7d13c..4f3c1499549 100644
--- a/lib/api/snippets.rb
+++ b/lib/api/snippets.rb
@@ -8,22 +8,19 @@ module API
feature_category :source_code_management
urgency :low
+ helpers do
+ def find_snippets(user: current_user, params: {})
+ SnippetsFinder.new(user, params).execute
+ end
+
+ def snippets_for_current_user
+ find_snippets(params: { author: current_user })
+ end
+ end
+
resource :snippets do
helpers Helpers::SnippetsHelpers
helpers SpammableActions::CaptchaCheck::RestApiActionsSupport
- helpers do
- def snippets_for_current_user
- SnippetsFinder.new(current_user, author: current_user).execute
- end
-
- def public_snippets
- Snippet.only_personal_snippets.are_public.fresh
- end
-
- def snippets
- SnippetsFinder.new(current_user).execute
- end
- end
desc 'Get a snippets list for an authenticated user' do
detail 'This feature was introduced in GitLab 8.15.'
@@ -44,7 +41,8 @@ module API
authenticate!
filter_params = declared_params(include_missing: false).merge(author: current_user)
- present paginate(SnippetsFinder.new(current_user, filter_params).execute), with: Entities::Snippet, current_user: current_user
+
+ present paginate(find_snippets(params: filter_params)), with: Entities::Snippet, current_user: current_user
end
desc 'List all public personal snippets current_user has access to' do
@@ -66,7 +64,32 @@ module API
authenticate!
filter_params = declared_params(include_missing: false).merge(only_personal: true)
- present paginate(SnippetsFinder.new(nil, filter_params).execute), with: Entities::PersonalSnippet, current_user: current_user
+
+ present paginate(find_snippets(user: nil, params: filter_params)), with: Entities::PersonalSnippet, current_user: current_user
+ end
+
+ desc 'List all snippets current_user has access to' do
+ detail 'This feature was introduced in GitLab 16.3.'
+ success Entities::Snippet
+ failure [
+ { code: 404, message: 'Not found' }
+ ]
+ tags %w[snippets]
+ is_array true
+ end
+ params do
+ optional :created_after, type: DateTime, desc: 'Return snippets created after the specified time'
+ optional :created_before, type: DateTime, desc: 'Return snippets created before the specified time'
+
+ use :pagination
+ use :optional_list_params_ee
+ end
+ get 'all' do
+ authenticate!
+
+ filter_params = declared_params(include_missing: false).merge(all_available: true)
+
+ present paginate(find_snippets(params: filter_params)), with: Entities::Snippet, current_user: current_user
end
desc 'Get a single snippet' do
@@ -81,7 +104,7 @@ module API
requires :id, type: Integer, desc: 'The ID of a snippet'
end
get ':id' do
- snippet = snippets.find_by_id(params[:id])
+ snippet = find_snippets.find_by_id(params[:id])
break not_found!('Snippet') unless snippet
@@ -105,6 +128,7 @@ module API
values: Gitlab::VisibilityLevel.string_values,
default: 'internal',
desc: 'The visibility of the snippet'
+
use :create_file_params
end
post do
@@ -135,7 +159,6 @@ module API
]
tags %w[snippets]
end
-
params do
requires :id, type: Integer, desc: 'The ID of a snippet'
optional :content, type: String, allow_blank: false, desc: 'The content of a snippet'
@@ -214,7 +237,7 @@ module API
requires :id, type: Integer, desc: 'The ID of a snippet'
end
get ":id/raw" do
- snippet = snippets.find_by_id(params.delete(:id))
+ snippet = find_snippets.find_by_id(params.delete(:id))
not_found!('Snippet') unless snippet
present content_for(snippet)
@@ -230,7 +253,7 @@ module API
use :raw_file_params
end
get ":id/files/:ref/:file_path/raw", requirements: { file_path: API::NO_SLASH_URL_PART_REGEX } do
- snippet = snippets.find_by_id(params.delete(:id))
+ snippet = find_snippets.find_by_id(params.delete(:id))
not_found!('Snippet') unless snippet&.repo_exists?
present file_content_for(snippet)
@@ -258,3 +281,5 @@ module API
end
end
end
+
+API::Snippets.prepend_mod_with('API::Snippets')
diff --git a/lib/api/time_tracking_endpoints.rb b/lib/api/time_tracking_endpoints.rb
index 3534edc3831..d68057a1947 100644
--- a/lib/api/time_tracking_endpoints.rb
+++ b/lib/api/time_tracking_endpoints.rb
@@ -55,10 +55,11 @@ module API
issuable_key = "#{issuable_name}_iid".to_sym
desc "Set a time estimate for a #{issuable_name}" do
- detail " Sets an estimated time of work for this #{issuable_name}."
+ detail "Sets an estimated time of work for this #{issuable_name}."
success Entities::IssuableTimeStats
failure [
{ code: 401, message: 'Unauthorized' },
+ { code: 400, message: 'Bad request' },
{ code: 404, message: 'Not found' }
]
tags [issuable_collection_name]
@@ -70,8 +71,14 @@ module API
post ":id/#{issuable_collection_name}/:#{issuable_key}/time_estimate" do
authorize! admin_issuable_key, load_issuable
- status :ok
- update_issuable(time_estimate: Gitlab::TimeTrackingFormatter.parse(params.delete(:duration)))
+ time_estimate = Gitlab::TimeTrackingFormatter.parse(params.delete(:duration), keep_zero: true)
+
+ if time_estimate && time_estimate >= 0
+ status :ok
+ update_issuable(time_estimate: time_estimate)
+ else
+ bad_request!(reason: 'Time estimate must have a valid format and be greater than or equal to zero.')
+ end
end
desc "Reset the time estimate for a project #{issuable_name}" do
diff --git a/lib/atlassian/jira_connect/serializers/deployment_entity.rb b/lib/atlassian/jira_connect/serializers/deployment_entity.rb
index 96e7b1726cb..1feb8a60f73 100644
--- a/lib/atlassian/jira_connect/serializers/deployment_entity.rb
+++ b/lib/atlassian/jira_connect/serializers/deployment_entity.rb
@@ -98,8 +98,6 @@ module Atlassian
# Extract Jira issue keys from commits made to the deployment's branch or tag
# since the last successful deployment was made to the environment.
def issue_keys_from_commits_since_last_deploy
- return [] if Feature.disabled?(:jira_deployment_issue_keys, project)
-
last_deployed_commit = environment
.successful_deployments
.id_not_in(deployment.id)
diff --git a/lib/backup/gitaly_backup.rb b/lib/backup/gitaly_backup.rb
index 53c998efd71..5b55c2cbdf7 100644
--- a/lib/backup/gitaly_backup.rb
+++ b/lib/backup/gitaly_backup.rb
@@ -10,11 +10,13 @@ module Backup
# @param [Integer] max_parallelism max parallelism when running backups
# @param [Integer] storage_parallelism max parallelism per storage (is affected by max_parallelism)
# @param [Boolean] incremental if incremental backups should be created.
- def initialize(progress, max_parallelism: nil, storage_parallelism: nil, incremental: false)
+ # @param [Boolean] server_side if server-side backups should be used.
+ def initialize(progress, max_parallelism: nil, storage_parallelism: nil, incremental: false, server_side: false)
@progress = progress
@max_parallelism = max_parallelism
@storage_parallelism = storage_parallelism
@incremental = incremental
+ @server_side = server_side
end
def start(type, backup_repos_path, backup_id: nil, remove_all_repositories: nil)
@@ -24,28 +26,11 @@ module Backup
FileUtils.rm_rf(backup_repos_path)
end
- command = case type
- when :create
- 'create'
- when :restore
- 'restore'
- else
- raise Error, "unknown backup type: #{type}"
- end
-
- args = ['-layout', 'pointer']
- args += ['-parallel', @max_parallelism.to_s] if @max_parallelism
- args += ['-parallel-storage', @storage_parallelism.to_s] if @storage_parallelism
-
- case type
- when :create
- args += ['-incremental'] if incremental?
- args += ['-id', backup_id] if backup_id
- when :restore
- args += ['-remove-all-repositories', remove_all_repositories.join(',')] if remove_all_repositories
- end
-
- @input_stream, stdout, @thread = Open3.popen2(build_env, bin_path, command, '-path', backup_repos_path, *args)
+ @input_stream, stdout, @thread = Open3.popen2(
+ build_env,
+ bin_path,
+ *gitaly_backup_args(type, backup_repos_path, backup_id, remove_all_repositories)
+ )
@out_reader = Thread.new do
IO.copy_stream(stdout, @progress)
@@ -78,6 +63,41 @@ module Backup
@incremental
end
+ def server_side?
+ @server_side
+ end
+
+ def gitaly_backup_args(type, backup_repos_path, backup_id, remove_all_repositories)
+ command = case type
+ when :create
+ 'create'
+ when :restore
+ 'restore'
+ else
+ raise Error, "unknown backup type: #{type}"
+ end
+
+ args = [command] + if server_side?
+ ['-server-side']
+ else
+ ['-path', backup_repos_path, '-layout', 'pointer']
+ end
+
+ args += ['-parallel', @max_parallelism.to_s] if @max_parallelism
+ args += ['-parallel-storage', @storage_parallelism.to_s] if @storage_parallelism
+
+ case type
+ when :create
+ args += ['-incremental'] if incremental?
+ args += ['-id', backup_id] if backup_id
+ when :restore
+ args += ['-remove-all-repositories', remove_all_repositories.join(',')] if remove_all_repositories
+ args += ['-id', backup_id] if backup_id && server_side?
+ end
+
+ args
+ end
+
# Schedule a new backup job through a non-blocking JSON based pipe protocol
#
# @see https://gitlab.com/gitlab-org/gitaly/-/blob/master/doc/gitaly-backup.md
@@ -107,7 +127,11 @@ module Backup
'SSL_CERT_FILE' => Gitlab::X509::Certificate.default_cert_file,
'SSL_CERT_DIR' => Gitlab::X509::Certificate.default_cert_dir,
'GITALY_SERVERS' => gitaly_servers_encoded
- }.merge(ENV)
+ }.merge(current_env)
+ end
+
+ def current_env
+ ENV
end
def started?
diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb
index d56f852b23c..60239781926 100644
--- a/lib/backup/manager.rb
+++ b/lib/backup/manager.rb
@@ -195,7 +195,12 @@ module Backup
def build_repositories_task
max_concurrency = ENV['GITLAB_BACKUP_MAX_CONCURRENCY'].presence&.to_i
max_storage_concurrency = ENV['GITLAB_BACKUP_MAX_STORAGE_CONCURRENCY'].presence&.to_i
- strategy = Backup::GitalyBackup.new(progress, incremental: incremental?, max_parallelism: max_concurrency, storage_parallelism: max_storage_concurrency)
+ strategy = Backup::GitalyBackup.new(progress,
+ incremental: incremental?,
+ max_parallelism: max_concurrency,
+ storage_parallelism: max_storage_concurrency,
+ server_side: backup_information[:repositories_server_side]
+ )
Repositories.new(progress,
strategy: strategy,
@@ -286,7 +291,8 @@ module Backup
skipped: ENV['SKIP'],
repositories_storages: ENV['REPOSITORIES_STORAGES'],
repositories_paths: ENV['REPOSITORIES_PATHS'],
- skip_repositories_paths: ENV['SKIP_REPOSITORIES_PATHS']
+ skip_repositories_paths: ENV['SKIP_REPOSITORIES_PATHS'],
+ repositories_server_side: Gitlab::Utils.to_boolean(ENV['REPOSITORIES_SERVER_SIDE'], default: false)
}
end
diff --git a/lib/banzai/filter/issuable_reference_expansion_filter.rb b/lib/banzai/filter/issuable_reference_expansion_filter.rb
index ec7778a3630..1c3e25b3b27 100644
--- a/lib/banzai/filter/issuable_reference_expansion_filter.rb
+++ b/lib/banzai/filter/issuable_reference_expansion_filter.rb
@@ -45,7 +45,7 @@ module Banzai
# Example: Issue Title (#123 - closed)
def expand_reference_with_title_and_state(node, issuable)
- node.content = "#{issuable.title.truncate(50)} (#{node.content}"
+ node.content = "#{expand_emoji(issuable.title).truncate(50)} (#{node.content}"
node.content += " - #{issuable_state_text(issuable)}" if VISIBLE_STATES.include?(issuable.state)
node.content += ')'
end
@@ -124,6 +124,13 @@ module Banzai
def group
context[:group]
end
+
+ def expand_emoji(string)
+ string.gsub(/(?<!\w):(\w+):(?!\w)/) do |match|
+ emoji_codepoint = TanukiEmoji.find_by_alpha_code(::Regexp.last_match(1))&.codepoints
+ !emoji_codepoint.nil? ? emoji_codepoint : match
+ end
+ end
end
end
end
diff --git a/lib/banzai/filter/references/project_reference_filter.rb b/lib/banzai/filter/references/project_reference_filter.rb
index 678d2aa3468..0c433f78f91 100644
--- a/lib/banzai/filter/references/project_reference_filter.rb
+++ b/lib/banzai/filter/references/project_reference_filter.rb
@@ -58,6 +58,7 @@ module Banzai
# corresponding Project objects.
def projects_hash
@projects ||= Project.eager_load(:route, namespace: [:route])
+ .allow_cross_joins_across_databases(url: "https://gitlab.com/gitlab-org/gitlab/-/issues/420046")
.where_full_path_in(projects)
.index_by(&:full_path)
.transform_keys(&:downcase)
diff --git a/lib/banzai/filter/references/reference_cache.rb b/lib/banzai/filter/references/reference_cache.rb
index 759c34ab7e6..9005971240f 100644
--- a/lib/banzai/filter/references/reference_cache.rb
+++ b/lib/banzai/filter/references/reference_cache.rb
@@ -108,8 +108,10 @@ module Banzai
klass = parent_type.to_s.camelize.constantize
result = klass.where_full_path_in(paths)
return result if parent_type == :group
+ return unless parent_type == :project
- result.includes(namespace: :route) if parent_type == :project
+ result.includes(namespace: :route)
+ .allow_cross_joins_across_databases(url: "https://gitlab.com/gitlab-org/gitlab/-/issues/420046")
end
# Returns projects for the given paths.
diff --git a/lib/bulk_imports/common/graphql/get_members_query.rb b/lib/bulk_imports/common/graphql/get_members_query.rb
index 00977f694d7..8fa8d7f4c0b 100644
--- a/lib/bulk_imports/common/graphql/get_members_query.rb
+++ b/lib/bulk_imports/common/graphql/get_members_query.rb
@@ -14,7 +14,7 @@ module BulkImports
<<-GRAPHQL
query($full_path: ID!, $cursor: String, $per_page: Int) {
portable: #{context.entity.entity_type}(fullPath: $full_path) {
- members: #{members_type}(relations: [DIRECT, INHERITED], first: $per_page, after: $cursor) {
+ members: #{members_type}(relations: #{relations}, first: $per_page, after: $cursor) {
page_info: pageInfo {
next_page: endCursor
has_next_page: hasNextPage
@@ -66,6 +66,14 @@ module BulkImports
'projectMembers'
end
end
+
+ def relations
+ if context.entity.group?
+ "[DIRECT INHERITED SHARED_FROM_GROUPS]"
+ else
+ "[DIRECT INHERITED INVITED_GROUPS SHARED_INTO_ANCESTORS]"
+ end
+ end
end
end
end
diff --git a/lib/bulk_imports/file_downloads/validations.rb b/lib/bulk_imports/file_downloads/validations.rb
index e1844843408..14f036e469c 100644
--- a/lib/bulk_imports/file_downloads/validations.rb
+++ b/lib/bulk_imports/file_downloads/validations.rb
@@ -45,7 +45,7 @@ module BulkImports
def validate_size!(size)
if size.blank?
raise_error 'Missing content-length header'
- elsif size.to_i > file_size_limit
+ elsif file_size_limit > 0 && size.to_i > file_size_limit
raise_error format(
"File size %{size} exceeds limit of %{limit}",
size: ActiveSupport::NumberHelper.number_to_human_size(size),
diff --git a/lib/bulk_imports/visibility_level.rb b/lib/bulk_imports/visibility_level.rb
index 6b0af15dd7b..13bf25ff662 100644
--- a/lib/bulk_imports/visibility_level.rb
+++ b/lib/bulk_imports/visibility_level.rb
@@ -4,23 +4,24 @@ module BulkImports
module VisibilityLevel
private
+ # Calculates visbility level based on the source and the destination namespace visbility levels
+ # If there are visibility_level restrictions on the destination instance,
+ # the highest allowed level less than the calculated level is returned
def visibility_level(entity, namespace, visibility_string)
requested = requested_visibility_level(entity, visibility_string)
- max_allowed = max_allowed_visibility_level(namespace)
+ namespace_level = namespace&.visibility_level
- return requested if max_allowed >= requested
+ lowest_level = [requested, namespace_level].compact.min
- max_allowed
+ closet_allowed_level(lowest_level)
end
def requested_visibility_level(entity, visibility_string)
Gitlab::VisibilityLevel.string_options[visibility_string] || entity.default_visibility_level
end
- def max_allowed_visibility_level(namespace)
- return Gitlab::VisibilityLevel.allowed_levels.max if namespace.blank?
-
- Gitlab::VisibilityLevel.closest_allowed_level(namespace.visibility_level)
+ def closet_allowed_level(level)
+ Gitlab::VisibilityLevel.closest_allowed_level(level)
end
end
end
diff --git a/lib/click_house/bind_index_manager.rb b/lib/click_house/bind_index_manager.rb
new file mode 100644
index 00000000000..96b0940ce71
--- /dev/null
+++ b/lib/click_house/bind_index_manager.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module ClickHouse
+ class BindIndexManager
+ def initialize(start_index = 1)
+ @current_index = start_index
+ end
+
+ def next_bind_str
+ bind_str = "$#{@current_index}"
+ @current_index += 1
+ bind_str
+ end
+ end
+end
diff --git a/lib/click_house/query_builder.rb b/lib/click_house/query_builder.rb
new file mode 100644
index 00000000000..a2136420b2f
--- /dev/null
+++ b/lib/click_house/query_builder.rb
@@ -0,0 +1,137 @@
+# frozen_string_literal: true
+
+# rubocop:disable CodeReuse/ActiveRecord
+module ClickHouse
+ class QueryBuilder
+ attr_reader :table
+ attr_accessor :conditions, :manager
+
+ VALID_NODES = [
+ Arel::Nodes::In,
+ Arel::Nodes::Equality,
+ Arel::Nodes::LessThan,
+ Arel::Nodes::LessThanOrEqual,
+ Arel::Nodes::GreaterThan,
+ Arel::Nodes::GreaterThanOrEqual
+ ].freeze
+
+ def initialize(table_name)
+ @table = Arel::Table.new(table_name)
+ @manager = Arel::SelectManager.new(Arel::Table.engine).from(@table).project(Arel.star)
+ @conditions = []
+ end
+
+ # The `where` method currently does only supports IN and equal to queries along
+ # with above listed VALID_NODES.
+ # For example, using a range (start_date..end_date) will result in incorrect SQL.
+ # If you need to query a range, use greater than and less than conditions with Arel.
+ #
+ # Correct usage:
+ # query.where(query.table[:created_at].lteq(Date.today)).to_sql
+ # "SELECT * FROM \"table\" WHERE \"table\".\"created_at\" <= '2023-08-01'"
+ #
+ # This also supports array conditions which will result in an IN query.
+ # query.where(entity_id: [1,2,3]).to_sql
+ # "SELECT * FROM \"table\" WHERE \"table\".\"entity_id\" IN (1, 2, 3)"
+ #
+ # Range support and more `Arel::Nodes` could be considered for future iterations.
+ # @return [ClickHouse::QueryBuilder] New instance of query builder.
+ def where(conditions)
+ validate_condition_type!(conditions)
+
+ new_instance = deep_clone
+
+ if conditions.is_a?(Arel::Nodes::Node)
+ new_instance.conditions << conditions
+ else
+ add_conditions_to(new_instance, conditions)
+ end
+
+ new_instance
+ end
+
+ def select(*fields)
+ new_instance = deep_clone
+
+ existing_fields = new_instance.manager.projections.filter_map do |projection|
+ if projection.is_a?(Arel::Attributes::Attribute)
+ projection.name.to_s
+ elsif projection.to_s == '*'
+ nil
+ end
+ end
+
+ new_projections = existing_fields + fields.map(&:to_s)
+
+ new_instance.manager.projections = new_projections.uniq.map { |field| new_instance.table[field] }
+ new_instance
+ end
+
+ def order(field, direction = :asc)
+ validate_order_direction!(direction)
+
+ new_instance = deep_clone
+
+ new_order = new_instance.table[field].public_send(direction.to_s.downcase) # rubocop:disable GitlabSecurity/PublicSend
+ new_instance.manager.order(new_order)
+
+ new_instance
+ end
+
+ def limit(count)
+ manager.take(count)
+ self
+ end
+
+ def offset(count)
+ manager.skip(count)
+ self
+ end
+
+ def to_sql
+ apply_conditions!
+ manager.to_sql
+ end
+
+ def to_redacted_sql
+ ::ClickHouse::Redactor.redact(self)
+ end
+
+ private
+
+ def validate_condition_type!(condition)
+ return unless condition.is_a?(Arel::Nodes::Node) && VALID_NODES.exclude?(condition.class)
+
+ raise ArgumentError, "Unsupported Arel node type for QueryBuilder: #{condition.class.name}"
+ end
+
+ def add_conditions_to(instance, conditions)
+ conditions.each do |key, value|
+ instance.conditions << if value.is_a?(Array)
+ instance.table[key].in(value)
+ else
+ instance.table[key].eq(value)
+ end
+ end
+ end
+
+ def deep_clone
+ new_instance = self.class.new(table.name)
+ new_instance.manager = manager.clone
+ new_instance.conditions = conditions.map(&:clone)
+ new_instance
+ end
+
+ def apply_conditions!
+ manager.constraints.clear
+ conditions.each { |condition| manager.where(condition) }
+ end
+
+ def validate_order_direction!(direction)
+ return if %w[asc desc].include?(direction.to_s.downcase)
+
+ raise ArgumentError, "Invalid order direction '#{direction}'. Must be :asc or :desc"
+ end
+ end
+end
+# rubocop:enable CodeReuse/ActiveRecord
diff --git a/lib/click_house/redactor.rb b/lib/click_house/redactor.rb
new file mode 100644
index 00000000000..9b8e2bc90d9
--- /dev/null
+++ b/lib/click_house/redactor.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+# rubocop:disable CodeReuse/ActiveRecord
+module ClickHouse
+ module Redactor
+ # Redacts the SQL query represented by the query builder.
+ #
+ # @param query_builder [::ClickHouse::Querybuilder] The query builder object to be redacted.
+ # @return [String] The redacted SQL query as a string.
+ # @raise [ArgumentError] when the condition in the query is of an unsupported type.
+ #
+ # Example:
+ # query_builder = ClickHouse::QueryBuilder.new('users').where(name: 'John Doe')
+ # redacted_query = ClickHouse::Redactor.redact(query_builder)
+ # # The redacted_query will contain the SQL query with values replaced by placeholders.
+ # output: "SELECT * FROM \"users\" WHERE \"users\".\"name\" = $1"
+ def self.redact(query_builder)
+ cloned_query_builder = query_builder.clone
+ bind_manager = ::ClickHouse::BindIndexManager.new
+
+ cloned_query_builder.conditions = cloned_query_builder.conditions.map do |condition|
+ redact_condition(condition, bind_manager)
+ end
+
+ cloned_query_builder.manager.constraints.clear
+ cloned_query_builder.conditions.each do |condition|
+ cloned_query_builder.manager.where(condition)
+ end
+
+ cloned_query_builder.manager.to_sql
+ end
+
+ def self.redact_condition(condition, bind_manager)
+ case condition
+ when Arel::Nodes::In
+ condition.left.in(Array.new(condition.right.size) { Arel.sql(bind_manager.next_bind_str) })
+ when Arel::Nodes::Equality
+ condition.left.eq(Arel.sql(bind_manager.next_bind_str))
+ when Arel::Nodes::LessThan
+ condition.left.lt(Arel.sql(bind_manager.next_bind_str))
+ when Arel::Nodes::LessThanOrEqual
+ condition.left.lteq(Arel.sql(bind_manager.next_bind_str))
+ when Arel::Nodes::GreaterThan
+ condition.left.gt(Arel.sql(bind_manager.next_bind_str))
+ when Arel::Nodes::GreaterThanOrEqual
+ condition.left.gteq(Arel.sql(bind_manager.next_bind_str))
+ else
+ raise ArgumentError, "Unsupported Arel node type for Redactor: #{condition.class}"
+ end
+ end
+ end
+end
+# rubocop:enable CodeReuse/ActiveRecord
diff --git a/lib/container_registry/gitlab_api_client.rb b/lib/container_registry/gitlab_api_client.rb
index f5982e96622..ab7566119c4 100644
--- a/lib/container_registry/gitlab_api_client.rb
+++ b/lib/container_registry/gitlab_api_client.rb
@@ -148,13 +148,16 @@ module ContainerRegistry
end
# https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs-gitlab/api.md#list-repository-tags
- def tags(path, page_size: 100, last: nil)
+ def tags(path, page_size: 100, last: nil, before: nil, name: nil, sort: nil)
limited_page_size = [page_size, MAX_TAGS_PAGE_SIZE].min
with_token_faraday do |faraday_client|
url = "/gitlab/v1/repositories/#{path}/tags/list/"
response = faraday_client.get(url) do |req|
req.params['n'] = limited_page_size
req.params['last'] = last if last
+ req.params['before'] = before if before
+ req.params['name'] = name if name
+ req.params['sort'] = sort if sort
end
unless response.success?
diff --git a/lib/csv_builder.rb b/lib/csv_builder.rb
deleted file mode 100644
index a54c355396d..00000000000
--- a/lib/csv_builder.rb
+++ /dev/null
@@ -1,130 +0,0 @@
-# frozen_string_literal: true
-
-# Generates CSV when given a collection and a mapping.
-#
-# Example:
-#
-# columns = {
-# 'Title' => 'title',
-# 'Comment' => 'comment',
-# 'Author' => -> (post) { post.author.full_name }
-# 'Created At (UTC)' => -> (post) { post.created_at&.strftime('%Y-%m-%d %H:%M:%S') }
-# }
-#
-# CsvBuilder.new(@posts, columns).render
-#
-class CsvBuilder
- DEFAULT_ORDER_BY = 'id'
- DEFAULT_BATCH_SIZE = 1000
- PREFIX_REGEX = /\A[=\+\-@;]/.freeze
-
- attr_reader :rows_written
-
- #
- # * +collection+ - The data collection to be used
- # * +header_to_hash_value+ - A hash of 'Column Heading' => 'value_method'.
- # * +associations_to_preload+ - An array of records to preload with a batch of records.
- #
- # The value method will be called once for each object in the collection, to
- # determine the value for that row. It can either be the name of a method on
- # the object, or a lamda to call passing in the object.
- def initialize(collection, header_to_value_hash, associations_to_preload = [])
- @header_to_value_hash = header_to_value_hash
- @collection = collection
- @truncated = false
- @rows_written = 0
- @associations_to_preload = associations_to_preload
- end
-
- # Renders the csv to a string
- def render(truncate_after_bytes = nil)
- Tempfile.open(['csv']) do |tempfile|
- csv = CSV.new(tempfile)
-
- write_csv csv, until_condition: -> do
- truncate_after_bytes && tempfile.size > truncate_after_bytes
- end
-
- if block_given?
- yield tempfile
- else
- tempfile.rewind
- tempfile.read
- end
- end
- end
-
- def truncated?
- @truncated
- end
-
- def rows_expected
- if truncated? || rows_written == 0
- @collection.count
- else
- rows_written
- end
- end
-
- def status
- {
- truncated: truncated?,
- rows_written: rows_written,
- rows_expected: rows_expected
- }
- end
-
- protected
-
- def each(&block)
- if @associations_to_preload.present? && @collection.respond_to?(:each_batch)
- @collection.each_batch(order_hint: :created_at) do |relation|
- relation.preload(@associations_to_preload).order(:id).each(&block) # rubocop:disable CodeReuse/ActiveRecord
- end
- else
- @collection.find_each(&block) # rubocop: disable CodeReuse/ActiveRecord
- end
- end
-
- private
-
- def headers
- @headers ||= @header_to_value_hash.keys
- end
-
- def attributes
- @attributes ||= @header_to_value_hash.values
- end
-
- def row(object)
- attributes.map do |attribute|
- if attribute.respond_to?(:call)
- excel_sanitize(attribute.call(object))
- else
- excel_sanitize(object.public_send(attribute)) # rubocop:disable GitlabSecurity/PublicSend
- end
- end
- end
-
- def write_csv(csv, until_condition:)
- csv << headers
-
- each do |object|
- csv << row(object)
-
- @rows_written += 1
-
- if until_condition.call
- @truncated = true
- break
- end
- end
- end
-
- def excel_sanitize(line)
- return if line.nil?
- return line unless line.is_a?(String) && line.match?(PREFIX_REGEX)
-
- ["'", line].join
- end
-end
diff --git a/lib/csv_builders/single_batch.rb b/lib/csv_builders/single_batch.rb
deleted file mode 100644
index bed6b7424b3..00000000000
--- a/lib/csv_builders/single_batch.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# frozen_string_literal: true
-
-module CsvBuilders
- class SingleBatch < CsvBuilder
- protected
-
- def each(&block)
- @collection.each(&block)
- end
- end
-end
diff --git a/lib/csv_builders/stream.rb b/lib/csv_builders/stream.rb
deleted file mode 100644
index a2b9fca84cb..00000000000
--- a/lib/csv_builders/stream.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-
-module CsvBuilders
- class Stream < CsvBuilder
- def render(max_rows = 100_000)
- max_rows_including_header = max_rows + 1
-
- Enumerator.new do |csv|
- csv << CSV.generate_line(headers)
-
- each do |object|
- csv << CSV.generate_line(row(object))
- end
- end.lazy.take(max_rows_including_header) # rubocop: disable CodeReuse/ActiveRecord
- end
- end
-end
diff --git a/lib/extracts_ref.rb b/lib/extracts_ref.rb
index 2a48b66bb5c..49ec564eb8d 100644
--- a/lib/extracts_ref.rb
+++ b/lib/extracts_ref.rb
@@ -100,7 +100,7 @@ module ExtractsRef
# rubocop:enable Gitlab/ModuleWithInstanceVariables
def tree
- @tree ||= @repo.tree(@commit.id, @path, ref_type: ref_type) # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ @tree ||= @repo.tree(@commit.id, @path) # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
def extract_ref_path
diff --git a/lib/generators/batched_background_migration/templates/batched_background_migration_job.template b/lib/generators/batched_background_migration/templates/batched_background_migration_job.template
index c57ac637cb8..bfcd36a0df9 100644
--- a/lib/generators/batched_background_migration/templates/batched_background_migration_job.template
+++ b/lib/generators/batched_background_migration/templates/batched_background_migration_job.template
@@ -8,7 +8,7 @@
module Gitlab
module BackgroundMigration
class <%= class_name %> < BatchedMigrationJob
- # operation_name :my_operation
+ # operation_name :my_operation # This is used as the key on collecting metrics
# scope_to ->(relation) { relation.where(column: "value") }
feature_category :<%= feature_category %>
diff --git a/lib/generators/batched_background_migration/templates/ee_batched_background_migration_job.template b/lib/generators/batched_background_migration/templates/ee_batched_background_migration_job.template
index b36fc216acd..b450d567203 100644
--- a/lib/generators/batched_background_migration/templates/ee_batched_background_migration_job.template
+++ b/lib/generators/batched_background_migration/templates/ee_batched_background_migration_job.template
@@ -13,7 +13,7 @@ module EE
extend ::Gitlab::Utils::Override
prepended do
- # operation_name :my_operation
+ # operation_name :my_operation # This is used as the key on collecting metrics
# scope_to ->(relation) { relation.where(column: "value") }
end
diff --git a/lib/generators/gitlab/analytics/internal_events_generator.rb b/lib/generators/gitlab/analytics/internal_events_generator.rb
index d4c3a10c00e..ae738c5dcb0 100644
--- a/lib/generators/gitlab/analytics/internal_events_generator.rb
+++ b/lib/generators/gitlab/analytics/internal_events_generator.rb
@@ -31,8 +31,6 @@ module Gitlab
TOP_LEVEL_DIR = 'config'
TOP_LEVEL_DIR_EE = 'ee'
DESCRIPTION_MIN_LENGTH = 50
- KNOWN_EVENTS_PATH = 'lib/gitlab/usage_data_counters/known_events/common.yml'
- KNOWN_EVENTS_PATH_EE = 'ee/lib/ee/gitlab/usage_data_counters/known_events/common.yml'
DESCRIPTION_INQUIRY = %(
Please describe in at least #{DESCRIPTION_MIN_LENGTH} characters
@@ -43,7 +41,7 @@ module Gitlab
source_root File.expand_path('../../../../generator_templates/gitlab_internal_events', __dir__)
- desc 'Generates metric definitions, event definition yml files and known events entries'
+ desc 'Generates metric definitions and event definition yml files'
class_option :skip_namespace,
hide: true
@@ -104,9 +102,6 @@ module Gitlab
"events, and event attributes in the description"
)
end
-
- # ToDo: Delete during https://gitlab.com/groups/gitlab-org/-/epics/9542 cleanup
- append_file known_events_file_name, known_event_entry
end
private
@@ -194,12 +189,7 @@ module Gitlab
path
end
- def known_events_file_name
- (free? ? KNOWN_EVENTS_PATH : KNOWN_EVENTS_PATH_EE)
- end
-
def validate!
- raise "Required file: #{known_events_file_name} does not exists." unless File.exist?(known_events_file_name)
raise "An event '#{event}' already exists" if event_exists?
validate_tiers!
diff --git a/lib/generators/gitlab/usage_metric_definition_generator.rb b/lib/generators/gitlab/usage_metric_definition_generator.rb
index 4ddbe8b9f09..cdbfda2495b 100644
--- a/lib/generators/gitlab/usage_metric_definition_generator.rb
+++ b/lib/generators/gitlab/usage_metric_definition_generator.rb
@@ -73,10 +73,6 @@ module Gitlab
private
- def metric_name_suggestion(key_path)
- "\nname: \"#{Usage::Metrics::NamesSuggestions::Generator.generate(key_path)}\""
- end
-
def file_path(key_path)
path = File.join(TOP_LEVEL_DIR, 'metrics', directory&.name, "#{file_name(key_path)}.yml")
path = File.join(TOP_LEVEL_DIR_EE, path) if ee?
diff --git a/lib/gitlab/action_cable/request_store_callbacks.rb b/lib/gitlab/action_cable/request_store_callbacks.rb
index 14d80a7c40c..f6dda18a444 100644
--- a/lib/gitlab/action_cable/request_store_callbacks.rb
+++ b/lib/gitlab/action_cable/request_store_callbacks.rb
@@ -9,7 +9,7 @@ module Gitlab
def self.wrapper
lambda do |_, inner|
- ::Gitlab::WithRequestStore.with_request_store do
+ ::Gitlab::SafeRequestStore.ensure_request_store do
inner.call
end
end
diff --git a/lib/gitlab/alert_management/payload.rb b/lib/gitlab/alert_management/payload.rb
index de34a0f5d47..c6244124022 100644
--- a/lib/gitlab/alert_management/payload.rb
+++ b/lib/gitlab/alert_management/payload.rb
@@ -18,31 +18,20 @@ module Gitlab
# @param monitoring_tool [String]
# @param integration [AlertManagement::HttpIntegration]
def parse(project, payload, monitoring_tool: nil, integration: nil)
- payload_class = payload_class_for(
- monitoring_tool: monitoring_tool || payload&.dig('monitoring_tool'),
- payload: payload
- )
+ payload_class = payload_class_for(monitoring_tool: monitoring_tool || payload&.dig('monitoring_tool'))
payload_class.new(project: project, payload: payload, integration: integration)
end
private
- def payload_class_for(monitoring_tool:, payload:)
+ def payload_class_for(monitoring_tool:)
if monitoring_tool == MONITORING_TOOLS[:prometheus]
- if gitlab_managed_prometheus?(payload)
- ::Gitlab::AlertManagement::Payload::ManagedPrometheus
- else
- ::Gitlab::AlertManagement::Payload::Prometheus
- end
+ ::Gitlab::AlertManagement::Payload::Prometheus
else
::Gitlab::AlertManagement::Payload::Generic
end
end
-
- def gitlab_managed_prometheus?(payload)
- payload&.dig('labels', 'gitlab_alert_id').present?
- end
end
end
end
diff --git a/lib/gitlab/alert_management/payload/managed_prometheus.rb b/lib/gitlab/alert_management/payload/managed_prometheus.rb
deleted file mode 100644
index 4ed21108d3e..00000000000
--- a/lib/gitlab/alert_management/payload/managed_prometheus.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-# frozen_string_literal: true
-
-# Attribute mapping for alerts via prometheus alerting integration,
-# and for which payload includes gitlab-controlled attributes.
-module Gitlab
- module AlertManagement
- module Payload
- class ManagedPrometheus < ::Gitlab::AlertManagement::Payload::Prometheus
- attribute :gitlab_prometheus_alert_id,
- paths: %w(labels gitlab_prometheus_alert_id),
- type: :integer
- attribute :metric_id,
- paths: %w(labels gitlab_alert_id),
- type: :integer
-
- def gitlab_alert
- strong_memoize(:gitlab_alert) do
- next unless metric_id || gitlab_prometheus_alert_id
-
- alerts = Projects::Prometheus::AlertsFinder
- .new(project: project, metric: metric_id, id: gitlab_prometheus_alert_id)
- .execute
-
- next if alerts.blank? || alerts.size > 1
-
- alerts.first
- end
- end
-
- def full_query
- gitlab_alert&.full_query || super
- end
-
- def environment
- gitlab_alert&.environment || super
- end
-
- private
-
- def plain_gitlab_fingerprint
- [metric_id, starts_at_raw].join('/')
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/audit/auditor.rb b/lib/gitlab/audit/auditor.rb
index a59237fbb1f..a035b6face9 100644
--- a/lib/gitlab/audit/auditor.rb
+++ b/lib/gitlab/audit/auditor.rb
@@ -76,13 +76,11 @@ module Gitlab
@authentication_event = @context.fetch(:authentication_event, false)
@authentication_provider = @context[:authentication_provider]
- # TODO: Remove this code once we close https://gitlab.com/gitlab-org/gitlab/-/issues/367870
return if @is_audit_event_yaml_defined
- message = 'Logging audit events without an event type definition will be deprecated soon ' \
- '(https://docs.gitlab.com/ee/development/audit_event_guide/#event-type-definitions)'
-
- Gitlab::AppLogger.warn(message: message, event_type: @name)
+ raise StandardError, "Audit event type YML file is not defined for #{@name}. Please read " \
+ "https://docs.gitlab.com/ee/development/audit_event_guide/" \
+ "#how-to-instrument-new-audit-events for adding a new audit event"
end
def single_audit
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 1bb92b7fa62..cafa75d5f59 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -222,11 +222,11 @@ module Gitlab
return unless valid_scoped_token?(token, all_available_scopes)
- if project && token.user.project_bot?
+ if project && (token.user.project_bot? || token.user.service_account?)
return unless can_read_project?(token.user, project)
end
- if token.user.can_log_in_with_non_expired_password? || token.user.project_bot?
+ if token.user.can_log_in_with_non_expired_password? || (token.user.project_bot? || token.user.service_account?)
::PersonalAccessTokens::LastUsedService.new(token).execute
Gitlab::Auth::Result.new(token.user, nil, :personal_access_token, abilities_for_scopes(token.scopes))
@@ -238,7 +238,7 @@ module Gitlab
end
def bot_user_can_read_project?(user, project)
- (user.project_bot? || user.security_policy_bot?) && can_read_project?(user, project)
+ (user.project_bot? || user.service_account? || user.security_policy_bot?) && can_read_project?(user, project)
end
def valid_oauth_token?(token)
diff --git a/lib/gitlab/auth/auth_finders.rb b/lib/gitlab/auth/auth_finders.rb
index 966520655a5..a715f17ecd6 100644
--- a/lib/gitlab/auth/auth_finders.rb
+++ b/lib/gitlab/auth/auth_finders.rb
@@ -403,10 +403,14 @@ module Gitlab
end
def revoke_token_family(token)
- return unless Feature.enabled?(:pat_reuse_detection)
+ return unless access_token_rotation_request?
PersonalAccessTokens::RevokeTokenFamilyService.new(token).execute
end
+
+ def access_token_rotation_request?
+ current_request.path.match(%r{access_tokens/\d+/rotate$})
+ end
end
end
end
diff --git a/lib/gitlab/auth/saml/auth_hash.rb b/lib/gitlab/auth/saml/auth_hash.rb
index 592d88264e9..4a88d30fa93 100644
--- a/lib/gitlab/auth/saml/auth_hash.rb
+++ b/lib/gitlab/auth/saml/auth_hash.rb
@@ -8,6 +8,10 @@ module Gitlab
Array.wrap(get_raw(Gitlab::Auth::Saml::Config.new(auth_hash.provider).groups))
end
+ def azure_group_overage_claim?
+ get_raw('http://schemas.microsoft.com/claims/groups.link').present?
+ end
+
def authn_context
response_object = auth_hash.extra[:response_object]
return if response_object.blank?
diff --git a/lib/gitlab/auth/two_factor_auth_verifier.rb b/lib/gitlab/auth/two_factor_auth_verifier.rb
index 5a203a1fe9c..fbdfd105ee3 100644
--- a/lib/gitlab/auth/two_factor_auth_verifier.rb
+++ b/lib/gitlab/auth/two_factor_auth_verifier.rb
@@ -3,10 +3,11 @@
module Gitlab
module Auth
class TwoFactorAuthVerifier
- attr_reader :current_user
+ attr_reader :current_user, :request
- def initialize(current_user)
+ def initialize(current_user, request = nil)
@current_user = current_user
+ @request = request
end
def two_factor_authentication_enforced?
@@ -14,6 +15,8 @@ module Gitlab
end
def two_factor_authentication_required?
+ return false if allow_2fa_bypass_for_provider
+
Gitlab::CurrentSettings.require_two_factor_authentication? ||
current_user&.require_two_factor_authentication_from_group?
end
@@ -35,6 +38,12 @@ module Gitlab
two_factor_grace_period.hours.since(time) < Time.current
end
+
+ def allow_2fa_bypass_for_provider
+ return false if Feature.disabled?(:by_pass_two_factor_for_current_session)
+
+ request.session[:provider_2FA].present? if request
+ end
end
end
end
diff --git a/lib/gitlab/authorized_keys.rb b/lib/gitlab/authorized_keys.rb
index 3e529a0d2f3..00d9480334e 100644
--- a/lib/gitlab/authorized_keys.rb
+++ b/lib/gitlab/authorized_keys.rb
@@ -153,7 +153,7 @@ module Gitlab
end
def command(id)
- unless /\A[a-z0-9-]+\z/ =~ id
+ unless /\A[a-z0-9-]+\z/.match?(id)
raise KeyError, "Invalid ID: #{id.inspect}"
end
diff --git a/lib/gitlab/background_migration/backfill_default_branch_protection_namespace_setting.rb b/lib/gitlab/background_migration/backfill_default_branch_protection_namespace_setting.rb
new file mode 100644
index 00000000000..8da29a61d61
--- /dev/null
+++ b/lib/gitlab/background_migration/backfill_default_branch_protection_namespace_setting.rb
@@ -0,0 +1,131 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # This class is used to update the default_branch_protection_defaults column
+ # for user namespaces of the namespace_settings table.
+ class BackfillDefaultBranchProtectionNamespaceSetting < BatchedMigrationJob
+ operation_name :set_default_branch_protection_defaults
+ feature_category :database
+
+ # Migration only version of `namespaces` table
+ class Namespace < ::ApplicationRecord
+ self.table_name = 'namespaces'
+ self.inheritance_column = :_type_disabled
+
+ has_one :namespace_setting,
+ class_name: '::Gitlab::BackgroundMigration::BackfillDefaultBranchProtectionNamespaceSetting::NamespaceSetting'
+ end
+
+ # Migration only version of `namespace_settings` table
+ class NamespaceSetting < ::ApplicationRecord
+ self.table_name = 'namespace_settings'
+ belongs_to :namespace,
+ class_name: '::Gitlab::BackgroundMigration::BackfillDefaultBranchProtectionNamespaceSetting::Namespace'
+ end
+
+ # Migration only version of Gitlab::Access:BranchProtection application code.
+ class BranchProtection
+ attr_reader :level
+
+ def initialize(level)
+ @level = level
+ end
+
+ PROTECTION_NONE = 0
+ PROTECTION_DEV_CAN_PUSH = 1
+ PROTECTION_FULL = 2
+ PROTECTION_DEV_CAN_MERGE = 3
+ PROTECTION_DEV_CAN_INITIAL_PUSH = 4
+
+ DEVELOPER = 30
+ MAINTAINER = 40
+
+ def to_hash
+ case level
+ when PROTECTION_NONE
+ self.class.protection_none
+ when PROTECTION_DEV_CAN_PUSH
+ self.class.protection_partial
+ when PROTECTION_FULL
+ self.class.protected_fully
+ when PROTECTION_DEV_CAN_MERGE
+ self.class.protected_against_developer_pushes
+ when PROTECTION_DEV_CAN_INITIAL_PUSH
+ self.class.protected_after_initial_push
+ end
+ end
+
+ class << self
+ def protection_none
+ {
+ allowed_to_push: [{ 'access_level' => DEVELOPER }],
+ allowed_to_merge: [{ 'access_level' => DEVELOPER }],
+ allow_force_push: true
+ }
+ end
+
+ def protection_partial
+ protection_none.merge(allow_force_push: false)
+ end
+
+ def protected_fully
+ {
+ allowed_to_push: [{ 'access_level' => MAINTAINER }],
+ allowed_to_merge: [{ 'access_level' => MAINTAINER }],
+ allow_force_push: false
+ }
+ end
+
+ def protected_against_developer_pushes
+ {
+ allowed_to_push: [{ 'access_level' => MAINTAINER }],
+ allowed_to_merge: [{ 'access_level' => DEVELOPER }],
+ allow_force_push: true
+ }
+ end
+
+ def protected_after_initial_push
+ {
+ allowed_to_push: [{ 'access_level' => MAINTAINER }],
+ allowed_to_merge: [{ 'access_level' => DEVELOPER }],
+ allow_force_push: true,
+ developer_can_initial_push: true
+ }
+ end
+ end
+ end
+
+ def perform
+ each_sub_batch do |sub_batch|
+ update_default_protection_branch_defaults(sub_batch)
+ end
+ end
+
+ private
+
+ def update_default_protection_branch_defaults(batch)
+ namespace_settings = NamespaceSetting.where(namespace_id: batch.pluck(:namespace_id)).includes(:namespace)
+
+ values_list = namespace_settings.map do |namespace_setting|
+ level = namespace_setting.namespace.default_branch_protection.to_i
+ value = BranchProtection.new(level).to_hash.to_json
+ "(#{namespace_setting.namespace_id}, '#{value}'::jsonb)"
+ end.join(", ")
+
+ sql = <<~SQL
+ WITH new_values (namespace_id, default_branch_protection_defaults) AS (
+ VALUES
+ #{values_list}
+ )
+ UPDATE namespace_settings
+ SET default_branch_protection_defaults = new_values.default_branch_protection_defaults
+ FROM new_values
+ WHERE namespace_settings.namespace_id = new_values.namespace_id;
+ SQL
+
+ connection.execute(sql)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/backfill_dismissal_reason_in_vulnerability_reads.rb b/lib/gitlab/background_migration/backfill_dismissal_reason_in_vulnerability_reads.rb
new file mode 100644
index 00000000000..d7972a6a7a9
--- /dev/null
+++ b/lib/gitlab/background_migration/backfill_dismissal_reason_in_vulnerability_reads.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # This batched background migration is EE-only,
+ # see ee/lib/ee/gitlab/background_migration/backfill_dismissal_reason_in_vulnerability_reads.rb for the actual
+ # migration code.
+ #
+ # This batched background migration will backfill `dismissal_reason` field in `vulnerability_reads` table for
+ # records with `state: 2` and `dismissal_reason: null`.
+ class BackfillDismissalReasonInVulnerabilityReads < BatchedMigrationJob
+ feature_category :vulnerability_management
+
+ def perform; end
+ end
+ end
+end
+
+Gitlab::BackgroundMigration::BackfillDismissalReasonInVulnerabilityReads.prepend_mod
diff --git a/lib/gitlab/background_migration/backfill_missing_vulnerability_dismissal_details.rb b/lib/gitlab/background_migration/backfill_missing_vulnerability_dismissal_details.rb
new file mode 100644
index 00000000000..8399f53b724
--- /dev/null
+++ b/lib/gitlab/background_migration/backfill_missing_vulnerability_dismissal_details.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # rubocop: disable Style/Documentation
+ class BackfillMissingVulnerabilityDismissalDetails < BatchedMigrationJob
+ feature_category :vulnerability_management
+
+ def perform
+ # no-op. The logic is defined in EE module.
+ end
+ end
+ # rubocop: enable Style/Documentation
+ end
+end
+
+::Gitlab::BackgroundMigration::BackfillMissingVulnerabilityDismissalDetails.prepend_mod
diff --git a/lib/gitlab/background_migration/backfill_namespace_id_for_project_route.rb b/lib/gitlab/background_migration/backfill_namespace_id_for_project_route.rb
index 0282531ae17..b0b7882d54d 100644
--- a/lib/gitlab/background_migration/backfill_namespace_id_for_project_route.rb
+++ b/lib/gitlab/background_migration/backfill_namespace_id_for_project_route.rb
@@ -54,6 +54,7 @@ module Gitlab
.where(namespace_id: nil)
.where(source_type: 'Project')
.where.not(projects: { project_namespace_id: nil })
+ .allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/421843')
.select("routes.id, projects.project_namespace_id")
end
end
diff --git a/lib/gitlab/background_migration/backfill_project_statistics_storage_size_without_pipeline_artifacts_size_job.rb b/lib/gitlab/background_migration/backfill_project_statistics_storage_size_without_pipeline_artifacts_size_job.rb
new file mode 100644
index 00000000000..88d0f27282a
--- /dev/null
+++ b/lib/gitlab/background_migration/backfill_project_statistics_storage_size_without_pipeline_artifacts_size_job.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # rubocop:disable Style/Documentation
+ class BackfillProjectStatisticsStorageSizeWithoutPipelineArtifactsSizeJob < Gitlab::BackgroundMigration::BatchedMigrationJob # rubocop:disable Layout/LineLength
+ class Project < ::ApplicationRecord
+ self.table_name = 'projects'
+
+ has_one :statistics, class_name: '::Gitlab::BackgroundMigration::BackfillProjectStatisticsStorageSizeWithoutPipelineArtifactsSizeJob::ProjectStatistics' # rubocop:disable Layout/LineLength
+ end
+
+ class ProjectStatistics < ::ApplicationRecord
+ include ::EachBatch
+
+ self.table_name = 'project_statistics'
+
+ belongs_to :project, class_name: '::Gitlab::BackgroundMigration::BackfillProjectStatisticsStorageSizeWithoutPipelineArtifactsSizeJob::Project' # rubocop:disable Layout/LineLength
+
+ def update_storage_size(storage_size_components)
+ new_storage_size = storage_size_components.sum { |component| method(component).call }
+
+ # Only update storage_size if storage_size needs updating
+ return unless storage_size != new_storage_size
+
+ self.storage_size = new_storage_size
+ save!
+
+ ::Namespaces::ScheduleAggregationWorker.perform_async(project.namespace_id)
+ log_with_data('Scheduled Namespaces::ScheduleAggregationWorker')
+ end
+
+ def wiki_size
+ super.to_i
+ end
+
+ def snippets_size
+ super.to_i
+ end
+
+ private
+
+ def log_with_data(log_line)
+ log_info(
+ log_line,
+ project_id: project.id,
+ pipeline_artifacts_size: pipeline_artifacts_size,
+ storage_size: storage_size,
+ namespace_id: project.namespace_id
+ )
+ end
+
+ def log_info(message, **extra)
+ ::Gitlab::BackgroundMigration::Logger.info(
+ migrator: 'BackfillProjectStatisticsStorageSizeWithoutPipelineArtifactsSizeJob',
+ message: message,
+ **extra
+ )
+ end
+ end
+
+ scope_to ->(relation) {
+ relation.where.not(pipeline_artifacts_size: 0)
+ }
+ operation_name :update_storage_size
+ feature_category :consumables_cost_management
+
+ def perform
+ each_sub_batch do |sub_batch|
+ ProjectStatistics.merge(sub_batch).each do |statistics|
+ statistics.update_storage_size(storage_size_components)
+ end
+ end
+ end
+
+ private
+
+ # Overridden in EE
+ def storage_size_components
+ [
+ :repository_size,
+ :wiki_size,
+ :lfs_objects_size,
+ :build_artifacts_size,
+ :packages_size,
+ :snippets_size,
+ :uploads_size
+ ]
+ end
+ end
+ # rubocop:enable Style/Documentation
+ end
+end
+
+Gitlab::BackgroundMigration::BackfillProjectStatisticsStorageSizeWithoutPipelineArtifactsSizeJob.prepend_mod
diff --git a/lib/gitlab/background_migration/cleanup_orphaned_routes.rb b/lib/gitlab/background_migration/cleanup_orphaned_routes.rb
index 5c0ddf0ba8b..c221f8ea411 100644
--- a/lib/gitlab/background_migration/cleanup_orphaned_routes.rb
+++ b/lib/gitlab/background_migration/cleanup_orphaned_routes.rb
@@ -35,25 +35,31 @@ module Gitlab
end
def update_namespace_id(batch_column, non_orphaned_namespace_routes, sub_batch_size)
- non_orphaned_namespace_routes.each_batch(column: batch_column, of: sub_batch_size) do |sub_batch|
- batch_metrics.time_operation(:fix_missing_namespace_id) do
- ApplicationRecord.connection.execute <<~SQL
- WITH route_and_ns(route_id, namespace_id) AS #{::Gitlab::Database::AsWithMaterialized.materialized_if_supported} (
- #{sub_batch.to_sql}
- )
- UPDATE routes
- SET namespace_id = route_and_ns.namespace_id
- FROM route_and_ns
- WHERE id = route_and_ns.route_id
- SQL
+ Gitlab::Database.allow_cross_joins_across_databases(
+ url: "https://gitlab.com/gitlab-org/gitlab/-/issues/420046") do
+ non_orphaned_namespace_routes.each_batch(column: batch_column, of: sub_batch_size) do |sub_batch|
+ batch_metrics.time_operation(:fix_missing_namespace_id) do
+ ApplicationRecord.connection.execute <<~SQL
+ WITH route_and_ns(route_id, namespace_id) AS #{::Gitlab::Database::AsWithMaterialized.materialized_if_supported} (
+ #{sub_batch.to_sql}
+ )
+ UPDATE routes
+ SET namespace_id = route_and_ns.namespace_id
+ FROM route_and_ns
+ WHERE id = route_and_ns.route_id
+ SQL
+ end
end
end
end
def cleanup_relations(batch_column, orphaned_namespace_routes, pause_ms, sub_batch_size)
- orphaned_namespace_routes.each_batch(column: batch_column, of: sub_batch_size) do |sub_batch|
- batch_metrics.time_operation(:cleanup_orphaned_routes) do
- sub_batch.delete_all
+ Gitlab::Database.allow_cross_joins_across_databases(
+ url: "https://gitlab.com/gitlab-org/gitlab/-/issues/420046") do
+ orphaned_namespace_routes.each_batch(column: batch_column, of: sub_batch_size) do |sub_batch|
+ batch_metrics.time_operation(:cleanup_orphaned_routes) do
+ sub_batch.delete_all
+ end
end
end
end
diff --git a/lib/gitlab/background_migration/delete_orphaned_transferred_project_approval_rules.rb b/lib/gitlab/background_migration/delete_orphaned_transferred_project_approval_rules.rb
new file mode 100644
index 00000000000..c0f87644b59
--- /dev/null
+++ b/lib/gitlab/background_migration/delete_orphaned_transferred_project_approval_rules.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # Background migration for deleting orphaned approval project rules for projects that were transferred
+ class DeleteOrphanedTransferredProjectApprovalRules < BatchedMigrationJob
+ feature_category :security_policy_management
+
+ def perform; end
+ end
+ end
+end
+
+Gitlab::BackgroundMigration::DeleteOrphanedTransferredProjectApprovalRules.prepend_mod
diff --git a/lib/gitlab/background_migration/fix_allow_descendants_override_disabled_shared_runners.rb b/lib/gitlab/background_migration/fix_allow_descendants_override_disabled_shared_runners.rb
new file mode 100644
index 00000000000..44bda3fe2b6
--- /dev/null
+++ b/lib/gitlab/background_migration/fix_allow_descendants_override_disabled_shared_runners.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # Fixes invalid combination of shared runners being enabled and
+ # allow_descendants_override = true
+ # This combination fails validation and doesn't make sense:
+ # we always allow descendants to disable shared runners
+ class FixAllowDescendantsOverrideDisabledSharedRunners < BatchedMigrationJob
+ feature_category :runner_fleet
+ operation_name :fix_allow_descendants_override_disabled_shared_runners
+
+ def perform
+ each_sub_batch do |sub_batch|
+ sub_batch.where(shared_runners_enabled: true,
+ allow_descendants_override_disabled_shared_runners: true)
+ .update_all(allow_descendants_override_disabled_shared_runners: false)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/nullify_creator_id_column_of_orphaned_projects.rb b/lib/gitlab/background_migration/nullify_creator_id_column_of_orphaned_projects.rb
index 74f5bc3f725..e1ea0c66ad4 100644
--- a/lib/gitlab/background_migration/nullify_creator_id_column_of_orphaned_projects.rb
+++ b/lib/gitlab/background_migration/nullify_creator_id_column_of_orphaned_projects.rb
@@ -9,14 +9,18 @@ module Gitlab
relation.where.not(creator_id: nil)
.joins('LEFT OUTER JOIN users ON users.id = projects.creator_id')
.where(users: { id: nil })
+ .allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/421843')
end
operation_name :update_all
feature_category :groups_and_projects
def perform
- each_sub_batch do |sub_batch|
- sub_batch.update_all(creator_id: nil)
+ ::Gitlab::Database.allow_cross_joins_across_databases(url:
+ 'https://gitlab.com/gitlab-org/gitlab/-/issues/421843') do
+ each_sub_batch do |sub_batch|
+ sub_batch.update_all(creator_id: nil)
+ end
end
end
end
diff --git a/lib/gitlab/background_migration/populate_projects_star_count.rb b/lib/gitlab/background_migration/populate_projects_star_count.rb
index 8417dc91b1b..0790bd98018 100644
--- a/lib/gitlab/background_migration/populate_projects_star_count.rb
+++ b/lib/gitlab/background_migration/populate_projects_star_count.rb
@@ -39,20 +39,23 @@ module Gitlab
# rubocop:enable Database/RescueStatementTimeout
def update_batch(sub_batch)
- ApplicationRecord.connection.execute <<~SQL
- WITH batched_relation AS #{Gitlab::Database::AsWithMaterialized.materialized_if_supported} (#{sub_batch.select(:id).to_sql})
- UPDATE projects
- SET star_count = (
- SELECT COUNT(*)
- FROM users_star_projects
- INNER JOIN users
- ON users_star_projects.user_id = users.id
- WHERE users_star_projects.project_id = batched_relation.id
- AND users.state = 'active'
- )
- FROM batched_relation
- WHERE projects.id = batched_relation.id
- SQL
+ ::Gitlab::Database.allow_cross_joins_across_databases(url:
+ 'https://gitlab.com/gitlab-org/gitlab/-/issues/421843') do
+ ApplicationRecord.connection.execute <<~SQL
+ WITH batched_relation AS #{Gitlab::Database::AsWithMaterialized.materialized_if_supported} (#{sub_batch.select(:id).to_sql})
+ UPDATE projects
+ SET star_count = (
+ SELECT COUNT(*)
+ FROM users_star_projects
+ INNER JOIN users
+ ON users_star_projects.user_id = users.id
+ WHERE users_star_projects.project_id = batched_relation.id
+ AND users.state = 'active'
+ )
+ FROM batched_relation
+ WHERE projects.id = batched_relation.id
+ SQL
+ end
end
end
end
diff --git a/lib/gitlab/blame.rb b/lib/gitlab/blame.rb
index e210c18e3d1..ac39daf375f 100644
--- a/lib/gitlab/blame.rb
+++ b/lib/gitlab/blame.rb
@@ -20,13 +20,13 @@ module Gitlab
current_group = nil
i = first_line - 1
- blame.each do |commit, line, previous_path|
+ blame.each do |commit, line, previous_path, span|
commit = Commit.new(commit, project)
commit.lazy_author # preload author
if prev_sha != commit.sha
groups << current_group if current_group
- current_group = { commit: commit, lines: [], previous_path: previous_path }
+ current_group = { commit: commit, lines: [], previous_path: previous_path, span: span, lineno: i + 1 }
end
current_group[:lines] << (highlight ? highlighted_lines[i].html_safe : line)
diff --git a/lib/gitlab/checks/branch_check.rb b/lib/gitlab/checks/branch_check.rb
index aa89c2711f9..b675eca826a 100644
--- a/lib/gitlab/checks/branch_check.rb
+++ b/lib/gitlab/checks/branch_check.rb
@@ -43,7 +43,7 @@ module Gitlab
def prohibited_branch_checks
return if deletion?
- if branch_name =~ %r{\A\h{40}(-/|/|\z)}
+ if %r{\A#{Gitlab::Git::Commit::RAW_FULL_SHA_PATTERN}(-/|/|\z)}o.match?(branch_name)
raise GitAccess::ForbiddenError, ERROR_MESSAGES[:prohibited_hex_branch_name]
end
diff --git a/lib/gitlab/checks/diff_check.rb b/lib/gitlab/checks/diff_check.rb
index bce4f969284..15b38188f13 100644
--- a/lib/gitlab/checks/diff_check.rb
+++ b/lib/gitlab/checks/diff_check.rb
@@ -18,10 +18,7 @@ module Gitlab
return unless should_run_validations?
return if commits.empty?
- paths = project.repository.find_changed_paths(
- commits.map(&:sha), merge_commit_diff_mode: :all_parents
- )
-
+ paths = project.repository.find_changed_paths(treeish_objects, merge_commit_diff_mode: :all_parents)
paths.each do |path|
validate_path(path)
end
@@ -31,6 +28,29 @@ module Gitlab
private
+ def treeish_objects
+ objects = commits
+
+ return objects unless project.repository.empty? &&
+ Feature.enabled?(:verify_push_rules_for_first_commit, project)
+
+ # It's a special case for the push to the empty repository
+ #
+ # Git doesn't display a diff of the initial commit of the repository
+ # if we just provide a commit sha.
+ #
+ # To fix that we can use TreeRequest to check the difference
+ # between empty tree sha and the tree sha of the initial commit
+ #
+ # `commits` are sorted in reverse order, the initial commit is the last one.
+ init_commit = objects.last
+
+ diff_tree = Gitlab::Git::DiffTree.from_commit(init_commit)
+ return [diff_tree] + objects if diff_tree
+
+ objects
+ end
+
def validate_lfs_file_locks?
strong_memoize(:validate_lfs_file_locks) do
project.lfs_enabled? && project.any_lfs_file_locks?
diff --git a/lib/gitlab/checks/file_size_check/allow_existing_oversized_blobs.rb b/lib/gitlab/checks/file_size_check/allow_existing_oversized_blobs.rb
deleted file mode 100644
index 78f1716274e..00000000000
--- a/lib/gitlab/checks/file_size_check/allow_existing_oversized_blobs.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Checks
- module FileSizeCheck
- class AllowExistingOversizedBlobs
- def initialize(project:, changes:, file_size_limit_megabytes:)
- @project = project
- @changes = changes
- @oldrevs = changes.pluck(:oldrev).compact # rubocop:disable CodeReuse/ActiveRecord just plucking from an array
- @file_size_limit_megabytes = file_size_limit_megabytes
- end
-
- def find(timeout: nil)
- oversize_blobs = any_oversize_blobs.find(timeout: timeout)
-
- return oversize_blobs unless oldrevs.present?
-
- revs_paths = oldrevs.product(oversize_blobs.map(&:path))
- existing_blobs = project.repository.blobs_at(revs_paths, blob_size_limit: 1)
- map_existing_path_to_size = existing_blobs.group_by(&:path).transform_values { |blobs| blobs.map(&:size).max }
-
- # return blobs that are going to be over the limit that were previously within the limit
- oversize_blobs.select { |blob| map_existing_path_to_size.fetch(blob.path, 0) <= file_size_limit_megabytes }
- end
-
- private
-
- attr_reader :project, :changes, :newrevs, :oldrevs, :file_size_limit_megabytes
-
- def any_oversize_blobs
- AnyOversizedBlobs.new(project: project, changes: changes,
- file_size_limit_megabytes: file_size_limit_megabytes)
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/checks/file_size_check/hook_environment_aware_any_oversized_blobs.rb b/lib/gitlab/checks/file_size_check/hook_environment_aware_any_oversized_blobs.rb
new file mode 100644
index 00000000000..952def83658
--- /dev/null
+++ b/lib/gitlab/checks/file_size_check/hook_environment_aware_any_oversized_blobs.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Checks
+ module FileSizeCheck
+ class HookEnvironmentAwareAnyOversizedBlobs
+ def initialize(project:, changes:, file_size_limit_megabytes:)
+ @project = project
+ @repository = project.repository
+ @changes = changes
+ @file_size_limit_megabytes = file_size_limit_megabytes
+ end
+
+ def find(timeout: nil)
+ if ignore_alternate_directories?
+ blobs = repository.list_all_blobs(bytes_limit: 0, dynamic_timeout: timeout,
+ ignore_alternate_object_directories: true).to_a
+
+ blobs.select! do |blob|
+ ::Gitlab::Utils.bytes_to_megabytes(blob.size) > file_size_limit_megabytes
+ end
+ filter_existing(blobs)
+ else
+ any_oversize_blobs.find(timeout: timeout)
+ end
+ end
+
+ private
+
+ attr_reader :project, :repository, :changes, :file_size_limit_megabytes
+
+ def filter_existing(blobs)
+ gitaly_repo = repository.gitaly_repository.dup.tap { |repo| repo.git_object_directory = "" }
+
+ map_blob_id_to_existence = repository.gitaly_commit_client.object_existence_map(blobs.map(&:id),
+ gitaly_repo: gitaly_repo)
+
+ blobs.reject { |blob| map_blob_id_to_existence[blob.id].present? }
+ end
+
+ def ignore_alternate_directories?
+ git_env = ::Gitlab::Git::HookEnv.all(repository.gl_repository)
+
+ git_env['GIT_OBJECT_DIRECTORY_RELATIVE'].present?
+ end
+
+ def any_oversize_blobs
+ AnyOversizedBlobs.new(project: project, changes: changes,
+ file_size_limit_megabytes: file_size_limit_megabytes)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/checks/global_file_size_check.rb b/lib/gitlab/checks/global_file_size_check.rb
index 418d2d32b57..62facf52239 100644
--- a/lib/gitlab/checks/global_file_size_check.rb
+++ b/lib/gitlab/checks/global_file_size_check.rb
@@ -3,7 +3,6 @@
module Gitlab
module Checks
class GlobalFileSizeCheck < BaseBulkChecker
- MAX_FILE_SIZE_MB = 100
LOG_MESSAGE = 'Checking for blobs over the file size limit'
def validate!
@@ -11,19 +10,38 @@ module Gitlab
Gitlab::AppJsonLogger.info(LOG_MESSAGE)
logger.log_timed(LOG_MESSAGE) do
- Gitlab::Checks::FileSizeCheck::AllowExistingOversizedBlobs.new(
+ oversized_blobs = Gitlab::Checks::FileSizeCheck::HookEnvironmentAwareAnyOversizedBlobs.new(
project: project,
changes: changes,
- file_size_limit_megabytes: MAX_FILE_SIZE_MB
+ file_size_limit_megabytes: file_size_limit
).find
- # TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/393535
- # - set limit per plan tier
- # - raise an error if large blobs are found
+ if oversized_blobs.present?
+ Gitlab::AppJsonLogger.info(
+ message: 'Found blob over global limit',
+ blob_sizes: oversized_blobs.map(&:size)
+ )
+
+ if enforce_global_file_size_limit?
+ raise ::Gitlab::GitAccess::ForbiddenError,
+ "Changes include a file that is larger than the allowed size of #{file_size_limit} MiB. " \
+ "Use Git LFS to manage this file.)"
+ end
+ end
end
true
end
+
+ private
+
+ def file_size_limit
+ project.actual_limits.file_size_limit_mb
+ end
+
+ def enforce_global_file_size_limit?
+ Feature.enabled?(:enforce_global_file_size_limit, project)
+ end
end
end
end
diff --git a/lib/gitlab/ci/build/artifacts/metadata.rb b/lib/gitlab/ci/build/artifacts/metadata.rb
index 88d624503df..d0ab4916c90 100644
--- a/lib/gitlab/ci/build/artifacts/metadata.rb
+++ b/lib/gitlab/ci/build/artifacts/metadata.rb
@@ -70,8 +70,8 @@ module Gitlab
# abort if we don't get any data.
next unless path && meta
next unless path.valid_encoding? && meta.valid_encoding?
- next unless path =~ match_pattern
- next if path =~ INVALID_PATH_PATTERN
+ next unless match_pattern.match?(path)
+ next if INVALID_PATH_PATTERN.match?(path)
entries[path] = Gitlab::Json.parse(meta, symbolize_names: true)
rescue JSON::ParserError, Encoding::CompatibilityError
@@ -90,7 +90,7 @@ module Gitlab
raise ParserError, 'Artifacts metadata file empty!'
end
- unless version_string =~ VERSION_PATTERN
+ unless VERSION_PATTERN.match?(version_string)
raise ParserError, 'Invalid version!'
end
diff --git a/lib/gitlab/ci/build/artifacts/metadata/entry.rb b/lib/gitlab/ci/build/artifacts/metadata/entry.rb
index 355fffbf9c6..23db15d58b6 100644
--- a/lib/gitlab/ci/build/artifacts/metadata/entry.rb
+++ b/lib/gitlab/ci/build/artifacts/metadata/entry.rb
@@ -102,7 +102,7 @@ module Gitlab
def total_size
descendant_pattern = /^#{Regexp.escape(@path.to_s)}/
entries.sum do |path, entry|
- (entry[:size] if path =~ descendant_pattern).to_i
+ (entry[:size] if descendant_pattern.match?(path)).to_i
end
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/lib/gitlab/ci/config/entry/include/rules.rb b/lib/gitlab/ci/config/entry/include/rules.rb
index 8eaf9e35aaf..71418e6752d 100644
--- a/lib/gitlab/ci/config/entry/include/rules.rb
+++ b/lib/gitlab/ci/config/entry/include/rules.rb
@@ -5,6 +5,12 @@ module Gitlab
class Config
module Entry
class Include
+ ##
+ # Include rules are validated separately from all other entries. This
+ # is because included files are expanded before `@root.compose!` runs
+ # in Ci::Config. As such, this class is directly instantiated and
+ # composed in lib/gitlab/ci/config/external/rules.rb.
+ #
class Rules < ::Gitlab::Config::Entry::ComposableArray
include ::Gitlab::Config::Entry::Validatable
@@ -13,8 +19,9 @@ module Gitlab
validates :config, type: Array
end
+ # Remove this method when FF `ci_refactor_external_rules` is removed
def value
- @config
+ Feature.enabled?(:ci_refactor_external_rules) ? super : @config
end
def composable_class
diff --git a/lib/gitlab/ci/config/entry/include/rules/rule.rb b/lib/gitlab/ci/config/entry/include/rules/rule.rb
index 9cdbd8cd037..1a68e95913c 100644
--- a/lib/gitlab/ci/config/entry/include/rules/rule.rb
+++ b/lib/gitlab/ci/config/entry/include/rules/rule.rb
@@ -14,9 +14,6 @@ module Gitlab
attributes :if, :exists, :when
- # Include rules are validated before Entry validations. This is because
- # the include files are expanded before `compose!` runs in Ci::Config.
- # The actual validation logic is in lib/gitlab/ci/config/external/rules.rb.
validations do
validates :config, presence: true
validates :config, type: { with: Hash }
@@ -24,8 +21,14 @@ module Gitlab
with_options allow_nil: true do
validates :if, expression: true
+ validates :exists, array_of_strings_or_string: true, allow_blank: true
+ validates :when, allowed_values: { in: ALLOWED_WHEN }
end
end
+
+ def value
+ Feature.enabled?(:ci_refactor_external_rules) ? config.compact : super
+ end
end
end
end
diff --git a/lib/gitlab/ci/config/entry/need.rb b/lib/gitlab/ci/config/entry/need.rb
index f1b67635c08..cf727134f32 100644
--- a/lib/gitlab/ci/config/entry/need.rb
+++ b/lib/gitlab/ci/config/entry/need.rb
@@ -42,11 +42,15 @@ module Gitlab
end
class JobHash < ::Gitlab::Config::Entry::Node
- include ::Gitlab::Config::Entry::Validatable
include ::Gitlab::Config::Entry::Attributable
+ include ::Gitlab::Config::Entry::Configurable
+
+ ALLOWED_KEYS = %i[job artifacts optional parallel].freeze
+ attributes :job, :artifacts, :optional, :parallel
- ALLOWED_KEYS = %i[job artifacts optional].freeze
- attributes :job, :artifacts, :optional
+ entry :parallel, Entry::Product::Parallel,
+ description: 'Parallel needs configuration for this job',
+ inherit: true
validations do
validates :config, presence: true
@@ -61,9 +65,15 @@ module Gitlab
end
def value
- { name: job,
+ result = {
+ name: job,
artifacts: artifacts || artifacts.nil?,
- optional: !!optional }
+ optional: !!optional
+ }
+
+ result[:parallel] = parallel_value if has_parallel?
+
+ result
end
end
diff --git a/lib/gitlab/ci/config/entry/needs.rb b/lib/gitlab/ci/config/entry/needs.rb
index 11b202ddde9..f0bfad80e6f 100644
--- a/lib/gitlab/ci/config/entry/needs.rb
+++ b/lib/gitlab/ci/config/entry/needs.rb
@@ -21,6 +21,10 @@ module Gitlab
if config.is_a?(Hash) && config.empty?
errors.add(:config, 'can not be an empty Hash')
end
+
+ if number_parallel_build?
+ errors.add(:config, 'cannot use "parallel: <number>".')
+ end
end
validate on: :composed do
@@ -47,6 +51,14 @@ module Gitlab
end
end
+ def number_parallel_build?
+ if config.is_a?(Array)
+ config.any? { |need_values| need_values.is_a?(Hash) && need_values[:parallel].is_a?(Numeric) }
+ elsif config.is_a?(Hash)
+ config[:parallel].is_a?(Numeric)
+ end
+ end
+
def composable_class
Entry::Need
end
diff --git a/lib/gitlab/ci/config/entry/reports.rb b/lib/gitlab/ci/config/entry/reports.rb
index 6408f412e6f..3c180674f2a 100644
--- a/lib/gitlab/ci/config/entry/reports.rb
+++ b/lib/gitlab/ci/config/entry/reports.rb
@@ -17,7 +17,7 @@ module Gitlab
dast performance browser_performance load_performance license_scanning metrics lsif
dotenv terraform accessibility
coverage_fuzzing api_fuzzing cluster_image_scanning
- requirements requirements_v2 coverage_report cyclonedx].freeze
+ requirements requirements_v2 coverage_report cyclonedx annotations].freeze
attributes ALLOWED_KEYS
@@ -50,6 +50,7 @@ module Gitlab
validates :requirements, array_of_strings_or_string: true
validates :requirements_v2, array_of_strings_or_string: true
validates :cyclonedx, array_of_strings_or_string: true
+ validates :annotations, array_of_strings_or_string: true
end
end
diff --git a/lib/gitlab/ci/config/external/context.rb b/lib/gitlab/ci/config/external/context.rb
index b8e012ec851..c57391d355c 100644
--- a/lib/gitlab/ci/config/external/context.rb
+++ b/lib/gitlab/ci/config/external/context.rb
@@ -14,7 +14,9 @@ module Gitlab
include ::Gitlab::Utils::StrongMemoize
attr_reader :project, :sha, :user, :parent_pipeline, :variables, :pipeline_config
- attr_reader :expandset, :execution_deadline, :logger, :max_includes
+ attr_reader :expandset, :execution_deadline, :logger, :max_includes, :max_total_yaml_size_bytes
+
+ attr_accessor :total_file_size_in_bytes
delegate :instrument, to: :logger
@@ -32,6 +34,9 @@ module Gitlab
@execution_deadline = 0
@logger = logger || Gitlab::Ci::Pipeline::Logger.new(project: project)
@max_includes = Gitlab::CurrentSettings.current_application_settings.ci_max_includes
+ @max_total_yaml_size_bytes =
+ Gitlab::CurrentSettings.current_application_settings.ci_max_total_yaml_size_bytes
+ @total_file_size_in_bytes = 0
yield self if block_given?
end
@@ -59,6 +64,7 @@ module Gitlab
ctx.execution_deadline = execution_deadline
ctx.logger = logger
ctx.max_includes = max_includes
+ ctx.max_total_yaml_size_bytes = max_total_yaml_size_bytes
end
end
@@ -100,7 +106,7 @@ module Gitlab
protected
- attr_writer :expandset, :execution_deadline, :logger, :max_includes
+ attr_writer :expandset, :execution_deadline, :logger, :max_includes, :max_total_yaml_size_bytes
private
diff --git a/lib/gitlab/ci/config/external/file/base.rb b/lib/gitlab/ci/config/external/file/base.rb
index 8bcb2a389d2..efba81c7420 100644
--- a/lib/gitlab/ci/config/external/file/base.rb
+++ b/lib/gitlab/ci/config/external/file/base.rb
@@ -92,6 +92,11 @@ module Gitlab
def load_and_validate_expanded_hash!
return errors.push("`#{masked_location}`: #{content_result.error}") unless content_result.valid?
+ if content_result.interpolated? && context.user.present?
+ ::Gitlab::UsageDataCounters::HLLRedisCounter
+ .track_event('ci_interpolation_users', values: context.user.id)
+ end
+
context.logger.instrument(:config_file_expand_content_includes) do
expanded_content_hash # calling the method expands then memoizes the result
end
@@ -109,7 +114,7 @@ module Gitlab
def content_result
context.logger.instrument(:config_file_fetch_content_hash) do
- ::Gitlab::Ci::Config::Yaml::Loader.new(content, inputs: content_inputs, current_user: context.user).load
+ ::Gitlab::Ci::Config::Yaml::Loader.new(content, inputs: content_inputs).load
end
end
strong_memoize_attr :content_result
diff --git a/lib/gitlab/ci/config/external/file/component.rb b/lib/gitlab/ci/config/external/file/component.rb
index 9679d78a1aa..15cc0783b86 100644
--- a/lib/gitlab/ci/config/external/file/component.rb
+++ b/lib/gitlab/ci/config/external/file/component.rb
@@ -15,10 +15,6 @@ module Gitlab
super
end
- def matching?
- super && ::Feature.enabled?(:ci_include_components, context.project&.root_namespace)
- end
-
def content
return unless component_result.success?
diff --git a/lib/gitlab/ci/config/external/mapper.rb b/lib/gitlab/ci/config/external/mapper.rb
index 61b4d1ada10..cff7954235f 100644
--- a/lib/gitlab/ci/config/external/mapper.rb
+++ b/lib/gitlab/ci/config/external/mapper.rb
@@ -10,6 +10,7 @@ module Gitlab
Error = Class.new(StandardError)
AmbigiousSpecificationError = Class.new(Error)
TooManyIncludesError = Class.new(Error)
+ TooMuchDataInPipelineTreeError = Class.new(Error)
def initialize(values, context)
@locations = Array.wrap(values.fetch(:include, [])).compact
diff --git a/lib/gitlab/ci/config/external/mapper/matcher.rb b/lib/gitlab/ci/config/external/mapper/matcher.rb
index 5072d0971cf..f9d5b0ebd01 100644
--- a/lib/gitlab/ci/config/external/mapper/matcher.rb
+++ b/lib/gitlab/ci/config/external/mapper/matcher.rb
@@ -40,19 +40,14 @@ module Gitlab
strong_memoize_attr :file_subkeys
def file_classes
- classes = [
+ [
External::File::Local,
External::File::Project,
External::File::Remote,
External::File::Template,
- External::File::Artifact
+ External::File::Artifact,
+ External::File::Component
]
-
- if Feature.enabled?(:ci_include_components, context.project&.root_namespace)
- classes << External::File::Component
- end
-
- classes
end
strong_memoize_attr :file_classes
end
diff --git a/lib/gitlab/ci/config/external/mapper/verifier.rb b/lib/gitlab/ci/config/external/mapper/verifier.rb
index 95975e4661b..580cae8a207 100644
--- a/lib/gitlab/ci/config/external/mapper/verifier.rb
+++ b/lib/gitlab/ci/config/external/mapper/verifier.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require 'objspace'
+
module Gitlab
module Ci
class Config
@@ -37,6 +39,13 @@ module Gitlab
file.validate_content! if file.valid?
file.load_and_validate_expanded_hash! if file.valid?
+
+ next unless Feature.enabled?(:introduce_ci_max_total_yaml_size_bytes, context.project) && file.valid?
+
+ # We are checking the file.content.to_s because that is returning the actual content of the file,
+ # whereas file.content would return the BatchLoader.
+ context.total_file_size_in_bytes += ObjectSpace.memsize_of(file.content.to_s)
+ verify_max_total_pipeline_size!
end
end
# rubocop: enable Metrics/CyclomaticComplexity
@@ -50,6 +59,12 @@ module Gitlab
def verify_execution_time!
context.check_execution_time!
end
+
+ def verify_max_total_pipeline_size!
+ return if context.total_file_size_in_bytes <= context.max_total_yaml_size_bytes
+
+ raise Mapper::TooMuchDataInPipelineTreeError, "Total size of combined CI/CD configuration is too big"
+ end
end
end
end
diff --git a/lib/gitlab/ci/config/external/rules.rb b/lib/gitlab/ci/config/external/rules.rb
index 59e666b8bb5..0e6209460e0 100644
--- a/lib/gitlab/ci/config/external/rules.rb
+++ b/lib/gitlab/ci/config/external/rules.rb
@@ -5,15 +5,29 @@ module Gitlab
class Config
module External
class Rules
+ # Remove these two constants when FF `ci_refactor_external_rules` is removed
ALLOWED_KEYS = Entry::Include::Rules::Rule::ALLOWED_KEYS
ALLOWED_WHEN = Entry::Include::Rules::Rule::ALLOWED_WHEN
InvalidIncludeRulesError = Class.new(Mapper::Error)
def initialize(rule_hashes)
- validate(rule_hashes)
+ if Feature.enabled?(:ci_refactor_external_rules)
+ return unless rule_hashes
- @rule_list = Build::Rules::Rule.fabricate_list(rule_hashes)
+ # We must compose the include rules entry here because included
+ # files are expanded before `@root.compose!` runs in Ci::Config.
+ rules_entry = Entry::Include::Rules.new(rule_hashes)
+ rules_entry.compose!
+
+ raise InvalidIncludeRulesError, "include:#{rules_entry.errors.first}" unless rules_entry.valid?
+
+ @rule_list = Build::Rules::Rule.fabricate_list(rules_entry.value)
+ else
+ validate(rule_hashes)
+
+ @rule_list = Build::Rules::Rule.fabricate_list(rule_hashes)
+ end
end
def evaluate(context)
@@ -32,6 +46,7 @@ module Gitlab
@rule_list.find { |rule| rule.matches?(nil, context) }
end
+ # Remove this method when FF `ci_refactor_external_rules` is removed
def validate(rule_hashes)
return unless rule_hashes.is_a?(Array)
@@ -42,6 +57,7 @@ module Gitlab
end
end
+ # Remove this method when FF `ci_refactor_external_rules` is removed
def valid_when?(rule_hash)
rule_hash[:when].nil? || rule_hash[:when].in?(ALLOWED_WHEN)
end
diff --git a/lib/gitlab/ci/config/header/input.rb b/lib/gitlab/ci/config/header/input.rb
index 7f0edaaac4c..76a89a3080e 100644
--- a/lib/gitlab/ci/config/header/input.rb
+++ b/lib/gitlab/ci/config/header/input.rb
@@ -11,12 +11,13 @@ module Gitlab
include ::Gitlab::Config::Entry::Validatable
include ::Gitlab::Config::Entry::Attributable
- attributes :default, prefix: :input
+ attributes :default, :type, prefix: :input
validations do
- validates :config, type: Hash, allowed_keys: [:default]
+ validates :config, type: Hash, allowed_keys: [:default, :type]
validates :key, alphanumeric: true
validates :input_default, alphanumeric: true, allow_nil: true
+ validates :input_type, allow_nil: true, allowed_values: Interpolation::Inputs.input_types
end
end
end
diff --git a/lib/gitlab/ci/config/interpolation/access.rb b/lib/gitlab/ci/config/interpolation/access.rb
new file mode 100644
index 00000000000..4c1cd32d28d
--- /dev/null
+++ b/lib/gitlab/ci/config/interpolation/access.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module Interpolation
+ class Access
+ attr_reader :content, :errors
+
+ MAX_ACCESS_OBJECTS = 5
+ MAX_ACCESS_BYTESIZE = 1024
+
+ def initialize(access, ctx)
+ @content = access
+ @ctx = ctx
+ @errors = []
+
+ if objects.count <= 1 # rubocop:disable Style/IfUnlessModifier
+ @errors.push('invalid interpolation access pattern')
+ end
+
+ if access.bytesize > MAX_ACCESS_BYTESIZE # rubocop:disable Style/IfUnlessModifier
+ @errors.push('maximum interpolation expression size exceeded')
+ end
+
+ evaluate! if valid?
+ end
+
+ def valid?
+ errors.none?
+ end
+
+ def objects
+ @objects ||= @content.split('.', MAX_ACCESS_OBJECTS)
+ end
+
+ def value
+ raise ArgumentError, 'access path invalid' unless valid?
+
+ @value
+ end
+
+ private
+
+ def evaluate!
+ raise ArgumentError, 'access path invalid' unless valid?
+
+ @value ||= objects.inject(@ctx) do |memo, value|
+ key = value.to_sym
+
+ break @errors.push("unknown interpolation key: `#{key}`") unless memo.key?(key)
+
+ memo.fetch(key)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/interpolation/block.rb b/lib/gitlab/ci/config/interpolation/block.rb
new file mode 100644
index 00000000000..cf8420f924e
--- /dev/null
+++ b/lib/gitlab/ci/config/interpolation/block.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module Interpolation
+ ##
+ # This class represents an interpolation block. The format supported is:
+ # $[[ <access> | <function1> | <function2> | ... <functionN> ]]
+ #
+ # <access> specifies the value to retrieve (e.g. `inputs.key`).
+ # <function> can be optionally provided with or without arguments to
+ # manipulate the access value. Functions are evaluated in the order
+ # they are presented.
+ class Block
+ PREFIX = '$[['
+ PATTERN = /(?<block>\$\[\[\s*(?<data>.*?)\s*\]\])/
+ MAX_FUNCTIONS = 3
+
+ attr_reader :block, :data, :ctx, :errors
+
+ def initialize(block, data, ctx)
+ @block = block
+ @data = data
+ @ctx = ctx
+ @errors = []
+ @value = nil
+
+ evaluate!
+ end
+
+ def valid?
+ errors.none?
+ end
+
+ def content
+ data
+ end
+
+ def value
+ raise ArgumentError, 'block invalid' unless valid?
+
+ @value
+ end
+
+ def self.match(data)
+ return data unless data.is_a?(String) && data.include?(PREFIX)
+
+ data.gsub(PATTERN) do
+ yield ::Regexp.last_match(1), ::Regexp.last_match(2)
+ end
+ end
+
+ private
+
+ # We expect the block data to be a string with one or more entities delimited by pipes:
+ # <access> | <function1> | <function2> | ... <functionN>
+ def evaluate!
+ data_access, *functions = data.split('|').map(&:strip)
+ access = Interpolation::Access.new(data_access, ctx)
+
+ return @errors.concat(access.errors) unless access.valid?
+ return @errors.push('too many functions in interpolation block') if functions.count > MAX_FUNCTIONS
+
+ result = Interpolation::FunctionsStack.new(functions).evaluate(access.value)
+
+ if result.success?
+ @value = result.value
+ else
+ @errors.concat(result.errors)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/interpolation/config.rb b/lib/gitlab/ci/config/interpolation/config.rb
new file mode 100644
index 00000000000..064d451b0b2
--- /dev/null
+++ b/lib/gitlab/ci/config/interpolation/config.rb
@@ -0,0 +1,129 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module Interpolation
+ ##
+ # Interpolation::Config represents a configuration artifact that we want to perform interpolation on.
+ #
+ class Config
+ include Gitlab::Utils::StrongMemoize
+ ##
+ # Total number of hash nodes traversed.
+ # For example, loading a YAML below would result in a hash having 12 nodes
+ # instead of 9, because hash values are being counted before we recursively traverse them.
+ #
+ # test:
+ # spec:
+ # env: $[[ inputs.env ]]
+ #
+ # $[[ inputs.key ]]:
+ # name: $[[ inputs.key ]]
+ # script: my-value
+ #
+ # According to our benchmarks performed when developing this code, the worst-case scenario of processing
+ # a hash with 500_000 nodes takes around 1 second and consumes around 225 megabytes of memory.
+ #
+ # The typical scenario, using just a few interpolations,
+ # takes 250ms and consumes around 20 megabytes of memory.
+ #
+ # Given the above the 500_000 nodes should be an upper limit, provided that the are additional safeguard
+ # present in other parts of the code
+ # (example: maximum number of interpolation blocks found). Typical size of a
+ # YAML configuration with 500k nodes might be around 10 megabytes, which is an order of magnitude higher than
+ # the 1MB limit for loading YAML on GitLab.com
+ #
+ MAX_NODES = 500_000
+ MAX_NODE_SIZE = 1024 * 1024 # 1MB
+
+ TooManyNodesError = Class.new(StandardError)
+ NodeTooLargeError = Class.new(StandardError)
+
+ Visitor = Class.new do
+ def initialize
+ @visited = 0
+ end
+
+ def visit!
+ @visited += 1
+
+ raise Config::TooManyNodesError if @visited > Config::MAX_NODES
+ end
+ end
+
+ attr_reader :errors
+
+ def initialize(hash)
+ @config = hash
+ @errors = []
+ end
+
+ def to_h
+ @config
+ end
+
+ ##
+ # The replace! method will yield a block and replace each of the hash config nodes with
+ # the return value of the block.
+ #
+ # It returns `nil` if there were errors found during the process.
+ #
+ def replace!(&block)
+ recursive_replace(@config, Visitor.new, &block)
+ rescue TooManyNodesError
+ @errors.push('config too large')
+ nil
+ rescue NodeTooLargeError
+ @errors.push('config node too large')
+ nil
+ end
+ strong_memoize_attr :replace!
+
+ def self.fabricate(config)
+ case config
+ when Hash
+ new(config)
+ when Interpolation::Config
+ config
+ else
+ raise ArgumentError, 'unknown interpolation config'
+ end
+ end
+
+ private
+
+ def recursive_replace(config, visitor, &block)
+ visitor.visit!
+
+ case config
+ when Hash
+ {}.tap do |new_hash|
+ config.each_pair do |key, value|
+ new_key = recursive_replace(key, visitor, &block)
+ new_value = recursive_replace(value, visitor, &block)
+
+ if new_key != key
+ new_hash[new_key] = new_value
+ else
+ new_hash[key] = new_value
+ end
+ end
+ end
+ when Array
+ config.map { |value| recursive_replace(value, visitor, &block) }
+ when Symbol
+ recursive_replace(config.to_s, visitor, &block)
+ when String
+ raise NodeTooLargeError if config.bytesize > MAX_NODE_SIZE
+
+ yield config
+ else
+ config
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/interpolation/context.rb b/lib/gitlab/ci/config/interpolation/context.rb
new file mode 100644
index 00000000000..f5e7db03291
--- /dev/null
+++ b/lib/gitlab/ci/config/interpolation/context.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module Interpolation
+ ##
+ # Interpolation::Context is a class that represents the data that can be used
+ # when performing string interpolation on a CI configuration.
+ #
+ class Context
+ ContextTooComplexError = Class.new(StandardError)
+ NotSymbolizedContextError = Class.new(StandardError)
+
+ MAX_DEPTH = 3
+
+ def initialize(hash)
+ @context = hash
+
+ raise ContextTooComplexError if depth > MAX_DEPTH
+ end
+
+ def valid?
+ errors.none?
+ end
+
+ ##
+ # This method is here because `Context` will be responsible for validating specs, inputs and defaults.
+ #
+ def errors
+ []
+ end
+
+ def depth
+ deep_depth(@context)
+ end
+
+ def fetch(field)
+ @context.fetch(field)
+ end
+
+ def key?(name)
+ @context.key?(name)
+ end
+
+ def to_h
+ @context.to_h
+ end
+
+ private
+
+ def deep_depth(context, depth = 0)
+ values = context.values.map do |value|
+ if value.is_a?(Hash)
+ deep_depth(value, depth + 1)
+ else
+ depth + 1
+ end
+ end
+
+ values.max.to_i
+ end
+
+ def self.fabricate(context)
+ case context
+ when Hash
+ new(context)
+ when Interpolation::Context
+ context
+ else
+ raise ArgumentError, 'unknown interpolation context'
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/interpolation/functions/base.rb b/lib/gitlab/ci/config/interpolation/functions/base.rb
new file mode 100644
index 00000000000..b9ce8cdc5bc
--- /dev/null
+++ b/lib/gitlab/ci/config/interpolation/functions/base.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module Interpolation
+ module Functions
+ class Base
+ attr_reader :errors
+
+ def self.function_expression_pattern
+ raise NotImplementedError
+ end
+
+ def self.name
+ raise NotImplementedError
+ end
+
+ def self.matches?(function_expression)
+ function_expression_pattern.match?(function_expression)
+ end
+
+ def initialize(function_expression)
+ @errors = []
+ @function_args = parse_args(function_expression)
+ end
+
+ def valid?
+ errors.empty?
+ end
+
+ def execute(_input_value)
+ raise NotImplementedError
+ end
+
+ private
+
+ attr_reader :function_args
+
+ def error(message)
+ errors << "error in `#{self.class.name}` function: #{message}"
+ end
+
+ def parse_args(function_expression)
+ self.class.function_expression_pattern.match(function_expression)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/interpolation/functions/truncate.rb b/lib/gitlab/ci/config/interpolation/functions/truncate.rb
new file mode 100644
index 00000000000..3771756ba48
--- /dev/null
+++ b/lib/gitlab/ci/config/interpolation/functions/truncate.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module Interpolation
+ module Functions
+ class Truncate < Base
+ def self.function_expression_pattern
+ /^#{name}\(\s*(?<offset>\d+)\s*,\s*(?<length>\d+)\s*\)?$/
+ end
+
+ def self.name
+ 'truncate'
+ end
+
+ def execute(input_value)
+ if input_value.is_a?(String)
+ input_value[offset, length].to_s
+ else
+ error('invalid input type: truncate can only be used with string inputs')
+ nil
+ end
+ end
+
+ private
+
+ def offset
+ function_args[:offset].to_i
+ end
+
+ def length
+ function_args[:length].to_i
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/interpolation/functions_stack.rb b/lib/gitlab/ci/config/interpolation/functions_stack.rb
new file mode 100644
index 00000000000..951d1121d4f
--- /dev/null
+++ b/lib/gitlab/ci/config/interpolation/functions_stack.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module Interpolation
+ ##
+ # This class matches the given function string with a predefined
+ # function and then applies it to the input value.
+ #
+ class FunctionsStack
+ Output = Struct.new(:value, :errors) do
+ def success?
+ errors.empty?
+ end
+ end
+
+ FUNCTIONS = [
+ Functions::Truncate
+ ].freeze
+
+ attr_reader :errors
+
+ def initialize(function_expressions)
+ @errors = []
+ @functions = build_stack(function_expressions)
+ end
+
+ def valid?
+ errors.none?
+ end
+
+ def evaluate(input_value)
+ return Output.new(nil, errors) unless valid?
+
+ functions.reduce(Output.new(input_value, [])) do |output, function|
+ break output unless output.success?
+
+ output_value = function.execute(output.value)
+
+ if function.valid?
+ Output.new(output_value, [])
+ else
+ Output.new(nil, function.errors)
+ end
+ end
+ end
+
+ private
+
+ attr_reader :functions
+
+ def build_stack(function_expressions)
+ function_expressions.map do |function_expression|
+ matching_function = FUNCTIONS.find { |function| function.matches?(function_expression) }
+
+ if matching_function.present?
+ matching_function.new(function_expression)
+ else
+ message = "no function matching `#{function_expression}`: " \
+ 'check that the function name, arguments, and types are correct'
+
+ errors << message
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/interpolation/inputs.rb b/lib/gitlab/ci/config/interpolation/inputs.rb
new file mode 100644
index 00000000000..0fd3238d503
--- /dev/null
+++ b/lib/gitlab/ci/config/interpolation/inputs.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module Interpolation
+ # Interpolation inputs provided by the user.
+ class Inputs
+ UnknownInputTypeError = Class.new(StandardError)
+
+ TYPES = [
+ BooleanInput,
+ NumberInput,
+ StringInput
+ ].freeze
+
+ def self.input_types
+ TYPES.map(&:type_name)
+ end
+
+ def initialize(specs, args)
+ @specs = specs.to_h
+ @args = args.to_h
+ @inputs = []
+ @errors = []
+
+ validate!
+ fabricate!
+ end
+
+ def errors
+ @errors + @inputs.flat_map(&:errors)
+ end
+
+ def valid?
+ errors.none?
+ end
+
+ def to_hash
+ @inputs.inject({}) do |hash, input|
+ hash.merge(input.to_hash)
+ end
+ end
+
+ private
+
+ def validate!
+ unknown_inputs = @args.keys - @specs.keys
+ return if unknown_inputs.empty?
+
+ @errors.push("unknown input arguments: #{unknown_inputs.join(', ')}")
+ end
+
+ def fabricate!
+ @specs.each do |input_name, spec|
+ input_type = TYPES.find { |klass| klass.matches?(spec) }
+
+ unless input_type
+ @errors.push(
+ "unknown input specification for `#{input_name}` (valid types: #{valid_type_names.join(', ')})")
+ next
+ end
+
+ @inputs.push(input_type.new(
+ name: input_name,
+ spec: spec,
+ value: @args[input_name]))
+ end
+ end
+
+ def valid_type_names
+ TYPES.map(&:type_name)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/interpolation/inputs/base_input.rb b/lib/gitlab/ci/config/interpolation/inputs/base_input.rb
new file mode 100644
index 00000000000..5648c4d31ea
--- /dev/null
+++ b/lib/gitlab/ci/config/interpolation/inputs/base_input.rb
@@ -0,0 +1,96 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module Interpolation
+ class Inputs
+ ##
+ # This is a common abstraction for all input types
+ class BaseInput
+ ArgumentNotValidError = Class.new(StandardError)
+
+ # Checks whether the class matches the type in the specification
+ def self.matches?(spec)
+ raise NotImplementedError
+ end
+
+ # Human readable type used in error messages
+ def self.type_name
+ raise NotImplementedError
+ end
+
+ # Checks whether the provided value is of the given type
+ def valid_value?(value)
+ raise NotImplementedError
+ end
+
+ attr_reader :errors, :name, :spec, :value
+
+ def initialize(name:, spec:, value:)
+ @name = name
+ @errors = []
+
+ # Treat minimal spec definition (nil) as a valid hash:
+ # spec:
+ # inputs:
+ # website:
+ @spec = spec || {} # specification from input definition
+ @value = value # actual value provided by the user
+
+ validate!
+ end
+
+ def to_hash
+ raise ArgumentNotValidError unless valid?
+
+ { name => actual_value }
+ end
+
+ def valid?
+ @errors.none?
+ end
+
+ private
+
+ def validate!
+ return error('required value has not been provided') if required_input? && value.nil?
+
+ # validate default value
+ if !required_input? && !valid_value?(default)
+ return error("default value is not a #{self.class.type_name}")
+ end
+
+ # validate provided value
+ error("provided value is not a #{self.class.type_name}") unless valid_value?(actual_value)
+ end
+
+ def error(message)
+ @errors.push("`#{name}` input: #{message}")
+ end
+
+ def actual_value
+ # nil check is to support boolean values.
+ value.nil? ? default : value
+ end
+
+ # An input specification without a default value is required.
+ # For example:
+ # ```yaml
+ # spec:
+ # inputs:
+ # website:
+ # ```
+ def required_input?
+ !spec.key?(:default)
+ end
+
+ def default
+ spec[:default]
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/interpolation/inputs/boolean_input.rb b/lib/gitlab/ci/config/interpolation/inputs/boolean_input.rb
new file mode 100644
index 00000000000..0293c01a5a8
--- /dev/null
+++ b/lib/gitlab/ci/config/interpolation/inputs/boolean_input.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module Interpolation
+ class Inputs
+ class BooleanInput < BaseInput
+ def self.matches?(spec)
+ spec.is_a?(Hash) && spec[:type] == type_name
+ end
+
+ def self.type_name
+ 'boolean'
+ end
+
+ def valid_value?(value)
+ [true, false].include?(value)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/interpolation/inputs/number_input.rb b/lib/gitlab/ci/config/interpolation/inputs/number_input.rb
new file mode 100644
index 00000000000..314315d2b6d
--- /dev/null
+++ b/lib/gitlab/ci/config/interpolation/inputs/number_input.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module Interpolation
+ class Inputs
+ class NumberInput < BaseInput
+ def self.matches?(spec)
+ spec.is_a?(Hash) && spec[:type] == type_name
+ end
+
+ def self.type_name
+ 'number'
+ end
+
+ def valid_value?(value)
+ value.is_a?(Numeric)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/interpolation/inputs/string_input.rb b/lib/gitlab/ci/config/interpolation/inputs/string_input.rb
new file mode 100644
index 00000000000..39870582d0c
--- /dev/null
+++ b/lib/gitlab/ci/config/interpolation/inputs/string_input.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module Interpolation
+ class Inputs
+ class StringInput < BaseInput
+ def self.matches?(spec)
+ # The input spec can be `nil` when using a minimal specification
+ # and also when `type` is not specified.
+ #
+ # ```yaml
+ # spec:
+ # inputs:
+ # foo:
+ # ```
+ spec.nil? || (spec.is_a?(Hash) && [nil, type_name].include?(spec[:type]))
+ end
+
+ def self.type_name
+ 'string'
+ end
+
+ def valid_value?(value)
+ value.nil? || value.is_a?(String)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/yaml/interpolator.rb b/lib/gitlab/ci/config/interpolation/interpolator.rb
index 2909c2ac798..58965890184 100644
--- a/lib/gitlab/ci/config/yaml/interpolator.rb
+++ b/lib/gitlab/ci/config/interpolation/interpolator.rb
@@ -3,19 +3,18 @@
module Gitlab
module Ci
class Config
- module Yaml
+ module Interpolation
##
- # Config::Yaml::Interpolator performs CI config file interpolation, and surfaces all possible interpolation
- # errors. It is designed to provide an external file's validation context too.
+ # Performs CI config file interpolation, and surfaces all possible interpolation errors.
#
class Interpolator
- attr_reader :config, :args, :current_user, :errors
+ attr_reader :config, :args, :errors
- def initialize(config, args, current_user: nil)
+ def initialize(config, args)
@config = config
@args = args.to_h
- @current_user = current_user
@errors = []
+ @interpolated = false
end
def valid?
@@ -38,6 +37,7 @@ module Gitlab
def interpolate!
return @errors.push(config.error) unless config.valid?
+ return @errors.push('unknown input arguments') if inputs_without_header?
return @result ||= config.content unless config.has_header?
return @errors.concat(header.errors) unless header.valid?
@@ -45,18 +45,23 @@ module Gitlab
return @errors.concat(context.errors) unless context.valid?
return @errors.concat(template.errors) unless template.valid?
- if current_user.present?
- ::Gitlab::UsageDataCounters::HLLRedisCounter
- .track_event('ci_interpolation_users', values: current_user.id)
- end
+ @interpolated = true
@result ||= template.interpolated.to_h.deep_symbolize_keys
end
+ def interpolated?
+ @interpolated
+ end
+
private
+ def inputs_without_header?
+ args.any? && !config.has_header?
+ end
+
def header
- @entry ||= Ci::Config::Header::Root.new(config.header).tap do |header|
+ @entry ||= Header::Root.new(config.header).tap do |header|
header.key = 'header'
header.compose!
@@ -72,16 +77,15 @@ module Gitlab
end
def inputs
- @inputs ||= Ci::Input::Inputs.new(spec, args)
+ @inputs ||= Inputs.new(spec, args)
end
def context
- @context ||= Ci::Interpolation::Context.new({ inputs: inputs.to_hash })
+ @context ||= Context.new({ inputs: inputs.to_hash })
end
def template
- @template ||= ::Gitlab::Ci::Interpolation::Template
- .new(content, context)
+ @template ||= Template.new(content, context)
end
end
end
diff --git a/lib/gitlab/ci/config/interpolation/template.rb b/lib/gitlab/ci/config/interpolation/template.rb
new file mode 100644
index 00000000000..ece2a4756aa
--- /dev/null
+++ b/lib/gitlab/ci/config/interpolation/template.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module Interpolation
+ class Template
+ include Gitlab::Utils::StrongMemoize
+
+ attr_reader :blocks, :ctx
+
+ TooManyBlocksError = Class.new(StandardError)
+ InvalidBlockError = Class.new(StandardError)
+
+ MAX_BLOCKS = 10_000
+
+ def initialize(config, ctx)
+ @config = Interpolation::Config.fabricate(config)
+ @ctx = Interpolation::Context.fabricate(ctx)
+ @errors = []
+ @blocks = {}
+
+ interpolate! if valid?
+ end
+
+ def valid?
+ errors.none?
+ end
+
+ def errors
+ @errors + @config.errors + @ctx.errors + @blocks.values.flat_map(&:errors)
+ end
+
+ def size
+ @blocks.size
+ end
+
+ def interpolated
+ @result if valid?
+ end
+
+ private
+
+ def interpolate!
+ @result = @config.replace! do |data|
+ Interpolation::Block.match(data) do |block, data|
+ evaluate_block(block, data)
+ end
+ end
+ rescue TooManyBlocksError
+ @errors.push('too many interpolation blocks')
+ rescue InvalidBlockError
+ @errors.push('interpolation interrupted by errors')
+ end
+ strong_memoize_attr :interpolate!
+
+ def evaluate_block(block, data)
+ block = (@blocks[block] ||= Interpolation::Block.new(block, data, ctx))
+
+ raise TooManyBlocksError if @blocks.count > MAX_BLOCKS
+ raise InvalidBlockError unless block.valid?
+
+ block.value
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/normalizer.rb b/lib/gitlab/ci/config/normalizer.rb
index 22fcd84c968..a5b692b26d6 100644
--- a/lib/gitlab/ci/config/normalizer.rb
+++ b/lib/gitlab/ci/config/normalizer.rb
@@ -44,6 +44,10 @@ module Gitlab
job_need_name = job_need[:name].to_sym
if all_jobs = parallelized_jobs[job_need_name]
+ if job_need.key?(:parallel)
+ all_jobs = parallelize_job_config(job_need_name, job_need.delete(:parallel))
+ end
+
all_jobs.map { |job| job_need.merge(name: job.name) }
else
job_need
diff --git a/lib/gitlab/ci/config/yaml.rb b/lib/gitlab/ci/config/yaml.rb
index e3010ac3fdb..51221304430 100644
--- a/lib/gitlab/ci/config/yaml.rb
+++ b/lib/gitlab/ci/config/yaml.rb
@@ -7,8 +7,8 @@ module Gitlab
LoadError = Class.new(StandardError)
class << self
- def load!(content, current_user: nil)
- Loader.new(content, current_user: current_user).load.then do |result|
+ def load!(content)
+ Loader.new(content).load.then do |result|
raise result.error_class, result.error if !result.valid? && result.error_class.present?
raise LoadError, result.error unless result.valid?
diff --git a/lib/gitlab/ci/config/yaml/loader.rb b/lib/gitlab/ci/config/yaml/loader.rb
index fb24a2874e4..5d56061a8bb 100644
--- a/lib/gitlab/ci/config/yaml/loader.rb
+++ b/lib/gitlab/ci/config/yaml/loader.rb
@@ -10,9 +10,8 @@ module Gitlab
AVAILABLE_TAGS = [Config::Yaml::Tags::Reference].freeze
MAX_DOCUMENTS = 2
- def initialize(content, inputs: {}, current_user: nil)
+ def initialize(content, inputs: {})
@content = content
- @current_user = current_user
@inputs = inputs
end
@@ -21,21 +20,21 @@ module Gitlab
return yaml_result unless yaml_result.valid?
- interpolator = Yaml::Interpolator.new(yaml_result, inputs, current_user: current_user)
+ interpolator = Interpolation::Interpolator.new(yaml_result, inputs)
interpolator.interpolate!
if interpolator.valid?
# This Result contains only the interpolated config and does not have a header
- Yaml::Result.new(config: interpolator.to_hash, error: nil)
+ Yaml::Result.new(config: interpolator.to_hash, error: nil, interpolated: interpolator.interpolated?)
else
- Yaml::Result.new(error: interpolator.error_message)
+ Yaml::Result.new(error: interpolator.error_message, interpolated: interpolator.interpolated?)
end
end
private
- attr_reader :content, :current_user, :inputs
+ attr_reader :content, :inputs
def load_uninterpolated_yaml
Yaml::Result.new(config: load_yaml!, error: nil)
diff --git a/lib/gitlab/ci/config/yaml/result.rb b/lib/gitlab/ci/config/yaml/result.rb
index 6b20eeae203..a68cfde6653 100644
--- a/lib/gitlab/ci/config/yaml/result.rb
+++ b/lib/gitlab/ci/config/yaml/result.rb
@@ -7,16 +7,21 @@ module Gitlab
class Result
attr_reader :error, :error_class
- def initialize(config: nil, error: nil, error_class: nil)
+ def initialize(config: nil, error: nil, error_class: nil, interpolated: false)
@config = Array.wrap(config)
@error = error
@error_class = error_class
+ @interpolated = interpolated
end
def valid?
error.nil?
end
+ def interpolated?
+ !!@interpolated
+ end
+
def has_header?
return false unless @config.first.is_a?(Hash)
diff --git a/lib/gitlab/ci/input/arguments/base.rb b/lib/gitlab/ci/input/arguments/base.rb
deleted file mode 100644
index a46037c40ce..00000000000
--- a/lib/gitlab/ci/input/arguments/base.rb
+++ /dev/null
@@ -1,62 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Ci
- module Input
- module Arguments
- ##
- # Input::Arguments::Base is a common abstraction for input arguments:
- # - required
- # - optional
- # - with a default value
- #
- class Base
- attr_reader :key, :value, :spec, :errors
-
- ArgumentNotValidError = Class.new(StandardError)
-
- def initialize(key, spec, value)
- @key = key # hash key / argument name
- @value = value # user-provided value
- @spec = spec # configured specification
- @errors = []
-
- unless value.is_a?(String) || value.nil? # rubocop:disable Style/IfUnlessModifier
- @errors.push("unsupported value in input argument `#{key}`")
- end
-
- validate!
- end
-
- def valid?
- @errors.none?
- end
-
- def validate!
- raise NotImplementedError
- end
-
- def to_value
- raise NotImplementedError
- end
-
- def to_hash
- raise ArgumentNotValidError unless valid?
-
- @output ||= { key => to_value }
- end
-
- def self.matches?(spec)
- raise NotImplementedError
- end
-
- private
-
- def error(message)
- @errors.push("`#{@key}` input: #{message}")
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/ci/input/arguments/default.rb b/lib/gitlab/ci/input/arguments/default.rb
deleted file mode 100644
index c6762b04870..00000000000
--- a/lib/gitlab/ci/input/arguments/default.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Ci
- module Input
- module Arguments
- ##
- # Input::Arguments::Default class represents user-provided input argument that has a default value.
- #
- class Default < Input::Arguments::Base
- def validate!
- return error('argument specification invalid') unless spec.key?(:default)
-
- error('invalid default value') unless default.is_a?(String) || default.nil?
- end
-
- ##
- # User-provided value needs to be specified, but it may be an empty string:
- #
- # ```yaml
- # inputs:
- # env:
- # default: development
- #
- # with:
- # env: ""
- # ```
- #
- # The configuration above will result in `env` being an empty string.
- #
- def to_value
- value.nil? ? default : value
- end
-
- def default
- spec[:default]
- end
-
- def self.matches?(spec)
- return false unless spec.is_a?(Hash)
-
- spec.count == 1 && spec.each_key.first == :default
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/ci/input/arguments/options.rb b/lib/gitlab/ci/input/arguments/options.rb
deleted file mode 100644
index 855dab129be..00000000000
--- a/lib/gitlab/ci/input/arguments/options.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Ci
- module Input
- module Arguments
- ##
- # Input::Arguments::Options class represents user-provided input argument that is an enum, and is only valid
- # when the value provided is listed as an acceptable one.
- #
- class Options < Input::Arguments::Base
- ##
- # An empty value is valid if it is allowlisted:
- #
- # ```yaml
- # inputs:
- # run:
- # - ""
- # - tests
- #
- # with:
- # run: ""
- # ```
- #
- # The configuration above will return an empty value.
- #
- def validate!
- return error('argument specification invalid') unless options.is_a?(Array)
- return error('options argument empty') if options.empty?
-
- if !value.nil?
- error("argument value #{value} not allowlisted") unless options.include?(value)
- else
- error('argument not provided')
- end
- end
-
- def to_value
- value
- end
-
- def options
- spec[:options]
- end
-
- def self.matches?(spec)
- return false unless spec.is_a?(Hash)
-
- spec.count == 1 && spec.each_key.first == :options
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/ci/input/arguments/required.rb b/lib/gitlab/ci/input/arguments/required.rb
deleted file mode 100644
index 2e39f548731..00000000000
--- a/lib/gitlab/ci/input/arguments/required.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Ci
- module Input
- module Arguments
- ##
- # Input::Arguments::Required class represents user-provided required input argument.
- #
- class Required < Input::Arguments::Base
- ##
- # The value has to be defined, but it may be empty.
- #
- def validate!
- error('required value has not been provided') if value.nil?
- end
-
- def to_value
- value
- end
-
- ##
- # Required arguments do not have nested configuration. It has to be defined a null value.
- #
- # ```yaml
- # spec:
- # inputs:
- # website:
- # ```
- #
- # An empty string value, that has no specification is also considered as a "required" input, however we should
- # never see that being used, because it will be rejected by Ci::Config::Header validation.
- #
- # ```yaml
- # spec:
- # inputs:
- # website: ""
- # ```
- #
- # An empty hash value is also considered to be a required argument:
- #
- # ```yaml
- # spec:
- # inputs:
- # website: {}
- # ```
- #
- def self.matches?(spec)
- spec.blank?
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/ci/input/arguments/unknown.rb b/lib/gitlab/ci/input/arguments/unknown.rb
deleted file mode 100644
index 5873e6e66a6..00000000000
--- a/lib/gitlab/ci/input/arguments/unknown.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Ci
- module Input
- module Arguments
- ##
- # Input::Arguments::Unknown object gets fabricated when we can't match an input argument entry with any known
- # specification. It is matched as the last one, and always returns an error.
- #
- class Unknown < Input::Arguments::Base
- def validate!
- if spec.is_a?(Hash) && spec.count == 1
- error("unrecognized input argument specification: `#{spec.each_key.first}`")
- else
- error('unrecognized input argument definition')
- end
- end
-
- def to_value
- raise ArgumentError, 'unknown argument value'
- end
-
- def self.matches?(*)
- true
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/ci/input/inputs.rb b/lib/gitlab/ci/input/inputs.rb
deleted file mode 100644
index 1b544e63e7d..00000000000
--- a/lib/gitlab/ci/input/inputs.rb
+++ /dev/null
@@ -1,73 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Ci
- module Input
- ##
- # Inputs::Input class represents user-provided inputs, configured using `with:` keyword.
- #
- # Input arguments are only valid with an associated component's inputs specification from component's header.
- #
- class Inputs
- UnknownSpecArgumentError = Class.new(StandardError)
-
- ARGUMENTS = [
- Input::Arguments::Required, # Input argument is required
- Input::Arguments::Default, # Input argument has a default value
- Input::Arguments::Options, # Input argument that needs to be allowlisted
- Input::Arguments::Unknown # Input argument has not been recognized
- ].freeze
-
- def initialize(spec, args)
- @spec = spec.to_h
- @args = args.to_h
- @inputs = []
- @errors = []
-
- validate!
- fabricate!
- end
-
- def errors
- @errors + @inputs.flat_map(&:errors)
- end
-
- def valid?
- errors.none?
- end
-
- def unknown
- @args.keys - @spec.keys
- end
-
- def count
- @inputs.count
- end
-
- def to_hash
- @inputs.inject({}) do |hash, argument|
- raise ArgumentError unless argument.valid?
-
- hash.merge(argument.to_hash)
- end
- end
-
- private
-
- def validate!
- @errors.push("unknown input arguments: #{unknown.inspect}") if unknown.any?
- end
-
- def fabricate!
- @spec.each do |key, spec|
- argument = ARGUMENTS.find { |klass| klass.matches?(spec) }
-
- raise UnknownSpecArgumentError if argument.nil?
-
- @inputs.push(argument.new(key, spec, @args[key]))
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/ci/interpolation/access.rb b/lib/gitlab/ci/interpolation/access.rb
deleted file mode 100644
index f9bbd3e118d..00000000000
--- a/lib/gitlab/ci/interpolation/access.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Ci
- module Interpolation
- class Access
- attr_reader :content, :errors
-
- MAX_ACCESS_OBJECTS = 5
- MAX_ACCESS_BYTESIZE = 1024
-
- def initialize(access, ctx)
- @content = access
- @ctx = ctx
- @errors = []
-
- if objects.count <= 1 # rubocop:disable Style/IfUnlessModifier
- @errors.push('invalid interpolation access pattern')
- end
-
- if access.bytesize > MAX_ACCESS_BYTESIZE # rubocop:disable Style/IfUnlessModifier
- @errors.push('maximum interpolation expression size exceeded')
- end
-
- evaluate! if valid?
- end
-
- def valid?
- errors.none?
- end
-
- def objects
- @objects ||= @content.split('.', MAX_ACCESS_OBJECTS)
- end
-
- def value
- raise ArgumentError, 'access path invalid' unless valid?
-
- @value
- end
-
- private
-
- def evaluate!
- raise ArgumentError, 'access path invalid' unless valid?
-
- @value ||= objects.inject(@ctx) do |memo, value|
- key = value.to_sym
-
- break @errors.push("unknown interpolation key: `#{key}`") unless memo.key?(key)
-
- memo.fetch(key)
- end
- rescue KeyError => e
- @errors.push(e)
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/ci/interpolation/block.rb b/lib/gitlab/ci/interpolation/block.rb
deleted file mode 100644
index 389cbf378a2..00000000000
--- a/lib/gitlab/ci/interpolation/block.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Ci
- module Interpolation
- class Block
- PREFIX = '$[['
- PATTERN = /(?<block>\$\[\[\s*(?<access>.*?)\s*\]\])/.freeze
-
- attr_reader :block, :data, :ctx
-
- def initialize(block, data, ctx)
- @block = block
- @ctx = ctx
- @data = data
-
- @access = Interpolation::Access.new(@data, ctx)
- end
-
- def valid?
- errors.none?
- end
-
- def errors
- @access.errors
- end
-
- def content
- @access.content
- end
-
- def value
- raise ArgumentError, 'block invalid' unless valid?
-
- @access.value
- end
-
- def self.match(data)
- return data unless data.is_a?(String) && data.include?(PREFIX)
-
- data.gsub(PATTERN) do
- yield ::Regexp.last_match(1), ::Regexp.last_match(2)
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/ci/interpolation/config.rb b/lib/gitlab/ci/interpolation/config.rb
deleted file mode 100644
index 32f58521139..00000000000
--- a/lib/gitlab/ci/interpolation/config.rb
+++ /dev/null
@@ -1,124 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Ci
- module Interpolation
- ##
- # Interpolation::Config represents a configuration artifact that we want to perform interpolation on.
- #
- class Config
- include Gitlab::Utils::StrongMemoize
- ##
- # Total number of hash nodes traversed. For example, loading a YAML below would result in a hash having 12 nodes
- # instead of 9, because hash values are being counted before we recursively traverse them.
- #
- # test:
- # spec:
- # env: $[[ inputs.env ]]
- #
- # $[[ inputs.key ]]:
- # name: $[[ inputs.key ]]
- # script: my-value
- #
- # According to our benchmarks performed when developing this code, the worst-case scenario of processing
- # a hash with 500_000 nodes takes around 1 second and consumes around 225 megabytes of memory.
- #
- # The typical scenario, using just a few interpolations takes 250ms and consumes around 20 megabytes of memory.
- #
- # Given the above the 500_000 nodes should be an upper limit, provided that the are additional safeguard
- # present in other parts of the code (example: maximum number of interpolation blocks found). Typical size of a
- # YAML configuration with 500k nodes might be around 10 megabytes, which is an order of magnitude higher than
- # the 1MB limit for loading YAML on GitLab.com
- #
- MAX_NODES = 500_000
- MAX_NODE_SIZE = 1024 * 1024 # 1MB
-
- TooManyNodesError = Class.new(StandardError)
- NodeTooLargeError = Class.new(StandardError)
-
- Visitor = Class.new do
- def initialize
- @visited = 0
- end
-
- def visit!
- @visited += 1
-
- raise Config::TooManyNodesError if @visited > Config::MAX_NODES
- end
- end
-
- attr_reader :errors
-
- def initialize(hash)
- @config = hash
- @errors = []
- end
-
- def to_h
- @config
- end
-
- ##
- # The replace! method will yield a block and replace a each of the hash config nodes with a return value of the
- # block.
- #
- # It returns `nil` if there were errors found during the process.
- #
- def replace!(&block)
- recursive_replace(@config, Visitor.new, &block)
- rescue TooManyNodesError
- @errors.push('config too large')
- nil
- rescue NodeTooLargeError
- @errors.push('config node too large')
- nil
- end
- strong_memoize_attr :replace!
-
- def self.fabricate(config)
- case config
- when Hash
- new(config)
- when Interpolation::Config
- config
- else
- raise ArgumentError, 'unknown interpolation config'
- end
- end
-
- private
-
- def recursive_replace(config, visitor, &block)
- visitor.visit!
-
- case config
- when Hash
- {}.tap do |new_hash|
- config.each_pair do |key, value|
- new_key = recursive_replace(key, visitor, &block)
- new_value = recursive_replace(value, visitor, &block)
-
- if new_key != key
- new_hash[new_key] = new_value
- else
- new_hash[key] = new_value
- end
- end
- end
- when Array
- config.map { |value| recursive_replace(value, visitor, &block) }
- when Symbol
- recursive_replace(config.to_s, visitor, &block)
- when String
- raise NodeTooLargeError if config.bytesize > MAX_NODE_SIZE
-
- yield config
- else
- config
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/ci/interpolation/context.rb b/lib/gitlab/ci/interpolation/context.rb
deleted file mode 100644
index 69c1fbb792c..00000000000
--- a/lib/gitlab/ci/interpolation/context.rb
+++ /dev/null
@@ -1,76 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Ci
- module Interpolation
- ##
- # Interpolation::Context is a class that represents the data that can be used when performing string interpolation
- # on a CI configuration.
- #
- class Context
- ContextTooComplexError = Class.new(StandardError)
- NotSymbolizedContextError = Class.new(StandardError)
-
- MAX_DEPTH = 3
-
- def initialize(hash)
- @context = hash
-
- raise ContextTooComplexError if depth > MAX_DEPTH
- end
-
- def valid?
- errors.none?
- end
-
- ##
- # This method is here because `Context` will be responsible for validating specs, inputs and defaults.
- #
- def errors
- []
- end
-
- def depth
- deep_depth(@context)
- end
-
- def fetch(field)
- @context.fetch(field)
- end
-
- def key?(name)
- @context.key?(name)
- end
-
- def to_h
- @context.to_h
- end
-
- private
-
- def deep_depth(context, depth = 0)
- values = context.values.map do |value|
- if value.is_a?(Hash)
- deep_depth(value, depth + 1)
- else
- depth + 1
- end
- end
-
- values.max.to_i
- end
-
- def self.fabricate(context)
- case context
- when Hash
- new(context)
- when Interpolation::Context
- context
- else
- raise ArgumentError, 'unknown interpolation context'
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/ci/interpolation/template.rb b/lib/gitlab/ci/interpolation/template.rb
deleted file mode 100644
index 0211279f266..00000000000
--- a/lib/gitlab/ci/interpolation/template.rb
+++ /dev/null
@@ -1,67 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Ci
- module Interpolation
- class Template
- include Gitlab::Utils::StrongMemoize
-
- attr_reader :blocks, :ctx
-
- TooManyBlocksError = Class.new(StandardError)
- InvalidBlockError = Class.new(StandardError)
-
- MAX_BLOCKS = 10_000
-
- def initialize(config, ctx)
- @config = Interpolation::Config.fabricate(config)
- @ctx = Interpolation::Context.fabricate(ctx)
- @errors = []
- @blocks = {}
-
- interpolate! if valid?
- end
-
- def valid?
- errors.none?
- end
-
- def errors
- @errors + @config.errors + @ctx.errors + @blocks.values.flat_map(&:errors)
- end
-
- def size
- @blocks.size
- end
-
- def interpolated
- @result if valid?
- end
-
- private
-
- def interpolate!
- @result = @config.replace! do |data|
- Interpolation::Block.match(data) do |block, data|
- evaluate_block(block, data)
- end
- end
- rescue TooManyBlocksError
- @errors.push('too many interpolation blocks')
- rescue InvalidBlockError
- @errors.push('interpolation interrupted by errors')
- end
- strong_memoize_attr :interpolate!
-
- def evaluate_block(block, data)
- block = (@blocks[block] ||= Interpolation::Block.new(block, data, ctx))
-
- raise TooManyBlocksError if @blocks.count > MAX_BLOCKS
- raise InvalidBlockError unless block.valid?
-
- block.value
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/ci/jwt_v2.rb b/lib/gitlab/ci/jwt_v2.rb
index 6ce662bdead..8c730a9548f 100644
--- a/lib/gitlab/ci/jwt_v2.rb
+++ b/lib/gitlab/ci/jwt_v2.rb
@@ -44,26 +44,14 @@ module Gitlab
end
def custom_claims
- additional_claims = {
+ mapper = ClaimMapper.new(project_config, pipeline)
+
+ super.merge({
runner_id: runner&.id,
runner_environment: runner_environment,
- sha: pipeline.sha
- }
-
- if project_config&.source == :repository_source
- additional_claims[:ci_config_ref_uri] = ci_config_ref_uri
- additional_claims[:ci_config_sha] = pipeline.sha
- end
-
- super.merge(additional_claims)
- end
-
- def ci_config_ref_uri
- "#{project_config&.url}@#{pipeline.source_ref_path}"
- rescue StandardError => e
- # We don't want endpoints relying on this code to fail if there's an error here.
- Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e, pipeline_id: pipeline.id)
- nil
+ sha: pipeline.sha,
+ project_visibility: Gitlab::VisibilityLevel.string_level(project.visibility_level)
+ }).merge(mapper.to_h)
end
def project_config
diff --git a/lib/gitlab/ci/jwt_v2/claim_mapper.rb b/lib/gitlab/ci/jwt_v2/claim_mapper.rb
new file mode 100644
index 00000000000..8e7f024d2d6
--- /dev/null
+++ b/lib/gitlab/ci/jwt_v2/claim_mapper.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class JwtV2
+ class ClaimMapper
+ MAPPER_FOR_CONFIG_SOURCE = {
+ repository_source: ClaimMapper::Repository
+ }.freeze
+
+ def initialize(project_config, pipeline)
+ return unless project_config
+
+ mapper_class = MAPPER_FOR_CONFIG_SOURCE[project_config.source]
+ @mapper = mapper_class&.new(project_config, pipeline)
+ end
+
+ def to_h
+ mapper&.to_h || {}
+ end
+
+ private
+
+ attr_reader :mapper
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/jwt_v2/claim_mapper/repository.rb b/lib/gitlab/ci/jwt_v2/claim_mapper/repository.rb
new file mode 100644
index 00000000000..1949d44b8b5
--- /dev/null
+++ b/lib/gitlab/ci/jwt_v2/claim_mapper/repository.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class JwtV2
+ class ClaimMapper
+ class Repository
+ def initialize(project_config, pipeline)
+ @project_config = project_config
+ @pipeline = pipeline
+ end
+
+ def to_h
+ {
+ ci_config_ref_uri: ci_config_ref_uri,
+ ci_config_sha: pipeline.sha
+ }
+ end
+
+ private
+
+ attr_reader :project_config, :pipeline
+
+ def ci_config_ref_uri
+ "#{project_config.url}@#{pipeline.source_ref_path}"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/pipeline/chain/ensure_environments.rb b/lib/gitlab/ci/pipeline/chain/ensure_environments.rb
index ebea6a538ef..3ff9aff68ac 100644
--- a/lib/gitlab/ci/pipeline/chain/ensure_environments.rb
+++ b/lib/gitlab/ci/pipeline/chain/ensure_environments.rb
@@ -16,7 +16,7 @@ module Gitlab
private
def ensure_environment(build)
- ::Environments::CreateForBuildService.new.execute(build)
+ ::Environments::CreateForJobService.new.execute(build)
end
end
end
diff --git a/lib/gitlab/ci/project_config/remote.rb b/lib/gitlab/ci/project_config/remote.rb
index 19cbf8e9c1e..23ed510e378 100644
--- a/lib/gitlab/ci/project_config/remote.rb
+++ b/lib/gitlab/ci/project_config/remote.rb
@@ -6,7 +6,7 @@ module Gitlab
class Remote < Source
def content
strong_memoize(:content) do
- next unless ci_config_path =~ URI::DEFAULT_PARSER.make_regexp(%w[http https])
+ next unless URI::DEFAULT_PARSER.make_regexp(%w[http https]).match?(ci_config_path)
YAML.dump('include' => [{ 'remote' => ci_config_path }])
end
diff --git a/lib/gitlab/ci/project_config/repository.rb b/lib/gitlab/ci/project_config/repository.rb
index a08cf27b74c..93e13f57943 100644
--- a/lib/gitlab/ci/project_config/repository.rb
+++ b/lib/gitlab/ci/project_config/repository.rb
@@ -33,7 +33,7 @@ module Gitlab
return unless project
return unless sha
- project.repository.gitlab_ci_yml_for(sha, ci_config_path).present?
+ project.repository.blob_at(sha, ci_config_path).present?
rescue GRPC::NotFound, GRPC::Internal
nil
end
diff --git a/lib/gitlab/ci/queue/metrics.rb b/lib/gitlab/ci/queue/metrics.rb
index 5cee73238ca..a18542288c9 100644
--- a/lib/gitlab/ci/queue/metrics.rb
+++ b/lib/gitlab/ci/queue/metrics.rb
@@ -74,7 +74,7 @@ module Gitlab
end
def observe_queue_depth(queue, size)
- return unless Feature.enabled?(:gitlab_ci_builds_queuing_metrics)
+ return unless Feature.enabled?(:gitlab_ci_builds_queuing_metrics, type: :ops)
if !Rails.env.production? && !QUEUE_DEPTH_HISTOGRAMS.include?(queue)
raise ArgumentError, "unknown queue depth label: #{queue}"
@@ -84,7 +84,7 @@ module Gitlab
end
def observe_queue_size(size_proc, runner_type)
- return unless Feature.enabled?(:gitlab_ci_builds_queuing_metrics)
+ return unless Feature.enabled?(:gitlab_ci_builds_queuing_metrics, type: :ops)
size = size_proc.call.to_f
self.class.queue_size_total.observe({ runner_type: runner_type }, size)
@@ -96,7 +96,7 @@ module Gitlab
result = yield
- return result unless Feature.enabled?(:gitlab_ci_builds_queuing_metrics)
+ return result unless Feature.enabled?(:gitlab_ci_builds_queuing_metrics, type: :ops)
seconds = ::Gitlab::Metrics::System.monotonic_time - start_time
@@ -121,7 +121,7 @@ module Gitlab
end
def self.observe_active_runners(runners_proc)
- return unless Feature.enabled?(:gitlab_ci_builds_queuing_metrics)
+ return unless Feature.enabled?(:gitlab_ci_builds_queuing_metrics, type: :ops)
queue_active_runners_total.observe({}, runners_proc.call.to_f)
end
@@ -133,7 +133,7 @@ module Gitlab
def self.failed_attempt_counter
strong_memoize(:failed_attempt_counter) do
name = :job_register_attempts_failed_total
- comment = 'Counts the times a runner tries to register a job'
+ comment = 'Counts the times a runner fails to register a job'
Gitlab::Metrics.counter(name, comment)
end
diff --git a/lib/gitlab/ci/reports/sbom/component.rb b/lib/gitlab/ci/reports/sbom/component.rb
index 08307580987..51fd6af7bc4 100644
--- a/lib/gitlab/ci/reports/sbom/component.rb
+++ b/lib/gitlab/ci/reports/sbom/component.rb
@@ -5,23 +5,36 @@ module Gitlab
module Reports
module Sbom
class Component
- attr_reader :component_type, :name, :version
+ include Gitlab::Utils::StrongMemoize
+
+ attr_reader :component_type, :version
def initialize(type:, name:, purl:, version:)
@component_type = type
@name = name
- @purl = purl
+ @raw_purl = purl
@version = version
end
+ def <=>(other)
+ sort_by_attributes(self) <=> sort_by_attributes(other)
+ end
+
def ingestible?
supported_component_type? && supported_purl_type?
end
def purl
- return unless @purl
+ return unless @raw_purl
+
+ ::Sbom::PackageUrl.parse(@raw_purl)
+ end
+ strong_memoize_attr :purl
+
+ def name
+ return @name unless purl
- ::Sbom::PackageUrl.parse(@purl)
+ [purl.namespace, purl.name].compact.join('/')
end
private
@@ -37,6 +50,23 @@ module Gitlab
# however, if the purl type is provided, it _must be valid_
::Enums::Sbom.purl_types.include?(purl.type.to_sym)
end
+
+ def sort_by_attributes(component)
+ [
+ component.name,
+ purl_type_int(component),
+ component_type_int(component),
+ component.version.to_s
+ ]
+ end
+
+ def component_type_int(component)
+ ::Enums::Sbom::COMPONENT_TYPES.fetch(component.component_type.to_sym)
+ end
+
+ def purl_type_int(component)
+ ::Enums::Sbom::PURL_TYPES.fetch(component.purl&.type&.to_sym, 0)
+ end
end
end
end
diff --git a/lib/gitlab/ci/status/bridge/factory.rb b/lib/gitlab/ci/status/bridge/factory.rb
index fbe2e2282a0..20773237547 100644
--- a/lib/gitlab/ci/status/bridge/factory.rb
+++ b/lib/gitlab/ci/status/bridge/factory.rb
@@ -9,6 +9,7 @@ module Gitlab
[[Status::Bridge::Retryable],
[Status::Bridge::Failed],
[Status::Bridge::Manual],
+ [Status::Bridge::WaitingForApproval],
[Status::Bridge::WaitingForResource],
[Status::Bridge::Play],
[Status::Bridge::Action],
diff --git a/lib/gitlab/ci/status/bridge/waiting_for_approval.rb b/lib/gitlab/ci/status/bridge/waiting_for_approval.rb
new file mode 100644
index 00000000000..2d3165f41ed
--- /dev/null
+++ b/lib/gitlab/ci/status/bridge/waiting_for_approval.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Status
+ module Bridge
+ class WaitingForApproval < Status::Extended
+ ## Extended in EE
+ def self.matches?(_bridge, _user)
+ false
+ end
+ end
+ end
+ end
+ end
+end
+
+Gitlab::Ci::Status::Bridge::WaitingForApproval.prepend_mod_with('Gitlab::Ci::Status::Bridge::WaitingForApproval')
diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
index 4f12f0cd3b8..2a07530c00d 100644
--- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
@@ -16,7 +16,6 @@
# Test jobs may be disabled by setting environment variables:
# * test: TEST_DISABLED
# * code_quality: CODE_QUALITY_DISABLED
-# * license_management: LICENSE_MANAGEMENT_DISABLED
# * browser_performance: BROWSER_PERFORMANCE_DISABLED
# * load_performance: LOAD_PERFORMANCE_DISABLED
# * sast: SAST_DISABLED
@@ -178,7 +177,6 @@ include:
- template: Security/DAST.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml
- template: Jobs/Container-Scanning.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Container-Scanning.gitlab-ci.yml
- template: Jobs/Dependency-Scanning.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Dependency-Scanning.gitlab-ci.yml
- - template: Jobs/License-Scanning.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/License-Scanning.gitlab-ci.yml
- template: Jobs/SAST.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/SAST.gitlab-ci.yml
- template: Jobs/Secret-Detection.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Secret-Detection.gitlab-ci.yml
diff --git a/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml
index 46d0b92b243..c1aedbe1111 100644
--- a/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml
@@ -1,5 +1,5 @@
variables:
- AUTO_BUILD_IMAGE_VERSION: 'v1.37.0'
+ AUTO_BUILD_IMAGE_VERSION: 'v1.38.1'
build:
stage: build
diff --git a/lib/gitlab/ci/templates/Jobs/Build.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Build.latest.gitlab-ci.yml
index 46d0b92b243..c1aedbe1111 100644
--- a/lib/gitlab/ci/templates/Jobs/Build.latest.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Build.latest.gitlab-ci.yml
@@ -1,5 +1,5 @@
variables:
- AUTO_BUILD_IMAGE_VERSION: 'v1.37.0'
+ AUTO_BUILD_IMAGE_VERSION: 'v1.38.1'
build:
stage: build
diff --git a/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml
index 7c9aa82b1ae..f9440bfe904 100644
--- a/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml
@@ -10,6 +10,7 @@ code_quality:
DOCKER_TLS_CERTDIR: ""
CODE_QUALITY_IMAGE_TAG: "0.96.0"
CODE_QUALITY_IMAGE: "$CI_TEMPLATE_REGISTRY_HOST/gitlab-org/ci-cd/codequality:$CODE_QUALITY_IMAGE_TAG"
+ DOCKER_SOCKET_PATH: /var/run/docker.sock
needs: []
script:
- export SOURCE_CODE=$PWD
@@ -46,9 +47,10 @@ code_quality:
CODECLIMATE_PREFIX \
CODECLIMATE_REGISTRY_USERNAME \
CODECLIMATE_REGISTRY_PASSWORD \
+ DOCKER_SOCKET_PATH \
) \
--volume "$PWD":/code \
- --volume /var/run/docker.sock:/var/run/docker.sock \
+ --volume "$DOCKER_SOCKET_PATH":/var/run/docker.sock \
"$CODE_QUALITY_IMAGE" /code
artifacts:
reports:
diff --git a/lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml
index b1e498a9d09..7b2fb49b65e 100644
--- a/lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml
@@ -1,5 +1,5 @@
variables:
- DAST_AUTO_DEPLOY_IMAGE_VERSION: 'v2.51.0'
+ DAST_AUTO_DEPLOY_IMAGE_VERSION: 'v2.53.0'
.dast-auto-deploy:
image: "${CI_TEMPLATE_REGISTRY_HOST}/gitlab-org/cluster-integration/auto-deploy-image:${DAST_AUTO_DEPLOY_IMAGE_VERSION}"
diff --git a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
index 5a7e69b62d9..1e482ccca82 100644
--- a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
@@ -1,5 +1,5 @@
variables:
- AUTO_DEPLOY_IMAGE_VERSION: 'v2.51.0'
+ AUTO_DEPLOY_IMAGE_VERSION: 'v2.53.0'
.auto-deploy:
image: "${CI_TEMPLATE_REGISTRY_HOST}/gitlab-org/cluster-integration/auto-deploy-image:${AUTO_DEPLOY_IMAGE_VERSION}"
diff --git a/lib/gitlab/ci/templates/Jobs/Deploy.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Deploy.latest.gitlab-ci.yml
index dac559db8d5..6eac691b293 100644
--- a/lib/gitlab/ci/templates/Jobs/Deploy.latest.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Deploy.latest.gitlab-ci.yml
@@ -1,5 +1,5 @@
variables:
- AUTO_DEPLOY_IMAGE_VERSION: 'v2.51.0'
+ AUTO_DEPLOY_IMAGE_VERSION: 'v2.53.0'
.auto-deploy:
image: "${CI_TEMPLATE_REGISTRY_HOST}/gitlab-org/cluster-integration/auto-deploy-image:${AUTO_DEPLOY_IMAGE_VERSION}"
diff --git a/lib/gitlab/ci/templates/Jobs/License-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/License-Scanning.gitlab-ci.yml
index b1c81e9ed5b..58846d31e2f 100644
--- a/lib/gitlab/ci/templates/Jobs/License-Scanning.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/License-Scanning.gitlab-ci.yml
@@ -14,7 +14,7 @@ variables:
SECURE_ANALYZERS_PREFIX: "$CI_TEMPLATE_REGISTRY_HOST/security-products"
LICENSE_MANAGEMENT_SETUP_CMD: '' # If needed, specify a command to setup your environment with a custom package manager.
- LICENSE_MANAGEMENT_VERSION: 4
+ LICENSE_MANAGEMENT_VERSION: 'removed'
license_scanning:
stage: test
diff --git a/lib/gitlab/ci/templates/Security/BAS.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/BAS.latest.gitlab-ci.yml
index b626a7ca770..8b3f4a619d0 100644
--- a/lib/gitlab/ci/templates/Security/BAS.latest.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/BAS.latest.gitlab-ci.yml
@@ -18,11 +18,8 @@
#
# Read more about this feature here: https://docs.gitlab.com/ee/user/application_security/breach_and_attack_simulation/index.html#extend-dynamic-application-security-testing-dast
-# Include the DAST.latest template if $DAST_VERSION is null because this means a DAST template has not been included already.
include:
- template: Security/DAST.latest.gitlab-ci.yml
- rules:
- - if: $DAST_VERSION == null
variables:
BAS_CALLBACK_IMAGE_TAG: "latest"
diff --git a/lib/gitlab/ci/variables/builder/pipeline.rb b/lib/gitlab/ci/variables/builder/pipeline.rb
index c3b0cb856ba..3e66290ecde 100644
--- a/lib/gitlab/ci/variables/builder/pipeline.rb
+++ b/lib/gitlab/ci/variables/builder/pipeline.rb
@@ -16,6 +16,7 @@ module Gitlab
variables.append(key: 'CI_PIPELINE_IID', value: pipeline.iid.to_s)
variables.append(key: 'CI_PIPELINE_SOURCE', value: pipeline.source.to_s)
variables.append(key: 'CI_PIPELINE_CREATED_AT', value: pipeline.created_at&.iso8601)
+ variables.append(key: 'CI_PIPELINE_NAME', value: pipeline.name)
variables.concat(predefined_commit_variables) if pipeline.sha.present?
variables.concat(predefined_commit_tag_variables) if pipeline.tag?
diff --git a/lib/gitlab/ci/variables/downstream/expandable_variable_generator.rb b/lib/gitlab/ci/variables/downstream/expandable_variable_generator.rb
index 6690e9f1c1f..bb7a6e7ab59 100644
--- a/lib/gitlab/ci/variables/downstream/expandable_variable_generator.rb
+++ b/lib/gitlab/ci/variables/downstream/expandable_variable_generator.rb
@@ -6,9 +6,40 @@ module Gitlab
module Downstream
class ExpandableVariableGenerator < Base
def for(item)
- expanded_value = ::ExpandVariables.expand(item.value, context.all_bridge_variables)
+ expanded_var = expanded_var_for(item)
+ file_vars = file_var_dependencies_for(item)
- [{ key: item.key, value: expanded_value }]
+ [expanded_var].concat(file_vars)
+ end
+
+ private
+
+ def expanded_var_for(item)
+ {
+ key: item.key,
+ value: ::ExpandVariables.expand(
+ item.value,
+ context.all_bridge_variables,
+ expand_file_refs: context.expand_file_refs
+ )
+ }
+ end
+
+ def file_var_dependencies_for(item)
+ return [] if context.expand_file_refs
+ return [] unless item.depends_on
+
+ item.depends_on.filter_map do |dependency|
+ dependency_variable = context.all_bridge_variables[dependency]
+
+ if dependency_variable&.file?
+ {
+ key: dependency_variable.key,
+ value: dependency_variable.value,
+ variable_type: :file
+ }
+ end
+ end
end
end
end
diff --git a/lib/gitlab/ci/variables/downstream/generator.rb b/lib/gitlab/ci/variables/downstream/generator.rb
index 93c995cc918..350d29958cf 100644
--- a/lib/gitlab/ci/variables/downstream/generator.rb
+++ b/lib/gitlab/ci/variables/downstream/generator.rb
@@ -7,12 +7,12 @@ module Gitlab
class Generator
include Gitlab::Utils::StrongMemoize
- Context = Struct.new(:all_bridge_variables, keyword_init: true)
+ Context = Struct.new(:all_bridge_variables, :expand_file_refs, keyword_init: true)
def initialize(bridge)
@bridge = bridge
- context = Context.new(all_bridge_variables: bridge.variables)
+ context = Context.new(all_bridge_variables: bridge.variables, expand_file_refs: bridge.expand_file_refs?)
@raw_variable_generator = RawVariableGenerator.new(context)
@expandable_variable_generator = ExpandableVariableGenerator.new(context)
diff --git a/lib/gitlab/config/entry/validators.rb b/lib/gitlab/config/entry/validators.rb
index 28cfb6d8fee..87b7cab3f6d 100644
--- a/lib/gitlab/config/entry/validators.rb
+++ b/lib/gitlab/config/entry/validators.rb
@@ -40,6 +40,17 @@ module Gitlab
end
end
+ class OnlyOneOfKeysValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ present_keys = value.try(:keys).to_a
+
+ unless options[:in].one? { |key| present_keys.include?(key) }
+ record.errors.add(attribute, "must use exactly one of these keys: " +
+ options[:in].join(', '))
+ end
+ end
+ end
+
class MutuallyExclusiveKeysValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
mutually_exclusive_keys = value.try(:keys).to_a & options[:in]
diff --git a/lib/gitlab/content_security_policy/config_loader.rb b/lib/gitlab/content_security_policy/config_loader.rb
index 669c447c09b..9fb3c7d362f 100644
--- a/lib/gitlab/content_security_policy/config_loader.rb
+++ b/lib/gitlab/content_security_policy/config_loader.rb
@@ -3,186 +3,228 @@
module Gitlab
module ContentSecurityPolicy
class ConfigLoader
- DIRECTIVES = %w(base_uri child_src connect_src default_src font_src
- form_action frame_ancestors frame_src img_src manifest_src
- media_src object_src report_uri script_src style_src worker_src).freeze
-
+ DIRECTIVES = %w[
+ base_uri child_src connect_src default_src font_src form_action
+ frame_ancestors frame_src img_src manifest_src media_src object_src
+ report_uri script_src style_src worker_src
+ ].freeze
DEFAULT_FALLBACK_VALUE = '<default_value>'
+ HTTP_PORTS = [80, 443].freeze
- def self.default_enabled
- Rails.env.development? || Rails.env.test?
- end
+ class << self
+ def default_enabled
+ Rails.env.development? || Rails.env.test?
+ end
- def self.default_directives
- directives = {
- 'default_src' => "'self'",
- 'base_uri' => "'self'",
- 'connect_src' => ContentSecurityPolicy::Directives.connect_src,
- 'font_src' => "'self'",
- 'form_action' => "'self' https: http:",
- 'frame_ancestors' => "'self'",
- 'frame_src' => ContentSecurityPolicy::Directives.frame_src,
- 'img_src' => "'self' data: blob: http: https:",
- 'manifest_src' => "'self'",
- 'media_src' => "'self' data: blob: http: https:",
- 'script_src' => ContentSecurityPolicy::Directives.script_src,
- 'style_src' => ContentSecurityPolicy::Directives.style_src,
- 'worker_src' => "#{Gitlab::Utils.append_path(Gitlab.config.gitlab.url, 'assets/')} blob: data:",
- 'object_src' => "'none'",
- 'report_uri' => nil
- }
+ def default_directives
+ directives = default_directives_defaults
+
+ allow_development_tooling(directives)
+ allow_websocket_connections(directives)
+ allow_lfs(directives)
+ allow_cdn(directives)
+ allow_zuora(directives)
+ allow_sentry(directives)
+ allow_framed_gitlab_paths(directives)
+ allow_customersdot(directives)
+ allow_review_apps(directives)
+ csp_level_3_backport(directives)
+
+ directives
+ end
+
+ def default_directives_defaults
+ {
+ 'default_src' => "'self'",
+ 'base_uri' => "'self'",
+ 'connect_src' => ContentSecurityPolicy::Directives.connect_src,
+ 'font_src' => "'self'",
+ 'form_action' => "'self' https: http:",
+ 'frame_ancestors' => "'self'",
+ 'frame_src' => ContentSecurityPolicy::Directives.frame_src,
+ 'img_src' => "'self' data: blob: http: https:",
+ 'manifest_src' => "'self'",
+ 'media_src' => "'self' data: blob: http: https:",
+ 'script_src' => ContentSecurityPolicy::Directives.script_src,
+ 'style_src' => ContentSecurityPolicy::Directives.style_src,
+ 'worker_src' => "#{Gitlab::Utils.append_path(Gitlab.config.gitlab.url, 'assets/')} blob: data:",
+ 'object_src' => "'none'",
+ 'report_uri' => nil
+ }
+ end
# connect_src with 'self' includes https/wss variations of the origin,
# however, safari hasn't covered this yet and we need to explicitly add
# support for websocket origins until Safari catches up with the specs
- if Rails.env.development?
+ def allow_development_tooling(directives)
+ return unless Rails.env.development?
+
allow_webpack_dev_server(directives)
allow_letter_opener(directives)
allow_snowplow_micro(directives) if Gitlab::Tracking.snowplow_micro_enabled?
end
- allow_websocket_connections(directives)
- allow_cdn(directives, Settings.gitlab.cdn_host) if Settings.gitlab.cdn_host.present?
- allow_zuora(directives) if Gitlab.com?
- # Support for Sentry setup via configuration files will be removed in 16.0
- # in favor of Gitlab::CurrentSettings.
- allow_legacy_sentry(directives) if Gitlab.config.sentry&.enabled && Gitlab.config.sentry&.clientside_dsn
- allow_sentry(directives) if Gitlab::CurrentSettings.try(:sentry_enabled) && Gitlab::CurrentSettings.try(:sentry_clientside_dsn)
- allow_framed_gitlab_paths(directives)
- allow_customersdot(directives) if ENV['CUSTOMER_PORTAL_URL'].present?
- allow_review_apps(directives) if ENV['REVIEW_APPS_ENABLED']
-
- # The follow section contains workarounds to patch Safari's lack of support for CSP Level 3
- # See https://gitlab.com/gitlab-org/gitlab/-/issues/343579
- # frame-src was deprecated in CSP level 2 in favor of child-src
- # CSP level 3 "undeprecated" frame-src and browsers fall back on child-src if it's missing
- # However Safari seems to read child-src first so we'll just keep both equal
- append_to_directive(directives, 'child_src', directives['frame_src'])
-
- # Safari also doesn't support worker-src and only checks child-src
- # So for compatibility until it catches up to other browsers we need to
- # append worker-src's content to child-src
- append_to_directive(directives, 'child_src', directives['worker_src'])
-
- directives
- end
+ def allow_webpack_dev_server(directives)
+ secure = Settings.webpack.dev_server['https']
+ host_and_port = "#{Settings.webpack.dev_server['host']}:#{Settings.webpack.dev_server['port']}"
+ http_url = "#{secure ? 'https' : 'http'}://#{host_and_port}"
+ ws_url = "#{secure ? 'wss' : 'ws'}://#{host_and_port}"
- def initialize(csp_directives)
- # Using <default_value> falls back to the default values.
- directives = csp_directives.reject { |_, value| value == DEFAULT_FALLBACK_VALUE }
- @merged_csp_directives =
- HashWithIndifferentAccess.new(directives)
- .reverse_merge(::Gitlab::ContentSecurityPolicy::ConfigLoader.default_directives)
- end
+ append_to_directive(directives, 'connect_src', "#{http_url} #{ws_url}")
+ end
- def load(policy)
- DIRECTIVES.each do |directive|
- arguments = arguments_for(directive)
+ def allow_letter_opener(directives)
+ url = Gitlab::Utils.append_path(Gitlab.config.gitlab.url, '/rails/letter_opener/')
+ append_to_directive(directives, 'frame_src', url)
+ end
- next unless arguments.present?
+ def allow_snowplow_micro(directives)
+ url = URI.join(Gitlab::Tracking::Destinations::SnowplowMicro.new.uri, '/').to_s
+ append_to_directive(directives, 'connect_src', url)
+ end
- policy.public_send(directive, *arguments) # rubocop:disable GitlabSecurity/PublicSend
+ def allow_lfs(directives)
+ return unless Gitlab.config.lfs.enabled && LfsObjectUploader.direct_download_enabled?
+
+ lfs_url = build_lfs_url
+ return unless lfs_url.present?
+
+ append_to_directive(directives, 'connect_src', lfs_url)
end
- end
- private
+ def allow_websocket_connections(directives)
+ host = Gitlab.config.gitlab.host
+ port = Gitlab.config.gitlab.port
+ secure = Gitlab.config.gitlab.https
+ protocol = secure ? 'wss' : 'ws'
- def arguments_for(directive)
- # In order to disable a directive, the user can explicitly
- # set a falsy value like nil, false or empty string
- arguments = @merged_csp_directives[directive]
- return unless arguments.present? && arguments.is_a?(String)
+ ws_url = "#{protocol}://#{host}"
+ ws_url = "#{ws_url}:#{port}" unless HTTP_PORTS.include?(port)
- arguments.strip.split(' ').map(&:strip)
- end
+ append_to_directive(directives, 'connect_src', ws_url)
+ end
- def self.allow_websocket_connections(directives)
- http_ports = [80, 443]
- host = Gitlab.config.gitlab.host
- port = Gitlab.config.gitlab.port
- secure = Gitlab.config.gitlab.https
- protocol = secure ? 'wss' : 'ws'
+ def allow_cdn(directives)
+ cdn_host = Settings.gitlab.cdn_host.presence
+ return unless cdn_host
+
+ append_to_directive(directives, 'script_src', cdn_host)
+ append_to_directive(directives, 'style_src', cdn_host)
+ append_to_directive(directives, 'font_src', cdn_host)
+ append_to_directive(directives, 'worker_src', cdn_host)
+ append_to_directive(directives, 'frame_src', cdn_host)
+ end
- ws_url = "#{protocol}://#{host}"
+ def allow_zuora(directives)
+ return unless Gitlab.com?
- unless http_ports.include?(port)
- ws_url = "#{ws_url}:#{port}"
+ append_to_directive(directives, 'frame_src', zuora_host)
end
- append_to_directive(directives, 'connect_src', ws_url)
- end
+ def allow_sentry(directives)
+ allow_legacy_sentry(directives) if legacy_sentry_configured?
+ return unless sentry_client_side_dsn_enabled?
- def self.allow_webpack_dev_server(directives)
- secure = Settings.webpack.dev_server['https']
- host_and_port = "#{Settings.webpack.dev_server['host']}:#{Settings.webpack.dev_server['port']}"
- http_url = "#{secure ? 'https' : 'http'}://#{host_and_port}"
- ws_url = "#{secure ? 'wss' : 'ws'}://#{host_and_port}"
+ sentry_uri = URI(Gitlab::CurrentSettings.sentry_clientside_dsn)
- append_to_directive(directives, 'connect_src', "#{http_url} #{ws_url}")
- end
+ append_to_directive(directives, 'connect_src', "#{sentry_uri.scheme}://#{sentry_uri.host}")
+ end
- def self.allow_cdn(directives, cdn_host)
- append_to_directive(directives, 'script_src', cdn_host)
- append_to_directive(directives, 'style_src', cdn_host)
- append_to_directive(directives, 'font_src', cdn_host)
- append_to_directive(directives, 'worker_src', cdn_host)
- append_to_directive(directives, 'frame_src', cdn_host)
- end
+ def allow_legacy_sentry(directives)
+ # Support for Sentry setup via configuration files will be removed in 16.0
+ # in favor of Gitlab::CurrentSettings.
+ sentry_uri = URI(Gitlab.config.sentry.clientside_dsn)
- def self.zuora_host
- "https://*.zuora.com/apps/PublicHostedPageLite.do"
- end
+ append_to_directive(directives, 'connect_src', "#{sentry_uri.scheme}://#{sentry_uri.host}")
+ end
- def self.allow_zuora(directives)
- append_to_directive(directives, 'frame_src', zuora_host)
- end
+ def legacy_sentry_configured?
+ Gitlab.config.sentry&.enabled && Gitlab.config.sentry&.clientside_dsn
+ end
- def self.append_to_directive(directives, directive, text)
- directives[directive] = "#{directives[directive]} #{text}".strip
- end
+ def sentry_client_side_dsn_enabled?
+ Gitlab::CurrentSettings.try(:sentry_enabled) && Gitlab::CurrentSettings.try(:sentry_clientside_dsn)
+ end
- def self.allow_customersdot(directives)
- customersdot_host = ENV['CUSTOMER_PORTAL_URL']
+ # Using 'self' in the CSP introduces several CSP bypass opportunities
+ # for this reason we list the URLs where GitLab frames itself instead
+ def allow_framed_gitlab_paths(directives)
+ ['/admin/', '/assets/', '/-/speedscope/index.html', '/-/sandbox/'].map do |path|
+ append_to_directive(directives, 'frame_src', Gitlab::Utils.append_path(Gitlab.config.gitlab.url, path))
+ end
+ end
- append_to_directive(directives, 'frame_src', customersdot_host)
- end
+ def allow_customersdot(directives)
+ customersdot_host = ENV['CUSTOMER_PORTAL_URL'].presence
+ return unless customersdot_host
- def self.allow_legacy_sentry(directives)
- # Support for Sentry setup via configuration files will be removed in 16.0
- # in favor of Gitlab::CurrentSettings.
- sentry_dsn = Gitlab.config.sentry.clientside_dsn
- sentry_uri = URI(sentry_dsn)
+ append_to_directive(directives, 'frame_src', customersdot_host)
+ end
- append_to_directive(directives, 'connect_src', "#{sentry_uri.scheme}://#{sentry_uri.host}")
- end
+ def allow_review_apps(directives)
+ return unless ENV['REVIEW_APPS_ENABLED'].presence
- def self.allow_sentry(directives)
- sentry_dsn = Gitlab::CurrentSettings.sentry_clientside_dsn
- sentry_uri = URI(sentry_dsn)
+ # Allow-listed to allow POSTs to https://gitlab.com/api/v4/projects/278964/merge_requests/:merge_request_iid/visual_review_discussions
+ append_to_directive(directives, 'connect_src', 'https://gitlab.com/api/v4/projects/278964/merge_requests/')
+ end
- append_to_directive(directives, 'connect_src', "#{sentry_uri.scheme}://#{sentry_uri.host}")
- end
+ # The follow contains workarounds to patch Safari's lack of support for CSP Level 3
+ def csp_level_3_backport(directives)
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/343579
+ # frame-src was deprecated in CSP level 2 in favor of child-src
+ # CSP level 3 "undeprecated" frame-src and browsers fall back on child-src if it's missing
+ # However Safari seems to read child-src first so we'll just keep both equal
+ append_to_directive(directives, 'child_src', directives['frame_src'])
+
+ # Safari also doesn't support worker-src and only checks child-src
+ # So for compatibility until it catches up to other browsers we need to
+ # append worker-src's content to child-src
+ append_to_directive(directives, 'child_src', directives['worker_src'])
+ end
+
+ def append_to_directive(directives, directive, text)
+ directives[directive] = "#{directives[directive]} #{text}".strip
+ end
+
+ def zuora_host
+ "https://*.zuora.com/apps/PublicHostedPageLite.do"
+ end
- def self.allow_letter_opener(directives)
- append_to_directive(directives, 'frame_src', Gitlab::Utils.append_path(Gitlab.config.gitlab.url, '/rails/letter_opener/'))
+ def build_lfs_url
+ uploader = LfsObjectUploader.new(nil)
+ fog = CarrierWave::Storage::Fog.new(uploader)
+ fog_file = CarrierWave::Storage::Fog::File.new(uploader, fog, nil)
+ fog_file.public_url || fog_file.url
+ end
end
- def self.allow_snowplow_micro(directives)
- url = URI.join(Gitlab::Tracking::Destinations::SnowplowMicro.new.uri, '/').to_s
- append_to_directive(directives, 'connect_src', url)
+ def initialize(csp_directives)
+ # Using <default_value> falls back to the default values.
+ @merged_csp_directives = csp_directives
+ .reject { |_, value| value == DEFAULT_FALLBACK_VALUE }
+ .with_indifferent_access
+ .reverse_merge(ConfigLoader.default_directives)
end
- # Using 'self' in the CSP introduces several CSP bypass opportunities
- # for this reason we list the URLs where GitLab frames itself instead
- def self.allow_framed_gitlab_paths(directives)
- ['/admin/', '/assets/', '/-/speedscope/index.html', '/-/sandbox/'].map do |path|
- append_to_directive(directives, 'frame_src', Gitlab::Utils.append_path(Gitlab.config.gitlab.url, path))
+ def load(policy)
+ DIRECTIVES.each do |directive|
+ arguments = arguments_for(directive)
+
+ next unless arguments.present?
+
+ policy.public_send(directive, *arguments) # rubocop:disable GitlabSecurity/PublicSend
end
end
- def self.allow_review_apps(directives)
- # Allow-listed to allow POSTs to https://gitlab.com/api/v4/projects/278964/merge_requests/:merge_request_iid/visual_review_discussions
- append_to_directive(directives, 'connect_src', 'https://gitlab.com/api/v4/projects/278964/merge_requests/')
+ private
+
+ def arguments_for(directive)
+ # In order to disable a directive, the user can explicitly
+ # set a falsy value like nil, false or empty string
+ arguments = @merged_csp_directives[directive]
+ return unless arguments.is_a?(String)
+
+ arguments.split(' ')
end
end
end
diff --git a/lib/gitlab/data_builder/build.rb b/lib/gitlab/data_builder/build.rb
index e1e9e4720bb..7abad66cf13 100644
--- a/lib/gitlab/data_builder/build.rb
+++ b/lib/gitlab/data_builder/build.rb
@@ -12,7 +12,7 @@ module Gitlab
author_url = build_author_url(build.commit, commit)
- {
+ attrs = {
object_kind: 'build',
ref: build.ref,
@@ -68,8 +68,13 @@ module Gitlab
visibility_level: project.visibility_level
},
+ project: project.hook_attrs(backward: false),
+
environment: build_environment(build)
}
+
+ attrs[:source_pipeline] = source_pipeline_attrs(commit.source_pipeline) if commit.source_pipeline.present?
+ attrs
end
private
@@ -100,6 +105,20 @@ module Gitlab
action: build.environment_action
}
end
+
+ def source_pipeline_attrs(source_pipeline)
+ project = source_pipeline.source_project
+
+ {
+ project: {
+ id: project.id,
+ web_url: project.web_url,
+ path_with_namespace: project.full_path
+ },
+ job_id: source_pipeline.source_job_id,
+ pipeline_id: source_pipeline.source_pipeline_id
+ }
+ end
end
end
end
diff --git a/lib/gitlab/data_builder/issuable.rb b/lib/gitlab/data_builder/issuable.rb
index 9a7b4d0e2aa..57a69c7c0c1 100644
--- a/lib/gitlab/data_builder/issuable.rb
+++ b/lib/gitlab/data_builder/issuable.rb
@@ -16,12 +16,12 @@ module Gitlab
object_kind: object_kind,
event_type: event_type,
user: user.hook_attrs,
- project: issuable.project.hook_attrs,
+ project: issuable.project&.hook_attrs,
object_attributes: issuable_builder.new(issuable).build,
labels: issuable.labels_hook_attrs,
changes: final_changes(changes.slice(*safe_keys)),
# DEPRECATED
- repository: issuable.project.hook_attrs.slice(:name, :url, :description, :homepage)
+ repository: issuable.project&.hook_attrs&.slice(:name, :url, :description, :homepage)
}
hook_data[:assignees] = issuable.assignees.map(&:hook_attrs) if issuable.assignees.any?
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index fd83f27ef31..89065c11c4f 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -328,14 +328,19 @@ module Gitlab
db_config&.name || 'unknown'
end
- # Currently the database configuration can only be shared with `main:`
- # If the `database_tasks: false` is being used
- # This is to be refined: https://gitlab.com/gitlab-org/gitlab/-/issues/356580
+ # If the `database_tasks: false` is being used,
+ # return the expected fallback database for this database configuration
def self.db_config_share_with(db_config)
- if db_config.database_tasks?
- nil # no sharing
+ # no sharing
+ return if db_config.database_tasks?
+
+ database_connection_info = all_database_connections[db_config.name]
+
+ if database_connection_info
+ database_connection_info.fallback_database&.to_s
else
- 'main' # share with `main:`
+ # legacy behaviour
+ 'main'
end
end
diff --git a/lib/gitlab/database/batch_counter.rb b/lib/gitlab/database/batch_counter.rb
index abb62140503..7abcef5c93b 100644
--- a/lib/gitlab/database/batch_counter.rb
+++ b/lib/gitlab/database/batch_counter.rb
@@ -16,17 +16,18 @@ module Gitlab
DEFAULT_DISTINCT_BATCH_SIZE = 10_000
DEFAULT_BATCH_SIZE = 100_000
- def initialize(relation, column: nil, operation: :count, operation_args: nil)
+ def initialize(relation, column: nil, operation: :count, operation_args: nil, max_allowed_loops: nil)
@relation = relation
@column = column || relation.primary_key
@operation = operation
@operation_args = operation_args
+ @max_allowed_loops = max_allowed_loops || MAX_ALLOWED_LOOPS
end
def unwanted_configuration?(finish, batch_size, start)
(@operation == :count && batch_size <= MIN_REQUIRED_BATCH_SIZE) ||
(@operation == :sum && batch_size < DEFAULT_SUM_BATCH_SIZE) ||
- (finish - start) / batch_size >= MAX_ALLOWED_LOOPS ||
+ (finish - start) / batch_size >= @max_allowed_loops ||
start >= finish
end
@@ -139,6 +140,7 @@ module Gitlab
relation: @relation.table_name,
operation: @operation,
operation_args: @operation_args,
+ max_allowed_loops: @max_allowed_loops,
start: batch_start,
mode: mode,
query: query,
diff --git a/lib/gitlab/database/bump_sequences.rb b/lib/gitlab/database/bump_sequences.rb
new file mode 100644
index 00000000000..f1b725b2de9
--- /dev/null
+++ b/lib/gitlab/database/bump_sequences.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ class BumpSequences
+ SEQUENCE_NAME_MATCHER = /nextval\('([a-z_]+)'::regclass\)/
+
+ # gitlab_schema: can be 'gitlab_main', 'gitlab_ci', 'gitlab_main_cell', 'gitlab_shared'
+ # increase_by: positive number, to increase the sequence by
+ # base_model: is to choose which connection to use to query the tables
+ def initialize(gitlab_schema, increase_by, base_model = ApplicationRecord)
+ @base_model = base_model
+ @gitlab_schema = gitlab_schema
+ @increase_by = increase_by
+ end
+
+ def execute
+ sequences_by_gitlab_schema(base_model, gitlab_schema).each do |sequence_name|
+ increment_sequence_by(base_model.connection, sequence_name, increase_by)
+ end
+ end
+
+ private
+
+ attr_reader :base_model, :gitlab_schema, :increase_by
+
+ def sequences_by_gitlab_schema(base_model, gitlab_schema)
+ tables = Gitlab::Database::GitlabSchema.tables_to_schema.select do |_table_name, schema_name|
+ schema_name == gitlab_schema
+ end.keys
+
+ sequences = []
+
+ tables.each do |table|
+ model = Class.new(base_model) do
+ self.table_name = table
+ end
+
+ model.columns.each do |column|
+ match_result = column.default_function&.match(SEQUENCE_NAME_MATCHER)
+ next unless match_result
+
+ sequences << match_result[1]
+ end
+ end
+
+ sequences
+ end
+
+ # This method is going to increase the sequence next_value by:
+ # - increment_by + 1 if the sequence has the attribute is_called = True (which is the common case)
+ # - increment_by if the sequence has the attribute is_called = False (for example, a newly created sequence)
+ # It uses ALTER SEQUENCE as a safety mechanism to avoid that no concurrent insertions
+ # will cause conflicts on the sequence.
+ # This is because ALTER SEQUENCE blocks concurrent nextval, currval, lastval, and setval calls.
+ def increment_sequence_by(connection, sequence_name, increment_by)
+ connection.transaction do
+ # The first call is to make sure that the sequence's is_called value is set to `true`
+ # This guarantees that the next call to `nextval` will increase the sequence by `increment_by`
+ connection.select_value("SELECT nextval($1)", nil, [sequence_name])
+ connection.execute("ALTER SEQUENCE #{sequence_name} INCREMENT BY #{increment_by}")
+ connection.select_value("select nextval($1)", nil, [sequence_name])
+ connection.execute("ALTER SEQUENCE #{sequence_name} INCREMENT BY 1")
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database/ci_builds_partitioning.rb b/lib/gitlab/database/ci_builds_partitioning.rb
deleted file mode 100644
index 9f8b19f2d23..00000000000
--- a/lib/gitlab/database/ci_builds_partitioning.rb
+++ /dev/null
@@ -1,224 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Database
- class CiBuildsPartitioning
- include AsyncDdlExclusiveLeaseGuard
-
- ATTEMPTS = 5
- LOCK_TIMEOUT = 10.seconds
- LEASE_TIMEOUT = 30.minutes
-
- FK_NAME = :fk_e20479742e_p
- TEMP_FK_NAME = :temp_fk_e20479742e_p
- NEXT_PARTITION_ID = 101
- BUILDS_PARTITION_NAME = 'gitlab_partitions_dynamic.ci_builds_101'
- ANNOTATION_PARTITION_NAME = 'gitlab_partitions_dynamic.ci_job_annotations_101'
- RUNNER_MACHINE_PARTITION_NAME = 'gitlab_partitions_dynamic.ci_runner_machine_builds_101'
-
- def initialize(logger: Gitlab::AppLogger)
- @connection = ::Ci::ApplicationRecord.connection
- @timing_configuration = Array.new(ATTEMPTS) { [LOCK_TIMEOUT, 3.minutes] }
- @logger = logger
- end
-
- def execute
- return unless can_execute?
-
- try_obtain_lease do
- swap_foreign_keys
- create_new_ci_builds_partition
- create_new_job_annotations_partition
- create_new_runner_machine_partition
- end
-
- rescue StandardError => e
- log_info("Failed to execute: #{e.message}")
- end
-
- private
-
- attr_reader :connection, :timing_configuration, :logger
-
- delegate :quote_table_name, :quote_column_name, to: :connection
-
- def swap_foreign_keys
- if new_foreign_key_exists?
- log_info('Foreign key already renamed, nothing to do')
-
- return
- end
-
- with_lock_retries do
- connection.execute drop_old_foreign_key_sql
-
- rename_constraint :p_ci_builds_metadata, TEMP_FK_NAME, FK_NAME
-
- each_partition do |partition|
- rename_constraint partition.identifier, TEMP_FK_NAME, FK_NAME
- end
- end
-
- log_info('Foreign key successfully renamed')
- end
-
- def create_new_ci_builds_partition
- if connection.table_exists?(BUILDS_PARTITION_NAME)
- log_info('p_ci_builds partition exists, nothing to do')
- return
- end
-
- with_lock_retries do
- connection.execute new_ci_builds_partition_sql
- end
-
- log_info('Partition for p_ci_builds successfully created')
- end
-
- def create_new_job_annotations_partition
- if connection.table_exists?(ANNOTATION_PARTITION_NAME)
- log_info('p_ci_job_annotations partition exists, nothing to do')
- return
- end
-
- with_lock_retries do
- connection.execute new_job_annotations_partition_sql
- end
-
- log_info('Partition for p_ci_job_annotations successfully created')
- end
-
- def create_new_runner_machine_partition
- if connection.table_exists?(RUNNER_MACHINE_PARTITION_NAME)
- log_info('p_ci_runner_machine_builds partition exists, nothing to do')
- return
- end
-
- with_lock_retries do
- connection.execute new_runner_machine_partition_sql
- end
-
- log_info('Partition for p_ci_runner_machine_builds successfully created')
- end
-
- def can_execute?
- return false if process_disabled?
- return false unless Gitlab.com?
-
- if vacuum_running?
- log_info('Autovacuum detected')
-
- return false
- end
-
- true
- end
-
- def process_disabled?
- ::Feature.disabled?(:complete_p_ci_builds_partitioning)
- end
-
- def new_foreign_key_exists?
- Gitlab::Database::SharedModel.using_connection(connection) do
- Gitlab::Database::PostgresForeignKey
- .by_constrained_table_name_or_identifier(:p_ci_builds_metadata)
- .by_referenced_table_name(:p_ci_builds)
- .by_name(FK_NAME)
- .exists?
- end
- end
-
- def vacuum_running?
- Gitlab::Database::SharedModel.using_connection(connection) do
- Gitlab::Database::PostgresAutovacuumActivity
- .wraparound_prevention
- .for_tables(%i[ci_builds ci_builds_metadata])
- .any?
- end
- end
-
- def drop_old_foreign_key_sql
- <<~SQL.squish
- SET LOCAL statement_timeout TO '11s';
-
- LOCK TABLE ci_builds, p_ci_builds_metadata IN ACCESS EXCLUSIVE MODE;
-
- ALTER TABLE p_ci_builds_metadata DROP CONSTRAINT #{FK_NAME};
- SQL
- end
-
- def rename_constraint(table_name, old_name, new_name)
- connection.execute <<~SQL
- ALTER TABLE #{quote_table_name(table_name)}
- RENAME CONSTRAINT #{quote_column_name(old_name)} TO #{quote_column_name(new_name)}
- SQL
- end
-
- def new_ci_builds_partition_sql
- <<~SQL
- SET LOCAL statement_timeout TO '11s';
-
- LOCK ci_pipelines, ci_stages IN SHARE ROW EXCLUSIVE MODE;
- LOCK TABLE ONLY p_ci_builds IN ACCESS EXCLUSIVE MODE;
-
- CREATE TABLE IF NOT EXISTS #{BUILDS_PARTITION_NAME}
- PARTITION OF p_ci_builds
- FOR VALUES IN (#{NEXT_PARTITION_ID});
- SQL
- end
-
- def new_job_annotations_partition_sql
- <<~SQL
- SET LOCAL statement_timeout TO '11s';
-
- LOCK TABLE p_ci_builds IN SHARE ROW EXCLUSIVE MODE;
- LOCK TABLE ONLY p_ci_job_annotations IN ACCESS EXCLUSIVE MODE;
-
- CREATE TABLE IF NOT EXISTS #{ANNOTATION_PARTITION_NAME}
- PARTITION OF p_ci_job_annotations
- FOR VALUES IN (#{NEXT_PARTITION_ID});
- SQL
- end
-
- def new_runner_machine_partition_sql
- <<~SQL
- SET LOCAL statement_timeout TO '11s';
-
- LOCK TABLE p_ci_builds IN SHARE ROW EXCLUSIVE MODE;
- LOCK TABLE ONLY p_ci_runner_machine_builds IN ACCESS EXCLUSIVE MODE;
-
- CREATE TABLE IF NOT EXISTS #{RUNNER_MACHINE_PARTITION_NAME}
- PARTITION OF p_ci_runner_machine_builds
- FOR VALUES IN (#{NEXT_PARTITION_ID});
- SQL
- end
-
- def with_lock_retries(&block)
- Gitlab::Database::WithLockRetries.new(
- timing_configuration: timing_configuration,
- connection: connection,
- logger: logger,
- klass: self.class
- ).run(raise_on_exhaustion: true, &block)
- end
-
- def each_partition(&block)
- Gitlab::Database::SharedModel.using_connection(connection) do
- Gitlab::Database::PostgresPartitionedTable.each_partition(:p_ci_builds_metadata, &block)
- end
- end
-
- def log_info(message)
- logger.info(message: message, class: self.class.to_s)
- end
-
- def connection_db_config
- ::Ci::ApplicationRecord.connection_db_config
- end
-
- def lease_timeout
- LEASE_TIMEOUT
- end
- end
- end
-end
diff --git a/lib/gitlab/database/health_status.rb b/lib/gitlab/database/health_status.rb
index 69bb8a70afd..b8dba802b56 100644
--- a/lib/gitlab/database/health_status.rb
+++ b/lib/gitlab/database/health_status.rb
@@ -6,7 +6,8 @@ module Gitlab
DEFAULT_INIDICATORS = [
Indicators::AutovacuumActiveOnTable,
Indicators::WriteAheadLog,
- Indicators::PatroniApdex
+ Indicators::PatroniApdex,
+ Indicators::WalRate
].freeze
class << self
diff --git a/lib/gitlab/database/health_status/indicators/patroni_apdex.rb b/lib/gitlab/database/health_status/indicators/patroni_apdex.rb
index 680c86cf7b2..5631b6db6ff 100644
--- a/lib/gitlab/database/health_status/indicators/patroni_apdex.rb
+++ b/lib/gitlab/database/health_status/indicators/patroni_apdex.rb
@@ -4,82 +4,19 @@ module Gitlab
module Database
module HealthStatus
module Indicators
- class PatroniApdex
- include Gitlab::Utils::StrongMemoize
-
- def initialize(context)
- @gitlab_schema = context.gitlab_schema.to_sym
- end
-
- def evaluate
- return Signals::NotAvailable.new(self.class, reason: 'indicator disabled') unless enabled?
-
- connection_error_message = fetch_connection_error_message
- return unknown_signal(connection_error_message) if connection_error_message.present?
-
- apdex_sli = fetch_sli(apdex_sli_query)
- return unknown_signal('Patroni service apdex can not be calculated') unless apdex_sli.present?
-
- if apdex_sli.to_f > apdex_slo.to_f
- Signals::Normal.new(self.class, reason: 'Patroni service apdex is above SLO')
- else
- Signals::Stop.new(self.class, reason: 'Patroni service apdex is below SLO')
- end
- end
-
+ class PatroniApdex < PrometheusAlertIndicator
private
- attr_reader :gitlab_schema
-
def enabled?
Feature.enabled?(:batched_migrations_health_status_patroni_apdex, type: :ops)
end
- def unknown_signal(reason)
- Signals::Unknown.new(self.class, reason: reason)
- end
-
- def fetch_connection_error_message
- return 'Patroni Apdex Settings not configured' unless database_apdex_settings.present?
- return 'Prometheus client is not ready' unless client.ready?
- return 'Apdex SLI query is not configured' unless apdex_sli_query
- return 'Apdex SLO is not configured' unless apdex_slo
- end
-
- def client
- @client ||= Gitlab::PrometheusClient.new(
- database_apdex_settings[:prometheus_api_url],
- allow_local_requests: true,
- verify: true
- )
- end
-
- def database_apdex_settings
- @database_apdex_settings ||= Gitlab::CurrentSettings.database_apdex_settings&.with_indifferent_access
+ def sli_query_key
+ :apdex_sli_query
end
- def apdex_sli_query
- {
- gitlab_main: database_apdex_settings[:apdex_sli_query][:main],
- gitlab_ci: database_apdex_settings[:apdex_sli_query][:ci]
- }.fetch(gitlab_schema)
- end
- strong_memoize_attr :apdex_sli_query
-
- def apdex_slo
- {
- gitlab_main: database_apdex_settings[:apdex_slo][:main],
- gitlab_ci: database_apdex_settings[:apdex_slo][:ci]
- }.fetch(gitlab_schema)
- end
- strong_memoize_attr :apdex_slo
-
- def fetch_sli(query)
- response = client.query(query)
- metric = response&.first || {}
- value = metric.fetch('value', [])
-
- Array.wrap(value).second
+ def slo_key
+ :apdex_slo
end
end
end
diff --git a/lib/gitlab/database/health_status/indicators/prometheus_alert_indicator.rb b/lib/gitlab/database/health_status/indicators/prometheus_alert_indicator.rb
new file mode 100644
index 00000000000..3d630d21d4c
--- /dev/null
+++ b/lib/gitlab/database/health_status/indicators/prometheus_alert_indicator.rb
@@ -0,0 +1,113 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ module HealthStatus
+ module Indicators
+ class PrometheusAlertIndicator
+ include Gitlab::Utils::StrongMemoize
+
+ ALERT_CONDITIONS = {
+ above: :above,
+ below: :below
+ }.freeze
+
+ def initialize(context)
+ @gitlab_schema = context.gitlab_schema.to_sym
+ end
+
+ def evaluate
+ return Signals::NotAvailable.new(self.class, reason: 'indicator disabled') unless enabled?
+
+ connection_error_message = fetch_connection_error_message
+ return unknown_signal(connection_error_message) if connection_error_message.present?
+
+ sli = fetch_sli(sli_query)
+ return unknown_signal("#{indicator_name} can not be calculated") unless sli.present?
+
+ if alert_condition == ALERT_CONDITIONS[:above] ? sli.to_f > slo.to_f : sli.to_f < slo.to_f
+ Signals::Normal.new(self.class, reason: "#{indicator_name} SLI condition met")
+ else
+ Signals::Stop.new(self.class, reason: "#{indicator_name} SLI condition not met")
+ end
+ end
+
+ private
+
+ attr_reader :gitlab_schema
+
+ def indicator_name
+ self.class.name.demodulize
+ end
+
+ # By default SLIs are expected to be above SLOs, but there can be cases
+ # where we want it to be below SLO (eg: WAL rate). For such indicators
+ # the sub-class should override this default alert_condition.
+ def alert_condition
+ ALERT_CONDITIONS[:above]
+ end
+
+ def enabled?
+ raise NotImplementedError, "prometheus alert based indicators must implement #{__method__}"
+ end
+
+ def slo_key
+ raise NotImplementedError, "prometheus alert based indicators must implement #{__method__}"
+ end
+
+ def sli_key
+ raise NotImplementedError, "prometheus alert based indicators must implement #{__method__}"
+ end
+
+ def fetch_connection_error_message
+ return 'Prometheus Settings not configured' unless prometheus_alert_db_indicators_settings.present?
+ return 'Prometheus client is not ready' unless client.ready?
+ return "#{indicator_name} SLI query is not configured" unless sli_query
+ return "#{indicator_name} SLO is not configured" unless slo
+ end
+
+ def prometheus_alert_db_indicators_settings
+ @prometheus_alert_db_indicators_settings ||= Gitlab::CurrentSettings
+ .prometheus_alert_db_indicators_settings&.with_indifferent_access
+ end
+
+ def client
+ @client ||= Gitlab::PrometheusClient.new(
+ prometheus_alert_db_indicators_settings[:prometheus_api_url],
+ allow_local_requests: true,
+ verify: true
+ )
+ end
+
+ def sli_query
+ {
+ gitlab_main: prometheus_alert_db_indicators_settings[sli_query_key][:main],
+ gitlab_ci: prometheus_alert_db_indicators_settings[sli_query_key][:ci]
+ }.fetch(gitlab_schema)
+ end
+ strong_memoize_attr :sli_query
+
+ def slo
+ {
+ gitlab_main: prometheus_alert_db_indicators_settings[slo_key][:main],
+ gitlab_ci: prometheus_alert_db_indicators_settings[slo_key][:ci]
+ }.fetch(gitlab_schema)
+ end
+ strong_memoize_attr :slo
+
+ def fetch_sli(query)
+ response = client.query(query)
+ metric = response&.first || {}
+ value = metric.fetch('value', [])
+
+ Array.wrap(value).second
+ end
+
+ def unknown_signal(reason)
+ Signals::Unknown.new(self.class, reason: reason)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database/health_status/indicators/wal_rate.rb b/lib/gitlab/database/health_status/indicators/wal_rate.rb
new file mode 100644
index 00000000000..de31b5899eb
--- /dev/null
+++ b/lib/gitlab/database/health_status/indicators/wal_rate.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ module HealthStatus
+ module Indicators
+ class WalRate < PrometheusAlertIndicator
+ private
+
+ def enabled?
+ Feature.enabled?(:db_health_check_wal_rate, type: :ops)
+ end
+
+ def sli_query_key
+ :wal_rate_sli_query
+ end
+
+ def slo_key
+ :wal_rate_slo
+ end
+
+ def alert_condition
+ ALERT_CONDITIONS[:below]
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index 256c524e989..60cec12b4b5 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -1191,6 +1191,19 @@ into similar problems in the future (e.g. when new tables are created).
.present?
end
+ # While it is safe to call `change_column_default` on a column without
+ # default it would still require access exclusive lock on the table
+ # and for tables with high autovacuum(wraparound prevention) it will
+ # fail if their executions overlap.
+ #
+ def remove_column_default(table_name, column_name)
+ column = connection.columns(table_name).find { |col| col.name == column_name.to_s }
+
+ if column.default || column.default_function
+ change_column_default(table_name, column_name, to: nil)
+ end
+ end
+
private
def multiple_columns(columns, separator: ', ')
diff --git a/lib/gitlab/database/migration_helpers/convert_to_bigint.rb b/lib/gitlab/database/migration_helpers/convert_to_bigint.rb
index 63928d7dc09..11f1e62e8b9 100644
--- a/lib/gitlab/database/migration_helpers/convert_to_bigint.rb
+++ b/lib/gitlab/database/migration_helpers/convert_to_bigint.rb
@@ -15,6 +15,20 @@ module Gitlab
Gitlab.com? && !Gitlab.jh?
end
+
+ def temp_column_removed?(table_name, column_name)
+ !column_exists?(table_name.to_s, convert_to_bigint_column(column_name))
+ end
+
+ def columns_swapped?(table_name, column_name)
+ table_columns = columns(table_name.to_s)
+ temp_column_name = convert_to_bigint_column(column_name)
+
+ column = table_columns.find { |c| c.name == column_name.to_s }
+ temp_column = table_columns.find { |c| c.name == temp_column_name }
+
+ column.sql_type == 'bigint' && temp_column.sql_type == 'integer'
+ end
end
end
end
diff --git a/lib/gitlab/database/migrations/batched_background_migration_helpers.rb b/lib/gitlab/database/migrations/batched_background_migration_helpers.rb
index cb2a98b553f..efb1957d5e7 100644
--- a/lib/gitlab/database/migrations/batched_background_migration_helpers.rb
+++ b/lib/gitlab/database/migrations/batched_background_migration_helpers.rb
@@ -242,7 +242,7 @@ module Gitlab
"\n\n" \
"For more information, check the documentation" \
"\n\n" \
- "\thttps://docs.gitlab.com/ee/user/admin_area/monitoring/background_migrations.html#database-migrations-failing-because-of-batched-background-migration-not-finished"
+ "\thttps://docs.gitlab.com/ee/update/background_migrations.html#database-migrations-failing-because-of-batched-background-migration-not-finished"
end
end
end
diff --git a/lib/gitlab/database/migrations/squasher.rb b/lib/gitlab/database/migrations/squasher.rb
new file mode 100644
index 00000000000..98fdf873aa5
--- /dev/null
+++ b/lib/gitlab/database/migrations/squasher.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+require 'set'
+
+module Gitlab
+ module Database
+ module Migrations
+ class Squasher
+ RSPEC_FILENAME_REGEXP = /\A([0-9]+_)?([_a-z0-9]*)\.rb\z/
+
+ def initialize(git_output)
+ @migration_data = migration_files_from_git(git_output).filter_map do |mf|
+ basename = Pathname(mf).basename.to_s
+ file_name_match = ActiveRecord::Migration::MigrationFilenameRegexp.match(basename)
+ slug = file_name_match[2]
+ unless slug == 'init_schema'
+ {
+ path: mf,
+ basename: basename,
+ timestamp: file_name_match[1],
+ slug: slug
+ }
+ end
+ end
+ end
+
+ def files_to_delete
+ @migration_data.pluck(:path) + schema_migrations + find_migration_specs
+ end
+
+ private
+
+ def schema_migrations
+ @migration_data.map { |m| "db/schema_migrations/#{m[:timestamp]}" }
+ end
+
+ def find_migration_specs
+ @file_slugs = Set.new @migration_data.pluck(:slug)
+ (migration_specs + ee_migration_specs).select { |f| file_has_slug?(f) }
+ end
+
+ def migration_files_from_git(body)
+ body.chomp
+ .split("\n")
+ .select { |fn| fn.end_with?('.rb') }
+ end
+
+ def match_file_slug(filename)
+ m = RSPEC_FILENAME_REGEXP.match(filename)
+ return if m.nil?
+
+ m[2].sub(/_spec$/, '')
+ end
+
+ def file_has_slug?(filename)
+ spec_slug = match_file_slug(Pathname(filename).basename.to_s)
+ return false if spec_slug.nil?
+
+ @file_slugs.include?(spec_slug)
+ end
+
+ def migration_specs
+ Dir.glob(Rails.root.join('spec/migrations/*.rb'))
+ end
+
+ def ee_migration_specs
+ Dir.glob(Rails.root.join('ee/spec/migrations/*.rb'))
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database/postgres_constraint.rb b/lib/gitlab/database/postgres_constraint.rb
index fa3870cb9c7..9c49251664b 100644
--- a/lib/gitlab/database/postgres_constraint.rb
+++ b/lib/gitlab/database/postgres_constraint.rb
@@ -17,7 +17,7 @@ module Gitlab
scope :valid, -> { where(constraint_valid: true) }
scope :by_table_identifier, ->(identifier) do
- unless identifier =~ Gitlab::Database::FULLY_QUALIFIED_IDENTIFIER
+ unless Gitlab::Database::FULLY_QUALIFIED_IDENTIFIER.match?(identifier)
raise ArgumentError, "Table name is not fully qualified with a schema: #{identifier}"
end
diff --git a/lib/gitlab/database/postgres_foreign_key.rb b/lib/gitlab/database/postgres_foreign_key.rb
index bb3e1d45f15..9fb3098efe0 100644
--- a/lib/gitlab/database/postgres_foreign_key.rb
+++ b/lib/gitlab/database/postgres_foreign_key.rb
@@ -21,7 +21,7 @@ module Gitlab
enum on_update_action: ACTION_TYPES, _prefix: :on_update
scope :by_referenced_table_identifier, ->(identifier) do
- unless identifier =~ Database::FULLY_QUALIFIED_IDENTIFIER
+ unless Database::FULLY_QUALIFIED_IDENTIFIER.match?(identifier)
raise ArgumentError, "Referenced table name is not fully qualified with a schema: #{identifier}"
end
@@ -31,7 +31,7 @@ module Gitlab
scope :by_referenced_table_name, ->(name) { where(referenced_table_name: name) }
scope :by_constrained_table_identifier, ->(identifier) do
- unless identifier =~ Database::FULLY_QUALIFIED_IDENTIFIER
+ unless Database::FULLY_QUALIFIED_IDENTIFIER.match?(identifier)
raise ArgumentError, "Constrained table name is not fully qualified with a schema: #{identifier}"
end
@@ -41,7 +41,7 @@ module Gitlab
scope :by_constrained_table_name, ->(name) { where(constrained_table_name: name) }
scope :by_constrained_table_name_or_identifier, ->(name) do
- if name =~ Database::FULLY_QUALIFIED_IDENTIFIER
+ if Database::FULLY_QUALIFIED_IDENTIFIER.match?(name)
by_constrained_table_identifier(name)
else
by_constrained_table_name(name)
diff --git a/lib/gitlab/database/postgres_index.rb b/lib/gitlab/database/postgres_index.rb
index 50009cadf5d..1c775482e7e 100644
--- a/lib/gitlab/database/postgres_index.rb
+++ b/lib/gitlab/database/postgres_index.rb
@@ -14,7 +14,7 @@ module Gitlab
has_many :queued_reindexing_actions, class_name: 'Gitlab::Database::Reindexing::QueuedAction', foreign_key: :index_identifier
scope :by_identifier, ->(identifier) do
- unless identifier =~ Gitlab::Database::FULLY_QUALIFIED_IDENTIFIER
+ unless Gitlab::Database::FULLY_QUALIFIED_IDENTIFIER.match?(identifier)
raise ArgumentError, "Index name is not fully qualified with a schema: #{identifier}"
end
diff --git a/lib/gitlab/database/postgres_partition.rb b/lib/gitlab/database/postgres_partition.rb
index e63c6fc86ea..f79b8b5e32c 100644
--- a/lib/gitlab/database/postgres_partition.rb
+++ b/lib/gitlab/database/postgres_partition.rb
@@ -10,7 +10,7 @@ module Gitlab
# identifier includes the partition schema.
# For example 'gitlab_partitions_static.events_03', or 'gitlab_partitions_dynamic.logs_03'
scope :for_identifier, ->(identifier) do
- unless identifier =~ Gitlab::Database::FULLY_QUALIFIED_IDENTIFIER
+ unless Gitlab::Database::FULLY_QUALIFIED_IDENTIFIER.match?(identifier)
raise ArgumentError, "Partition name is not fully qualified with a schema: #{identifier}"
end
@@ -22,7 +22,7 @@ module Gitlab
end
scope :for_parent_table, ->(parent_table) do
- if parent_table =~ Database::FULLY_QUALIFIED_IDENTIFIER
+ if Database::FULLY_QUALIFIED_IDENTIFIER.match?(parent_table)
where(parent_identifier: parent_table).order(:name)
else
where("parent_identifier = concat(current_schema(), '.', ?)", parent_table).order(:name)
diff --git a/lib/gitlab/database/postgres_partitioned_table.rb b/lib/gitlab/database/postgres_partitioned_table.rb
index fead7379e43..76e2cd48f80 100644
--- a/lib/gitlab/database/postgres_partitioned_table.rb
+++ b/lib/gitlab/database/postgres_partitioned_table.rb
@@ -10,7 +10,7 @@ module Gitlab
has_many :postgres_partitions, foreign_key: 'parent_identifier', primary_key: 'identifier'
scope :by_identifier, ->(identifier) do
- unless identifier =~ Gitlab::Database::FULLY_QUALIFIED_IDENTIFIER
+ unless Gitlab::Database::FULLY_QUALIFIED_IDENTIFIER.match?(identifier)
raise ArgumentError, "Table name is not fully qualified with a schema: #{identifier}"
end
diff --git a/lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin.rb b/lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin.rb
index 71d2554844e..21392283ccf 100644
--- a/lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin.rb
+++ b/lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin.rb
@@ -11,6 +11,8 @@ module Gitlab
end
def force_disconnect_if_old!
+ return if Rails.env.test? && transaction_open?
+
if force_disconnect_timer.expired?
disconnect!
reset_force_disconnect_timer!
diff --git a/lib/gitlab/database/query_analyzers/query_recorder.rb b/lib/gitlab/database/query_analyzers/query_recorder.rb
deleted file mode 100644
index 63b4fbb8c1d..00000000000
--- a/lib/gitlab/database/query_analyzers/query_recorder.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Database
- module QueryAnalyzers
- class QueryRecorder < Base
- LOG_PATH = 'query_recorder/'
- LIST_PARAMETER_REGEX = %r{\$\d+(?:\s*,\s*\$\d+)+}.freeze
- SINGLE_PARAMETER_REGEX = %r{\$\d+}.freeze
-
- class << self
- def enabled?
- # Only enable QueryRecorder in CI on database MRs or default branch
- ENV['CI_MERGE_REQUEST_LABELS']&.include?('database') ||
- (ENV['CI_COMMIT_REF_NAME'].present? && ENV['CI_COMMIT_REF_NAME'] == ENV['CI_DEFAULT_BRANCH'])
- end
-
- def analyze(parsed)
- payload = {
- normalized: normalize_query(parsed.sql)
- }
-
- log_query(payload)
- end
-
- def log_file
- Rails.root.join(LOG_PATH, "#{ENV.fetch('CI_JOB_NAME_SLUG', 'rspec')}.ndjson")
- end
-
- private
-
- def log_query(payload)
- log_dir = Rails.root.join(LOG_PATH)
-
- # Create log directory if it does not exist since it is only created
- # ahead of time by certain CI jobs
- FileUtils.mkdir_p(log_dir) unless Dir.exist?(log_dir)
-
- log_line = "#{Gitlab::Json.dump(payload)}\n"
-
- File.write(log_file, log_line, mode: 'a')
- end
-
- def normalize_query(query)
- query
- .gsub(LIST_PARAMETER_REGEX, '?,?,?') # Replace list parameters with ?,?,?
- .gsub(SINGLE_PARAMETER_REGEX, '?') # Replace single parameters with ?
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/database/reindexing.rb b/lib/gitlab/database/reindexing.rb
index 9c860ebc6aa..4a1b0be848e 100644
--- a/lib/gitlab/database/reindexing.rb
+++ b/lib/gitlab/database/reindexing.rb
@@ -59,7 +59,6 @@ module Gitlab
# most bloated indexes for reindexing.
def self.perform_with_heuristic(candidate_indexes = Gitlab::Database::PostgresIndex.reindexing_support, maximum_records: DEFAULT_INDEXES_PER_INVOCATION)
IndexSelection.new(candidate_indexes).take(maximum_records).each do |index|
- Gitlab::Database::CiBuildsPartitioning.new.execute
Coordinator.new(index).perform
end
end
diff --git a/lib/gitlab/database/reindexing/reindex_concurrently.rb b/lib/gitlab/database/reindexing/reindex_concurrently.rb
index 60fa4deda39..aa445082aa9 100644
--- a/lib/gitlab/database/reindexing/reindex_concurrently.rb
+++ b/lib/gitlab/database/reindexing/reindex_concurrently.rb
@@ -20,7 +20,7 @@ module Gitlab
def perform
raise ReindexError, 'indexes serving an exclusion constraint are currently not supported' if index.exclusion?
- raise ReindexError, 'index is a left-over temporary index from a previous reindexing run' if index.name =~ /#{TEMPORARY_INDEX_PATTERN}/o
+ raise ReindexError, 'index is a left-over temporary index from a previous reindexing run' if /#{TEMPORARY_INDEX_PATTERN}/o.match?(index.name)
# Expression indexes require additional statistics in `pg_statistic`:
# select * from pg_statistic where starelid = (select oid from pg_class where relname = 'some_index');
diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb
index 562e651cabc..72ae2849911 100644
--- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb
+++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb
@@ -23,6 +23,7 @@ module Gitlab
with_paths = MigrationClasses::Route.arel_table[:path]
.matches_any(path_patterns)
namespaces.joins(:route).where(with_paths)
+ .allow_cross_joins_across_databases(url: "https://gitlab.com/gitlab-org/gitlab/-/issues/420046")
end
def rename_namespace(namespace)
@@ -45,6 +46,7 @@ module Gitlab
reverts_for_type('namespace') do |path_before_rename, current_path|
matches_path = MigrationClasses::Route.arel_table[:path].matches(current_path)
namespace = MigrationClasses::Namespace.joins(:route)
+ .allow_cross_joins_across_databases(url: "https://gitlab.com/gitlab-org/gitlab/-/issues/420046")
.find_by(matches_path)&.becomes(MigrationClasses::Namespace) # rubocop: disable Cop/AvoidBecomes
if namespace
diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb
index 5dbf30bad4e..155e35b64f4 100644
--- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb
+++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb
@@ -37,6 +37,8 @@ module Gitlab
reverts_for_type('project') do |path_before_rename, current_path|
matches_path = MigrationClasses::Route.arel_table[:path].matches(current_path)
project = MigrationClasses::Project.joins(:route)
+ .allow_cross_joins_across_databases(url:
+ 'https://gitlab.com/gitlab-org/gitlab/-/issues/421843')
.find_by(matches_path)
if project
@@ -67,6 +69,7 @@ module Gitlab
.matches_any(path_patterns)
@projects_for_paths = MigrationClasses::Project.joins(:route).where(with_paths)
+ .allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/421843')
end
end
end
diff --git a/lib/gitlab/database/schema_validation/schema_inconsistency.rb b/lib/gitlab/database/schema_validation/schema_inconsistency.rb
deleted file mode 100644
index 9f39db5b4c0..00000000000
--- a/lib/gitlab/database/schema_validation/schema_inconsistency.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Database
- module SchemaValidation
- class SchemaInconsistency < ApplicationRecord
- self.table_name = :schema_inconsistencies
-
- belongs_to :issue
-
- validates :object_name, :valitador_name, :table_name, :diff, presence: true
-
- scope :with_open_issues, -> { joins(:issue).where('issue.state_id': Issue.available_states[:opened]) }
- end
- end
- end
-end
diff --git a/lib/gitlab/database/tables_sorted_by_foreign_keys.rb b/lib/gitlab/database/tables_sorted_by_foreign_keys.rb
index b2a7f5442e9..9593cb45947 100644
--- a/lib/gitlab/database/tables_sorted_by_foreign_keys.rb
+++ b/lib/gitlab/database/tables_sorted_by_foreign_keys.rb
@@ -34,7 +34,7 @@ module Gitlab
def all_foreign_keys
@all_foreign_keys ||= @tables.each_with_object(Hash.new { |h, k| h[k] = [] }) do |table, hash|
foreign_keys_for(table).each do |fk|
- hash[fk.to_table] << table
+ hash[fk.referenced_table_name] << table
end
end
end
@@ -45,12 +45,10 @@ module Gitlab
#
# See spec/lib/gitlab/database/tables_sorted_by_foreign_keys_spec.rb
# for an example
- name = ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(table)
+ table = ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(table)
- if name.schema == ::Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA.to_s
- @connection.foreign_keys(name.identifier)
- else
- @connection.foreign_keys(table)
+ Gitlab::Database::SharedModel.using_connection(@connection) do
+ Gitlab::Database::PostgresForeignKey.by_constrained_table_name_or_identifier(table.identifier).load
end
end
end
diff --git a/lib/gitlab/database_importers/work_items/base_type_importer.rb b/lib/gitlab/database_importers/work_items/base_type_importer.rb
index 8f8f44e8392..990fd53a370 100644
--- a/lib/gitlab/database_importers/work_items/base_type_importer.rb
+++ b/lib/gitlab/database_importers/work_items/base_type_importer.rb
@@ -21,7 +21,8 @@ module Gitlab
test_reports: 'Test reports',
notifications: 'Notifications',
current_user_todos: "Current user todos",
- award_emoji: 'Award emoji'
+ award_emoji: 'Award emoji',
+ linked_items: 'Linked items'
}.freeze
WIDGETS_FOR_TYPE = {
@@ -38,7 +39,8 @@ module Gitlab
:health_status,
:notifications,
:current_user_todos,
- :award_emoji
+ :award_emoji,
+ :linked_items
],
incident: [
:assignees,
@@ -47,14 +49,16 @@ module Gitlab
:notes,
:notifications,
:current_user_todos,
- :award_emoji
+ :award_emoji,
+ :linked_items
],
test_case: [
:description,
:notes,
:notifications,
:current_user_todos,
- :award_emoji
+ :award_emoji,
+ :linked_items
],
requirement: [
:description,
@@ -64,7 +68,8 @@ module Gitlab
:test_reports,
:notifications,
:current_user_todos,
- :award_emoji
+ :award_emoji,
+ :linked_items
],
task: [
:assignees,
@@ -78,7 +83,8 @@ module Gitlab
:weight,
:notifications,
:current_user_todos,
- :award_emoji
+ :award_emoji,
+ :linked_items
],
objective: [
:assignees,
@@ -91,7 +97,8 @@ module Gitlab
:progress,
:notifications,
:current_user_todos,
- :award_emoji
+ :award_emoji,
+ :linked_items
],
key_result: [
:assignees,
@@ -104,6 +111,35 @@ module Gitlab
:progress,
:notifications,
:current_user_todos,
+ :award_emoji,
+ :linked_items
+ ],
+ epic: [
+ :assignees,
+ :description,
+ :hierarchy,
+ :labels,
+ :notes,
+ :start_and_due_date,
+ :health_status,
+ :status,
+ :notifications,
+ :award_emoji,
+ :linked_items
+ ],
+ ticket: [
+ :assignees,
+ :labels,
+ :description,
+ :hierarchy,
+ :start_and_due_date,
+ :milestone,
+ :notes,
+ :iteration,
+ :weight,
+ :health_status,
+ :notifications,
+ :current_user_todos,
:award_emoji
]
}.freeze
diff --git a/lib/gitlab/database_importers/work_items/hierarchy_restrictions_importer.rb b/lib/gitlab/database_importers/work_items/hierarchy_restrictions_importer.rb
index 1181c259a5c..4e7a4ec748b 100644
--- a/lib/gitlab/database_importers/work_items/hierarchy_restrictions_importer.rb
+++ b/lib/gitlab/database_importers/work_items/hierarchy_restrictions_importer.rb
@@ -10,12 +10,17 @@ module Gitlab
issue = find_or_create_type(::WorkItems::Type::TYPE_NAMES[:issue])
task = find_or_create_type(::WorkItems::Type::TYPE_NAMES[:task])
incident = find_or_create_type(::WorkItems::Type::TYPE_NAMES[:incident])
+ epic = find_or_create_type(::WorkItems::Type::TYPE_NAMES[:epic])
+ ticket = find_or_create_type(::WorkItems::Type::TYPE_NAMES[:ticket])
restrictions = [
{ parent_type_id: objective.id, child_type_id: objective.id, maximum_depth: 9 },
{ parent_type_id: objective.id, child_type_id: key_result.id, maximum_depth: 1 },
{ parent_type_id: issue.id, child_type_id: task.id, maximum_depth: 1 },
- { parent_type_id: incident.id, child_type_id: task.id, maximum_depth: 1 }
+ { parent_type_id: incident.id, child_type_id: task.id, maximum_depth: 1 },
+ { parent_type_id: epic.id, child_type_id: epic.id, maximum_depth: 9 },
+ { parent_type_id: epic.id, child_type_id: issue.id, maximum_depth: 1 },
+ { parent_type_id: ticket.id, child_type_id: task.id, maximum_depth: 1 }
]
::WorkItems::HierarchyRestriction.upsert_all(
diff --git a/lib/gitlab/dependency_linker/base_linker.rb b/lib/gitlab/dependency_linker/base_linker.rb
index 203cee1fd5e..aba6e0f033a 100644
--- a/lib/gitlab/dependency_linker/base_linker.rb
+++ b/lib/gitlab/dependency_linker/base_linker.rb
@@ -31,7 +31,7 @@ module Gitlab
end
def external_url(name, external_ref)
- return if external_ref =~ GIT_INVALID_URL_REGEX
+ return if GIT_INVALID_URL_REGEX.match?(external_ref)
case external_ref
when /\A#{URL_REGEX}\z/o
diff --git a/lib/gitlab/dependency_linker/cargo_toml_linker.rb b/lib/gitlab/dependency_linker/cargo_toml_linker.rb
index cba4319ce83..7d442429d61 100644
--- a/lib/gitlab/dependency_linker/cargo_toml_linker.rb
+++ b/lib/gitlab/dependency_linker/cargo_toml_linker.rb
@@ -33,8 +33,16 @@ module Gitlab
def link_toml(key, value, type, &url_proc)
if value.is_a? String
link_regex(/^(?<name>#{key})\s*=\s*"#{value}"/, &url_proc)
- else
- link_regex(/^\[#{type}\.(?<name>#{key})]/, &url_proc)
+ elsif value.is_a? Hash
+ # Don't link when using a custom registry
+ return if value['registry']
+
+ # Don't link unless a crates.io version is provided
+ # See https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#multiple-locations
+ return unless value['version']
+
+ link_regex(/^(?<name>#{key})\s*=\s*\{/, &url_proc)
+ link_regex(/^\[#{type}\.(?<name>#{key})\]/, &url_proc)
end
end
diff --git a/lib/gitlab/dependency_linker/composer_json_linker.rb b/lib/gitlab/dependency_linker/composer_json_linker.rb
index 965ed8bb95e..762d7f3e73e 100644
--- a/lib/gitlab/dependency_linker/composer_json_linker.rb
+++ b/lib/gitlab/dependency_linker/composer_json_linker.rb
@@ -13,7 +13,7 @@ module Gitlab
end
def package_url(name)
- "https://packagist.org/packages/#{name}" if name =~ /\A#{REPO_REGEX}\z/o
+ "https://packagist.org/packages/#{name}" if /\A#{REPO_REGEX}\z/o.match?(name)
end
end
end
diff --git a/lib/gitlab/diff/parser.rb b/lib/gitlab/diff/parser.rb
index a506bc3aaa2..ad901dc958b 100644
--- a/lib/gitlab/diff/parser.rb
+++ b/lib/gitlab/diff/parser.rb
@@ -25,7 +25,7 @@ module Gitlab
full_line = line.delete("\n")
- if line =~ /^@@ -/
+ if /^@@ -/.match?(line)
type = "match"
diff_hunk = Gitlab::WordDiff::Segments::DiffHunk.new(line)
diff --git a/lib/gitlab/discussions_diff/highlight_cache.rb b/lib/gitlab/discussions_diff/highlight_cache.rb
index 31b214a4af9..18ff7c28e17 100644
--- a/lib/gitlab/discussions_diff/highlight_cache.rb
+++ b/lib/gitlab/discussions_diff/highlight_cache.rb
@@ -42,10 +42,8 @@ module Gitlab
with_redis do |redis|
Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
if Gitlab::Redis::ClusterUtil.cluster?(redis)
- redis.with_readonly_pipeline do
- Gitlab::Redis::CrossSlot::Pipeline.new(redis).pipelined do |pipeline|
- keys.each { |key| pipeline.get(key) }
- end
+ Gitlab::Redis::CrossSlot::Pipeline.new(redis).pipelined do |pipeline|
+ keys.each { |key| pipeline.get(key) }
end
else
redis.mget(keys)
diff --git a/lib/gitlab/email/reply_parser.rb b/lib/gitlab/email/reply_parser.rb
index e7462b711f1..ecacd02996d 100644
--- a/lib/gitlab/email/reply_parser.rb
+++ b/lib/gitlab/email/reply_parser.rb
@@ -54,7 +54,7 @@ module Gitlab
return "" unless decoded
# Certain trigger phrases that means we didn't parse correctly
- if decoded =~ %r{(Content\-Type\:|multipart/alternative|text/plain)}
+ if %r{(Content\-Type\:|multipart/alternative|text/plain)}.match?(decoded)
return ""
end
diff --git a/lib/gitlab/etag_caching/router/graphql.rb b/lib/gitlab/etag_caching/router/graphql.rb
index 92b21a0859d..7946e768e00 100644
--- a/lib/gitlab/etag_caching/router/graphql.rb
+++ b/lib/gitlab/etag_caching/router/graphql.rb
@@ -14,7 +14,7 @@ module Gitlab
'continuous_integration'
],
[
- %r(\Apipelines/sha/\w{7,40}\z),
+ %r(\Apipelines/sha/\w{#{Gitlab::Git::Commit::MIN_SHA_LENGTH},#{Gitlab::Git::Commit::MAX_SHA_LENGTH}}\z)o,
'ci_editor',
'pipeline_composition'
],
diff --git a/lib/gitlab/etag_caching/store.rb b/lib/gitlab/etag_caching/store.rb
index bc97c88ce85..f36a7a0603c 100644
--- a/lib/gitlab/etag_caching/store.rb
+++ b/lib/gitlab/etag_caching/store.rb
@@ -9,15 +9,15 @@ module Gitlab
SHARED_STATE_NAMESPACE = 'etag:'
def get(key)
- Gitlab::Redis::SharedState.with { |redis| redis.get(redis_shared_state_key(key)) }
+ with_redis { |redis| redis.get(redis_shared_state_key(key)) }
end
def touch(*keys, only_if_missing: false)
etags = keys.map { generate_etag }
Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
- Gitlab::Redis::SharedState.with do |redis|
- redis.pipelined do |pipeline|
+ with_redis do |redis|
+ Gitlab::Redis::CrossSlot::Pipeline.new(redis).pipelined do |pipeline|
keys.each_with_index do |key, i|
pipeline.set(redis_shared_state_key(key), etags[i], ex: EXPIRY_TIME, nx: only_if_missing)
end
@@ -30,6 +30,12 @@ module Gitlab
private
+ def with_redis(&blk)
+ # We use multistore as n interweaving double-write will result in n-1 subsequent requests
+ # becoming a cache-miss, however, 2 interweaving .touch will lead to 1 cache miss anyway.
+ Gitlab::Redis::EtagCache.with(&blk) # rubocop:disable CodeReuse/ActiveRecord
+ end
+
def generate_etag
SecureRandom.hex
end
diff --git a/lib/gitlab/exclusive_lease.rb b/lib/gitlab/exclusive_lease.rb
index 0b18a337707..8679f17eb9b 100644
--- a/lib/gitlab/exclusive_lease.rb
+++ b/lib/gitlab/exclusive_lease.rb
@@ -12,6 +12,8 @@ module Gitlab
# ExclusiveLease.
#
class ExclusiveLease
+ include Gitlab::Utils::StrongMemoize
+
PREFIX = 'gitlab:exclusive_lease'
NoKey = Class.new(ArgumentError)
@@ -31,7 +33,7 @@ module Gitlab
EOS
def self.get_uuid(key)
- Gitlab::Redis::SharedState.with do |redis|
+ with_read_redis do |redis|
redis.get(redis_shared_state_key(key)) || false
end
end
@@ -61,7 +63,7 @@ module Gitlab
def self.cancel(key, uuid)
return unless key.present?
- Gitlab::Redis::SharedState.with do |redis|
+ with_write_redis do |redis|
redis.eval(LUA_CANCEL_SCRIPT, keys: [ensure_prefixed_key(key)], argv: [uuid])
end
end
@@ -84,6 +86,21 @@ module Gitlab
redis.del(key)
end
end
+
+ Gitlab::Redis::ClusterSharedState.with do |redis|
+ redis.scan_each(match: redis_shared_state_key(scope)).each do |key|
+ redis.del(key)
+ end
+ end
+ end
+
+ def self.use_cluster_shared_state?
+ Gitlab::SafeRequestStore[:use_cluster_shared_state] ||=
+ Feature.enabled?(:use_cluster_shared_state_for_exclusive_lease)
+ end
+
+ def self.use_double_lock?
+ Gitlab::SafeRequestStore[:use_double_lock] ||= Feature.enabled?(:enable_exclusive_lease_double_lock_rw)
end
def initialize(key, uuid: nil, timeout:)
@@ -95,10 +112,23 @@ module Gitlab
# Try to obtain the lease. Return lease UUID on success,
# false if the lease is already taken.
def try_obtain
+ return try_obtain_with_new_lock if self.class.use_cluster_shared_state?
+
# Performing a single SET is atomic
- Gitlab::Redis::SharedState.with do |redis|
- redis.set(@redis_shared_state_key, @uuid, nx: true, ex: @timeout) && @uuid
- end
+ obtained = set_lease(Gitlab::Redis::SharedState) && @uuid
+
+ # traffic to new store is minimal since only the first lock holder can run SETNX in ClusterSharedState
+ return false unless obtained
+ return obtained unless self.class.use_double_lock?
+ return obtained if same_store # 2nd setnx will surely fail if store are the same
+
+ second_lock_obtained = set_lease(Gitlab::Redis::ClusterSharedState) && @uuid
+
+ # cancel is safe since it deletes key only if value matches uuid
+ # i.e. it will not delete the held lock on ClusterSharedState
+ cancel unless second_lock_obtained
+
+ second_lock_obtained
end
# This lease is waiting to obtain
@@ -109,7 +139,7 @@ module Gitlab
# Try to renew an existing lease. Return lease UUID on success,
# false if the lease is taken by a different UUID or inexistent.
def renew
- Gitlab::Redis::SharedState.with do |redis|
+ self.class.with_write_redis do |redis|
result = redis.eval(LUA_RENEW_SCRIPT, keys: [@redis_shared_state_key], argv: [@uuid, @timeout])
result == @uuid
end
@@ -117,7 +147,7 @@ module Gitlab
# Returns true if the key for this lease is set.
def exists?
- Gitlab::Redis::SharedState.with do |redis|
+ self.class.with_read_redis do |redis|
redis.exists?(@redis_shared_state_key) # rubocop:disable CodeReuse/ActiveRecord
end
end
@@ -126,17 +156,66 @@ module Gitlab
#
# This method will return `nil` if no TTL could be obtained.
def ttl
- Gitlab::Redis::SharedState.with do |redis|
+ self.class.with_read_redis do |redis|
ttl = redis.ttl(@redis_shared_state_key)
ttl if ttl > 0
end
end
+ # rubocop:disable CodeReuse/ActiveRecord
+ def self.with_write_redis(&blk)
+ if use_cluster_shared_state?
+ result = Gitlab::Redis::ClusterSharedState.with(&blk)
+ Gitlab::Redis::SharedState.with(&blk)
+
+ result
+ elsif use_double_lock?
+ result = Gitlab::Redis::SharedState.with(&blk)
+ Gitlab::Redis::ClusterSharedState.with(&blk)
+
+ result
+ else
+ Gitlab::Redis::SharedState.with(&blk)
+ end
+ end
+
+ def self.with_read_redis(&blk)
+ if use_cluster_shared_state?
+ Gitlab::Redis::ClusterSharedState.with(&blk)
+ elsif use_double_lock?
+ Gitlab::Redis::SharedState.with(&blk) || Gitlab::Redis::ClusterSharedState.with(&blk)
+ else
+ Gitlab::Redis::SharedState.with(&blk)
+ end
+ end
+ # rubocop:enable CodeReuse/ActiveRecord
+
# Gives up this lease, allowing it to be obtained by others.
def cancel
self.class.cancel(@redis_shared_state_key, @uuid)
end
+
+ private
+
+ def set_lease(redis_class)
+ redis_class.with do |redis|
+ redis.set(@redis_shared_state_key, @uuid, nx: true, ex: @timeout)
+ end
+ end
+
+ def try_obtain_with_new_lock
+ # checks shared-state to avoid 2 versions of the application acquiring 1 lock
+ # wait for held lock to expire or yielded in case any process on old version is running
+ return false if Gitlab::Redis::SharedState.with { |c| c.exists?(@redis_shared_state_key) } # rubocop:disable CodeReuse/ActiveRecord
+
+ set_lease(Gitlab::Redis::ClusterSharedState) && @uuid
+ end
+
+ def same_store
+ Gitlab::Redis::ClusterSharedState.with(&:id) == Gitlab::Redis::SharedState.with(&:id) # rubocop:disable CodeReuse/ActiveRecord
+ end
+ strong_memoize_attr :same_store
end
end
diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb
index ef5c242e68a..4e574d6de74 100644
--- a/lib/gitlab/git.rb
+++ b/lib/gitlab/git.rb
@@ -8,7 +8,7 @@ module Gitlab
# https://github.com/git/git/blob/3ad8b5bf26362ac67c9020bf8c30eee54a84f56d/cache.h#L1011-L1012
EMPTY_TREE_ID = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'
BLANK_SHA = ('0' * 40).freeze
- COMMIT_ID = /\A[0-9a-f]{40}\z/.freeze
+ COMMIT_ID = /\A#{Gitlab::Git::Commit::RAW_FULL_SHA_PATTERN}\z/.freeze
TAG_REF_PREFIX = "refs/tags/"
BRANCH_REF_PREFIX = "refs/heads/"
diff --git a/lib/gitlab/git/blame.rb b/lib/gitlab/git/blame.rb
index 30977adaea1..21d2eaec041 100644
--- a/lib/gitlab/git/blame.rb
+++ b/lib/gitlab/git/blame.rb
@@ -18,7 +18,7 @@ module Gitlab
def each
@blames.each do |blame|
- yield(blame.commit, blame.line, blame.previous_path)
+ yield(blame.commit, blame.line, blame.previous_path, blame.span)
end
end
@@ -49,12 +49,12 @@ module Gitlab
output.split("\n").each do |line|
if line[0, 1] == "\t"
lines << line[1, line.size]
- elsif m = /^(\w{40}) (\d+) (\d+)/.match(line)
+ elsif m = /^(\w{40}) (\d+) (\d+)\s?(\d+)?/.match(line)
# Removed these instantiations for performance but keeping them for reference:
- # commit_id, old_lineno, lineno = m[1], m[2].to_i, m[3].to_i
+ # commit_id, old_lineno, lineno, span = m[1], m[2].to_i, m[3].to_i, m[4].to_i
commit_id = m[1]
commits[commit_id] = nil unless commits.key?(commit_id)
- info[m[3].to_i] = [commit_id, m[2].to_i]
+ info[m[3].to_i] = [commit_id, m[2].to_i, m[4].to_i]
# Assumption: the first line returned by git blame is lowest-numbered
# This is true unless we start passing it `--incremental`.
@@ -72,13 +72,14 @@ module Gitlab
end
# get it together
- info.sort.each do |lineno, (commit_id, old_lineno)|
+ info.sort.each do |lineno, (commit_id, old_lineno, span)|
final << BlameLine.new(
lineno,
old_lineno,
commits[commit_id],
lines[lineno - start_line],
- previous_paths[commit_id]
+ previous_paths[commit_id],
+ span
)
end
@@ -87,14 +88,15 @@ module Gitlab
end
class BlameLine
- attr_accessor :lineno, :oldlineno, :commit, :line, :previous_path
+ attr_accessor :lineno, :oldlineno, :commit, :line, :previous_path, :span
- def initialize(lineno, oldlineno, commit, line, previous_path)
+ def initialize(lineno, oldlineno, commit, line, previous_path, span)
@lineno = lineno
@oldlineno = oldlineno
@commit = commit
@line = line
@previous_path = previous_path
+ @span = span
end
end
end
diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb
index c0601c7795c..571dde6fcfc 100644
--- a/lib/gitlab/git/commit.rb
+++ b/lib/gitlab/git/commit.rb
@@ -12,7 +12,20 @@ module Gitlab
attr_accessor :raw_commit, :head
MAX_COMMIT_MESSAGE_DISPLAY_SIZE = 10.megabytes
+
+ SHA1_LENGTH = 40
+ SHA256_LENGTH = 64
+
MIN_SHA_LENGTH = 7
+ MAX_SHA_LENGTH = SHA256_LENGTH
+
+ RAW_SHA_PATTERN = "\\h{#{MIN_SHA_LENGTH},#{MAX_SHA_LENGTH}}".freeze
+ SHA_PATTERN = /#{RAW_SHA_PATTERN}/
+ # Match a full SHA. Note that because this expression is not anchored it will match any SHA that is at
+ # least SHA1_LENGTH long.
+ RAW_FULL_SHA_PATTERN = "\\h{#{SHA1_LENGTH}}(?:\\h{#{SHA256_LENGTH - SHA1_LENGTH}})?".freeze
+ FULL_SHA_PATTERN = /#{RAW_FULL_SHA_PATTERN}/
+
SERIALIZE_KEYS = [
:id, :message, :parent_ids,
:authored_date, :author_name, :author_email,
@@ -226,6 +239,12 @@ module Gitlab
id.to_s[0..length]
end
+ def tree_id
+ return unless raw_commit
+
+ raw_commit.tree_id
+ end
+
def safe_message
@safe_message ||= message
end
diff --git a/lib/gitlab/git/diff_tree.rb b/lib/gitlab/git/diff_tree.rb
new file mode 100644
index 00000000000..df48baeb1c3
--- /dev/null
+++ b/lib/gitlab/git/diff_tree.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Git
+ # Represents a tree-ish object for git diff-tree command
+ # See: https://git-scm.com/docs/git-diff-tree
+ class DiffTree
+ attr_reader :left_tree_id, :right_tree_id
+
+ def initialize(left_tree_id, right_tree_id)
+ @left_tree_id = left_tree_id
+ @right_tree_id = right_tree_id
+ end
+
+ def self.from_commit(commit)
+ return unless commit.tree_id
+
+ parent_tree_id =
+ if commit.parent_ids.blank?
+ Gitlab::Git::EMPTY_TREE_ID
+ else
+ parent_id = commit.parent_ids.first
+ commit.repository.commit(parent_id).tree_id
+ end
+
+ new(parent_tree_id, commit.tree_id)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git/gitmodules_parser.rb b/lib/gitlab/git/gitmodules_parser.rb
index 92940c352d3..a5a84d79720 100644
--- a/lib/gitlab/git/gitmodules_parser.rb
+++ b/lib/gitlab/git/gitmodules_parser.rb
@@ -50,7 +50,7 @@ module Gitlab
@content.split("\n").each_with_object(iterator) do |text, iterator|
text.chomp!
- next if text =~ /^\s*#/
+ next if /^\s*#/.match?(text)
if text =~ /\A\[submodule "(?<name>[^"]+)"\]\z/
iterator.start_section($~[:name])
diff --git a/lib/gitlab/git/object_pool.rb b/lib/gitlab/git/object_pool.rb
index d0577d7a4ff..4b46fa5f82a 100644
--- a/lib/gitlab/git/object_pool.rb
+++ b/lib/gitlab/git/object_pool.rb
@@ -12,6 +12,17 @@ module Gitlab
attr_reader :storage, :relative_path, :source_repository, :gl_project_path
+ def self.init_from_gitaly(gitaly_object_pool, source_repository)
+ repository = gitaly_object_pool.repository
+
+ new(
+ repository.storage_name,
+ repository.relative_path,
+ source_repository,
+ repository.gl_project_path
+ )
+ end
+
def initialize(storage, relative_path, source_repository, gl_project_path)
@storage = storage
@relative_path = relative_path
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 71be986882c..d27f721bb2c 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -47,6 +47,8 @@ module Gitlab
attr_reader :storage, :gl_repository, :gl_project_path, :container
+ delegate :list_all_blobs, to: :gitaly_blob_client
+
# This remote name has to be stable for all types of repositories that
# can join an object pool. If it's structure ever changes, a migration
# has to be performed on the object pools to update the remote names.
@@ -338,16 +340,28 @@ module Gitlab
# Return repo size in megabytes
def size
if Feature.enabled?(:use_repository_info_for_repository_size)
- bytes = gitaly_repository_client.repository_info.size
-
- (bytes.to_f / 1024 / 1024).round(2)
+ repository_info_size_megabytes
else
kilobytes = gitaly_repository_client.repository_size
-
(kilobytes.to_f / 1024).round(2)
end
end
+ # Return repository recent objects size in mebibytes
+ #
+ # This differs from the #size method in that it does not include the size of:
+ # - stale objects
+ # - cruft packs of unreachable objects
+ #
+ # see: https://gitlab.com/gitlab-org/gitaly/-/blob/257ee33ca268d48c8f99dcbfeaaf7d8b19e07f06/internal/gitaly/service/repository/repository_info.go#L41-62
+ def recent_objects_size
+ wrapped_gitaly_errors do
+ recent_size_in_bytes = gitaly_repository_client.repository_info.objects.recent_size
+
+ Gitlab::Utils.bytes_to_megabytes(recent_size_in_bytes)
+ end
+ end
+
# Return git object directory size in bytes
def object_directory_size
gitaly_repository_client.get_object_directory_size.to_f * 1024
@@ -525,15 +539,13 @@ module Gitlab
empty_diff_stats
end
- def find_changed_paths(commits, merge_commit_diff_mode: nil)
- processed_commits = commits.reject { |ref| ref.blank? || Gitlab::Git.blank_ref?(ref) }
+ def find_changed_paths(treeish_objects, merge_commit_diff_mode: nil)
+ processed_objects = treeish_objects.compact
- return [] if processed_commits.empty?
+ return [] if processed_objects.empty?
wrapped_gitaly_errors do
- gitaly_commit_client.find_changed_paths(
- processed_commits, merge_commit_diff_mode: merge_commit_diff_mode
- )
+ gitaly_commit_client.find_changed_paths(processed_objects, merge_commit_diff_mode: merge_commit_diff_mode)
end
rescue CommandError, TypeError, NoRepository
[]
@@ -741,6 +753,16 @@ module Gitlab
raise DeleteBranchError, e
end
+ def async_delete_refs(*refs)
+ raise "async_delete_refs only supports project repositories" unless container.is_a?(Project)
+
+ records = refs.map do |ref|
+ BatchedGitRefUpdates::Deletion.new(project_id: container.id, ref: ref, created_at: Time.current, updated_at: Time.current)
+ end
+
+ BatchedGitRefUpdates::Deletion.bulk_insert!(records)
+ end
+
def delete_refs(...)
wrapped_gitaly_errors do
gitaly_delete_refs(...)
@@ -956,6 +978,18 @@ module Gitlab
end
end
+ def rebase_to_ref(user, source_sha:, target_ref:, first_parent_ref:, expected_old_oid: "")
+ wrapped_gitaly_errors do
+ gitaly_operation_client.user_rebase_to_ref(
+ user,
+ source_sha: source_sha,
+ target_ref: target_ref,
+ first_parent_ref: first_parent_ref,
+ expected_old_oid: expected_old_oid
+ )
+ end
+ end
+
def squash(user, start_sha:, end_sha:, author:, message:)
wrapped_gitaly_errors do
gitaly_operation_client.user_squash(user, start_sha, end_sha, author, message)
@@ -1164,8 +1198,26 @@ module Gitlab
end
end
+ def get_patch_id(old_revision, new_revision)
+ wrapped_gitaly_errors do
+ gitaly_commit_client.get_patch_id(old_revision, new_revision)
+ end
+ end
+
+ def object_pool
+ wrapped_gitaly_errors do
+ gitaly_repository_client.object_pool.object_pool
+ end
+ end
+
private
+ def repository_info_size_megabytes
+ bytes = gitaly_repository_client.repository_info.size
+
+ Gitlab::Utils.bytes_to_megabytes(bytes).round(2)
+ end
+
def empty_diff_stats
Gitlab::Git::DiffStatsCollection.new([])
end
diff --git a/lib/gitlab/git/rugged_impl/tree.rb b/lib/gitlab/git/rugged_impl/tree.rb
index c7a981c7dd4..bc3ff01e1e2 100644
--- a/lib/gitlab/git/rugged_impl/tree.rb
+++ b/lib/gitlab/git/rugged_impl/tree.rb
@@ -16,7 +16,7 @@ module Gitlab
TREE_SORT_ORDER = { tree: 0, blob: 1, commit: 2 }.freeze
override :tree_entries
- def tree_entries(repository, sha, path, recursive, skip_flat_paths, pagination_params = nil)
+ def tree_entries(repository, sha, path, recursive, skip_flat_paths, rescue_not_found, pagination_params = nil)
if use_rugged?(repository, :rugged_tree_entries)
entries = execute_rugged_call(
:tree_entries_with_flat_path_from_rugged, repository, sha, path, recursive, skip_flat_paths)
diff --git a/lib/gitlab/git/tree.rb b/lib/gitlab/git/tree.rb
index 140dc791135..0895c0b8a22 100644
--- a/lib/gitlab/git/tree.rb
+++ b/lib/gitlab/git/tree.rb
@@ -15,22 +15,27 @@ module Gitlab
# Uses rugged for raw objects
#
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/320
- def where(repository, sha, path = nil, recursive = false, skip_flat_paths = true, pagination_params = nil)
+ def where(
+ repository, sha, path = nil, recursive = false, skip_flat_paths = true, rescue_not_found = true,
+ pagination_params = nil)
path = nil if path == '' || path == '/'
- tree_entries(repository, sha, path, recursive, skip_flat_paths, pagination_params)
+ tree_entries(repository, sha, path, recursive, skip_flat_paths, rescue_not_found, pagination_params)
end
- def tree_entries(repository, sha, path, recursive, skip_flat_paths, pagination_params = nil)
+ def tree_entries(repository, sha, path, recursive, skip_flat_paths, rescue_not_found, pagination_params = nil)
wrapped_gitaly_errors do
repository.gitaly_commit_client.tree_entries(
repository, sha, path, recursive, skip_flat_paths, pagination_params)
end
# Incorrect revision or path could lead to index error.
- # We silently handle such errors by returning an empty set of entries and cursor.
- rescue Gitlab::Git::Index::IndexError
- [[], nil]
+ # We silently handle such errors by returning an empty set of entries and cursor
+ # unless the parameter rescue_not_found is set to false.
+ rescue Gitlab::Git::Index::IndexError => e
+ return [[], nil] if rescue_not_found
+
+ raise e
end
private
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index 9d19695363a..a28952ab7bc 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -513,7 +513,7 @@ module Gitlab
end
def check_size_against_limit(size)
- if size_checker.changes_will_exceed_size_limit?(size)
+ if size_checker.changes_will_exceed_size_limit?(size, project)
raise ForbiddenError, size_checker.error_message.new_changes_error
end
end
diff --git a/lib/gitlab/gitaly_client/blob_service.rb b/lib/gitlab/gitaly_client/blob_service.rb
index 6d87c3329d7..cd7b9a3c095 100644
--- a/lib/gitlab/gitaly_client/blob_service.rb
+++ b/lib/gitlab/gitaly_client/blob_service.rb
@@ -22,6 +22,32 @@ module Gitlab
consume_blob_response(response)
end
+ def list_all_blobs(limit: nil, bytes_limit: 0, dynamic_timeout: nil, ignore_alternate_object_directories: false)
+ repository = @gitaly_repo
+
+ if ignore_alternate_object_directories
+ repository = @gitaly_repo.dup.tap do |g_repo|
+ g_repo.git_alternate_object_directories = Google::Protobuf::RepeatedField.new(:string)
+ end
+ end
+
+ request = Gitaly::ListAllBlobsRequest.new(
+ repository: repository,
+ limit: limit,
+ bytes_limit: bytes_limit
+ )
+
+ timeout =
+ if dynamic_timeout
+ [dynamic_timeout, GitalyClient.medium_timeout].min
+ else
+ GitalyClient.medium_timeout
+ end
+
+ response = Gitlab::GitalyClient.call(repository.storage_name, :blob_service, :list_all_blobs, request, timeout: timeout)
+ GitalyClient::BlobsStitcher.new(GitalyClient::ListBlobsAdapter.new(response))
+ end
+
def list_blobs(revisions, limit: 0, bytes_limit: 0, with_paths: false, dynamic_timeout: nil)
request = Gitaly::ListBlobsRequest.new(
repository: @gitaly_repo,
diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb
index c10f780665c..1ef5b0f96c2 100644
--- a/lib/gitlab/gitaly_client/commit_service.rb
+++ b/lib/gitlab/gitaly_client/commit_service.rb
@@ -274,8 +274,10 @@ module Gitlab
# else # defaults to :include_merges behavior
# ['foo_bar.rb', 'bar_baz.rb'],
#
- def find_changed_paths(commits, merge_commit_diff_mode: nil)
- request = find_changed_paths_request(commits, merge_commit_diff_mode)
+ def find_changed_paths(objects, merge_commit_diff_mode: nil)
+ request = find_changed_paths_request(objects, merge_commit_diff_mode)
+
+ return [] if request.nil?
response = gitaly_client_call(@repository.storage, :diff_service, :find_changed_paths, request, timeout: GitalyClient.medium_timeout)
response.flat_map do |msg|
@@ -587,6 +589,15 @@ module Gitlab
Hash[commit_refs]
end
+ def get_patch_id(old_revision, new_revision)
+ request = Gitaly::GetPatchIDRequest
+ .new(repository: @gitaly_repo, old_revision: old_revision, new_revision: new_revision)
+
+ response = gitaly_client_call(@repository.storage, :diff_service, :get_patch_id, request, timeout: GitalyClient.medium_timeout)
+
+ response.patch_id
+ end
+
private
def parse_global_options!(options)
@@ -646,16 +657,27 @@ module Gitlab
response.commit
end
- def find_changed_paths_request(commits, merge_commit_diff_mode)
+ def find_changed_paths_request(objects, merge_commit_diff_mode)
diff_mode = MERGE_COMMIT_DIFF_MODES[merge_commit_diff_mode] if Feature.enabled?(:merge_commit_diff_modes)
- commit_requests = commits.map do |commit|
- Gitaly::FindChangedPathsRequest::Request.new(
- commit_request: Gitaly::FindChangedPathsRequest::Request::CommitRequest.new(commit_revision: commit)
- )
+ requests = objects.filter_map do |object|
+ case object
+ when Gitlab::Git::DiffTree
+ Gitaly::FindChangedPathsRequest::Request.new(
+ tree_request: Gitaly::FindChangedPathsRequest::Request::TreeRequest.new(left_tree_revision: object.left_tree_id, right_tree_revision: object.right_tree_id)
+ )
+ when Commit, Gitlab::Git::Commit
+ next if object.sha.blank? || Gitlab::Git.blank_ref?(object.sha)
+
+ Gitaly::FindChangedPathsRequest::Request.new(
+ commit_request: Gitaly::FindChangedPathsRequest::Request::CommitRequest.new(commit_revision: object.sha)
+ )
+ end
end
- Gitaly::FindChangedPathsRequest.new(repository: @gitaly_repo, requests: commit_requests, merge_commit_diff_mode: diff_mode)
+ return if requests.blank?
+
+ Gitaly::FindChangedPathsRequest.new(repository: @gitaly_repo, requests: requests, merge_commit_diff_mode: diff_mode)
end
def path_error_message(path_error)
diff --git a/lib/gitlab/gitaly_client/conflicts_service.rb b/lib/gitlab/gitaly_client/conflicts_service.rb
index 38f648ccc31..ffe65307c80 100644
--- a/lib/gitlab/gitaly_client/conflicts_service.rb
+++ b/lib/gitlab/gitaly_client/conflicts_service.rb
@@ -17,19 +17,21 @@ module Gitlab
self.repository_actor = repository
end
- def list_conflict_files(allow_tree_conflicts: false)
+ def list_conflict_files(allow_tree_conflicts: false, skip_content: false)
request = Gitaly::ListConflictFilesRequest.new(
repository: @gitaly_repo,
our_commit_oid: @our_commit_oid,
their_commit_oid: @their_commit_oid,
- allow_tree_conflicts: allow_tree_conflicts
+ allow_tree_conflicts: allow_tree_conflicts,
+ skip_content: skip_content
)
response = gitaly_client_call(@repository.storage, :conflicts_service, :list_conflict_files, request, timeout: GitalyClient.long_timeout)
GitalyClient::ConflictFilesStitcher.new(response, @gitaly_repo)
end
def conflicts?
- list_conflict_files.any?
+ skip_content = Feature.enabled?(:skip_conflict_files_in_gitaly, type: :experiment)
+ list_conflict_files(skip_content: skip_content).any?
rescue GRPC::FailedPrecondition, GRPC::Unknown
# The server raises FailedPrecondition when it encounters
# ConflictSideMissing, which means a conflict exists but its `theirs` or
diff --git a/lib/gitlab/gitaly_client/operation_service.rb b/lib/gitlab/gitaly_client/operation_service.rb
index 67e135bb530..fe76543548b 100644
--- a/lib/gitlab/gitaly_client/operation_service.rb
+++ b/lib/gitlab/gitaly_client/operation_service.rb
@@ -135,7 +135,7 @@ module Gitlab
end
end
- def user_merge_to_ref(user, source_sha:, branch:, target_ref:, message:, first_parent_ref:)
+ def user_merge_to_ref(user, source_sha:, branch:, target_ref:, message:, first_parent_ref:, expected_old_oid: "")
request = Gitaly::UserMergeToRefRequest.new(
repository: @gitaly_repo,
source_sha: source_sha,
@@ -144,6 +144,7 @@ module Gitlab
user: Gitlab::Git::User.from_gitlab(user).to_gitaly,
message: encode_binary(message),
first_parent_ref: encode_binary(first_parent_ref),
+ expected_old_oid: expected_old_oid,
timestamp: Google::Protobuf::Timestamp.new(seconds: Time.now.utc.to_i)
)
@@ -344,6 +345,23 @@ module Gitlab
request_enum.close
end
+ def user_rebase_to_ref(user, source_sha:, target_ref:, first_parent_ref:, expected_old_oid: "")
+ request = Gitaly::UserRebaseToRefRequest.new(
+ user: Gitlab::Git::User.from_gitlab(user).to_gitaly,
+ repository: @gitaly_repo,
+ source_sha: source_sha,
+ target_ref: encode_binary(target_ref),
+ first_parent_ref: encode_binary(first_parent_ref),
+ expected_old_oid: expected_old_oid,
+ timestamp: Google::Protobuf::Timestamp.new(seconds: Time.now.utc.to_i)
+ )
+
+ response = gitaly_client_call(@repository.storage, :operation_service,
+ :user_rebase_to_ref, request, timeout: GitalyClient.long_timeout)
+
+ response.commit_id
+ end
+
def user_squash(user, start_sha, end_sha, author, message, time = Time.now.utc)
request = Gitaly::UserSquashRequest.new(
repository: @gitaly_repo,
diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb
index b5b7d94b4d0..b2d5f9c7e13 100644
--- a/lib/gitlab/gitaly_client/repository_service.rb
+++ b/lib/gitlab/gitaly_client/repository_service.rb
@@ -363,6 +363,18 @@ module Gitlab
)
end
+ def object_pool
+ request = Gitaly::GetObjectPoolRequest.new(repository: @gitaly_repo)
+
+ gitaly_client_call(
+ @storage,
+ :object_pool_service,
+ :get_object_pool,
+ request,
+ timeout: GitalyClient.medium_timeout
+ )
+ end
+
private
def search_results_from_response(gitaly_response, options = {})
diff --git a/lib/gitlab/github_import.rb b/lib/gitlab/github_import.rb
index 24e77363e1b..3b19b9d16d2 100644
--- a/lib/gitlab/github_import.rb
+++ b/lib/gitlab/github_import.rb
@@ -16,7 +16,7 @@ module Gitlab
}
if token_pool
- ClientPool.new(token_pool: token_pool, **options)
+ ClientPool.new(token_pool: token_pool.append(token_to_use), **options)
else
Client.new(token_to_use, **options)
end
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index ff171c24549..27c4ec2f7be 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -51,6 +51,7 @@ module Gitlab
gon.dot_com = Gitlab.com?
gon.uf_error_prefix = ::Gitlab::Utils::ErrorMessage::UF_ERROR_PREFIX
gon.pat_prefix = Gitlab::CurrentSettings.current_application_settings.personal_access_token_prefix
+ gon.use_new_navigation = NavHelper.show_super_sidebar?(current_user)
gon.diagramsnet_url = Gitlab::CurrentSettings.diagramsnet_url if Gitlab::CurrentSettings.diagramsnet_enabled
@@ -61,7 +62,6 @@ module Gitlab
gon.current_user_fullname = current_user.name
gon.current_user_avatar_url = current_user.avatar_url
gon.time_display_relative = current_user.time_display_relative
- gon.use_new_navigation = NavHelper.show_super_sidebar?(current_user)
end
# Initialize gon.features with any flags that should be
@@ -76,6 +76,7 @@ module Gitlab
push_frontend_feature_flag(:remove_monitor_metrics)
push_frontend_feature_flag(:gitlab_duo, current_user)
push_frontend_feature_flag(:custom_emoji)
+ push_frontend_feature_flag(:super_sidebar_flyout_menus, current_user)
end
# Exposes the state of a feature flag to the frontend code.
diff --git a/lib/gitlab/graphql/loaders/full_path_model_loader.rb b/lib/gitlab/graphql/loaders/full_path_model_loader.rb
index 2ea3fa71d5e..a99b8c81930 100644
--- a/lib/gitlab/graphql/loaders/full_path_model_loader.rb
+++ b/lib/gitlab/graphql/loaders/full_path_model_loader.rb
@@ -20,7 +20,10 @@ module Gitlab
# this logic cannot be placed in the NamespaceResolver due to N+1
scope = scope.without_project_namespaces if scope == Namespace
# `with_route` avoids an N+1 calculating full_path
- scope.where_full_path_in(full_paths).with_route.each do |model_instance|
+ scope = scope.where_full_path_in(full_paths).with_route
+ .allow_cross_joins_across_databases(url: "https://gitlab.com/gitlab-org/gitlab/-/issues/420046")
+
+ scope.each do |model_instance|
loader.call(model_instance.full_path.downcase, model_instance)
end
end
diff --git a/lib/gitlab/hook_data/issue_builder.rb b/lib/gitlab/hook_data/issue_builder.rb
index bd0603c5e5b..aacc3f8b821 100644
--- a/lib/gitlab/hook_data/issue_builder.rb
+++ b/lib/gitlab/hook_data/issue_builder.rb
@@ -11,6 +11,7 @@ module Gitlab
time_change
severity
escalation_status
+ customer_relations_contacts
].freeze
end
@@ -56,7 +57,8 @@ module Gitlab
assignee_id: issue.assignee_ids.first, # This key is deprecated
labels: issue.labels_hook_attrs,
state: issue.state,
- severity: issue.severity
+ severity: issue.severity,
+ customer_relations_contacts: issue.customer_relations_contacts.map(&:hook_attrs)
}
if issue.supports_escalation? && issue.escalation_status
diff --git a/lib/gitlab/hook_data/merge_request_builder.rb b/lib/gitlab/hook_data/merge_request_builder.rb
index a6ca8323a20..22af06ba09b 100644
--- a/lib/gitlab/hook_data/merge_request_builder.rb
+++ b/lib/gitlab/hook_data/merge_request_builder.rb
@@ -10,6 +10,7 @@ module Gitlab
blocking_discussions_resolved
created_at
description
+ draft
head_pipeline_id
id
iid
@@ -55,6 +56,7 @@ module Gitlab
target: merge_request.target_project.hook_attrs,
last_commit: merge_request.diff_head_commit&.hook_attrs,
work_in_progress: merge_request.draft?,
+ draft: merge_request.draft?,
total_time_spent: merge_request.total_time_spent,
time_change: merge_request.time_change,
human_total_time_spent: merge_request.human_total_time_spent,
diff --git a/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb
index fabc02af70a..6b154c7033f 100644
--- a/lib/gitlab/i18n.rb
+++ b/lib/gitlab/i18n.rb
@@ -44,8 +44,8 @@ module Gitlab
TRANSLATION_LEVELS = {
'bg' => 0,
'cs_CZ' => 0,
- 'da_DK' => 30,
- 'de' => 99,
+ 'da_DK' => 29,
+ 'de' => 96,
'en' => 100,
'eo' => 0,
'es' => 29,
@@ -55,19 +55,19 @@ module Gitlab
'id_ID' => 0,
'it' => 1,
'ja' => 99,
- 'ko' => 17,
- 'nb_NO' => 22,
+ 'ko' => 20,
+ 'nb_NO' => 21,
'nl_NL' => 0,
'pl_PL' => 3,
- 'pt_BR' => 57,
- 'ro_RO' => 80,
- 'ru' => 23,
- 'si_LK' => 10,
+ 'pt_BR' => 55,
+ 'ro_RO' => 78,
+ 'ru' => 22,
+ 'si_LK' => 9,
'tr_TR' => 9,
- 'uk' => 53,
+ 'uk' => 52,
'zh_CN' => 98,
'zh_HK' => 1,
- 'zh_TW' => 100
+ 'zh_TW' => 99
}.freeze
private_constant :TRANSLATION_LEVELS
diff --git a/lib/gitlab/import_export/command_line_util.rb b/lib/gitlab/import_export/command_line_util.rb
index e2f365fcbf8..924ca4e83ea 100644
--- a/lib/gitlab/import_export/command_line_util.rb
+++ b/lib/gitlab/import_export/command_line_util.rb
@@ -56,7 +56,7 @@ module Gitlab
private
- def download_or_copy_upload(uploader, upload_path, size_limit: nil)
+ def download_or_copy_upload(uploader, upload_path, size_limit: 0)
if uploader.upload.local?
copy_files(uploader.path, upload_path)
else
@@ -64,7 +64,7 @@ module Gitlab
end
end
- def download(url, upload_path, size_limit: nil)
+ def download(url, upload_path, size_limit: 0)
File.open(upload_path, 'wb') do |file|
current_size = 0
@@ -74,7 +74,7 @@ module Gitlab
elsif fragment.code == 200
current_size += fragment.bytesize
- raise FileOversizedError if size_limit.present? && current_size > size_limit
+ raise FileOversizedError if size_limit > 0 && current_size > size_limit
file.write(fragment)
else
diff --git a/lib/gitlab/import_export/decompressed_archive_size_validator.rb b/lib/gitlab/import_export/decompressed_archive_size_validator.rb
index 2e39f3f38c2..3609df89958 100644
--- a/lib/gitlab/import_export/decompressed_archive_size_validator.rb
+++ b/lib/gitlab/import_export/decompressed_archive_size_validator.rb
@@ -5,7 +5,6 @@ module Gitlab
class DecompressedArchiveSizeValidator
include Gitlab::Utils::StrongMemoize
- DEFAULT_MAX_BYTES = 10.gigabytes.freeze
TIMEOUT_LIMIT = 210.seconds
ServiceError = Class.new(StandardError)
@@ -22,7 +21,7 @@ module Gitlab
end
def self.max_bytes
- DEFAULT_MAX_BYTES
+ Gitlab::CurrentSettings.current_application_settings.max_decompressed_archive_size.megabytes
end
private
@@ -52,7 +51,7 @@ module Gitlab
if status.success?
result = stdout.readline
- if result.to_i > @max_bytes
+ if @max_bytes > 0 && result.to_i > @max_bytes
valid_archive = false
log_error('Decompressed archive size limit reached')
diff --git a/lib/gitlab/import_export/file_importer.rb b/lib/gitlab/import_export/file_importer.rb
index 37c83e88ef2..7fb7a9f30a0 100644
--- a/lib/gitlab/import_export/file_importer.rb
+++ b/lib/gitlab/import_export/file_importer.rb
@@ -74,17 +74,21 @@ module Gitlab
download(
import_export_upload.remote_import_url,
@archive_file,
- size_limit: ::Import::GitlabProjects::RemoteFileValidator::FILE_SIZE_LIMIT
+ size_limit: file_size_limit
)
else
download_or_copy_upload(
import_export_upload.import_file,
@archive_file,
- size_limit: ::Import::GitlabProjects::RemoteFileValidator::FILE_SIZE_LIMIT
+ size_limit: file_size_limit
)
end
end
+ def file_size_limit
+ Gitlab::CurrentSettings.current_application_settings.max_import_remote_file_size.megabytes
+ end
+
def remove_import_file
FileUtils.rm_rf(@archive_file)
end
diff --git a/lib/gitlab/import_export/project/base_task.rb b/lib/gitlab/import_export/project/base_task.rb
index 356e261e251..3cbe3cb7153 100644
--- a/lib/gitlab/import_export/project/base_task.rb
+++ b/lib/gitlab/import_export/project/base_task.rb
@@ -4,8 +4,6 @@ module Gitlab
module ImportExport
module Project
class BaseTask
- include Gitlab::WithRequestStore
-
def initialize(opts, logger: Logger.new($stdout))
@project_path = opts.fetch(:project_path)
@file_path = opts.fetch(:file_path)
diff --git a/lib/gitlab/import_export/project/export_task.rb b/lib/gitlab/import_export/project/export_task.rb
index 5e105b4653d..3cd0d3f4c2b 100644
--- a/lib/gitlab/import_export/project/export_task.rb
+++ b/lib/gitlab/import_export/project/export_task.rb
@@ -35,7 +35,7 @@ module Gitlab
end
def with_export
- with_request_store do
+ ::Gitlab::SafeRequestStore.ensure_request_store do
# We are disabling ObjectStorage for `export`
# since when direct upload is enabled, remote storage will be used
# and Gitlab::ImportExport::AfterExportStrategies::MoveFileStrategy will fail to copy exported archive
diff --git a/lib/gitlab/import_export/project/import_export.yml b/lib/gitlab/import_export/project/import_export.yml
index 5986c5de441..850c89c1fb1 100644
--- a/lib/gitlab/import_export/project/import_export.yml
+++ b/lib/gitlab/import_export/project/import_export.yml
@@ -984,9 +984,11 @@ excluded_attributes:
notes:
- :noteable_id
- :review_id
+ - :namespace_id
commit_notes:
- :noteable_id
- :review_id
+ - :namespace_id
label_links:
- :label_id
- :target_id
diff --git a/lib/gitlab/import_export/project/import_task.rb b/lib/gitlab/import_export/project/import_task.rb
index 4ea47a5624a..47cdb630ada 100644
--- a/lib/gitlab/import_export/project/import_task.rb
+++ b/lib/gitlab/import_export/project/import_task.rb
@@ -25,7 +25,7 @@ module Gitlab
# to general Sidekiq clusters/nodes.
def with_isolated_sidekiq_job
Sidekiq::Testing.fake! do
- with_request_store do
+ ::Gitlab::SafeRequestStore.ensure_request_store do
# If you are attempting to import a large project into a development environment,
# you may see Gitaly throw an error about too many calls or invocations.
# This is due to a n+1 calls limit being set for development setups (not enforced in production)
diff --git a/lib/gitlab/internal_events/event_definitions.rb b/lib/gitlab/internal_events/event_definitions.rb
index e1c9faa12de..f3c8092bcb0 100644
--- a/lib/gitlab/internal_events/event_definitions.rb
+++ b/lib/gitlab/internal_events/event_definitions.rb
@@ -8,10 +8,6 @@ module Gitlab
class << self
VALID_UNIQUE_VALUES = %w[user.id project.id namespace.id].freeze
- def clear_events
- @events = nil
- end
-
def load_configurations
@events = load_metric_definitions
nil
diff --git a/lib/gitlab/jwt_authenticatable.rb b/lib/gitlab/jwt_authenticatable.rb
index d7a341b3ba2..b8282163cbc 100644
--- a/lib/gitlab/jwt_authenticatable.rb
+++ b/lib/gitlab/jwt_authenticatable.rb
@@ -13,10 +13,12 @@ module Gitlab
module ClassMethods
include Gitlab::Utils::StrongMemoize
- def decode_jwt(encoded_message, jwt_secret = secret, algorithm: 'HS256', issuer: nil, iat_after: nil)
+ def decode_jwt(
+ encoded_message, jwt_secret = secret, algorithm: 'HS256', issuer: nil, iat_after: nil, audience: nil)
options = { algorithm: algorithm }
options = options.merge(iss: issuer, verify_iss: true) if issuer.present?
options = options.merge(verify_iat: true) if iat_after.present?
+ options = options.merge(aud: audience, verify_aud: true) if audience.present?
decoded_message = JWT.decode(encoded_message, jwt_secret, true, options)
payload = decoded_message[0]
diff --git a/lib/gitlab/kas.rb b/lib/gitlab/kas.rb
index a1e290a54e6..255d8802c1c 100644
--- a/lib/gitlab/kas.rb
+++ b/lib/gitlab/kas.rb
@@ -5,13 +5,14 @@ module Gitlab
INTERNAL_API_REQUEST_HEADER = 'Gitlab-Kas-Api-Request'
VERSION_FILE = 'GITLAB_KAS_VERSION'
JWT_ISSUER = 'gitlab-kas'
+ JWT_AUDIENCE = 'gitlab'
K8S_PROXY_PATH = 'k8s-proxy'
include JwtAuthenticatable
class << self
def verify_api_request(request_headers)
- decode_jwt(request_headers[INTERNAL_API_REQUEST_HEADER], issuer: JWT_ISSUER)
+ decode_jwt(request_headers[INTERNAL_API_REQUEST_HEADER], issuer: JWT_ISSUER, audience: JWT_AUDIENCE)
rescue JWT::DecodeError
nil
end
@@ -54,6 +55,13 @@ module Gitlab
uri.to_s
end
+ def tunnel_ws_url
+ return tunnel_url if ws?
+ return tunnel_url.sub('https', 'wss') if ssl?
+
+ tunnel_url.sub('http', 'ws')
+ end
+
# Return GitLab KAS internal_url
#
# @return [String] internal_url
@@ -67,6 +75,16 @@ module Gitlab
def enabled?
!!Gitlab.config['gitlab_kas']&.fetch('enabled', false)
end
+
+ private
+
+ def ssl?
+ URI(tunnel_url).scheme === 'https'
+ end
+
+ def ws?
+ URI(tunnel_url).scheme.start_with?('ws')
+ end
end
end
end
diff --git a/lib/gitlab/markdown_cache/redis/store.rb b/lib/gitlab/markdown_cache/redis/store.rb
index 52260623c55..f742cb82b8d 100644
--- a/lib/gitlab/markdown_cache/redis/store.rb
+++ b/lib/gitlab/markdown_cache/redis/store.rb
@@ -11,11 +11,9 @@ module Gitlab
Gitlab::Redis::Cache.with do |r|
Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
- r.with_readonly_pipeline do
- Gitlab::Redis::CrossSlot::Pipeline.new(r).pipelined do |pipeline|
- subjects.each do |subject|
- results[subject.cache_key] = new(subject).read(pipeline)
- end
+ Gitlab::Redis::CrossSlot::Pipeline.new(r).pipelined do |pipeline|
+ subjects.each do |subject|
+ results[subject.cache_key] = new(subject).read(pipeline)
end
end
end
diff --git a/lib/gitlab/merge_requests/message_generator.rb b/lib/gitlab/merge_requests/message_generator.rb
index 523e0e665dc..5ca26fdae86 100644
--- a/lib/gitlab/merge_requests/message_generator.rb
+++ b/lib/gitlab/merge_requests/message_generator.rb
@@ -52,6 +52,7 @@ module Gitlab
'description' => ->(merge_request, _, _) { merge_request.description },
'reference' => ->(merge_request, _, _) { merge_request.to_reference(full: true) },
'local_reference' => ->(merge_request, _, _) { merge_request.to_reference(full: false) },
+ 'source_project_id' => ->(merge_request, _, _) { merge_request.source_project.id.to_s },
'first_commit' => -> (merge_request, _, _) {
return unless merge_request.persisted? || merge_request.compare_commits.present?
diff --git a/lib/gitlab/metrics/dashboard/defaults.rb b/lib/gitlab/metrics/dashboard/defaults.rb
deleted file mode 100644
index 6a5f98a18c8..00000000000
--- a/lib/gitlab/metrics/dashboard/defaults.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-# Central point for managing default attributes from within
-# the metrics dashboard module.
-module Gitlab
- module Metrics
- module Dashboard
- module Defaults
- DEFAULT_PANEL_TYPE = 'area-chart'
- end
- end
- end
-end
diff --git a/lib/gitlab/metrics/dashboard/finder.rb b/lib/gitlab/metrics/dashboard/finder.rb
deleted file mode 100644
index 12f7c347b2d..00000000000
--- a/lib/gitlab/metrics/dashboard/finder.rb
+++ /dev/null
@@ -1,95 +0,0 @@
-# frozen_string_literal: true
-
-# Returns DB-supplmented dashboard info for determining
-# the layout of UI. Intended entry-point for the Metrics::Dashboard
-# module.
-module Gitlab
- module Metrics
- module Dashboard
- class Finder
- PREDEFINED_DASHBOARD_LIST = [
- ::Metrics::Dashboard::PodDashboardService,
- ::Metrics::Dashboard::SystemDashboardService
- ].freeze
-
- class << self
- # Returns a formatted dashboard packed with DB info.
- # @param project [Project]
- # @param user [User]
- # @param environment [Environment]
- # @param options [Hash<Symbol,Any>]
- # @param options - embedded [Boolean] Determines whether the
- # dashboard is to be rendered as part of an
- # issue or location other than the primary
- # metrics dashboard UI. Returns only the
- # Memory/CPU charts of the system dash.
- # @param options - dashboard_path [String] Path at which the
- # dashboard can be found. Nil values will
- # default to the system dashboard.
- # @param options - group [String, Group] Title of the group
- # to which a panel might belong. Used by
- # embedded dashboards. If cluster dashboard,
- # refers to the Group corresponding to the cluster.
- # @param options - title [String] Title of the panel.
- # Used by embedded dashboards.
- # @param options - y_label [String] Y-Axis label of
- # a panel. Used by embedded dashboards.
- # @param options - cluster [Cluster]. Used by
- # embedded and un-embedded dashboards.
- # @param options - cluster_type [Symbol] The level of
- # cluster, one of [:admin, :project, :group]. Used by
- # embedded and un-embedded dashboards.
- # @param options - grafana_url [String] URL pointing
- # to a grafana dashboard panel
- # @param options - prometheus_alert_id [Integer] ID of
- # a PrometheusAlert. For dashboard embeds.
- # @return [Hash]
- def find(project, user, options = {})
- service_for(options)
- .new(project, user, options)
- .get_dashboard
- end
-
- # Returns a dashboard without any supplemental info.
- # Returns only full, yml-defined dashboards.
- # @return [Hash]
- def find_raw(project, dashboard_path: nil)
- service_for(dashboard_path: dashboard_path)
- .new(project, nil, dashboard_path: dashboard_path)
- .raw_dashboard
- end
-
- # Summary of all known dashboards.
- # @return [Array<Hash>] ex) [{ path: String,
- # display_name: String,
- # default: Boolean }]
- def find_all_paths(project)
- dashboards = user_facing_dashboard_services.flat_map do |service|
- service.all_dashboard_paths(project)
- end
-
- Gitlab::Utils.stable_sort_by(dashboards) { |dashboard| dashboard[:display_name].downcase }
- end
-
- private
-
- def user_facing_dashboard_services
- PREDEFINED_DASHBOARD_LIST + [project_service]
- end
-
- def system_service
- ::Metrics::Dashboard::SystemDashboardService
- end
-
- def project_service
- ::Metrics::Dashboard::CustomDashboardService
- end
-
- def service_for(options)
- Gitlab::Metrics::Dashboard::ServiceSelector.call(options)
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/metrics/dashboard/importer.rb b/lib/gitlab/metrics/dashboard/importer.rb
deleted file mode 100644
index ca835650648..00000000000
--- a/lib/gitlab/metrics/dashboard/importer.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Metrics
- module Dashboard
- class Importer
- def initialize(dashboard_path, project)
- @dashboard_path = dashboard_path.to_s
- @project = project
- end
-
- def execute
- return false unless Dashboard::Validator.validate(dashboard_hash, project: project, dashboard_path: dashboard_path)
-
- Dashboard::Importers::PrometheusMetrics.new(dashboard_hash, project: project, dashboard_path: dashboard_path).execute
- rescue Gitlab::Config::Loader::FormatError
- false
- end
-
- def execute!
- Dashboard::Validator.validate!(dashboard_hash, project: project, dashboard_path: dashboard_path)
-
- Dashboard::Importers::PrometheusMetrics.new(dashboard_hash, project: project, dashboard_path: dashboard_path).execute!
- end
-
- private
-
- attr_accessor :dashboard_path, :project
-
- def dashboard_hash
- @dashboard_hash ||= begin
- raw_dashboard = Dashboard::RepoDashboardFinder.read_dashboard(project, dashboard_path)
- return unless raw_dashboard.present?
-
- ::Gitlab::Config::Loader::Yaml.new(raw_dashboard).load_raw!
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/metrics/dashboard/importers/prometheus_metrics.rb b/lib/gitlab/metrics/dashboard/importers/prometheus_metrics.rb
deleted file mode 100644
index 531e4079632..00000000000
--- a/lib/gitlab/metrics/dashboard/importers/prometheus_metrics.rb
+++ /dev/null
@@ -1,76 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Metrics
- module Dashboard
- module Importers
- class PrometheusMetrics
- ALLOWED_ATTRIBUTES = %i(title query y_label unit legend group dashboard_path).freeze
-
- # Takes a JSON schema validated dashboard hash and
- # imports metrics to database
- def initialize(dashboard_hash, project:, dashboard_path:)
- @dashboard_hash = dashboard_hash
- @project = project
- @dashboard_path = dashboard_path
- @affected_environment_ids = []
- end
-
- def execute
- import
- rescue ActiveRecord::RecordInvalid, Dashboard::Transformers::Errors::BaseError
- false
- end
-
- def execute!
- import
- end
-
- private
-
- attr_reader :dashboard_hash, :project, :dashboard_path
-
- def import
- delete_stale_metrics
- create_or_update_metrics
- end
-
- # rubocop: disable CodeReuse/ActiveRecord
- def create_or_update_metrics
- # TODO: use upsert and worker for callbacks?
-
- affected_metric_ids = []
- prometheus_metrics_attributes.each do |attributes|
- prometheus_metric = PrometheusMetric.find_or_initialize_by(attributes.slice(:dashboard_path, :identifier, :project))
- prometheus_metric.update!(attributes.slice(*ALLOWED_ATTRIBUTES))
-
- affected_metric_ids << prometheus_metric.id
- end
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- def delete_stale_metrics
- identifiers_from_yml = prometheus_metrics_attributes.map { |metric_attributes| metric_attributes[:identifier] }
-
- stale_metrics = PrometheusMetric.for_project(project)
- .for_dashboard_path(dashboard_path)
- .for_group(Enums::PrometheusMetric.groups[:custom])
- .not_identifier(identifiers_from_yml)
-
- return unless stale_metrics.exists?
-
- stale_metrics.each_batch { |batch| batch.delete_all }
- end
-
- def prometheus_metrics_attributes
- @prometheus_metrics_attributes ||= Dashboard::Transformers::Yml::V1::PrometheusMetrics.new(
- dashboard_hash,
- project: project,
- dashboard_path: dashboard_path
- ).execute
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/metrics/dashboard/service_selector.rb b/lib/gitlab/metrics/dashboard/service_selector.rb
deleted file mode 100644
index 67bf4ce7e9a..00000000000
--- a/lib/gitlab/metrics/dashboard/service_selector.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-# frozen_string_literal: true
-
-# Responsible for determining which dashboard service should
-# be used to fetch or generate a dashboard hash.
-# The services can be considered in two categories - embeds
-# and dashboards. Embed hashes are identical to dashboard hashes except
-# that they contain a subset of panels.
-module Gitlab
- module Metrics
- module Dashboard
- class ServiceSelector
- class << self
- include Gitlab::Utils::StrongMemoize
-
- SERVICES = [
- ::Metrics::Dashboard::ClusterMetricsEmbedService,
- ::Metrics::Dashboard::ClusterDashboardService,
- ::Metrics::Dashboard::GitlabAlertEmbedService,
- ::Metrics::Dashboard::CustomMetricEmbedService,
- ::Metrics::Dashboard::GrafanaMetricEmbedService,
- ::Metrics::Dashboard::TransientEmbedService,
- ::Metrics::Dashboard::DynamicEmbedService,
- ::Metrics::Dashboard::DefaultEmbedService,
- ::Metrics::Dashboard::SystemDashboardService,
- ::Metrics::Dashboard::PodDashboardService,
- ::Metrics::Dashboard::CustomDashboardService
- ].freeze
-
- # Returns a class which inherits from the BaseService
- # class that can be used to obtain a dashboard for
- # the provided params.
- # @return [Metrics::Dashboard::BaseService]
- def call(params)
- service = services.find do |service_class|
- service_class.valid_params?(params)
- end
-
- service || default_service
- end
-
- private
-
- def services
- SERVICES
- end
-
- def default_service
- ::Metrics::Dashboard::SystemDashboardService
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/metrics/dashboard/stages/base_stage.rb b/lib/gitlab/metrics/dashboard/stages/base_stage.rb
index c2a8a88108f..b869a633030 100644
--- a/lib/gitlab/metrics/dashboard/stages/base_stage.rb
+++ b/lib/gitlab/metrics/dashboard/stages/base_stage.rb
@@ -5,8 +5,6 @@ module Gitlab
module Dashboard
module Stages
class BaseStage
- include Gitlab::Metrics::Dashboard::Defaults
-
attr_reader :project, :dashboard, :params
def initialize(project, dashboard, params)
diff --git a/lib/gitlab/metrics/dashboard/stages/cluster_endpoint_inserter.rb b/lib/gitlab/metrics/dashboard/stages/cluster_endpoint_inserter.rb
deleted file mode 100644
index 56a82d1df46..00000000000
--- a/lib/gitlab/metrics/dashboard/stages/cluster_endpoint_inserter.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Metrics
- module Dashboard
- module Stages
- class ClusterEndpointInserter < BaseStage
- def transform!
- verify_params
- end
-
- private
-
- def error!(message)
- raise Errors::DashboardProcessingError, message
- end
-
- def query_type(metric)
- metric[:query] ? :query : :query_range
- end
-
- def query_for_metric(metric)
- query = metric[query_type(metric)]
-
- raise Errors::MissingQueryError, 'Each "metric" must define one of :query or :query_range' unless query
-
- query
- end
-
- def verify_params
- raise Errors::DashboardProcessingError, _('Cluster is required for Stages::ClusterEndpointInserter') unless params[:cluster]
- raise Errors::DashboardProcessingError, _('Cluster type must be specified for Stages::ClusterEndpointInserter') unless params[:cluster_type]
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/metrics/dashboard/stages/common_metrics_inserter.rb b/lib/gitlab/metrics/dashboard/stages/common_metrics_inserter.rb
deleted file mode 100644
index 62479ed6de4..00000000000
--- a/lib/gitlab/metrics/dashboard/stages/common_metrics_inserter.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Metrics
- module Dashboard
- module Stages
- class CommonMetricsInserter < BaseStage
- # For each metric in the dashboard config, attempts to
- # find a corresponding database record. If found,
- # includes the record's id in the dashboard config.
- def transform!
- common_metrics = ::PrometheusMetricsFinder.new(common: true).execute
-
- for_metrics do |metric|
- metric_record = common_metrics.find { |m| m.identifier == metric[:id] }
- metric[:metric_id] = metric_record.id if metric_record
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/metrics/dashboard/stages/custom_dashboard_metrics_inserter.rb b/lib/gitlab/metrics/dashboard/stages/custom_dashboard_metrics_inserter.rb
deleted file mode 100644
index 5ed4466f440..00000000000
--- a/lib/gitlab/metrics/dashboard/stages/custom_dashboard_metrics_inserter.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Metrics
- module Dashboard
- module Stages
- # Acts on metrics which have been ingested from source controlled dashboards
- class CustomDashboardMetricsInserter < BaseStage
- # For each metric in the dashboard config, attempts to
- # find a corresponding database record. If found, includes
- # the record's id in the dashboard config.
- def transform!
- database_metrics = ::PrometheusMetricsFinder.new(common: false, group: :custom, project: project).execute
-
- for_metrics do |metric|
- metric_record = database_metrics.find { |m| m.identifier == metric[:id] }
- metric[:metric_id] = metric_record.id if metric_record
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/metrics/dashboard/stages/custom_metrics_details_inserter.rb b/lib/gitlab/metrics/dashboard/stages/custom_metrics_details_inserter.rb
deleted file mode 100644
index 06cfa5cc58e..00000000000
--- a/lib/gitlab/metrics/dashboard/stages/custom_metrics_details_inserter.rb
+++ /dev/null
@@ -1,40 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Metrics
- module Dashboard
- module Stages
- class CustomMetricsDetailsInserter < BaseStage
- def transform!
- dashboard[:panel_groups].each do |panel_group|
- next unless panel_group
-
- has_custom_metrics = custom_group_titles.include?(panel_group[:group])
- panel_group[:has_custom_metrics] = has_custom_metrics
-
- panel_group[:panels].each do |panel|
- next unless panel
-
- panel[:metrics].each do |metric|
- next unless metric
-
- metric[:edit_path] = has_custom_metrics ? edit_path(metric) : nil
- end
- end
- end
- end
-
- private
-
- def custom_group_titles
- @custom_group_titles ||= Enums::PrometheusMetric.custom_group_details.values.map { |group_details| group_details[:group_title] }
- end
-
- def edit_path(metric)
- Gitlab::Routing.url_helpers.edit_project_prometheus_metric_path(project, metric[:metric_id])
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/metrics/dashboard/stages/custom_metrics_inserter.rb b/lib/gitlab/metrics/dashboard/stages/custom_metrics_inserter.rb
deleted file mode 100644
index 3b49eb1c837..00000000000
--- a/lib/gitlab/metrics/dashboard/stages/custom_metrics_inserter.rb
+++ /dev/null
@@ -1,109 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Metrics
- module Dashboard
- module Stages
- class CustomMetricsInserter < BaseStage
- # Inserts project-specific metrics into the dashboard
- # config. If there are no project-specific metrics,
- # this will have no effect.
- def transform!
- custom_metrics = PrometheusMetricsFinder.new(project: project, ordered: true).execute
- custom_metrics = Gitlab::Utils.stable_sort_by(custom_metrics) { |metric| -metric.priority }
-
- custom_metrics.each do |project_metric|
- group = find_or_create_panel_group(dashboard[:panel_groups], project_metric)
- panel = find_or_create_panel(group[:panels], project_metric)
- find_or_create_metric(panel[:metrics], project_metric)
- end
- end
-
- private
-
- # Looks for a panel_group corresponding to the
- # provided metric object. If unavailable, inserts one.
- # @param panel_groups [Array<Hash>]
- # @param metric [PrometheusMetric]
- def find_or_create_panel_group(panel_groups, metric)
- panel_group = find_panel_group(panel_groups, metric)
- return panel_group if panel_group
-
- panel_group = new_panel_group(metric)
- panel_groups << panel_group
-
- panel_group
- end
-
- # Looks for a panel corresponding to the provided
- # metric object. If unavailable, inserts one.
- # @param panels [Array<Hash>]
- # @param metric [PrometheusMetric]
- def find_or_create_panel(panels, metric)
- panel = find_panel(panels, metric)
- return panel if panel
-
- panel = new_panel(metric)
- panels << panel
-
- panel
- end
-
- # Looks for a metric corresponding to the provided
- # metric object. If unavailable, inserts one.
- # @param metrics [Array<Hash>]
- # @param metric [PrometheusMetric]
- def find_or_create_metric(metrics, metric)
- target_metric = find_metric(metrics, metric)
- return target_metric if target_metric
-
- target_metric = new_metric(metric)
- metrics << target_metric
-
- target_metric
- end
-
- def find_panel_group(panel_groups, metric)
- return unless panel_groups
-
- panel_groups.find { |group| group[:group] == metric.group_title }
- end
-
- def find_panel(panels, metric)
- return unless panels
-
- panel_identifiers = [DEFAULT_PANEL_TYPE, metric.title, metric.y_label]
- panels.find { |panel| panel.values_at(:type, :title, :y_label) == panel_identifiers }
- end
-
- def find_metric(metrics, metric)
- return unless metrics
- return unless metric.identifier
-
- metrics.find { |m| m[:id] == metric.identifier }
- end
-
- def new_panel_group(metric)
- {
- group: metric.group_title,
- panels: []
- }
- end
-
- def new_panel(metric)
- {
- type: DEFAULT_PANEL_TYPE,
- title: metric.title,
- y_label: metric.y_label,
- metrics: []
- }
- end
-
- def new_metric(metric)
- metric.to_metric_hash
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/metrics/dashboard/stages/grafana_formatter.rb b/lib/gitlab/metrics/dashboard/stages/grafana_formatter.rb
deleted file mode 100644
index 03370ae7370..00000000000
--- a/lib/gitlab/metrics/dashboard/stages/grafana_formatter.rb
+++ /dev/null
@@ -1,151 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Metrics
- module Dashboard
- module Stages
- class GrafanaFormatter < BaseStage
- include Gitlab::Utils::StrongMemoize
-
- CHART_TYPE = 'area-chart'
- PROXY_PATH = 'api/v1/query_range'
-
- # Reformats the specified panel in the Gitlab
- # dashboard-yml format
- def transform!
- validate_input!
-
- new_dashboard = formatted_dashboard
-
- dashboard.clear
- dashboard.merge!(new_dashboard)
- end
-
- private
-
- def validate_input!
- ::Grafana::Validator.new(
- grafana_dashboard,
- datasource,
- panel,
- query_params
- ).validate!
- rescue ::Grafana::Validator::Error => e
- raise ::Gitlab::Metrics::Dashboard::Errors::DashboardProcessingError, e.message
- end
-
- def formatted_dashboard
- { panel_groups: [{ panels: [formatted_panel] }] }
- end
-
- def formatted_panel
- {
- title: panel[:title],
- type: CHART_TYPE,
- y_label: '', # Grafana panels do not include a Y-Axis label
- metrics: panel[:targets].map.with_index do |target, idx|
- formatted_metric(target, idx)
- end
- }
- end
-
- def formatted_metric(metric, idx)
- {
- id: "#{metric[:legendFormat]}_#{idx}",
- query_range: format_query(metric),
- label: replace_variables(metric[:legendFormat])
- }.compact
- end
-
- # Panel specified by the url from the Grafana dashboard
- def panel
- strong_memoize(:panel) do
- grafana_dashboard[:dashboard][:panels].find do |panel|
- query_params[:panelId] ? matching_panel?(panel) : valid_panel?(panel)
- end
- end
- end
-
- # Determines whether a given panel is the one
- # specified by the linked grafana url
- def matching_panel?(panel)
- panel[:id].to_s == query_params[:panelId]
- end
-
- # Determines whether any given panel has the potenial
- # to return valid results from grafana/prometheus
- def valid_panel?(panel)
- ::Grafana::Validator
- .new(grafana_dashboard, datasource, panel, query_params)
- .valid?
- end
-
- # Grafana url query parameters. Includes information
- # on which panel to select and time range.
- def query_params
- strong_memoize(:query_params) do
- Gitlab::Metrics::Dashboard::Url.parse_query(grafana_url)
- end
- end
-
- # Reformats query for compatibility with prometheus api.
- def format_query(metric)
- expression = remove_new_lines(metric[:expr])
- expression = replace_variables(expression)
- replace_global_variables(expression, metric)
- end
-
- # Accomodates instance-defined Grafana variables.
- # These are variables defined by users, and values
- # must be provided in the query parameters.
- def replace_variables(expression)
- return expression unless grafana_dashboard[:dashboard][:templating]
-
- grafana_dashboard[:dashboard][:templating][:list]
- .sort_by { |variable| variable[:name].length }
- .each do |variable|
- variable_value = query_params[:"var-#{variable[:name]}"]
-
- expression = expression.gsub("$#{variable[:name]}", variable_value)
- expression = expression.gsub("[[#{variable[:name]}]]", variable_value)
- expression = expression.gsub("{{#{variable[:name]}}}", variable_value)
- end
-
- expression
- end
-
- # Replaces Grafana global built-in variables with values.
- # Only $__interval and $__from and $__to are supported.
- #
- # See https://grafana.com/docs/reference/templating/#global-built-in-variables
- def replace_global_variables(expression, metric)
- expression = expression.gsub('$__interval', metric[:interval]) if metric[:interval]
- expression = expression.gsub('$__from', query_params[:from])
- expression.gsub('$__to', query_params[:to])
- end
-
- # Removes new lines from expression.
- def remove_new_lines(expression)
- expression.gsub(/\R+/, '')
- end
-
- # Grafana datasource object corresponding to the
- # specified dashboard
- def datasource
- params[:datasource]
- end
-
- # The specified Grafana dashboard
- def grafana_dashboard
- params[:grafana_dashboard]
- end
-
- # The URL specifying which Grafana panel to embed
- def grafana_url
- params[:grafana_url]
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/metrics/dashboard/stages/metric_endpoint_inserter.rb b/lib/gitlab/metrics/dashboard/stages/metric_endpoint_inserter.rb
deleted file mode 100644
index d885d978524..00000000000
--- a/lib/gitlab/metrics/dashboard/stages/metric_endpoint_inserter.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Metrics
- module Dashboard
- module Stages
- class MetricEndpointInserter < BaseStage
- def transform!
- raise Errors::DashboardProcessingError, _('Environment is required for Stages::MetricEndpointInserter') unless params[:environment]
-
- for_metrics do |metric|
- metric[:prometheus_endpoint_path] = endpoint_for_metric(metric)
- end
- end
-
- private
-
- def endpoint_for_metric(metric)
- if params[:sample_metrics]
- Gitlab::Routing.url_helpers.sample_metrics_project_environment_path(
- project,
- params[:environment],
- identifier: metric[:id]
- )
- else
- Gitlab::Routing.url_helpers.prometheus_api_project_environment_path(
- project,
- params[:environment],
- proxy_path: query_type(metric),
- query: query_for_metric(metric)
- )
- end
- end
-
- def query_type(metric)
- if metric[:query]
- ::Prometheus::ProxyService::PROMETHEUS_QUERY_API.to_sym
- else
- ::Prometheus::ProxyService::PROMETHEUS_QUERY_RANGE_API.to_sym
- end
- end
-
- def query_for_metric(metric)
- query = metric[query_type(metric)]
-
- raise Errors::MissingQueryError, 'Each "metric" must define one of :query or :query_range' unless query
-
- # We need to remove any newlines since our UrlBlocker does not allow
- # multiline URLs.
- query.to_s.squish
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter.rb b/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter.rb
deleted file mode 100644
index 239b5161256..00000000000
--- a/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Metrics
- module Dashboard
- module Stages
- class PanelIdsInserter < BaseStage
- # For each panel within given dashboard inserts panel_id unique in scope of the dashboard
- def transform!
- missing_panel_groups! unless dashboard[:panel_groups]
-
- for_panels_group_with_panels do |panel_group, panel|
- id = generate_panel_id(panel_group, panel)
- remove_panel_ids! && break if duplicated_panel_id?(id)
-
- insert_panel_id(id, panel)
- end
- rescue ActiveModel::UnknownAttributeError => error
- remove_panel_ids!
- Gitlab::ErrorTracking.log_exception(error)
- end
-
- private
-
- def generate_panel_id(group, panel)
- ::PerformanceMonitoring::PrometheusPanel.new(panel.with_indifferent_access).id(group[:group])
- end
-
- def insert_panel_id(id, panel)
- track_inserted_panel_ids(id, panel)
- panel[:id] = id
- end
-
- def track_inserted_panel_ids(id, panel)
- panel_ids[id] = panel
- end
-
- def duplicated_panel_id?(id)
- panel_ids.key?(id)
- end
-
- def remove_panel_ids!
- panel_ids.each_value { |panel| panel.delete(:id) }
- end
-
- def panel_ids
- @_panel_ids ||= {}
- end
-
- def for_panels_group_with_panels
- for_panel_groups do |panel_group|
- for_panels_in(panel_group) do |panel|
- yield panel_group, panel
- end
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/metrics/dashboard/stages/track_panel_type.rb b/lib/gitlab/metrics/dashboard/stages/track_panel_type.rb
deleted file mode 100644
index 71da779d16c..00000000000
--- a/lib/gitlab/metrics/dashboard/stages/track_panel_type.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Metrics
- module Dashboard
- module Stages
- class TrackPanelType < BaseStage
- def transform!
- for_panel_groups do |panel_group|
- for_panels_in(panel_group) do |panel|
- track_panel_type(panel)
- end
- end
- end
-
- private
-
- def track_panel_type(panel)
- panel_type = panel[:type]
-
- Gitlab::Tracking.event('MetricsDashboard::Chart', 'chart_rendered', label: panel_type)
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/metrics/dashboard/stages/variable_endpoint_inserter.rb b/lib/gitlab/metrics/dashboard/stages/variable_endpoint_inserter.rb
deleted file mode 100644
index b3ce0b79675..00000000000
--- a/lib/gitlab/metrics/dashboard/stages/variable_endpoint_inserter.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Metrics
- module Dashboard
- module Stages
- class VariableEndpointInserter < BaseStage
- VARIABLE_TYPE_METRIC_LABEL_VALUES = 'metric_label_values'
-
- def transform!
- raise Errors::DashboardProcessingError, _('Environment is required for Stages::VariableEndpointInserter') unless params[:environment]
-
- for_variables do |variable_name, variable|
- if variable.is_a?(Hash) && variable[:type] == VARIABLE_TYPE_METRIC_LABEL_VALUES
- variable[:options][:prometheus_endpoint_path] = endpoint_for_variable(variable.dig(:options, :series_selector))
- end
- end
- end
-
- private
-
- def endpoint_for_variable(series_selector)
- Gitlab::Routing.url_helpers.prometheus_api_project_environment_path(
- project,
- params[:environment],
- proxy_path: ::Prometheus::ProxyService::PROMETHEUS_SERIES_API,
- match: Array(series_selector)
- )
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/metrics/dashboard/transformers/yml/v1/prometheus_metrics.rb b/lib/gitlab/metrics/dashboard/transformers/yml/v1/prometheus_metrics.rb
deleted file mode 100644
index 3650ddf698a..00000000000
--- a/lib/gitlab/metrics/dashboard/transformers/yml/v1/prometheus_metrics.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Metrics
- module Dashboard
- module Transformers
- module Yml
- module V1
- # Takes a JSON schema validated dashboard hash and
- # maps it to PrometheusMetric model attributes
- class PrometheusMetrics
- def initialize(dashboard_hash, project: nil, dashboard_path: nil)
- @dashboard_hash = dashboard_hash.with_indifferent_access
- @project = project
- @dashboard_path = dashboard_path
-
- @dashboard_hash.default_proc = -> (h, k) { raise Transformers::Errors::MissingAttribute, k.to_s }
- end
-
- def execute
- prometheus_metrics = []
-
- dashboard_hash[:panel_groups].each do |panel_group|
- panel_group[:panels].each do |panel|
- panel[:metrics].each do |metric|
- prometheus_metrics << {
- project: project,
- title: panel[:title],
- y_label: panel[:y_label],
- query: metric[:query_range] || metric[:query],
- unit: metric[:unit],
- legend: metric[:label],
- identifier: metric[:id],
- group: Enums::PrometheusMetric.groups[:custom],
- common: false,
- dashboard_path: dashboard_path
- }.compact
- end
- end
- end
-
- prometheus_metrics
- end
-
- private
-
- attr_reader :dashboard_hash, :project, :dashboard_path
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/metrics/dashboard/validator.rb b/lib/gitlab/metrics/dashboard/validator.rb
deleted file mode 100644
index 57b4b5c068d..00000000000
--- a/lib/gitlab/metrics/dashboard/validator.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Metrics
- module Dashboard
- module Validator
- DASHBOARD_SCHEMA_PATH = Rails.root.join(*%w[lib gitlab metrics dashboard validator schemas dashboard.json]).freeze
-
- class << self
- def validate(content, schema_path = DASHBOARD_SCHEMA_PATH, dashboard_path: nil, project: nil)
- errors(content, schema_path, dashboard_path: dashboard_path, project: project).empty?
- end
-
- def validate!(content, schema_path = DASHBOARD_SCHEMA_PATH, dashboard_path: nil, project: nil)
- errors = errors(content, schema_path, dashboard_path: dashboard_path, project: project)
- errors.empty? || raise(errors.first)
- end
-
- private
-
- def errors(content, schema_path = DASHBOARD_SCHEMA_PATH, dashboard_path: nil, project: nil)
- Validator::Client
- .new(content, schema_path, dashboard_path: dashboard_path, project: project)
- .execute
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/metrics/dashboard/validator/client.rb b/lib/gitlab/metrics/dashboard/validator/client.rb
deleted file mode 100644
index 29f1274a097..00000000000
--- a/lib/gitlab/metrics/dashboard/validator/client.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Metrics
- module Dashboard
- module Validator
- class Client
- # @param content [Hash] Representing a raw, unprocessed
- # dashboard object
- # @param schema_path [String] Representing path to dashboard schema file
- # @param dashboard_path[String] Representing path to dashboard content file
- # @param project [Project] Project to validate dashboard against
- def initialize(content, schema_path, dashboard_path: nil, project: nil)
- @content = content
- @schema_path = schema_path
- @dashboard_path = dashboard_path
- @project = project
- end
-
- def execute
- errors = validate_against_schema
- errors += post_schema_validator.validate
-
- errors.compact
- end
-
- private
-
- attr_reader :content, :schema_path, :project, :dashboard_path
-
- def custom_formats
- @custom_formats ||= CustomFormats.new
- end
-
- def post_schema_validator
- PostSchemaValidator.new(
- project: project,
- metric_ids: custom_formats.metric_ids_cache,
- dashboard_path: dashboard_path
- )
- end
-
- def schemer
- @schemer ||= ::JSONSchemer.schema(Pathname.new(schema_path), formats: custom_formats.format_handlers)
- end
-
- def validate_against_schema
- schemer.validate(content).map do |error|
- ::Gitlab::Metrics::Dashboard::Validator::Errors::SchemaValidationError.new(error)
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/metrics/dashboard/validator/custom_formats.rb b/lib/gitlab/metrics/dashboard/validator/custom_formats.rb
deleted file mode 100644
index 485e80ad1b7..00000000000
--- a/lib/gitlab/metrics/dashboard/validator/custom_formats.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Metrics
- module Dashboard
- module Validator
- class CustomFormats
- def format_handlers
- # Key is custom JSON Schema format name. Value is a proc that takes data and schema and handles
- # validations.
- @format_handlers ||= {
- "add_to_metric_id_cache" => ->(data, schema) { metric_ids_cache << data }
- }
- end
-
- def metric_ids_cache
- @metric_ids_cache ||= []
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/metrics/dashboard/validator/errors.rb b/lib/gitlab/metrics/dashboard/validator/errors.rb
deleted file mode 100644
index 0f6e687d291..00000000000
--- a/lib/gitlab/metrics/dashboard/validator/errors.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Metrics
- module Dashboard
- module Validator
- module Errors
- InvalidDashboardError = Class.new(StandardError)
-
- class SchemaValidationError < InvalidDashboardError
- def initialize(error = {})
- super(error_message(error))
- end
-
- private
-
- def error_message(error)
- if error.is_a?(Hash) && error.present?
- pretty(error)
- else
- "Dashboard failed schema validation"
- end
- end
-
- # based on https://github.com/davishmcclurg/json_schemer/blob/master/lib/json_schemer/errors.rb
- # with addition ability to translate error messages
- def pretty(error)
- data, data_pointer, type, schema = error.values_at('data', 'data_pointer', 'type', 'schema')
- location = data_pointer.empty? ? 'root' : data_pointer
-
- case type
- when 'required'
- keys = error.fetch('details').fetch('missing_keys').join(', ')
- _("%{location} is missing required keys: %{keys}") % { location: location, keys: keys }
- when 'null', 'string', 'boolean', 'integer', 'number', 'array', 'object'
- _("'%{data}' at %{location} is not of type: %{type}") % { data: data, location: location, type: type }
- when 'pattern'
- _("'%{data}' at %{location} does not match pattern: %{pattern}") % { data: data, location: location, pattern: schema.fetch('pattern') }
- when 'format'
- _("'%{data}' at %{location} does not match format: %{format}") % { data: data, location: location, format: schema.fetch('format') }
- when 'const'
- _("'%{data}' at %{location} is not: %{const}") % { data: data, location: location, const: schema.fetch('const').inspect }
- when 'enum'
- _("'%{data}' at %{location} is not one of: %{enum}") % { data: data, location: location, enum: schema.fetch('enum') }
- else
- _("'%{data}' at %{location} is invalid: error_type=%{type}") % { data: data, location: location, type: type }
- end
- end
- end
-
- class DuplicateMetricIds < InvalidDashboardError
- def initialize
- super(_("metric_id must be unique across a project"))
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/metrics/dashboard/validator/post_schema_validator.rb b/lib/gitlab/metrics/dashboard/validator/post_schema_validator.rb
deleted file mode 100644
index 73bfc5a6294..00000000000
--- a/lib/gitlab/metrics/dashboard/validator/post_schema_validator.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Metrics
- module Dashboard
- module Validator
- class PostSchemaValidator
- def initialize(metric_ids:, project: nil, dashboard_path: nil)
- @metric_ids = metric_ids
- @project = project
- @dashboard_path = dashboard_path
- end
-
- def validate
- errors = []
- errors << uniq_metric_ids
- errors.compact
- end
-
- private
-
- attr_reader :project, :metric_ids, :dashboard_path
-
- def uniq_metric_ids
- return Validator::Errors::DuplicateMetricIds.new if metric_ids.uniq!
-
- uniq_metric_ids_across_project if project.present? || dashboard_path.present?
- end
-
- # rubocop: disable CodeReuse/ActiveRecord
- def uniq_metric_ids_across_project
- return ArgumentError.new(_('Both project and dashboard_path are required')) unless
- dashboard_path.present? && project.present?
-
- # If PrometheusMetric identifier is not unique across project and dashboard_path,
- # we need to error because we don't know if the user is trying to create a new metric
- # or update an existing one.
- identifier_on_other_dashboard = PrometheusMetric.where(
- project: project,
- identifier: metric_ids
- ).where.not(
- dashboard_path: dashboard_path
- ).exists?
-
- Validator::Errors::DuplicateMetricIds.new if identifier_on_other_dashboard
- end
- # rubocop: enable CodeReuse/ActiveRecord
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/metrics/dashboard/validator/schemas/axis.json b/lib/gitlab/metrics/dashboard/validator/schemas/axis.json
deleted file mode 100644
index 54334022426..00000000000
--- a/lib/gitlab/metrics/dashboard/validator/schemas/axis.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "type": "object",
- "properties": {
- "name": { "type": "string" },
- "format": {
- "type": "string",
- "default": "engineering"
- },
- "precision": {
- "type": "number",
- "default": 2
- }
- }
-}
diff --git a/lib/gitlab/metrics/dashboard/validator/schemas/dashboard.json b/lib/gitlab/metrics/dashboard/validator/schemas/dashboard.json
deleted file mode 100644
index 313f03be7dc..00000000000
--- a/lib/gitlab/metrics/dashboard/validator/schemas/dashboard.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "type": "object",
- "required": ["dashboard", "panel_groups"],
- "properties": {
- "dashboard": { "type": "string" },
- "panel_groups": {
- "type": "array",
- "items": { "$ref": "./panel_group.json" }
- },
- "templating": {
- "$ref": "./templating.json"
- },
- "links": {
- "type": "array",
- "items": { "$ref": "./link.json" }
- }
- }
-}
diff --git a/lib/gitlab/metrics/dashboard/validator/schemas/link.json b/lib/gitlab/metrics/dashboard/validator/schemas/link.json
deleted file mode 100644
index 4ea7b5dd324..00000000000
--- a/lib/gitlab/metrics/dashboard/validator/schemas/link.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "type": "object",
- "required": ["url"],
- "properties": {
- "url": { "type": "string" },
- "title": { "type": "string" },
- "type": {
- "type": "string",
- "enum": ["grafana"]
- }
- }
-}
diff --git a/lib/gitlab/metrics/dashboard/validator/schemas/metric.json b/lib/gitlab/metrics/dashboard/validator/schemas/metric.json
deleted file mode 100644
index 13831b77e3e..00000000000
--- a/lib/gitlab/metrics/dashboard/validator/schemas/metric.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "type": "object",
- "required": ["unit"],
- "oneOf": [{ "required": ["query"] }, { "required": ["query_range"] }],
- "properties": {
- "id": {
- "type": "string",
- "format": "add_to_metric_id_cache"
- },
- "unit": { "type": "string" },
- "label": { "type": "string" },
- "query": { "type": ["string", "number"] },
- "query_range": { "type": ["string", "number"] },
- "step": { "type": "number" }
- }
-}
diff --git a/lib/gitlab/metrics/dashboard/validator/schemas/panel.json b/lib/gitlab/metrics/dashboard/validator/schemas/panel.json
deleted file mode 100644
index 2ae9608036e..00000000000
--- a/lib/gitlab/metrics/dashboard/validator/schemas/panel.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "type": "object",
- "required": ["title", "metrics"],
- "properties": {
- "type": {
- "type": "string",
- "enum": ["area-chart", "line-chart", "anomaly-chart", "bar", "column", "stacked-column", "single-stat", "heatmap", "gauge"],
- "default": "area-chart"
- },
- "title": { "type": "string" },
- "y_label": { "type": "string" },
- "y_axis": { "$ref": "./axis.json" },
- "max_value": { "type": "number" },
- "weight": { "type": "number" },
- "metrics": {
- "type": "array",
- "items": { "$ref": "./metric.json" }
- },
- "links": {
- "type": "array",
- "items": { "$ref": "./link.json" }
- }
- }
-}
diff --git a/lib/gitlab/metrics/dashboard/validator/schemas/panel_group.json b/lib/gitlab/metrics/dashboard/validator/schemas/panel_group.json
deleted file mode 100644
index 1306fc475db..00000000000
--- a/lib/gitlab/metrics/dashboard/validator/schemas/panel_group.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "type": "object",
- "required": ["group", "panels"],
- "properties": {
- "group": { "type": "string" },
- "priority": { "type": "number" },
- "panels": {
- "type": "array",
- "items": { "$ref": "./panel.json" }
- }
- }
-}
diff --git a/lib/gitlab/metrics/dashboard/validator/schemas/templating.json b/lib/gitlab/metrics/dashboard/validator/schemas/templating.json
deleted file mode 100644
index 6f8664c89af..00000000000
--- a/lib/gitlab/metrics/dashboard/validator/schemas/templating.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "type": "object",
- "required": ["variables"],
- "properties": {
- "variables": { "type": "object" }
- }
-}
diff --git a/lib/gitlab/metrics/global_search_slis.rb b/lib/gitlab/metrics/global_search_slis.rb
index c361d755a12..530bebd72ab 100644
--- a/lib/gitlab/metrics/global_search_slis.rb
+++ b/lib/gitlab/metrics/global_search_slis.rb
@@ -11,6 +11,7 @@ module Gitlab
BASIC_CODE_TARGET_S = 27.538
ADVANCED_CONTENT_TARGET_S = 2.452
ADVANCED_CODE_TARGET_S = 15.52
+ ZOEKT_TARGET_S = 15.52
def initialize_slis!
Gitlab::Metrics::Sli::Apdex.initialize_sli(:global_search, possible_labels)
@@ -42,6 +43,8 @@ module Gitlab
ADVANCED_CONTENT_TARGET_S
elsif search_type == 'advanced' && code_search?(search_scope)
ADVANCED_CODE_TARGET_S
+ elsif search_type == 'zoekt' && code_search?(search_scope)
+ ZOEKT_TARGET_S
end
end
diff --git a/lib/gitlab/metrics/samplers/threads_sampler.rb b/lib/gitlab/metrics/samplers/threads_sampler.rb
index a460594fb59..1357e0a5d9b 100644
--- a/lib/gitlab/metrics/samplers/threads_sampler.rb
+++ b/lib/gitlab/metrics/samplers/threads_sampler.rb
@@ -54,7 +54,7 @@ module Gitlab
if thread_name.presence.nil?
'unnamed'
- elsif thread_name =~ /puma threadpool \d+/
+ elsif /puma threadpool \d+/.match?(thread_name)
# These are the puma workers processing requests
'puma threadpool'
elsif use_thread_name?(thread_name)
diff --git a/lib/gitlab/middleware/sidekiq_web_static.rb b/lib/gitlab/middleware/sidekiq_web_static.rb
index 61b5fb9e0c6..c5d2ecbe00e 100644
--- a/lib/gitlab/middleware/sidekiq_web_static.rb
+++ b/lib/gitlab/middleware/sidekiq_web_static.rb
@@ -15,7 +15,7 @@ module Gitlab
end
def call(env)
- env.delete('HTTP_X_SENDFILE_TYPE') if env['PATH_INFO'] =~ SIDEKIQ_REGEX
+ env.delete('HTTP_X_SENDFILE_TYPE') if SIDEKIQ_REGEX.match?(env['PATH_INFO'])
@app.call(env)
end
diff --git a/lib/gitlab/middleware/static.rb b/lib/gitlab/middleware/static.rb
index 972fed2134c..324d929a93d 100644
--- a/lib/gitlab/middleware/static.rb
+++ b/lib/gitlab/middleware/static.rb
@@ -6,7 +6,7 @@ module Gitlab
UPLOADS_REGEX = %r{\A/uploads(/|\z)}.freeze
def call(env)
- return @app.call(env) if env['PATH_INFO'] =~ UPLOADS_REGEX
+ return @app.call(env) if UPLOADS_REGEX.match?(env['PATH_INFO'])
super
end
diff --git a/lib/gitlab/null_request_store.rb b/lib/gitlab/null_request_store.rb
deleted file mode 100644
index 4642dcf9e91..00000000000
--- a/lib/gitlab/null_request_store.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-# frozen_string_literal: true
-
-# Used by Gitlab::SafeRequestStore
-module Gitlab
- # The methods `begin!`, `clear!`, and `end!` are not defined because they
- # should only be called directly on `RequestStore`.
- class NullRequestStore
- def store
- {}
- end
-
- def active?
- end
-
- def read(key)
- end
-
- def [](key)
- end
-
- def write(key, value)
- value
- end
-
- def []=(key, value)
- value
- end
-
- def exist?(key)
- false
- end
-
- def fetch(key, &block)
- yield
- end
-
- def delete(key, &block)
- yield(key) if block
- end
- end
-end
diff --git a/lib/gitlab/pages/url_builder.rb b/lib/gitlab/pages/url_builder.rb
index 215154b7248..5a28a5ffd23 100644
--- a/lib/gitlab/pages/url_builder.rb
+++ b/lib/gitlab/pages/url_builder.rb
@@ -82,8 +82,7 @@ module Gitlab
end
def unique_domain_enabled?
- Feature.enabled?(:pages_unique_domain, project) &&
- project.project_setting.pages_unique_domain_enabled?
+ project.project_setting.pages_unique_domain_enabled?
end
def config
diff --git a/lib/gitlab/pages/virtual_host_finder.rb b/lib/gitlab/pages/virtual_host_finder.rb
index d5e2159fb52..88ee0e44c00 100644
--- a/lib/gitlab/pages/virtual_host_finder.rb
+++ b/lib/gitlab/pages/virtual_host_finder.rb
@@ -28,7 +28,6 @@ module Gitlab
def by_unique_domain(name)
project = Project.by_pages_enabled_unique_domain(name)
- return unless Feature.enabled?(:pages_unique_domain, project)
return unless project&.pages_deployed?
::Pages::VirtualDomain.new(projects: [project])
diff --git a/lib/gitlab/patch/command_loader.rb b/lib/gitlab/patch/command_loader.rb
new file mode 100644
index 00000000000..357b6270b0d
--- /dev/null
+++ b/lib/gitlab/patch/command_loader.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Patch
+ module CommandLoader
+ extend ActiveSupport::Concern
+
+ class_methods do
+ # Shuffle the node list to spread out initial connection creation amongst all nodes
+ #
+ # The input is a Redis::Cluster::Node instance which is an Enumerable.
+ # `super` receives an Array of Redis::Client instead of a Redis::Cluster::Node
+ def load(nodes)
+ super(nodes.to_a.shuffle)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/patch/node_loader.rb b/lib/gitlab/patch/node_loader.rb
index 79f4b17dd93..85237abc137 100644
--- a/lib/gitlab/patch/node_loader.rb
+++ b/lib/gitlab/patch/node_loader.rb
@@ -9,6 +9,18 @@ end
module Gitlab
module Patch
module NodeLoader
+ extend ActiveSupport::Concern
+
+ class_methods do
+ # Shuffle the node list to spread out initial connection creation amongst all nodes
+ #
+ # The input is a Redis::Cluster::Node instance which is an Enumerable.
+ # `super` receives an Array of Redis::Client instead of a Redis::Cluster::Node
+ def load_flags(nodes)
+ super(nodes.to_a.shuffle)
+ end
+ end
+
def self.prepended(base)
base.class_eval do
# monkey-patches https://github.com/redis/redis-rb/blob/v4.8.0/lib/redis/cluster/node_loader.rb#L23
diff --git a/lib/gitlab/patch/redis_cache_store.rb b/lib/gitlab/patch/redis_cache_store.rb
index 041cb2d44bd..ea6e1f11bc9 100644
--- a/lib/gitlab/patch/redis_cache_store.rb
+++ b/lib/gitlab/patch/redis_cache_store.rb
@@ -44,11 +44,7 @@ module Gitlab
values = failsafe(:patched_read_multi_mget, returning: {}) do
redis.with do |c|
- if c.is_a?(Gitlab::Redis::MultiStore)
- c.with_readonly_pipeline { pipeline_mget(c, keys) }
- else
- pipeline_mget(c, keys)
- end
+ pipeline_mget(c, keys)
end
end
diff --git a/lib/gitlab/patch/sidekiq_poller.rb b/lib/gitlab/patch/sidekiq_poller.rb
index d4264cec1ab..4daee902a8e 100644
--- a/lib/gitlab/patch/sidekiq_poller.rb
+++ b/lib/gitlab/patch/sidekiq_poller.rb
@@ -5,7 +5,7 @@ module Gitlab
module SidekiqPoller
def enqueue
Rails.application.reloader.wrap do
- ::Gitlab::WithRequestStore.with_request_store do
+ ::Gitlab::SafeRequestStore.ensure_request_store do
super
ensure
::Gitlab::Database::LoadBalancing.release_hosts
diff --git a/lib/gitlab/patch/slot_loader.rb b/lib/gitlab/patch/slot_loader.rb
new file mode 100644
index 00000000000..e302d844078
--- /dev/null
+++ b/lib/gitlab/patch/slot_loader.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Patch
+ module SlotLoader
+ extend ActiveSupport::Concern
+
+ class_methods do
+ # Shuffle the node list to spread out initial connection creation amongst all nodes
+ #
+ # The input is a Redis::Cluster::Node instance which is an Enumerable.
+ # `super` receives an Array of Redis::Client instead of a Redis::Cluster::Node
+ def load(nodes)
+ super(nodes.to_a.shuffle)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/path_traversal.rb b/lib/gitlab/path_traversal.rb
index 1123ff73136..d42b5fde615 100644
--- a/lib/gitlab/path_traversal.rb
+++ b/lib/gitlab/path_traversal.rb
@@ -14,7 +14,7 @@ module Gitlab
# Ensure that the relative path will not traverse outside the base directory
# We url decode the path to avoid passing invalid paths forward in url encoded format.
# Also see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/24223#note_284122580
- # It also checks for ALT_SEPARATOR aka '\' (forward slash)
+ # It also checks for backslash '\', which is sometimes a File::ALT_SEPARATOR.
def check_path_traversal!(path)
return unless path
diff --git a/lib/gitlab/profiler.rb b/lib/gitlab/profiler.rb
index 5af06e82c55..1a6feff915f 100644
--- a/lib/gitlab/profiler.rb
+++ b/lib/gitlab/profiler.rb
@@ -2,8 +2,6 @@
module Gitlab
module Profiler
- extend WithRequestStore
-
FILTERED_STRING = '[FILTERED]'
IGNORE_BACKTRACES = %w[
@@ -62,7 +60,7 @@ module Gitlab
logger = create_custom_logger(logger, private_token: private_token)
- result = with_request_store do
+ result = ::Gitlab::SafeRequestStore.ensure_request_store do
# Make an initial call for an asset path in development mode to avoid
# sprockets dominating the profiler output.
ActionController::Base.helpers.asset_path('katex.css') if Rails.env.development?
diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb
index 13718e63b25..b4297cc695b 100644
--- a/lib/gitlab/project_search_results.rb
+++ b/lib/gitlab/project_search_results.rb
@@ -50,6 +50,7 @@ module Gitlab
if @project.is_a?(Array)
team_members_for_projects = User.joins(:project_authorizations).where(project_authorizations: { project_id: @project })
+ .allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/422045')
results = results.where(id: team_members_for_projects)
else
results = results.where(id: @project.team.members)
diff --git a/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb b/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb
index e01be4e0604..c94deea0dfb 100644
--- a/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb
+++ b/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb
@@ -145,10 +145,18 @@ module Gitlab
desc { _('Set time estimate') }
explanation do |time_estimate|
- formatted_time_estimate = format_time_estimate(time_estimate)
- _("Sets time estimate to %{time_estimate}.") % { time_estimate: formatted_time_estimate } if formatted_time_estimate
+ next unless time_estimate
+
+ if time_estimate == 0
+ _('Removes time estimate.')
+ elsif time_estimate > 0
+ formatted_time_estimate = format_time_estimate(time_estimate)
+ _("Sets time estimate to %{time_estimate}.") % { time_estimate: formatted_time_estimate } if formatted_time_estimate
+ end
end
execution_message do |time_estimate|
+ next _('Removed time estimate.') if time_estimate == 0
+
formatted_time_estimate = format_time_estimate(time_estimate)
_("Set time estimate to %{time_estimate}.") % { time_estimate: formatted_time_estimate } if formatted_time_estimate
end
@@ -159,12 +167,10 @@ module Gitlab
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
end
parse_params do |raw_duration|
- Gitlab::TimeTrackingFormatter.parse(raw_duration)
+ Gitlab::TimeTrackingFormatter.parse(raw_duration, keep_zero: true)
end
command :estimate, :estimate_time do |time_estimate|
- if time_estimate
- @updates[:time_estimate] = time_estimate
- end
+ @updates[:time_estimate] = time_estimate
end
desc { _('Add or subtract spent time') }
diff --git a/lib/gitlab/quick_actions/relate_actions.rb b/lib/gitlab/quick_actions/relate_actions.rb
index 058c1e7e9bf..294ddd985de 100644
--- a/lib/gitlab/quick_actions/relate_actions.rb
+++ b/lib/gitlab/quick_actions/relate_actions.rb
@@ -36,7 +36,7 @@ module Gitlab
extract_references(issue_param, :issue).first
end
command :unlink do |issue|
- link = IssueLink.for_issues(quick_action_target, issue).first
+ link = IssueLink.for_items(quick_action_target, issue).first
if link
call_link_service(IssueLinks::DestroyService.new(link, current_user))
diff --git a/lib/gitlab/quick_actions/work_item_actions.rb b/lib/gitlab/quick_actions/work_item_actions.rb
index a5c3c6a56be..0a96d502862 100644
--- a/lib/gitlab/quick_actions/work_item_actions.rb
+++ b/lib/gitlab/quick_actions/work_item_actions.rb
@@ -54,18 +54,9 @@ module Gitlab
def validate_promote_to(type)
return error_msg(:not_found, action: 'promote') unless type && supports_promote_to?(type.name)
+ return if current_user.can?(:"create_#{type.base_type}", quick_action_target)
- unless current_user.can?(:"create_#{type.base_type}", quick_action_target)
- return error_msg(:forbidden, action: 'promote')
- end
-
- validate_hierarchy
- end
-
- def validate_hierarchy
- return unless current_type.task? && quick_action_target.parent_link
-
- error_msg(:hierarchy, action: 'promote')
+ error_msg(:forbidden, action: 'promote')
end
def current_type
@@ -88,8 +79,7 @@ module Gitlab
message = {
not_found: 'Provided type is not supported',
same_type: 'Types are the same',
- forbidden: 'You have insufficient permissions',
- hierarchy: 'A task cannot be promoted when a parent issue is present'
+ forbidden: 'You have insufficient permissions'
}.freeze
format(_("Failed to %{action} this work item: %{reason}."), { action: action, reason: message[reason] })
diff --git a/lib/gitlab/rack_attack.rb b/lib/gitlab/rack_attack.rb
index d999b706d6c..829b305d1ee 100644
--- a/lib/gitlab/rack_attack.rb
+++ b/lib/gitlab/rack_attack.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
# When adding new user-configurable throttles, remember to update the documentation
-# in doc/user/admin_area/settings/user_and_ip_rate_limits.md
+# in doc/administration/settings/user_and_ip_rate_limits.md
#
# Integration specs for throttling can be found in:
# spec/requests/rack_attack_global_spec.rb
diff --git a/lib/gitlab/redis.rb b/lib/gitlab/redis.rb
index 06bce7649bf..e760a576253 100644
--- a/lib/gitlab/redis.rb
+++ b/lib/gitlab/redis.rb
@@ -9,7 +9,7 @@ module Gitlab
# config/initializers/7_redis.rb, instrumented, and used in health- & readiness checks.
ALL_CLASSES = [
Gitlab::Redis::Cache,
- Gitlab::Redis::ClusterCache,
+ Gitlab::Redis::ClusterSharedState,
Gitlab::Redis::DbLoadBalancing,
Gitlab::Redis::FeatureFlag,
Gitlab::Redis::Queues,
diff --git a/lib/gitlab/redis/cache.rb b/lib/gitlab/redis/cache.rb
index 60944268f91..d63905cd896 100644
--- a/lib/gitlab/redis/cache.rb
+++ b/lib/gitlab/redis/cache.rb
@@ -8,9 +8,14 @@ module Gitlab
class << self
# Full list of options:
# https://api.rubyonrails.org/classes/ActiveSupport/Cache/RedisCacheStore.html#method-c-new
+ # pool argument event not documented in the link above is handled by RedisCacheStore see:
+ # https://github.com/rails/rails/blob/593893c901f87b4ed205751f72df41519b4d2da3/activesupport/lib/active_support/cache/redis_cache_store.rb#L165
+ # and
+ # https://github.com/rails/rails/blob/ad790cb2f6bc724a89e4266b505b3c57d5089dae/activesupport/lib/active_support/cache.rb#L206
def active_support_config
{
redis: pool,
+ pool: false,
compress: Gitlab::Utils.to_boolean(ENV.fetch('ENABLE_REDIS_CACHE_COMPRESSION', '1')),
namespace: CACHE_NAMESPACE,
expires_in: default_ttl_seconds
@@ -20,20 +25,6 @@ module Gitlab
def default_ttl_seconds
ENV.fetch('GITLAB_RAILS_CACHE_DEFAULT_TTL_SECONDS', 8.hours).to_i
end
-
- # Exposes redis for Peek adapter. To be removed after ClusterCache migration.
- def multistore_redis
- redis
- end
-
- private
-
- def redis
- primary_store = ::Redis.new(Gitlab::Redis::ClusterCache.params)
- secondary_store = ::Redis.new(params)
-
- MultiStore.new(primary_store, secondary_store, store_name)
- end
end
end
end
diff --git a/lib/gitlab/redis/cluster_cache.rb b/lib/gitlab/redis/cluster_shared_state.rb
index 15a87739c6d..678566a0c9c 100644
--- a/lib/gitlab/redis/cluster_cache.rb
+++ b/lib/gitlab/redis/cluster_shared_state.rb
@@ -2,10 +2,10 @@
module Gitlab
module Redis
- class ClusterCache < ::Gitlab::Redis::Wrapper
+ class ClusterSharedState < ::Gitlab::Redis::Wrapper
class << self
def config_fallback
- Cache
+ SharedState
end
end
end
diff --git a/lib/gitlab/redis/etag_cache.rb b/lib/gitlab/redis/etag_cache.rb
new file mode 100644
index 00000000000..6aafdc8e518
--- /dev/null
+++ b/lib/gitlab/redis/etag_cache.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Redis
+ class EtagCache < ::Gitlab::Redis::Wrapper
+ class << self
+ def store_name
+ 'Cache'
+ end
+
+ private
+
+ def redis
+ primary_store = ::Redis.new(Gitlab::Redis::Cache.params)
+ secondary_store = ::Redis.new(Gitlab::Redis::SharedState.params)
+
+ MultiStore.new(primary_store, secondary_store, name.demodulize)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/redis/feature_flag.rb b/lib/gitlab/redis/feature_flag.rb
index 441ff669035..395805792d7 100644
--- a/lib/gitlab/redis/feature_flag.rb
+++ b/lib/gitlab/redis/feature_flag.rb
@@ -14,6 +14,7 @@ module Gitlab
def cache_store
@cache_store ||= FeatureFlagStore.new(
redis: pool,
+ pool: false,
compress: Gitlab::Utils.to_boolean(ENV.fetch('ENABLE_REDIS_CACHE_COMPRESSION', '1')),
namespace: Cache::CACHE_NAMESPACE,
expires_in: 1.hour
diff --git a/lib/gitlab/redis/repository_cache.rb b/lib/gitlab/redis/repository_cache.rb
index 966c6584aa5..6d0c35a6829 100644
--- a/lib/gitlab/redis/repository_cache.rb
+++ b/lib/gitlab/redis/repository_cache.rb
@@ -15,6 +15,7 @@ module Gitlab
def cache_store
@cache_store ||= RepositoryCacheStore.new(
redis: pool,
+ pool: false,
compress: Gitlab::Utils.to_boolean(ENV.fetch('ENABLE_REDIS_CACHE_COMPRESSION', '1')),
namespace: Cache::CACHE_NAMESPACE,
expires_in: Cache.default_ttl_seconds
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index 4e666dbaf77..40facabff23 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -2,308 +2,10 @@
module Gitlab
module Regex
- module Packages
- CONAN_RECIPE_FILES = %w[conanfile.py conanmanifest.txt conan_sources.tgz conan_export.tgz].freeze
- CONAN_PACKAGE_FILES = %w[conaninfo.txt conanmanifest.txt conan_package.tgz].freeze
-
- PYPI_NORMALIZED_NAME_REGEX_STRING = '[-_.]+'
-
- # see https://github.com/apache/maven/blob/c1dfb947b509e195c75d4275a113598cf3063c3e/maven-artifact/src/main/java/org/apache/maven/artifact/Artifact.java#L46
- MAVEN_SNAPSHOT_DYNAMIC_PARTS = /\A.{0,1000}(-\d{8}\.\d{6}-\d+).{0,1000}\z/.freeze
-
- API_PATH_REGEX = %r{^/api/v\d+/(projects/[^/]+/|groups?/[^/]+/-/)?packages/[A-Za-z]+}.freeze
-
- def conan_package_reference_regex
- @conan_package_reference_regex ||= %r{\A[A-Za-z0-9]+\z}.freeze
- end
-
- def conan_revision_regex
- @conan_revision_regex ||= %r{\A0\z}.freeze
- end
-
- def conan_recipe_user_channel_regex
- %r{\A(_|#{conan_name_regex})\z}.freeze
- end
-
- def conan_recipe_component_regex
- # https://docs.conan.io/en/latest/reference/conanfile/attributes.html#name
- @conan_recipe_component_regex ||= %r{\A#{conan_name_regex}\z}.freeze
- end
-
- def composer_package_version_regex
- # see https://github.com/composer/semver/blob/31f3ea725711245195f62e54ffa402d8ef2fdba9/src/VersionParser.php#L215
- @composer_package_version_regex ||= %r{\Av?((\d++)(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?)?\z}.freeze
- end
-
- def composer_dev_version_regex
- @composer_dev_version_regex ||= %r{(^dev-)|(-dev$)}.freeze
- end
-
- def package_name_regex
- @package_name_regex ||=
- %r{
- \A\@?
- (?> # atomic group to prevent backtracking
- (([\w\-\.\+]*)\/)*([\w\-\.]+)
- )
- @?
- (?> # atomic group to prevent backtracking
- (([\w\-\.\+]*)\/)*([\w\-\.]*)
- )
- \z
- }x.freeze
- end
-
- def maven_file_name_regex
- @maven_file_name_regex ||= %r{\A[A-Za-z0-9\.\_\-\+]+\z}.freeze
- end
-
- def maven_path_regex
- @maven_path_regex ||= %r{\A\@?(([\w\-\.]*)/)*([\w\-\.\+]*)\z}.freeze
- end
-
- def maven_app_name_regex
- @maven_app_name_regex ||= /\A[\w\-\.]+\z/.freeze
- end
-
- def maven_version_regex
- @maven_version_regex ||= /\A(?!.*\.\.)[\w+.-]+\z/.freeze
- end
-
- def maven_app_group_regex
- maven_app_name_regex
- end
-
- def npm_package_name_regex
- @npm_package_name_regex ||= %r{\A(?:@(#{Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX})/)?[-+\.\_a-zA-Z0-9]+\z}o
- end
-
- def npm_package_name_regex_message
- 'should be a valid NPM package name: https://github.com/npm/validate-npm-package-name#naming-rules.'
- end
-
- def nuget_package_name_regex
- @nuget_package_name_regex ||= %r{\A[-+\.\_a-zA-Z0-9]+\z}.freeze
- end
-
- def nuget_version_regex
- @nuget_version_regex ||= /
- \A#{_semver_major_regex}
- \.#{_semver_minor_regex}
- (\.#{_semver_patch_regex})?
- (\.\d*)?
- #{_semver_prerelease_build_regex}\z
- /x.freeze
- end
-
- def terraform_module_package_name_regex
- @terraform_module_package_name_regex ||= %r{\A[-a-z0-9]+\/[-a-z0-9]+\z}.freeze
- end
-
- def pypi_version_regex
- # See the official regex: https://github.com/pypa/packaging/blob/16.7/packaging/version.py#L159
-
- @pypi_version_regex ||= %r{
- \A(?:
- v?
- (?:([0-9]+)!)? (?# epoch)
- ([0-9]+(?:\.[0-9]+)*) (?# release segment)
- ([-_\.]?((a|b|c|rc|alpha|beta|pre|preview))[-_\.]?([0-9]+)?)? (?# pre-release)
- ((?:-([0-9]+))|(?:[-_\.]?(post|rev|r)[-_\.]?([0-9]+)?))? (?# post release)
- ([-_\.]?(dev)[-_\.]?([0-9]+)?)? (?# dev release)
- (?:\+([a-z0-9]+(?:[-_\.][a-z0-9]+)*))? (?# local version)
- )\z}xi.freeze
- end
-
- def debian_package_name_regex
- # See official parser
- # https://git.dpkg.org/cgit/dpkg/dpkg.git/tree/lib/dpkg/parsehelp.c?id=9e0c88ec09475f4d1addde9cdba1ad7849720356#n122
- # @debian_package_name_regex ||= %r{\A[a-z0-9][-+\._a-z0-9]*\z}i.freeze
- # But we prefer a more strict version from Lintian
- # https://salsa.debian.org/lintian/lintian/-/blob/5080c0068ffc4a9ddee92022a91d0c2ff53e56d1/lib/Lintian/Util.pm#L116
- @debian_package_name_regex ||= %r{\A[a-z0-9][-+\.a-z0-9]+\z}.freeze
- end
-
- def debian_version_regex
- # See official parser: https://git.dpkg.org/cgit/dpkg/dpkg.git/tree/lib/dpkg/parsehelp.c?id=9e0c88ec09475f4d1addde9cdba1ad7849720356#n205
- @debian_version_regex ||= %r{
- \A(?:
- (?:([0-9]{1,9}):)? (?# epoch)
- ([0-9][0-9a-z\.+~]*) (?# version)
- (-[0-9a-z\.+~]+){0,14} (?# -revision)
- (?<!-)
- )\z}xi.freeze
- end
-
- def debian_architecture_regex
- # See official parser: https://git.dpkg.org/cgit/dpkg/dpkg.git/tree/lib/dpkg/arch.c?id=9e0c88ec09475f4d1addde9cdba1ad7849720356#n43
- # But we limit to lower case
- @debian_architecture_regex ||= %r{\A#{::Packages::Debian::ARCHITECTURE_REGEX}\z}o.freeze
- end
-
- def debian_distribution_regex
- @debian_distribution_regex ||= %r{\A#{::Packages::Debian::DISTRIBUTION_REGEX}\z}io.freeze
- end
-
- def debian_component_regex
- @debian_component_regex ||= %r{\A#{::Packages::Debian::COMPONENT_REGEX}\z}o.freeze
- end
-
- def debian_direct_upload_filename_regex
- @debian_direct_upload_filename_regex ||= %r{\A.*\.(deb|udeb|ddeb)\z}o.freeze
- end
-
- def helm_channel_regex
- @helm_channel_regex ||= %r{\A([a-zA-Z0-9](\.|-|_)?){1,255}(?<!\.|-|_)\z}.freeze
- end
-
- def helm_package_regex
- @helm_package_regex ||= %r{#{helm_channel_regex}}.freeze
- end
-
- def helm_version_regex
- # identical to semver_regex, with optional preceding 'v'
- @helm_version_regex ||= Regexp.new("\\Av?#{::Gitlab::Regex.unbounded_semver_regex.source}\\z", ::Gitlab::Regex.unbounded_semver_regex.options)
- end
-
- def unbounded_semver_regex
- # See the official regex: https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
-
- # The order of the alternatives in <prerelease> are intentionally
- # reordered to be greedy. Without this change, the unbounded regex would
- # only partially match "v0.0.0-20201230123456-abcdefabcdef".
- @unbounded_semver_regex ||= /
- #{_semver_major_minor_patch_regex}#{_semver_prerelease_build_regex}
- /x.freeze
- end
-
- def semver_regex
- @semver_regex ||= Regexp.new("\\A#{::Gitlab::Regex.unbounded_semver_regex.source}\\z", ::Gitlab::Regex.unbounded_semver_regex.options).freeze
- end
-
- def semver_regex_message
- 'should follow SemVer: https://semver.org'
- end
-
- # These partial semver regexes are intended for use in composing other
- # regexes rather than being used alone.
- def _semver_major_minor_patch_regex
- @_semver_major_minor_patch_regex ||= /
- #{_semver_major_regex}\.#{_semver_minor_regex}\.#{_semver_patch_regex}
- /x.freeze
- end
-
- def _semver_major_regex
- @_semver_major_regex ||= /
- (?<major>0|[1-9]\d*)
- /x.freeze
- end
-
- def _semver_minor_regex
- @_semver_minor_regex ||= /
- (?<minor>0|[1-9]\d*)
- /x.freeze
- end
-
- def _semver_patch_regex
- @_semver_patch_regex ||= /
- (?<patch>0|[1-9]\d*)
- /x.freeze
- end
-
- def _semver_prerelease_build_regex
- @_semver_prerelease_build_regex ||= /
- (?:-(?<prerelease>(?:\d*[a-zA-Z-][0-9a-zA-Z-]*|[1-9]\d*|0)(?:\.(?:\d*[a-zA-Z-][0-9a-zA-Z-]*|[1-9]\d*|0))*))?
- (?:\+(?<build>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?
- /x.freeze
- end
-
- def prefixed_semver_regex
- # identical to semver_regex, except starting with 'v'
- @prefixed_semver_regex ||= Regexp.new("\\Av#{::Gitlab::Regex.unbounded_semver_regex.source}\\z", ::Gitlab::Regex.unbounded_semver_regex.options)
- end
-
- def go_package_regex
- # A Go package name looks like a URL but is not; it:
- # - Must not have a scheme, such as http:// or https://
- # - Must not have a port number, such as :8080 or :8443
-
- @go_package_regex ||= %r{
- \b (?# word boundary)
- (?<domain>
- [0-9a-z](?:(?:-|[0-9a-z]){0,61}[0-9a-z])? (?# first domain)
- (?:\.[0-9a-z](?:(?:-|[0-9a-z]){0,61}[0-9a-z])?)* (?# inner domains)
- \.[a-z]{2,} (?# top-level domain)
- )
- (?<path>/(?:
- [-/$_.+!*'(),0-9a-z] (?# plain URL character)
- | %[0-9a-f]{2})* (?# URL encoded character)
- )? (?# path)
- \b (?# word boundary)
- }ix.freeze
- end
-
- def generic_package_version_regex
- maven_version_regex
- end
-
- def generic_package_name_regex
- maven_file_name_regex
- end
-
- def generic_package_file_name_regex
- generic_package_name_regex
- end
-
- def sha256_regex
- @sha256_regex ||= /\A[0-9a-f]{64}\z/i.freeze
- end
-
- private
-
- def conan_name_regex
- @conan_name_regex ||= %r{[a-zA-Z0-9_][a-zA-Z0-9_\+\.-]{1,49}}.freeze
- end
- end
-
- module BulkImports
- def bulk_import_destination_namespace_path_regex
- # This regexp validates the string conforms to rules for a destination_namespace path:
- # i.e does not start with a non-alphanumeric character,
- # contains only alphanumeric characters, forward slashes, periods, and underscores,
- # does not end with a period or forward slash, and has a relative path structure
- # with no http protocol chars or leading or trailing forward slashes
- # eg 'source/full/path' or 'destination_namespace' not 'https://example.com/destination/namespace/path'
- # the regex also allows for an empty string ('') to be accepted as this is allowed in
- # a bulk_import POST request
- @bulk_import_destination_namespace_path_regex ||= %r/((\A\z)|(\A[0-9a-z]*(-_.)?[0-9a-z])(\/?[0-9a-z]*[-_.]?[0-9a-z])+\z)/i
- end
-
- def bulk_import_source_full_path_regex
- # This regexp validates the string conforms to rules for a source_full_path path:
- # i.e does not start with a non-alphanumeric character except for periods or underscores,
- # contains only alphanumeric characters, forward slashes, periods, and underscores,
- # does not end with a period or forward slash, and has a relative path structure
- # with no http protocol chars or leading or trailing forward slashes
- # eg 'source/full/path' or 'destination_namespace' not 'https://example.com/source/full/path'
- @bulk_import_source_full_path_regex ||= %r/\A([.]?)[^\W](\/?([-_.+]*)*[0-9a-z][-_]*)+\z/i
- end
-
- def bulk_import_source_full_path_regex_message
- bulk_import_destination_namespace_path_regex_message
- end
-
- def bulk_import_destination_namespace_path_regex_message
- "must have a relative path structure " \
- "with no HTTP protocol characters, or leading or trailing forward slashes. " \
- "Path segments must not start or end with a special character, " \
- "and must not contain consecutive special characters."
- end
- end
-
extend self
- extend Packages
extend BulkImports
+ extend MergeRequests
+ extend Packages
def group_path_regex
# This regexp validates the string conforms to rules for a group slug:
@@ -328,7 +30,7 @@ module Gitlab
end
def project_name_regex_message
- "can contain only letters, digits, emojis, '_', '.', '+', dashes, or spaces. " \
+ "can contain only letters, digits, emoji, '_', '.', '+', dashes, or spaces. " \
"It must start with a letter, digit, emoji, or '_'."
end
@@ -350,7 +52,7 @@ module Gitlab
end
def group_name_regex_message
- "can contain only letters, digits, emojis, '_', '.', dash, space, parenthesis. " \
+ "can contain only letters, digits, emoji, '_', '.', dash, space, parenthesis. " \
"It must start with letter, digit, emoji or '_'."
end
@@ -594,10 +296,6 @@ module Gitlab
@utc_date_regex ||= /\A[0-9]{4}-[0-9]{2}-[0-9]{2}\z/.freeze
end
- def merge_request_draft
- /\A(?i)(\[draft\]|\(draft\)|draft:)/
- end
-
def issue
@issue ||= /(?<issue>\d+)(?<format>\+s{,1})?(?=\W|\z)/
end
@@ -606,10 +304,6 @@ module Gitlab
@work_item ||= /(?<work_item>\d+)(?<format>\+s{,1})?(?=\W|\z)/
end
- def merge_request
- @merge_request ||= /(?<merge_request>\d+)(?<format>\+s{,1})?/
- end
-
def base64_regex
@base64_regex ||= %r{(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?}.freeze
end
@@ -618,11 +312,6 @@ module Gitlab
/\A[a-z]([-_a-z0-9]*[a-z0-9])?\z/
end
- def feature_flag_regex_message
- "can contain only lowercase letters, digits, '_' and '-'. " \
- "Must start with a letter, and cannot end with '-' or '_'"
- end
-
# One or more `part`s, separated by separator
def sep_by_1(separator, part)
%r(#{part} (#{separator} #{part})*)x
@@ -632,10 +321,6 @@ module Gitlab
@x509_subject_key_identifier_regex ||= /\A(?:\h{2}:)*\h{2}\z/.freeze
end
- def ml_model_version_regex
- maven_version_regex
- end
-
def ml_model_name_regex
package_name_regex
end
diff --git a/lib/gitlab/regex/bulk_imports.rb b/lib/gitlab/regex/bulk_imports.rb
new file mode 100644
index 00000000000..e9ec24b831f
--- /dev/null
+++ b/lib/gitlab/regex/bulk_imports.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Regex
+ module BulkImports
+ def bulk_import_destination_namespace_path_regex
+ # This regexp validates the string conforms to rules for a destination_namespace path:
+ # i.e does not start with a non-alphanumeric character,
+ # contains only alphanumeric characters, forward slashes, periods, and underscores,
+ # does not end with a period or forward slash, and has a relative path structure
+ # with no http protocol chars or leading or trailing forward slashes
+ # eg 'source/full/path' or 'destination_namespace' not 'https://example.com/destination/namespace/path'
+ # the regex also allows for an empty string ('') to be accepted as this is allowed in
+ # a bulk_import POST request
+ @bulk_import_destination_namespace_path_regex ||= %r/((\A\z)|(\A[0-9a-z]*(-_.)?[0-9a-z])(\/?[0-9a-z]*[-_.]?[0-9a-z])+\z)/i
+ end
+
+ def bulk_import_source_full_path_regex
+ # This regexp validates the string conforms to rules for a source_full_path path:
+ # i.e does not start with a non-alphanumeric character except for periods or underscores,
+ # contains only alphanumeric characters, forward slashes, periods, and underscores,
+ # does not end with a period or forward slash, and has a relative path structure
+ # with no http protocol chars or leading or trailing forward slashes
+ # eg 'source/full/path' or 'destination_namespace' not 'https://example.com/source/full/path'
+ @bulk_import_source_full_path_regex ||= %r/\A([.]?)[^\W](\/?([-_.+]*)*[0-9a-z][-_]*)+\z/i
+ end
+
+ def bulk_import_source_full_path_regex_message
+ bulk_import_destination_namespace_path_regex_message
+ end
+
+ def bulk_import_destination_namespace_path_regex_message
+ "must have a relative path structure " \
+ "with no HTTP protocol characters, or leading or trailing forward slashes. " \
+ "Path segments must not start or end with a special character, " \
+ "and must not contain consecutive special characters."
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/regex/merge_requests.rb b/lib/gitlab/regex/merge_requests.rb
new file mode 100644
index 00000000000..0fb47e4fbfe
--- /dev/null
+++ b/lib/gitlab/regex/merge_requests.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Regex
+ module MergeRequests
+ def merge_request
+ @merge_request ||= /(?<merge_request>\d+)(?<format>\+s{,1})?/
+ end
+
+ def merge_request_draft
+ /\A(?i)(\[draft\]|\(draft\)|draft:)/
+ end
+
+ def git_diff_prefix
+ /\A@@( -\d+,\d+ \+\d+(,\d+)? )@@/
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/regex/packages.rb b/lib/gitlab/regex/packages.rb
new file mode 100644
index 00000000000..107f2070801
--- /dev/null
+++ b/lib/gitlab/regex/packages.rb
@@ -0,0 +1,273 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Regex
+ module Packages
+ CONAN_RECIPE_FILES = %w[conanfile.py conanmanifest.txt conan_sources.tgz conan_export.tgz].freeze
+ CONAN_PACKAGE_FILES = %w[conaninfo.txt conanmanifest.txt conan_package.tgz].freeze
+
+ PYPI_NORMALIZED_NAME_REGEX_STRING = '[-_.]+'
+
+ # see https://github.com/apache/maven/blob/c1dfb947b509e195c75d4275a113598cf3063c3e/maven-artifact/src/main/java/org/apache/maven/artifact/Artifact.java#L46
+ MAVEN_SNAPSHOT_DYNAMIC_PARTS = /\A.{0,1000}(-\d{8}\.\d{6}-\d+).{0,1000}\z/.freeze
+
+ API_PATH_REGEX = %r{^/api/v\d+/(projects/[^/]+/|groups?/[^/]+/-/)?packages/[A-Za-z]+}.freeze
+
+ def conan_package_reference_regex
+ @conan_package_reference_regex ||= %r{\A[A-Za-z0-9]+\z}.freeze
+ end
+
+ def conan_revision_regex
+ @conan_revision_regex ||= %r{\A0\z}.freeze
+ end
+
+ def conan_recipe_user_channel_regex
+ %r{\A(_|#{conan_name_regex})\z}.freeze
+ end
+
+ def conan_recipe_component_regex
+ # https://docs.conan.io/en/latest/reference/conanfile/attributes.html#name
+ @conan_recipe_component_regex ||= %r{\A#{conan_name_regex}\z}.freeze
+ end
+
+ def composer_package_version_regex
+ # see https://github.com/composer/semver/blob/31f3ea725711245195f62e54ffa402d8ef2fdba9/src/VersionParser.php#L215
+ @composer_package_version_regex ||= %r{\Av?((\d++)(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?)?\z}.freeze
+ end
+
+ def composer_dev_version_regex
+ @composer_dev_version_regex ||= %r{(^dev-)|(-dev$)}.freeze
+ end
+
+ def package_name_regex
+ @package_name_regex ||=
+ %r{
+ \A\@?
+ (?> # atomic group to prevent backtracking
+ (([\w\-\.\+]*)\/)*([\w\-\.]+)
+ )
+ @?
+ (?> # atomic group to prevent backtracking
+ (([\w\-\.\+]*)\/)*([\w\-\.]*)
+ )
+ \z
+ }x.freeze
+ end
+
+ def maven_file_name_regex
+ @maven_file_name_regex ||= %r{\A[A-Za-z0-9\.\_\-\+]+\z}.freeze
+ end
+
+ def maven_path_regex
+ @maven_path_regex ||= %r{\A\@?(([\w\-\.]*)/)*([\w\-\.\+]*)\z}.freeze
+ end
+
+ def maven_app_name_regex
+ @maven_app_name_regex ||= /\A[\w\-\.]+\z/.freeze
+ end
+
+ def maven_version_regex
+ @maven_version_regex ||= /\A(?!.*\.\.)[\w+.-]+\z/.freeze
+ end
+
+ def maven_app_group_regex
+ maven_app_name_regex
+ end
+
+ def npm_package_name_regex
+ @npm_package_name_regex ||= %r{\A(?:@(#{Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX})/)?[-+\.\_a-zA-Z0-9]+\z}o
+ end
+
+ def npm_package_name_regex_message
+ 'should be a valid NPM package name: https://github.com/npm/validate-npm-package-name#naming-rules.'
+ end
+
+ def nuget_package_name_regex
+ @nuget_package_name_regex ||= %r{\A[-+\.\_a-zA-Z0-9]+\z}.freeze
+ end
+
+ def nuget_version_regex
+ @nuget_version_regex ||= /
+ \A#{_semver_major_regex}
+ \.#{_semver_minor_regex}
+ (\.#{_semver_patch_regex})?
+ (\.\d*)?
+ #{_semver_prerelease_build_regex}\z
+ /x.freeze
+ end
+
+ def terraform_module_package_name_regex
+ @terraform_module_package_name_regex ||= %r{\A[-a-z0-9]+\/[-a-z0-9]+\z}.freeze
+ end
+
+ def pypi_version_regex
+ # See the official regex: https://github.com/pypa/packaging/blob/16.7/packaging/version.py#L159
+
+ @pypi_version_regex ||= %r{
+ \A(?:
+ v?
+ (?:([0-9]+)!)? (?# epoch)
+ ([0-9]+(?:\.[0-9]+)*) (?# release segment)
+ ([-_\.]?((a|b|c|rc|alpha|beta|pre|preview))[-_\.]?([0-9]+)?)? (?# pre-release)
+ ((?:-([0-9]+))|(?:[-_\.]?(post|rev|r)[-_\.]?([0-9]+)?))? (?# post release)
+ ([-_\.]?(dev)[-_\.]?([0-9]+)?)? (?# dev release)
+ (?:\+([a-z0-9]+(?:[-_\.][a-z0-9]+)*))? (?# local version)
+ )\z}xi.freeze
+ end
+
+ def debian_package_name_regex
+ # See official parser
+ # https://git.dpkg.org/cgit/dpkg/dpkg.git/tree/lib/dpkg/parsehelp.c?id=9e0c88ec09475f4d1addde9cdba1ad7849720356#n122
+ # @debian_package_name_regex ||= %r{\A[a-z0-9][-+\._a-z0-9]*\z}i.freeze
+ # But we prefer a more strict version from Lintian
+ # https://salsa.debian.org/lintian/lintian/-/blob/5080c0068ffc4a9ddee92022a91d0c2ff53e56d1/lib/Lintian/Util.pm#L116
+ @debian_package_name_regex ||= %r{\A[a-z0-9][-+\.a-z0-9]+\z}.freeze
+ end
+
+ def debian_version_regex
+ # See official parser: https://git.dpkg.org/cgit/dpkg/dpkg.git/tree/lib/dpkg/parsehelp.c?id=9e0c88ec09475f4d1addde9cdba1ad7849720356#n205
+ @debian_version_regex ||= %r{
+ \A(?:
+ (?:([0-9]{1,9}):)? (?# epoch)
+ ([0-9][0-9a-z\.+~]*) (?# version)
+ (-[0-9a-z\.+~]+){0,14} (?# -revision)
+ (?<!-)
+ )\z}xi.freeze
+ end
+
+ def debian_architecture_regex
+ # See official parser: https://git.dpkg.org/cgit/dpkg/dpkg.git/tree/lib/dpkg/arch.c?id=9e0c88ec09475f4d1addde9cdba1ad7849720356#n43
+ # But we limit to lower case
+ @debian_architecture_regex ||= %r{\A#{::Packages::Debian::ARCHITECTURE_REGEX}\z}o.freeze
+ end
+
+ def debian_distribution_regex
+ @debian_distribution_regex ||= %r{\A#{::Packages::Debian::DISTRIBUTION_REGEX}\z}io.freeze
+ end
+
+ def debian_component_regex
+ @debian_component_regex ||= %r{\A#{::Packages::Debian::COMPONENT_REGEX}\z}o.freeze
+ end
+
+ def debian_direct_upload_filename_regex
+ @debian_direct_upload_filename_regex ||= %r{\A.*\.(deb|udeb|ddeb)\z}o.freeze
+ end
+
+ def helm_channel_regex
+ @helm_channel_regex ||= %r{\A([a-zA-Z0-9](\.|-|_)?){1,255}(?<!\.|-|_)\z}.freeze
+ end
+
+ def helm_package_regex
+ @helm_package_regex ||= %r{#{helm_channel_regex}}.freeze
+ end
+
+ def helm_version_regex
+ # identical to semver_regex, with optional preceding 'v'
+ @helm_version_regex ||= Regexp.new("\\Av?#{::Gitlab::Regex.unbounded_semver_regex.source}\\z", ::Gitlab::Regex.unbounded_semver_regex.options)
+ end
+
+ def unbounded_semver_regex
+ # See the official regex: https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
+
+ # The order of the alternatives in <prerelease> are intentionally
+ # reordered to be greedy. Without this change, the unbounded regex would
+ # only partially match "v0.0.0-20201230123456-abcdefabcdef".
+ @unbounded_semver_regex ||= /
+ #{_semver_major_minor_patch_regex}#{_semver_prerelease_build_regex}
+ /x.freeze
+ end
+
+ def semver_regex
+ @semver_regex ||= Regexp.new("\\A#{::Gitlab::Regex.unbounded_semver_regex.source}\\z", ::Gitlab::Regex.unbounded_semver_regex.options).freeze
+ end
+
+ def semver_regex_message
+ 'should follow SemVer: https://semver.org'
+ end
+
+ # These partial semver regexes are intended for use in composing other
+ # regexes rather than being used alone.
+ def _semver_major_minor_patch_regex
+ @_semver_major_minor_patch_regex ||= /
+ #{_semver_major_regex}\.#{_semver_minor_regex}\.#{_semver_patch_regex}
+ /x.freeze
+ end
+
+ def _semver_major_regex
+ @_semver_major_regex ||= /
+ (?<major>0|[1-9]\d*)
+ /x.freeze
+ end
+
+ def _semver_minor_regex
+ @_semver_minor_regex ||= /
+ (?<minor>0|[1-9]\d*)
+ /x.freeze
+ end
+
+ def _semver_patch_regex
+ @_semver_patch_regex ||= /
+ (?<patch>0|[1-9]\d*)
+ /x.freeze
+ end
+
+ def _semver_prerelease_build_regex
+ @_semver_prerelease_build_regex ||= /
+ (?:-(?<prerelease>(?:\d*[a-zA-Z-][0-9a-zA-Z-]*|[1-9]\d*|0)(?:\.(?:\d*[a-zA-Z-][0-9a-zA-Z-]*|[1-9]\d*|0))*))?
+ (?:\+(?<build>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?
+ /x.freeze
+ end
+
+ def prefixed_semver_regex
+ # identical to semver_regex, except starting with 'v'
+ @prefixed_semver_regex ||= Regexp.new("\\Av#{::Gitlab::Regex.unbounded_semver_regex.source}\\z", ::Gitlab::Regex.unbounded_semver_regex.options)
+ end
+
+ def go_package_regex
+ # A Go package name looks like a URL but is not; it:
+ # - Must not have a scheme, such as http:// or https://
+ # - Must not have a port number, such as :8080 or :8443
+
+ @go_package_regex ||= %r{
+ \b (?# word boundary)
+ (?<domain>
+ [0-9a-z](?:(?:-|[0-9a-z]){0,61}[0-9a-z])? (?# first domain)
+ (?:\.[0-9a-z](?:(?:-|[0-9a-z]){0,61}[0-9a-z])?)* (?# inner domains)
+ \.[a-z]{2,} (?# top-level domain)
+ )
+ (?<path>/(?:
+ [-/$_.+!*'(),0-9a-z] (?# plain URL character)
+ | %[0-9a-f]{2})* (?# URL encoded character)
+ )? (?# path)
+ \b (?# word boundary)
+ }ix.freeze
+ end
+
+ def generic_package_version_regex
+ maven_version_regex
+ end
+
+ def generic_package_name_regex
+ maven_file_name_regex
+ end
+
+ def generic_package_file_name_regex
+ generic_package_name_regex
+ end
+
+ def sha256_regex
+ @sha256_regex ||= /\A[0-9a-f]{64}\z/i.freeze
+ end
+
+ def slack_link_regex
+ @slack_link_regex ||= /<(.*[|].*)>/i.freeze
+ end
+
+ private
+
+ def conan_name_regex
+ @conan_name_regex ||= %r{[a-zA-Z0-9_][a-zA-Z0-9_\+\.-]{1,49}}.freeze
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/repository_size_checker.rb b/lib/gitlab/repository_size_checker.rb
index 2afc5e8d668..6ccdf1f4354 100644
--- a/lib/gitlab/repository_size_checker.rb
+++ b/lib/gitlab/repository_size_checker.rb
@@ -29,7 +29,7 @@ module Gitlab
end
# @param change_size [int] in bytes
- def changes_will_exceed_size_limit?(change_size)
+ def changes_will_exceed_size_limit?(change_size, _project)
return false unless enabled?
above_size_limit? || exceeded_size(change_size) > 0
diff --git a/lib/gitlab/repository_size_error_message.rb b/lib/gitlab/repository_size_error_message.rb
index e7d527dd4ce..fc700c3c62e 100644
--- a/lib/gitlab/repository_size_error_message.rb
+++ b/lib/gitlab/repository_size_error_message.rb
@@ -7,8 +7,7 @@ module Gitlab
delegate :current_size, :limit, :exceeded_size, :additional_repo_storage_available?, to: :@checker
# @param checker [RepositorySizeChecker]
- def initialize(checker, message_params = {})
- @message_params = message_params
+ def initialize(checker)
@checker = checker
end
@@ -20,14 +19,6 @@ module Gitlab
"This merge request cannot be merged, #{base_message}"
end
- def push_warning
- _("##### WARNING ##### You have used %{usage_percentage} of the storage quota for %{namespace_name} " \
- "(%{current_size} of %{size_limit}). If %{namespace_name} exceeds the storage quota, " \
- "all projects in the namespace will be locked and actions will be restricted. " \
- "To manage storage, or purchase additional storage, see %{manage_storage_url}. " \
- "To learn more about restricted actions, see %{restricted_actions_url}") % push_message_params
- end
-
def push_error(change_size = 0)
"Your push has been rejected, #{base_message(change_size)}. #{more_info_message}"
end
@@ -50,19 +41,6 @@ module Gitlab
private
- attr_reader :message_params
-
- def push_message_params
- {
- namespace_name: message_params[:namespace_name],
- manage_storage_url: help_page_url('user/usage_quotas', 'manage-your-storage-usage'),
- restricted_actions_url: help_page_url('user/read_only_namespaces', 'restricted-actions'),
- current_size: formatted(current_size),
- size_limit: formatted(limit),
- usage_percentage: usage_percentage
- }
- end
-
def base_message(change_size = 0)
"because this repository has exceeded its size limit of #{formatted(limit)} by #{formatted(exceeded_size(change_size))}"
end
@@ -70,13 +48,5 @@ module Gitlab
def formatted(number)
number_to_human_size(number, delimiter: ',', precision: 2)
end
-
- def usage_percentage
- number_to_percentage(@checker.usage_ratio * 100, precision: 0)
- end
-
- def help_page_url(path, anchor = nil)
- ::Gitlab::Routing.url_helpers.help_page_url(path, anchor: anchor)
- end
end
end
diff --git a/lib/gitlab/safe_request_store.rb b/lib/gitlab/safe_request_store.rb
deleted file mode 100644
index 203d7d10532..00000000000
--- a/lib/gitlab/safe_request_store.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module SafeRequestStore
- NULL_STORE = Gitlab::NullRequestStore.new
-
- class << self
- # These methods should always run directly against RequestStore
- delegate :clear!, :begin!, :end!, :active?, to: :RequestStore
-
- # These methods will run against NullRequestStore if RequestStore is disabled
- delegate :read, :[], :write, :[]=, :exist?, :fetch, :delete, to: :store
- end
-
- def self.store
- if RequestStore.active?
- RequestStore
- else
- NULL_STORE
- end
- end
-
- # Access to the backing storage of the request store. This returns an object
- # with `[]` and `[]=` methods that does not discard values.
- #
- # This can be useful if storage is needed for a delimited purpose, and the
- # forgetful nature of the null store is undesirable.
- def self.storage
- store.store
- end
-
- # This method accept an options hash to be compatible with
- # ActiveSupport::Cache::Store#write method. The options are
- # not passed to the underlying cache implementation because
- # RequestStore#write accepts only a key, and value params.
- def self.write(key, value, options = nil)
- store.write(key, value)
- end
-
- def self.delete_if(&block)
- return unless RequestStore.active?
-
- storage.delete_if { |k, v| yield(k) }
- end
- end
-end
diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb
index 4fedc450f9b..0e419d0162c 100644
--- a/lib/gitlab/search_results.rb
+++ b/lib/gitlab/search_results.rb
@@ -3,7 +3,7 @@
module Gitlab
class SearchResults
COUNT_LIMIT = 100
- COUNT_LIMIT_MESSAGE = "#{COUNT_LIMIT - 1}+"
+ COUNT_LIMIT_MESSAGE = "#{COUNT_LIMIT - 1}+".freeze
DEFAULT_PAGE = 1
DEFAULT_PER_PAGE = 20
@@ -24,7 +24,14 @@ module Gitlab
# query
attr_reader :default_project_filter
- def initialize(current_user, query, limit_projects = nil, order_by: nil, sort: nil, default_project_filter: false, filters: {})
+ def initialize(
+ current_user,
+ query,
+ limit_projects = nil,
+ order_by: nil,
+ sort: nil,
+ default_project_filter: false,
+ filters: {})
@current_user = current_user
@query = query
@limit_projects = limit_projects || Project.all
@@ -111,12 +118,12 @@ module Gitlab
end
# highlighting is only performed by Elasticsearch backed results
- def highlight_map(scope)
+ def highlight_map(_scope)
{}
end
# aggregations are only performed by Elasticsearch backed results
- def aggregations(scope)
+ def aggregations(_scope)
[]
end
@@ -152,13 +159,11 @@ module Gitlab
sort_by = ::Gitlab::Search::SortOptions.sort_and_direction(order_by, sort)
# Reset sort to default if the chosen one is not supported by scope
- sort_by = nil if SCOPE_ONLY_SORT[sort_by] && !SCOPE_ONLY_SORT[sort_by].include?(scope)
+ sort_by = nil if SCOPE_ONLY_SORT[sort_by] && SCOPE_ONLY_SORT[sort_by].exclude?(scope)
case sort_by
when :created_at_asc
results.reorder('created_at ASC')
- when :created_at_desc
- results.reorder('created_at DESC')
when :updated_at_asc
results.reorder('updated_at ASC')
when :updated_at_desc
@@ -168,6 +173,7 @@ module Gitlab
when :popularity_desc
results.reorder('upvotes_count DESC')
else
+ # :created_at_desc is default
results.reorder('created_at DESC')
end
end
@@ -175,7 +181,11 @@ module Gitlab
def projects
scope = limit_projects
- scope = scope.non_archived if Feature.enabled?(:search_projects_hide_archived) && !filters[:include_archived]
+
+ if Feature.enabled?(:search_projects_hide_archived, current_user) && !filters[:include_archived]
+ scope = scope.non_archived
+ end
+
scope.search(query)
end
@@ -184,6 +194,7 @@ module Gitlab
unless default_project_filter
issues = issues.in_projects(project_ids_relation)
+ .allow_cross_joins_across_databases(url: "https://gitlab.com/gitlab-org/gitlab/-/issues/420046")
end
apply_sort(issues, scope: 'issues')
@@ -203,7 +214,13 @@ module Gitlab
merge_requests = MergeRequestsFinder.new(current_user, issuable_params).execute
unless default_project_filter
- merge_requests = merge_requests.of_projects(project_ids_relation)
+ project_ids = project_ids_relation
+
+ if Feature.enabled?(:search_merge_requests_hide_archived_projects, current_user) && !filters[:include_archived]
+ project_ids = project_ids.non_archived
+ end
+
+ merge_requests = merge_requests.of_projects(project_ids)
end
apply_sort(merge_requests, scope: 'merge_requests')
@@ -251,9 +268,7 @@ module Gitlab
params[:state] = filters[:state] if filters.key?(:state)
- if [true, false].include?(filters[:confidential])
- params[:confidential] = filters[:confidential]
- end
+ params[:confidential] = filters[:confidential] if [true, false].include?(filters[:confidential])
end
end
diff --git a/lib/gitlab/sidekiq_logging/logs_jobs.rb b/lib/gitlab/sidekiq_logging/logs_jobs.rb
index 3e6e6e05e95..edf2cffa0c9 100644
--- a/lib/gitlab/sidekiq_logging/logs_jobs.rb
+++ b/lib/gitlab/sidekiq_logging/logs_jobs.rb
@@ -12,7 +12,8 @@ module Gitlab
# Error information from the previous try is in the payload for
# displaying in the Sidekiq UI, but is very confusing in logs!
job = job.except(
- 'exception.backtrace', 'exception.class', 'exception.message', 'exception.sql'
+ 'exception.backtrace', 'exception.class', 'exception.message', 'exception.sql',
+ 'error_message', 'error_class', 'error_backtrace', 'failed_at'
)
job['class'] = job.delete('wrapped') if job['wrapped'].present?
diff --git a/lib/gitlab/sidekiq_logging/pause_control_logger.rb b/lib/gitlab/sidekiq_logging/pause_control_logger.rb
new file mode 100644
index 00000000000..d48b2b12f9d
--- /dev/null
+++ b/lib/gitlab/sidekiq_logging/pause_control_logger.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SidekiqLogging
+ class PauseControlLogger
+ include Singleton
+ include LogsJobs
+
+ def paused_log(job, strategy:)
+ payload = parse_job(job)
+ payload['job_status'] = 'paused'
+ payload['message'] = "#{base_message(payload)}: paused: #{strategy}"
+ payload['pause_control.strategy'] = strategy
+
+ Sidekiq.logger.info payload
+ end
+
+ def resumed_log(worker_name, args)
+ job = {
+ 'class' => worker_name,
+ 'args' => args
+ }
+ payload = parse_job(job)
+ payload['job_status'] = 'resumed'
+ payload['message'] = "#{base_message(payload)}: resumed"
+
+ Sidekiq.logger.info payload
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/sidekiq_logging/structured_logger.rb b/lib/gitlab/sidekiq_logging/structured_logger.rb
index 56762c0fb4b..c65d9c5ddd5 100644
--- a/lib/gitlab/sidekiq_logging/structured_logger.rb
+++ b/lib/gitlab/sidekiq_logging/structured_logger.rb
@@ -100,6 +100,8 @@ module Gitlab
unless job_urgency.empty?
payload['urgency'] = job_urgency
payload['target_duration_s'] = Gitlab::Metrics::SidekiqSlis.execution_duration_for_urgency(job_urgency)
+ payload['target_scheduling_latency_s'] =
+ Gitlab::Metrics::SidekiqSlis.queueing_duration_for_urgency(job_urgency)
end
payload
diff --git a/lib/gitlab/sidekiq_middleware.rb b/lib/gitlab/sidekiq_middleware.rb
index 614cd11421e..e1c155a4848 100644
--- a/lib/gitlab/sidekiq_middleware.rb
+++ b/lib/gitlab/sidekiq_middleware.rb
@@ -36,6 +36,7 @@ module Gitlab
chain.add ::Gitlab::SidekiqVersioning::Middleware
chain.add ::Gitlab::SidekiqStatus::ServerMiddleware
chain.add ::Gitlab::SidekiqMiddleware::WorkerContext::Server
+ chain.add ::Gitlab::SidekiqMiddleware::PauseControl::Server
# DuplicateJobs::Server should be placed at the bottom, but before the SidekiqServerMiddleware,
# so we can compare the latest WAL location against replica
chain.add ::Gitlab::SidekiqMiddleware::DuplicateJobs::Server
@@ -54,6 +55,7 @@ module Gitlab
# Sidekiq Client Middleware should be placed before DuplicateJobs::Client middleware,
# so we can store WAL location before we deduplicate the job.
chain.add ::Gitlab::Database::LoadBalancing::SidekiqClientMiddleware
+ chain.add ::Gitlab::SidekiqMiddleware::PauseControl::Client
chain.add ::Gitlab::SidekiqMiddleware::DuplicateJobs::Client
chain.add ::Gitlab::SidekiqStatus::ClientMiddleware
chain.add ::Gitlab::SidekiqMiddleware::AdminMode::Client
diff --git a/lib/gitlab/sidekiq_middleware/pause_control.rb b/lib/gitlab/sidekiq_middleware/pause_control.rb
new file mode 100644
index 00000000000..2f0fd0cc799
--- /dev/null
+++ b/lib/gitlab/sidekiq_middleware/pause_control.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SidekiqMiddleware
+ module PauseControl
+ DEFAULT_STRATEGY = :none
+
+ UnknownStrategyError = Class.new(StandardError)
+
+ STRATEGIES = {
+ zoekt: ::Gitlab::SidekiqMiddleware::PauseControl::Strategies::Zoekt,
+ none: ::Gitlab::SidekiqMiddleware::PauseControl::Strategies::None
+ }.freeze
+
+ def self.for(name)
+ STRATEGIES.fetch(name, STRATEGIES[DEFAULT_STRATEGY])
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/sidekiq_middleware/pause_control/client.rb b/lib/gitlab/sidekiq_middleware/pause_control/client.rb
new file mode 100644
index 00000000000..406a956e9ff
--- /dev/null
+++ b/lib/gitlab/sidekiq_middleware/pause_control/client.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SidekiqMiddleware
+ module PauseControl
+ class Client
+ def call(worker_class, job, _queue, _redis_pool, &block)
+ ::Gitlab::SidekiqMiddleware::PauseControl::StrategyHandler.new(worker_class, job).schedule(&block)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/sidekiq_middleware/pause_control/pause_control_service.rb b/lib/gitlab/sidekiq_middleware/pause_control/pause_control_service.rb
new file mode 100644
index 00000000000..73f42beaf9e
--- /dev/null
+++ b/lib/gitlab/sidekiq_middleware/pause_control/pause_control_service.rb
@@ -0,0 +1,118 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SidekiqMiddleware
+ module PauseControl
+ class PauseControlService
+ # Class for managing queues for paused workers
+ # When a worker is paused all jobs are saved in a separate sorted sets in redis
+ LIMIT = 1000
+ PROJECT_CONTEXT_KEY = "#{Gitlab::ApplicationContext::LOG_KEY}.project".freeze
+
+ def initialize(worker_name)
+ @worker_name = worker_name
+
+ worker_name = @worker_name.underscore
+ @redis_set_key = "sidekiq:pause_control:paused_jobs:zset:{#{worker_name}}"
+ @redis_score_key = "sidekiq:pause_control:paused_jobs:score:{#{worker_name}}"
+ end
+
+ class << self
+ def add_to_waiting_queue!(worker_name, args, context)
+ new(worker_name).add_to_waiting_queue!(args, context)
+ end
+
+ def has_jobs_in_waiting_queue?(worker_name)
+ new(worker_name).has_jobs_in_waiting_queue?
+ end
+
+ def resume_processing!(worker_name)
+ new(worker_name).resume_processing!
+ end
+
+ def queue_size(worker_name)
+ new(worker_name).queue_size
+ end
+ end
+
+ def add_to_waiting_queue!(args, context)
+ with_redis do |redis|
+ redis.zadd(redis_set_key, generate_unique_score(redis), serialize(args, context))
+ end
+ end
+
+ def queue_size
+ with_redis { |redis| redis.zcard(redis_set_key) }
+ end
+
+ def has_jobs_in_waiting_queue?
+ with_redis { |redis| redis.exists?(redis_set_key) } # rubocop:disable CodeReuse/ActiveRecord
+ end
+
+ def resume_processing!(iterations: 1)
+ with_redis do |redis|
+ iterations.times do
+ jobs_with_scores = next_batch_from_waiting_queue(redis)
+ break if jobs_with_scores.empty?
+
+ parsed_jobs = jobs_with_scores.map { |j, _| deserialize(j) }
+
+ parsed_jobs.each { |j| send_to_processing_queue(j) }
+
+ remove_jobs_from_waiting_queue(redis, jobs_with_scores)
+ end
+
+ size = queue_size
+ redis.del(redis_score_key, redis_set_key) if size == 0
+
+ size
+ end
+ end
+
+ private
+
+ attr_reader :worker_name, :redis_set_key, :redis_score_key
+
+ def with_redis(&blk)
+ Gitlab::Redis::SharedState.with(&blk) # rubocop:disable CodeReuse/ActiveRecord
+ end
+
+ def serialize(args, context)
+ {
+ args: args,
+ # Only include part of the context that would not prevent deduplication
+ context: context.slice(PROJECT_CONTEXT_KEY)
+ }.to_json
+ end
+
+ def deserialize(json)
+ Gitlab::Json.parse(json)
+ end
+
+ def send_to_processing_queue(job)
+ Gitlab::ApplicationContext.with_raw_context(job['context']) do
+ args = job['args']
+
+ Gitlab::SidekiqLogging::PauseControlLogger.instance.resumed_log(worker_name, args)
+
+ worker_name.safe_constantize&.perform_async(*args)
+ end
+ end
+
+ def generate_unique_score(redis)
+ redis.incr(redis_score_key)
+ end
+
+ def next_batch_from_waiting_queue(redis)
+ redis.zrangebyscore(redis_set_key, '-inf', '+inf', limit: [0, LIMIT], with_scores: true)
+ end
+
+ def remove_jobs_from_waiting_queue(redis, jobs_with_scores)
+ first_score = jobs_with_scores.first.last
+ last_score = jobs_with_scores.last.last
+ redis.zremrangebyscore(redis_set_key, first_score, last_score)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/sidekiq_middleware/pause_control/server.rb b/lib/gitlab/sidekiq_middleware/pause_control/server.rb
new file mode 100644
index 00000000000..cfa02b3ec3a
--- /dev/null
+++ b/lib/gitlab/sidekiq_middleware/pause_control/server.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SidekiqMiddleware
+ module PauseControl
+ class Server
+ def call(worker_class, job, _queue, &block)
+ ::Gitlab::SidekiqMiddleware::PauseControl::StrategyHandler.new(worker_class, job).perform(&block)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/sidekiq_middleware/pause_control/strategies/base.rb b/lib/gitlab/sidekiq_middleware/pause_control/strategies/base.rb
new file mode 100644
index 00000000000..d92cbccc94e
--- /dev/null
+++ b/lib/gitlab/sidekiq_middleware/pause_control/strategies/base.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SidekiqMiddleware
+ module PauseControl
+ module Strategies
+ class Base
+ extend ::Gitlab::Utils::Override
+
+ def self.should_pause?
+ new.should_pause?
+ end
+
+ def schedule(job)
+ if should_pause?
+ pause_job!(job)
+
+ return
+ end
+
+ yield
+ end
+
+ def perform(job)
+ if should_pause?
+ pause_job!(job)
+
+ return
+ end
+
+ yield
+ end
+
+ def should_pause?
+ # All children must implement this method
+ # return false when the jobs shouldn't be paused and true when it should
+ # A cron job PauseControl::ResumeWorker will execute this method to check if jobs should remain paused
+ raise NotImplementedError
+ end
+
+ private
+
+ def pause_job!(job)
+ Gitlab::SidekiqLogging::PauseControlLogger.instance.paused_log(job, strategy: strategy_name)
+
+ Gitlab::SidekiqMiddleware::PauseControl::PauseControlService.add_to_waiting_queue!(
+ job['class'],
+ job['args'],
+ current_context
+ )
+ end
+
+ def strategy_name
+ Gitlab::SidekiqMiddleware::PauseControl::STRATEGIES.key(self.class)
+ end
+
+ def current_context
+ Gitlab::ApplicationContext.current
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/sidekiq_middleware/pause_control/strategies/none.rb b/lib/gitlab/sidekiq_middleware/pause_control/strategies/none.rb
new file mode 100644
index 00000000000..c31f0a9918e
--- /dev/null
+++ b/lib/gitlab/sidekiq_middleware/pause_control/strategies/none.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SidekiqMiddleware
+ module PauseControl
+ module Strategies
+ # This strategy will never pause a job
+ class None < Base
+ override :should_pause?
+ def should_pause?
+ false
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/sidekiq_middleware/pause_control/strategies/zoekt.rb b/lib/gitlab/sidekiq_middleware/pause_control/strategies/zoekt.rb
new file mode 100644
index 00000000000..23cba5553e2
--- /dev/null
+++ b/lib/gitlab/sidekiq_middleware/pause_control/strategies/zoekt.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SidekiqMiddleware
+ module PauseControl
+ module Strategies
+ class Zoekt < Base
+ override :should_pause?
+ def should_pause?
+ ::Feature.enabled?(:zoekt_pause_indexing, type: :ops)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/sidekiq_middleware/pause_control/strategy_handler.rb b/lib/gitlab/sidekiq_middleware/pause_control/strategy_handler.rb
new file mode 100644
index 00000000000..93c668052b0
--- /dev/null
+++ b/lib/gitlab/sidekiq_middleware/pause_control/strategy_handler.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SidekiqMiddleware
+ module PauseControl
+ class StrategyHandler
+ def initialize(worker_class, job)
+ @worker_class = worker_class
+ @job = job
+ end
+
+ # This will continue the middleware chain if the job should be scheduled
+ # It will return false if the job needs to be cancelled
+ def schedule(&block)
+ PauseControl.for(strategy).new.schedule(job, &block)
+ end
+
+ # This will continue the server middleware chain if the job should be
+ # executed.
+ # It will return false if the job should not be executed.
+ def perform(&block)
+ PauseControl.for(strategy).new.perform(job, &block)
+ end
+
+ private
+
+ attr_reader :job, :worker_class
+
+ def strategy
+ Gitlab::SidekiqMiddleware::PauseControl::WorkersMap.strategy_for(worker: worker_class)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/sidekiq_middleware/pause_control/workers_map.rb b/lib/gitlab/sidekiq_middleware/pause_control/workers_map.rb
new file mode 100644
index 00000000000..dc6aff92f50
--- /dev/null
+++ b/lib/gitlab/sidekiq_middleware/pause_control/workers_map.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SidekiqMiddleware
+ module PauseControl
+ class WorkersMap
+ class << self
+ attr_reader :workers
+
+ def set_strategy_for(strategy:, worker:)
+ raise ArgumentError, "Unknown strategy: #{strategy}" unless PauseControl::STRATEGIES.key?(strategy)
+
+ @workers ||= Hash.new { |h, k| h[k] = [] }
+ @workers[strategy].push(worker)
+ end
+
+ def strategy_for(worker:)
+ return unless @workers
+
+ @workers.find { |_, v| v.include?(worker) }&.first
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/sidekiq_middleware/request_store_middleware.rb b/lib/gitlab/sidekiq_middleware/request_store_middleware.rb
index f6142bd6ca5..32314bbe0f8 100644
--- a/lib/gitlab/sidekiq_middleware/request_store_middleware.rb
+++ b/lib/gitlab/sidekiq_middleware/request_store_middleware.rb
@@ -3,10 +3,8 @@
module Gitlab
module SidekiqMiddleware
class RequestStoreMiddleware
- include Gitlab::WithRequestStore
-
def call(worker, job, queue)
- with_request_store do
+ ::Gitlab::SafeRequestStore.ensure_request_store do
yield
end
end
diff --git a/lib/gitlab/sidekiq_middleware/server_metrics.rb b/lib/gitlab/sidekiq_middleware/server_metrics.rb
index 058c23178f8..a8b3683e09f 100644
--- a/lib/gitlab/sidekiq_middleware/server_metrics.rb
+++ b/lib/gitlab/sidekiq_middleware/server_metrics.rb
@@ -11,28 +11,25 @@ module Gitlab
# most of the durations for cpu, gitaly, db and elasticsearch
SIDEKIQ_LATENCY_BUCKETS = [0.1, 0.5, 1, 2.5].freeze
- # These are the buckets we currently use for alerting, we will likely
- # replace these histograms with Application SLIs
- # https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1313
+ # These buckets are only available on self-managed.
+ # We have replaced with Application SLIs on GitLab.com.
+ # https://gitlab.com/groups/gitlab-com/gl-infra/-/epics/700
SIDEKIQ_JOB_DURATION_BUCKETS = [10, 300].freeze
SIDEKIQ_QUEUE_DURATION_BUCKETS = [10, 60].freeze
# These labels from Gitlab::SidekiqMiddleware::MetricsHelper are included in SLI metrics
- SIDEKIQ_SLI_LABELS = [:worker, :feature_category, :urgency, :external_dependencies].freeze
+ SIDEKIQ_SLI_LABELS = [:worker, :feature_category, :urgency, :external_dependencies, :queue].freeze
class << self
include ::Gitlab::SidekiqMiddleware::MetricsHelper
def metrics
- {
+ metrics = {
sidekiq_jobs_cpu_seconds: ::Gitlab::Metrics.histogram(:sidekiq_jobs_cpu_seconds, 'Seconds this Sidekiq job spent on the CPU', {}, SIDEKIQ_LATENCY_BUCKETS),
- sidekiq_jobs_completion_seconds: ::Gitlab::Metrics.histogram(:sidekiq_jobs_completion_seconds, 'Seconds to complete Sidekiq job', {}, SIDEKIQ_JOB_DURATION_BUCKETS),
sidekiq_jobs_db_seconds: ::Gitlab::Metrics.histogram(:sidekiq_jobs_db_seconds, 'Seconds of database time to run Sidekiq job', {}, SIDEKIQ_LATENCY_BUCKETS),
sidekiq_jobs_gitaly_seconds: ::Gitlab::Metrics.histogram(:sidekiq_jobs_gitaly_seconds, 'Seconds of Gitaly time to run Sidekiq job', {}, SIDEKIQ_LATENCY_BUCKETS),
- sidekiq_jobs_queue_duration_seconds: ::Gitlab::Metrics.histogram(:sidekiq_jobs_queue_duration_seconds, 'Duration in seconds that a Sidekiq job was queued before being executed', {}, SIDEKIQ_QUEUE_DURATION_BUCKETS),
sidekiq_redis_requests_duration_seconds: ::Gitlab::Metrics.histogram(:sidekiq_redis_requests_duration_seconds, 'Duration in seconds that a Sidekiq job spent requests a Redis server', {}, Gitlab::Instrumentation::Redis::QUERY_TIME_BUCKETS),
sidekiq_elasticsearch_requests_duration_seconds: ::Gitlab::Metrics.histogram(:sidekiq_elasticsearch_requests_duration_seconds, 'Duration in seconds that a Sidekiq job spent in requests to an Elasticsearch server', {}, SIDEKIQ_LATENCY_BUCKETS),
- sidekiq_jobs_failed_total: ::Gitlab::Metrics.counter(:sidekiq_jobs_failed_total, 'Sidekiq jobs failed'),
sidekiq_jobs_retried_total: ::Gitlab::Metrics.counter(:sidekiq_jobs_retried_total, 'Sidekiq jobs retried'),
sidekiq_jobs_interrupted_total: ::Gitlab::Metrics.counter(:sidekiq_jobs_interrupted_total, 'Sidekiq jobs interrupted'),
sidekiq_redis_requests_total: ::Gitlab::Metrics.counter(:sidekiq_redis_requests_total, 'Redis requests during a Sidekiq job execution'),
@@ -41,6 +38,17 @@ module Gitlab
sidekiq_concurrency: ::Gitlab::Metrics.gauge(:sidekiq_concurrency, 'Maximum number of Sidekiq jobs', {}, :all),
sidekiq_mem_total_bytes: ::Gitlab::Metrics.gauge(:sidekiq_mem_total_bytes, 'Number of bytes allocated for both objects consuming an object slot and objects that required a malloc', {}, :all)
}
+
+ if Feature.enabled?(:emit_sidekiq_histogram_metrics, type: :ops)
+ metrics[:sidekiq_jobs_completion_seconds] = ::Gitlab::Metrics.histogram(:sidekiq_jobs_completion_seconds, 'Seconds to complete Sidekiq job', {}, SIDEKIQ_JOB_DURATION_BUCKETS)
+ metrics[:sidekiq_jobs_queue_duration_seconds] = ::Gitlab::Metrics.histogram(:sidekiq_jobs_queue_duration_seconds, 'Duration in seconds that a Sidekiq job was queued before being executed', {}, SIDEKIQ_QUEUE_DURATION_BUCKETS)
+ metrics[:sidekiq_jobs_failed_total] = ::Gitlab::Metrics.counter(:sidekiq_jobs_failed_total, 'Sidekiq jobs failed')
+ else
+ # The sum metric is still used in GitLab.com for dashboards
+ metrics[:sidekiq_jobs_completion_seconds_sum] = ::Gitlab::Metrics.counter(:sidekiq_jobs_completion_seconds_sum, 'Total of seconds to complete Sidekiq job')
+ end
+
+ metrics
end
def initialize_process_metrics
@@ -59,13 +67,15 @@ module Gitlab
base_labels = create_labels(worker_class, queue, {})
possible_sli_labels << base_labels.slice(*SIDEKIQ_SLI_LABELS)
+ next unless Feature.enabled?(:emit_sidekiq_histogram_metrics, type: :ops)
+
%w[done fail].each do |status|
metrics[:sidekiq_jobs_completion_seconds].get(base_labels.merge(job_status: status))
end
end
- Gitlab::Metrics::SidekiqSlis.initialize_execution_slis!(possible_sli_labels) if ::Feature.enabled?(:sidekiq_execution_application_slis)
- Gitlab::Metrics::SidekiqSlis.initialize_queueing_slis!(possible_sli_labels) if ::Feature.enabled?(:sidekiq_queueing_application_slis)
+ Gitlab::Metrics::SidekiqSlis.initialize_execution_slis!(possible_sli_labels)
+ Gitlab::Metrics::SidekiqSlis.initialize_queueing_slis!(possible_sli_labels)
end
end
@@ -92,7 +102,8 @@ module Gitlab
def instrument(job, labels)
queue_duration = ::Gitlab::InstrumentationHelper.queue_duration_for_job(job)
- @metrics[:sidekiq_jobs_queue_duration_seconds].observe(labels, queue_duration) if queue_duration
+ @metrics[:sidekiq_jobs_queue_duration_seconds]&.observe(labels, queue_duration) if queue_duration
+
@metrics[:sidekiq_running_jobs].increment(labels, 1)
if job['retry_count'].present?
@@ -119,13 +130,21 @@ module Gitlab
# sidekiq_running_jobs, sidekiq_jobs_failed_total should not include the job_status label
@metrics[:sidekiq_running_jobs].increment(labels, -1)
- @metrics[:sidekiq_jobs_failed_total].increment(labels, 1) unless job_succeeded
+
+ if Feature.enabled?(:emit_sidekiq_histogram_metrics, type: :ops)
+ @metrics[:sidekiq_jobs_failed_total].increment(labels, 1) unless job_succeeded
+ else
+ # we don't need job_status label here
+ @metrics[:sidekiq_jobs_completion_seconds_sum].increment(labels, monotonic_time)
+ end
# job_status: done, fail match the job_status attribute in structured logging
labels[:job_status] = job_succeeded ? "done" : "fail"
instrumentation = job[:instrumentation] || {}
@metrics[:sidekiq_jobs_cpu_seconds].observe(labels, job_thread_cputime)
- @metrics[:sidekiq_jobs_completion_seconds].observe(labels, monotonic_time)
+
+ @metrics[:sidekiq_jobs_completion_seconds]&.observe(labels, monotonic_time)
+
@metrics[:sidekiq_jobs_db_seconds].observe(labels, ActiveRecord::LogSubscriber.runtime / 1000)
@metrics[:sidekiq_jobs_gitaly_seconds].observe(labels, get_gitaly_time(instrumentation))
@metrics[:sidekiq_redis_requests_total].increment(labels, get_redis_calls(instrumentation))
@@ -143,16 +162,10 @@ module Gitlab
@metrics[:sidekiq_load_balancing_count].increment(labels.merge(load_balancing_labels), 1)
end
- if ::Feature.enabled?(:sidekiq_execution_application_slis)
- sli_labels = labels.slice(*SIDEKIQ_SLI_LABELS)
- Gitlab::Metrics::SidekiqSlis.record_execution_apdex(sli_labels, monotonic_time) if job_succeeded
- Gitlab::Metrics::SidekiqSlis.record_execution_error(sli_labels, !job_succeeded)
- end
-
- if ::Feature.enabled?(:sidekiq_queueing_application_slis)
- sli_labels = labels.slice(*SIDEKIQ_SLI_LABELS)
- Gitlab::Metrics::SidekiqSlis.record_queueing_apdex(sli_labels, queue_duration) if queue_duration
- end
+ sli_labels = labels.slice(*SIDEKIQ_SLI_LABELS)
+ Gitlab::Metrics::SidekiqSlis.record_execution_apdex(sli_labels, monotonic_time) if job_succeeded
+ Gitlab::Metrics::SidekiqSlis.record_execution_error(sli_labels, !job_succeeded)
+ Gitlab::Metrics::SidekiqSlis.record_queueing_apdex(sli_labels, queue_duration) if queue_duration
end
end
diff --git a/lib/gitlab/sidekiq_middleware/skip_jobs.rb b/lib/gitlab/sidekiq_middleware/skip_jobs.rb
index 8932607df52..6cc394aa5f4 100644
--- a/lib/gitlab/sidekiq_middleware/skip_jobs.rb
+++ b/lib/gitlab/sidekiq_middleware/skip_jobs.rb
@@ -84,8 +84,8 @@ module Gitlab
health_context = Gitlab::Database::HealthStatus::Context.new(
DatabaseHealthStatusChecker.new(job['jid'], worker_class.name),
job_base_model.connection,
- health_check_attrs[:gitlab_schema],
- health_check_attrs[:tables]
+ health_check_attrs[:tables],
+ health_check_attrs[:gitlab_schema]
)
Gitlab::Database::HealthStatus.evaluate(health_context).any?(&:stop?)
diff --git a/lib/gitlab/url_blocker.rb b/lib/gitlab/url_blocker.rb
index ba50a42cd37..31256101bd2 100644
--- a/lib/gitlab/url_blocker.rb
+++ b/lib/gitlab/url_blocker.rb
@@ -258,7 +258,7 @@ module Gitlab
def multiline_blocked?(parsed_url)
url = parsed_url.to_s
- return true if url =~ /\n|\r/
+ return true if /\n|\r/.match?(url)
# Google Cloud Storage uses a multi-line, encoded Signature query string
return false if %w(http https).include?(parsed_url.scheme&.downcase)
@@ -282,7 +282,7 @@ module Gitlab
def validate_user(value)
return if value.blank?
- return if value =~ /\A\p{Alnum}/
+ return if /\A\p{Alnum}/.match?(value)
raise BlockedUrlError, "Username needs to start with an alphanumeric character"
end
@@ -290,7 +290,7 @@ module Gitlab
def validate_hostname(value)
return if value.blank?
return if IPAddress.valid?(value)
- return if value =~ /\A\p{Alnum}/
+ return if /\A\p{Alnum}/.match?(value)
raise BlockedUrlError, "Hostname or IP address invalid"
end
diff --git a/lib/gitlab/usage/metric.rb b/lib/gitlab/usage/metric.rb
index 30f2efc8638..d7e983d126a 100644
--- a/lib/gitlab/usage/metric.rb
+++ b/lib/gitlab/usage/metric.rb
@@ -25,10 +25,6 @@ module Gitlab
with_availability(proc { instrumentation_object.instrumentation })
end
- def with_suggested_name
- with_availability(proc { instrumentation_object.suggested_name })
- end
-
private
def with_availability(value_proc)
diff --git a/lib/gitlab/usage/metrics/instrumentations/aggregated_metric.rb b/lib/gitlab/usage/metrics/instrumentations/aggregated_metric.rb
index 66be7a7b64e..de8726f71b5 100644
--- a/lib/gitlab/usage/metrics/instrumentations/aggregated_metric.rb
+++ b/lib/gitlab/usage/metrics/instrumentations/aggregated_metric.rb
@@ -40,10 +40,6 @@ module Gitlab
end
end
- def suggested_name
- Gitlab::Usage::Metrics::NameSuggestion.for(:alt)
- end
-
private
attr_accessor :source, :aggregate
diff --git a/lib/gitlab/usage/metrics/instrumentations/batched_background_migration_failed_jobs_metric.rb b/lib/gitlab/usage/metrics/instrumentations/batched_background_migration_failed_jobs_metric.rb
new file mode 100644
index 00000000000..f5d963cf522
--- /dev/null
+++ b/lib/gitlab/usage/metrics/instrumentations/batched_background_migration_failed_jobs_metric.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Usage
+ module Metrics
+ module Instrumentations
+ class BatchedBackgroundMigrationFailedJobsMetric < DatabaseMetric
+ relation do
+ Gitlab::Database::BackgroundMigration::BatchedMigration
+ .joins(:batched_jobs)
+ .where(batched_jobs: { status: '2' })
+ .group(%w[table_name job_class_name])
+ .order(%w[table_name job_class_name])
+ .select(['table_name', 'job_class_name', 'COUNT(batched_jobs) AS number_of_failed_jobs'])
+ end
+
+ timestamp_column(:created_at)
+
+ operation :count
+
+ def value
+ relation.map do |batched_migration|
+ {
+ job_class_name: batched_migration.job_class_name,
+ table_name: batched_migration.table_name,
+ number_of_failed_jobs: batched_migration.number_of_failed_jobs
+ }
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/usage/metrics/instrumentations/count_imported_projects_metric.rb b/lib/gitlab/usage/metrics/instrumentations/count_imported_projects_metric.rb
index 05e29f2d885..f3e81766b4c 100644
--- a/lib/gitlab/usage/metrics/instrumentations/count_imported_projects_metric.rb
+++ b/lib/gitlab/usage/metrics/instrumentations/count_imported_projects_metric.rb
@@ -44,7 +44,7 @@ module Gitlab
private
def relation
- super.imported_from(import_type) # rubocop: disable CodeReuse/ActiveRecord
+ super.imported_from(import_type)
end
def import_type
diff --git a/lib/gitlab/usage/metrics/instrumentations/database_metric.rb b/lib/gitlab/usage/metrics/instrumentations/database_metric.rb
index f731057309e..2af7c208fce 100644
--- a/lib/gitlab/usage/metrics/instrumentations/database_metric.rb
+++ b/lib/gitlab/usage/metrics/instrumentations/database_metric.rb
@@ -87,14 +87,6 @@ module Gitlab
to_sql
end
- def suggested_name
- Gitlab::Usage::Metrics::NameSuggestion.for(
- self.class.metric_operation,
- relation: relation,
- column: self.class.column
- )
- end
-
private
def start
diff --git a/lib/gitlab/usage/metrics/instrumentations/generic_metric.rb b/lib/gitlab/usage/metrics/instrumentations/generic_metric.rb
index d57dd7eac20..774f65da3bf 100644
--- a/lib/gitlab/usage/metrics/instrumentations/generic_metric.rb
+++ b/lib/gitlab/usage/metrics/instrumentations/generic_metric.rb
@@ -37,10 +37,6 @@ module Gitlab
self.class.metric_value.call(...)
end
end
-
- def suggested_name
- Gitlab::Usage::Metrics::NameSuggestion.for(:alt)
- end
end
end
end
diff --git a/lib/gitlab/usage/metrics/instrumentations/numbers_metric.rb b/lib/gitlab/usage/metrics/instrumentations/numbers_metric.rb
index 3b20e6ad100..67fcd226a0a 100644
--- a/lib/gitlab/usage/metrics/instrumentations/numbers_metric.rb
+++ b/lib/gitlab/usage/metrics/instrumentations/numbers_metric.rb
@@ -44,10 +44,6 @@ module Gitlab
method(self.class.metric_operation).call(*data)
end
- def suggested_name
- Gitlab::Usage::Metrics::NameSuggestion.for(:alt)
- end
-
private
def data
diff --git a/lib/gitlab/usage/metrics/instrumentations/redis_hll_metric.rb b/lib/gitlab/usage/metrics/instrumentations/redis_hll_metric.rb
index 17009f7638e..d3bbb3ee02c 100644
--- a/lib/gitlab/usage/metrics/instrumentations/redis_hll_metric.rb
+++ b/lib/gitlab/usage/metrics/instrumentations/redis_hll_metric.rb
@@ -30,10 +30,6 @@ module Gitlab
end
end
- def suggested_name
- Gitlab::Usage::Metrics::NameSuggestion.for(:redis)
- end
-
private
def time_constraints
diff --git a/lib/gitlab/usage/metrics/instrumentations/redis_metric.rb b/lib/gitlab/usage/metrics/instrumentations/redis_metric.rb
index ae3326fa845..ca5e5b706c4 100644
--- a/lib/gitlab/usage/metrics/instrumentations/redis_metric.rb
+++ b/lib/gitlab/usage/metrics/instrumentations/redis_metric.rb
@@ -48,10 +48,6 @@ module Gitlab
end
end
- def suggested_name
- Gitlab::Usage::Metrics::NameSuggestion.for(:redis)
- end
-
private
def redis_key
diff --git a/lib/gitlab/usage/metrics/instrumentations/work_items_activity_aggregated_metric.rb b/lib/gitlab/usage/metrics/instrumentations/work_items_activity_aggregated_metric.rb
deleted file mode 100644
index d045265495a..00000000000
--- a/lib/gitlab/usage/metrics/instrumentations/work_items_activity_aggregated_metric.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Usage
- module Metrics
- module Instrumentations
- class WorkItemsActivityAggregatedMetric < AggregatedMetric
- available? { Feature.enabled?(:track_work_items_activity) }
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/usage/metrics/name_suggestion.rb b/lib/gitlab/usage/metrics/name_suggestion.rb
deleted file mode 100644
index 44723b6f3d4..00000000000
--- a/lib/gitlab/usage/metrics/name_suggestion.rb
+++ /dev/null
@@ -1,216 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Usage
- module Metrics
- class NameSuggestion
- FREE_TEXT_METRIC_NAME = "<please fill metric name>"
- REDIS_EVENT_METRIC_NAME = "<please fill metric name, suggested format is: {subject}_{verb}{ing|ed}_{object} eg: users_creating_epics or merge_requests_viewed_in_single_file_mode>"
- CONSTRAINTS_PROMPT_TEMPLATE = "<adjective describing: '%{constraints}'>"
- EMPTY_CONSTRAINT = "()"
-
- class << self
- def for(operation, relation: nil, column: nil)
- case operation
- when :count
- name_suggestion(column: column, relation: relation, prefix: 'count')
- when :distinct_count
- name_suggestion(column: column, relation: relation, prefix: 'count_distinct', distinct: :distinct)
- when :estimate_batch_distinct_count
- name_suggestion(column: column, relation: relation, prefix: 'estimate_distinct_count')
- when :sum
- name_suggestion(column: column, relation: relation, prefix: 'sum')
- when :average
- name_suggestion(column: column, relation: relation, prefix: 'average')
- when :redis
- REDIS_EVENT_METRIC_NAME
- when :alt
- FREE_TEXT_METRIC_NAME
- else
- raise ArgumentError, "#{operation} operation not supported"
- end
- end
-
- private
-
- def name_suggestion(relation:, column: nil, prefix: nil, distinct: nil)
- # rubocop: disable CodeReuse/ActiveRecord
- relation = relation.unscope(where: :created_at)
- # rubocop: enable CodeReuse/ActiveRecord
-
- parts = [prefix]
- arel_column = arelize_column(relation, column)
-
- # nil as column indicates that the counting would use fallback value of primary key.
- # Because counting primary key from relation is the conceptual equal to counting all
- # records from given relation, in order to keep name suggestion more condensed
- # primary key column is skipped.
- # eg: SELECT COUNT(id) FROM issues would translate as count_issues and not
- # as count_id_from_issues since it does not add more information to the name suggestion
- if arel_column != Arel::Table.new(relation.table_name)[relation.primary_key]
- parts << arel_column.name
- parts << 'from'
- end
-
- arel = arel_query(relation: relation, column: arel_column, distinct: distinct)
- where_constraints = parse_where_constraints(relation: relation, arel: arel)
- having_constraints = parse_having_constraints(relation: relation, arel: arel)
-
- # In some cases due to performance reasons metrics are instrumented with joined relations
- # where relation listed in FROM statement is not the one that includes counted attribute
- # in such situations to make name suggestion more intuitive source should be inferred based
- # on the relation that provide counted attribute
- # EG: SELECT COUNT(deployments.environment_id) FROM clusters
- # JOIN deployments ON deployments.cluster_id = cluster.id
- # should be translated into:
- # count_environment_id_from_deployments_with_clusters
- # instead of
- # count_environment_id_from_clusters_with_deployments
- actual_source = parse_source(relation, arel_column)
-
- append_constraints_prompt(actual_source, [where_constraints], [having_constraints], parts)
-
- parts << actual_source
- parts += process_joined_relations(actual_source, arel, relation, where_constraints)
- parts.compact.join('_').delete('"')
- end
-
- def append_constraints_prompt(target, where_constraints, having_constraints, parts)
- where_constraints.select! do |constraint|
- constraint.include?(target)
- end
- having_constraints.delete(EMPTY_CONSTRAINT)
- applicable_constraints = where_constraints + having_constraints
- return unless applicable_constraints.any?
-
- parts << CONSTRAINTS_PROMPT_TEMPLATE % { constraints: applicable_constraints.join(' AND ') }
- end
-
- def parse_where_constraints(relation:, arel:)
- connection = relation.connection
- ::Gitlab::Usage::Metrics::NamesSuggestions::RelationParsers::WhereConstraints
- .new(connection)
- .accept(arel, collector(connection))
- .value
- end
-
- def parse_having_constraints(relation:, arel:)
- connection = relation.connection
- ::Gitlab::Usage::Metrics::NamesSuggestions::RelationParsers::HavingConstraints
- .new(connection)
- .accept(arel, collector(connection))
- .value
- end
-
- # TODO: joins with `USING` keyword
- def process_joined_relations(actual_source, arel, relation, where_constraints)
- joins = parse_joins(connection: relation.connection, arel: arel)
- return [] unless joins.any?
-
- sources = [relation.table_name, *joins.map { |join| join[:source] }]
- joins = extract_joins_targets(joins, sources)
-
- relations = if actual_source != relation.table_name
- build_relations_tree(joins + [{ source: relation.table_name }], actual_source)
- else
- # in case where counter attribute comes from joined relations, the relations
- # diagram has to be built bottom up, thus source and target are reverted
- build_relations_tree(joins + [{ source: relation.table_name }], actual_source, source_key: :target, target_key: :source)
- end
-
- collect_join_parts(relations: relations[actual_source], joins: joins, wheres: where_constraints)
- end
-
- def parse_joins(connection:, arel:)
- ::Gitlab::Usage::Metrics::NamesSuggestions::RelationParsers::Joins
- .new(connection)
- .accept(arel)
- end
-
- def extract_joins_targets(joins, sources)
- joins.map do |join|
- source_regex = /(#{join[:source]})\.(\w+_)*id/i
-
- tables_except_src = (sources - [join[:source]]).join('|')
- target_regex = /(?<target>#{tables_except_src})\.(\w+_)*id/i
-
- join_cond_regex = /(#{source_regex}\s+=\s+#{target_regex})|(#{target_regex}\s+=\s+#{source_regex})/i
- matched = join_cond_regex.match(join[:constraints])
-
- if matched
- join[:target] = matched[:target]
- join[:constraints].gsub!(/#{join_cond_regex}(\s+(and|or))*/i, '')
- end
-
- join
- end
- end
-
- def build_relations_tree(joins, parent, source_key: :source, target_key: :target)
- return [] if joins.blank?
-
- tree = {}
- tree[parent] = []
-
- joins.each do |join|
- if join[source_key] == parent
- tree[parent] << build_relations_tree(joins - [join], join[target_key], source_key: source_key, target_key: target_key)
- end
- end
- tree
- end
-
- def collect_join_parts(relations:, joins:, wheres:, parts: [], conjunctions: %w[with having including].cycle)
- conjunction = conjunctions.next
- relations.each do |subtree|
- subtree.each do |parent, children|
- parts << "<#{conjunction}>"
- join_constraints = joins.find { |join| join[:source] == parent }&.dig(:constraints)
- append_constraints_prompt(parent, [wheres, join_constraints].compact, [], parts)
- parts << parent
- collect_join_parts(relations: children, joins: joins, wheres: wheres, parts: parts, conjunctions: conjunctions)
- end
- end
- parts
- end
-
- def arelize_column(relation, column)
- case column
- when Arel::Attribute
- column
- when NilClass
- Arel::Table.new(relation.table_name)[relation.primary_key]
- when String
- if column.include?('.')
- table, col = column.split('.')
- Arel::Table.new(table)[col]
- else
- Arel::Table.new(relation.table_name)[column]
- end
- when Symbol
- arelize_column(relation, column.to_s)
- end
- end
-
- def parse_source(relation, column)
- column.relation.name || relation.table_name
- end
-
- def collector(connection)
- Arel::Collectors::SubstituteBinds.new(connection, Arel::Collectors::SQLString.new)
- end
-
- def arel_query(relation:, column: nil, distinct: nil)
- column ||= relation.primary_key
-
- if column.is_a?(Arel::Attribute)
- relation.select(column.count(distinct)).arel
- else
- relation.select(relation.all.table[column].count(distinct)).arel
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/usage/metrics/names_suggestions/generator.rb b/lib/gitlab/usage/metrics/names_suggestions/generator.rb
deleted file mode 100644
index 626bd3d4ad4..00000000000
--- a/lib/gitlab/usage/metrics/names_suggestions/generator.rb
+++ /dev/null
@@ -1,57 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Usage
- module Metrics
- module NamesSuggestions
- class Generator < ::Gitlab::UsageData
- class << self
- def generate(key_path)
- data.deep_stringify_keys.dig(*key_path.split('.'))
- end
-
- def add_metric(metric, time_frame: 'none', options: {})
- metric_class = "Gitlab::Usage::Metrics::Instrumentations::#{metric}".constantize
-
- metric_class.new(time_frame: time_frame, options: options).suggested_name
- end
-
- private
-
- def count(relation, column = nil, batch: true, batch_size: nil, start: nil, finish: nil)
- Gitlab::Usage::Metrics::NameSuggestion.for(:count, column: column, relation: relation)
- end
-
- def distinct_count(relation, column = nil, batch: true, batch_size: nil, start: nil, finish: nil)
- Gitlab::Usage::Metrics::NameSuggestion.for(:distinct_count, column: column, relation: relation)
- end
-
- def redis_usage_counter
- Gitlab::Usage::Metrics::NameSuggestion.for(:redis)
- end
-
- def alt_usage_data(*)
- Gitlab::Usage::Metrics::NameSuggestion.for(:alt)
- end
-
- def redis_usage_data_totals(counter)
- counter.fallback_totals.transform_values { |_| Gitlab::Usage::Metrics::NameSuggestion.for(:redis) }
- end
-
- def sum(relation, column, *rest)
- Gitlab::Usage::Metrics::NameSuggestion.for(:sum, column: column, relation: relation)
- end
-
- def estimate_batch_distinct_count(relation, column = nil, *rest)
- Gitlab::Usage::Metrics::NameSuggestion.for(:estimate_batch_distinct_count, column: column, relation: relation)
- end
-
- def add(*args)
- "add_#{args.join('_and_')}"
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/usage/metrics/names_suggestions/relation_parsers/having_constraints.rb b/lib/gitlab/usage/metrics/names_suggestions/relation_parsers/having_constraints.rb
deleted file mode 100644
index 8dd3b1ff5c6..00000000000
--- a/lib/gitlab/usage/metrics/names_suggestions/relation_parsers/having_constraints.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Usage
- module Metrics
- module NamesSuggestions
- module RelationParsers
- class HavingConstraints < ::Arel::Visitors::PostgreSQL
- # rubocop:disable Naming/MethodName
- def visit_Arel_Nodes_SelectCore(object, collector)
- collect_nodes_for(object.havings, collector, "") || collector
- end
- # rubocop:enable Naming/MethodName
-
- def quote(value)
- value.to_s
- end
-
- def quote_table_name(name)
- name.to_s
- end
-
- def quote_column_name(name)
- name.to_s
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/usage/metrics/names_suggestions/relation_parsers/joins.rb b/lib/gitlab/usage/metrics/names_suggestions/relation_parsers/joins.rb
deleted file mode 100644
index d52e4903f3c..00000000000
--- a/lib/gitlab/usage/metrics/names_suggestions/relation_parsers/joins.rb
+++ /dev/null
@@ -1,74 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Usage
- module Metrics
- module NamesSuggestions
- module RelationParsers
- class Joins < ::Arel::Visitors::PostgreSQL
- def accept(object)
- object.source.right.map do |join|
- visit(join, collector)
- end
- end
-
- private
-
- # rubocop:disable Naming/MethodName
- def visit_Arel_Nodes_StringJoin(object, collector)
- result = visit(object.left, collector)
- source, constraints = result.value.split('ON')
- {
- source: source.split('JOIN').last&.strip,
- constraints: constraints&.strip
- }.compact
- end
-
- def visit_Arel_Nodes_FullOuterJoin(object, _)
- parse_join(object)
- end
-
- def visit_Arel_Nodes_OuterJoin(object, _)
- parse_join(object)
- end
-
- def visit_Arel_Nodes_RightOuterJoin(object, _)
- parse_join(object)
- end
-
- def visit_Arel_Nodes_InnerJoin(object, _)
- {
- source: visit(object.left, collector).value,
- constraints: object.right ? visit(object.right.expr, collector).value : nil
- }.compact
- end
- # rubocop:enable Naming/MethodName
-
- def parse_join(object)
- {
- source: visit(object.left, collector).value,
- constraints: visit(object.right.expr, collector).value
- }
- end
-
- def quote(value)
- "#{value}"
- end
-
- def quote_table_name(name)
- "#{name}"
- end
-
- def quote_column_name(name)
- "#{name}"
- end
-
- def collector
- Arel::Collectors::SubstituteBinds.new(@connection, Arel::Collectors::SQLString.new)
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/usage/metrics/names_suggestions/relation_parsers/where_constraints.rb b/lib/gitlab/usage/metrics/names_suggestions/relation_parsers/where_constraints.rb
deleted file mode 100644
index 9f829067214..00000000000
--- a/lib/gitlab/usage/metrics/names_suggestions/relation_parsers/where_constraints.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Usage
- module Metrics
- module NamesSuggestions
- module RelationParsers
- class WhereConstraints < ::Arel::Visitors::PostgreSQL
- # rubocop:disable Naming/MethodName
- def visit_Arel_Nodes_SelectCore(object, collector)
- collect_nodes_for(object.wheres, collector, "") || collector
- end
- # rubocop:enable Naming/MethodName
-
- def quote(value)
- value.to_s
- end
-
- def quote_table_name(name)
- name.to_s
- end
-
- def quote_column_name(name)
- name.to_s
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/usage_data_counters/ci_template_unique_counter.rb b/lib/gitlab/usage_data_counters/ci_template_unique_counter.rb
index 97091ff975b..21cc9368f44 100644
--- a/lib/gitlab/usage_data_counters/ci_template_unique_counter.rb
+++ b/lib/gitlab/usage_data_counters/ci_template_unique_counter.rb
@@ -3,7 +3,6 @@
module Gitlab::UsageDataCounters
class CiTemplateUniqueCounter
PREFIX = 'ci_templates'
- KNOWN_EVENTS_FILE_PATH = File.expand_path('known_events/ci_templates.yml', __dir__)
class << self
def track_unique_project_event(project:, template:, config_source:, user:)
diff --git a/lib/gitlab/usage_data_counters/editor_unique_counter.rb b/lib/gitlab/usage_data_counters/editor_unique_counter.rb
index 4e4a01ed301..7955c19b7e6 100644
--- a/lib/gitlab/usage_data_counters/editor_unique_counter.rb
+++ b/lib/gitlab/usage_data_counters/editor_unique_counter.rb
@@ -9,24 +9,24 @@ module Gitlab
EDIT_CATEGORY = 'ide_edit'
class << self
- def track_web_ide_edit_action(author:, time: Time.zone.now, project:)
- track_unique_action(EDIT_BY_WEB_IDE, author, time, project)
+ def track_web_ide_edit_action(author:, project:)
+ track_internal_event(EDIT_BY_WEB_IDE, author, project)
end
def count_web_ide_edit_actions(date_from:, date_to:)
count_unique(EDIT_BY_WEB_IDE, date_from, date_to)
end
- def track_sfe_edit_action(author:, time: Time.zone.now, project:)
- track_unique_action(EDIT_BY_SFE, author, time, project)
+ def track_sfe_edit_action(author:, project:)
+ track_internal_event(EDIT_BY_SFE, author, project)
end
def count_sfe_edit_actions(date_from:, date_to:)
count_unique(EDIT_BY_SFE, date_from, date_to)
end
- def track_snippet_editor_edit_action(author:, time: Time.zone.now, project:)
- track_unique_action(EDIT_BY_SNIPPET_EDITOR, author, time, project)
+ def track_snippet_editor_edit_action(author:, project:)
+ track_internal_event(EDIT_BY_SNIPPET_EDITOR, author, project)
end
def count_snippet_editor_edit_actions(date_from:, date_to:)
@@ -35,21 +35,15 @@ module Gitlab
private
- def track_unique_action(event_name, author, time, project = nil)
+ def track_internal_event(event_name, author, project = nil)
return unless author
- Gitlab::Tracking.event(
- name,
- 'ide_edit',
- property: event_name.to_s,
- project: project,
- namespace: project&.namespace,
+ Gitlab::InternalEvents.track_event(
+ event_name,
user: author,
- label: 'usage_activity_by_stage_monthly.create.action_monthly_active_users_ide_edit',
- context: [Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll, event: event_name).to_context]
+ project: project,
+ namespace: project&.namespace
)
-
- Gitlab::UsageDataCounters::HLLRedisCounter.track_event(event_name, values: author.id, time: time)
end
def count_unique(actions, date_from, date_to)
diff --git a/lib/gitlab/usage_data_counters/hll_redis_counter.rb b/lib/gitlab/usage_data_counters/hll_redis_counter.rb
index e71061c4522..53594a27867 100644
--- a/lib/gitlab/usage_data_counters/hll_redis_counter.rb
+++ b/lib/gitlab/usage_data_counters/hll_redis_counter.rb
@@ -8,9 +8,6 @@ module Gitlab
EventError = Class.new(StandardError)
UnknownEvent = Class.new(EventError)
- InvalidContext = Class.new(EventError)
-
- KNOWN_EVENTS_PATH = File.expand_path('known_events/*.yml', __dir__)
# Track event on entity_id
# Increment a Redis HLL counter for unique event_name and entity_id
@@ -31,29 +28,13 @@ module Gitlab
track(values, event_name, time: time)
end
- # Track unique events
- #
- # event_name - The event name.
- # values - One or multiple values counted.
- # context - Event context, plan level tracking.
- # time - Time of the action, set to Time.current.
- def track_event_in_context(event_name, values:, context:, time: Time.zone.now)
- return if context.blank?
- return unless context.in?(valid_context_list)
-
- track(values, event_name, context: context, time: time)
- end
-
# Count unique events for a given time range.
#
# event_names - The list of the events to count.
# start_date - The start date of the time range.
# end_date - The end date of the time range.
- # context - Event context, plan level tracking. Available if set when tracking.
- def unique_events(event_names:, start_date:, end_date:, context: '')
- count_unique_events(event_names: event_names, start_date: start_date, end_date: end_date, context: context) do
- raise InvalidContext if context.present? && !context.in?(valid_context_list)
- end
+ def unique_events(event_names:, start_date:, end_date:)
+ count_unique_events(event_names: event_names, start_date: start_date, end_date: end_date)
end
def known_event?(event_name)
@@ -61,7 +42,7 @@ module Gitlab
end
def known_events
- @known_events ||= load_events(KNOWN_EVENTS_PATH)
+ @known_events ||= load_events
end
def calculate_events_union(event_names:, start_date:, end_date:)
@@ -70,7 +51,7 @@ module Gitlab
private
- def track(values, event_name, context: '', time: Time.zone.now)
+ def track(values, event_name, time: Time.zone.now)
return unless ::ServicePing::ServicePingSettings.enabled?
event = event_for(event_name)
@@ -79,7 +60,7 @@ module Gitlab
return if event.blank?
return unless Feature.enabled?(:redis_hll_tracking, type: :ops)
- Gitlab::Redis::HLL.add(key: redis_key(event, time, context), value: values, expiry: KEY_EXPIRY_LENGTH)
+ Gitlab::Redis::HLL.add(key: redis_key(event, time), value: values, expiry: KEY_EXPIRY_LENGTH)
rescue StandardError => e
# Ignore any exceptions unless is dev or test env
@@ -87,48 +68,33 @@ module Gitlab
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e)
end
- # The array of valid context on which we allow tracking
- def valid_context_list
- Plan.all_plans
- end
-
- def count_unique_events(event_names:, start_date:, end_date:, context: '')
+ def count_unique_events(event_names:, start_date:, end_date:)
events = events_for(Array(event_names).map(&:to_s))
- yield events if block_given?
-
- keys = keys_for_aggregation(events: events, start_date: start_date, end_date: end_date, context: context)
+ keys = keys_for_aggregation(events: events, start_date: start_date, end_date: end_date)
return FALLBACK unless keys.any?
redis_usage_data { Gitlab::Redis::HLL.count(keys: keys) }
end
- def keys_for_aggregation(events:, start_date:, end_date:, context: '')
+ def keys_for_aggregation(events:, start_date:, end_date:)
end_date = end_date.end_of_week - 1.week
(start_date.to_date..end_date.to_date).map do |date|
- events.map { |event| redis_key(event, date, context) }
+ events.map { |event| redis_key(event, date) }
end.flatten.uniq
end
- def load_events(wildcard)
- if Feature.enabled?(:use_metric_definitions_for_events_list)
- events = Gitlab::Usage::MetricDefinition.not_removed.values.map do |d|
- d.attributes[:options] && d.attributes[:options][:events]
- end.flatten.compact.uniq
-
- events.map do |e|
- { name: e }.with_indifferent_access
- end
- else
- Dir[wildcard].each_with_object([]) do |path, events|
- events.push(*load_yaml_from_path(path))
- end
- end
- end
+ def load_events
+ events = Gitlab::Usage::MetricDefinition.all.map do |d|
+ next unless d.available?
+
+ d.attributes[:options] && d.attributes[:options][:events]
+ end.flatten.compact.uniq
- def load_yaml_from_path(path)
- YAML.safe_load(File.read(path))&.map(&:with_indifferent_access)
+ events.map do |e|
+ { name: e }.with_indifferent_access
+ end
end
def known_events_names
@@ -144,20 +110,15 @@ module Gitlab
end
# Compose the key in order to store events daily or weekly
- def redis_key(event, time, context = '')
+ def redis_key(event, time)
raise UnknownEvent, "Unknown event #{event[:name]}" unless known_events_names.include?(event[:name].to_s)
key = "{#{REDIS_SLOT}}_#{event[:name]}"
year_week = time.strftime('%G-%V')
- key = "#{key}-#{year_week}"
-
- key = "#{context}_#{key}" if context.present?
- key
+ "#{key}-#{year_week}"
end
end
end
end
end
-
-Gitlab::UsageDataCounters::HLLRedisCounter.prepend_mod_with('Gitlab::UsageDataCounters::HLLRedisCounter')
diff --git a/lib/gitlab/usage_data_counters/ipynb_diff_activity_counter.rb b/lib/gitlab/usage_data_counters/ipynb_diff_activity_counter.rb
index a34ae909c82..05eb88468c7 100644
--- a/lib/gitlab/usage_data_counters/ipynb_diff_activity_counter.rb
+++ b/lib/gitlab/usage_data_counters/ipynb_diff_activity_counter.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: true
-# noinspection RubyConstantNamingConvention
module Gitlab
module UsageDataCounters
module IpynbDiffActivityCounter
diff --git a/lib/gitlab/usage_data_counters/known_events/analytics.yml b/lib/gitlab/usage_data_counters/known_events/analytics.yml
deleted file mode 100644
index 0b30308b552..00000000000
--- a/lib/gitlab/usage_data_counters/known_events/analytics.yml
+++ /dev/null
@@ -1,26 +0,0 @@
-- name: users_viewing_analytics_group_devops_adoption
- aggregation: weekly
-- name: i_analytics_dev_ops_adoption
- aggregation: weekly
-- name: i_analytics_dev_ops_score
- aggregation: weekly
-- name: i_analytics_instance_statistics
- aggregation: weekly
-- name: p_analytics_pipelines
- aggregation: weekly
-- name: p_analytics_valuestream
- aggregation: weekly
-- name: p_analytics_repo
- aggregation: weekly
-- name: i_analytics_cohorts
- aggregation: weekly
-- name: p_analytics_ci_cd_pipelines
- aggregation: weekly
-- name: p_analytics_ci_cd_deployment_frequency
- aggregation: weekly
-- name: p_analytics_ci_cd_lead_time
- aggregation: weekly
-- name: p_analytics_ci_cd_time_to_restore_service
- aggregation: weekly
-- name: p_analytics_ci_cd_change_failure_rate
- aggregation: weekly
diff --git a/lib/gitlab/usage_data_counters/known_events/ci_templates.yml b/lib/gitlab/usage_data_counters/known_events/ci_templates.yml
deleted file mode 100644
index c3e1c34151b..00000000000
--- a/lib/gitlab/usage_data_counters/known_events/ci_templates.yml
+++ /dev/null
@@ -1,311 +0,0 @@
-# This file is generated automatically by
-# bin/rake gitlab:usage_data:generate_ci_template_events
-#
-# Do not edit it manually!
----
-- name: p_ci_templates_terraform_base_latest
- aggregation: weekly
-- name: p_ci_templates_terraform_base
- aggregation: weekly
-- name: p_ci_templates_dotnet
- aggregation: weekly
-- name: p_ci_templates_nodejs
- aggregation: weekly
-- name: p_ci_templates_openshift
- aggregation: weekly
-- name: p_ci_templates_auto_devops
- aggregation: weekly
-- name: p_ci_templates_bash
- aggregation: weekly
-- name: p_ci_templates_rust
- aggregation: weekly
-- name: p_ci_templates_elixir
- aggregation: weekly
-- name: p_ci_templates_clojure
- aggregation: weekly
-- name: p_ci_templates_crystal
- aggregation: weekly
-- name: p_ci_templates_getting_started
- aggregation: weekly
-- name: p_ci_templates_code_quality
- aggregation: weekly
-- name: p_ci_templates_verify_load_performance_testing
- aggregation: weekly
-- name: p_ci_templates_verify_accessibility
- aggregation: weekly
-- name: p_ci_templates_verify_failfast
- aggregation: weekly
-- name: p_ci_templates_verify_browser_performance
- aggregation: weekly
-- name: p_ci_templates_verify_browser_performance_latest
- aggregation: weekly
-- name: p_ci_templates_grails
- aggregation: weekly
-- name: p_ci_templates_security_sast
- aggregation: weekly
-- name: p_ci_templates_security_dast_runner_validation
- aggregation: weekly
-- name: p_ci_templates_security_dast_on_demand_scan
- aggregation: weekly
-- name: p_ci_templates_security_secret_detection
- aggregation: weekly
-- name: p_ci_templates_security_license_scanning
- aggregation: weekly
-- name: p_ci_templates_security_coverage_fuzzing_latest
- aggregation: weekly
-- name: p_ci_templates_security_dast_on_demand_api_scan
- aggregation: weekly
-- name: p_ci_templates_security_coverage_fuzzing
- aggregation: weekly
-- name: p_ci_templates_security_api_fuzzing_latest
- aggregation: weekly
-- name: p_ci_templates_security_secure_binaries
- aggregation: weekly
-- name: p_ci_templates_security_dast_api
- aggregation: weekly
-- name: p_ci_templates_security_container_scanning
- aggregation: weekly
-- name: p_ci_templates_security_dast_latest
- aggregation: weekly
-- name: p_ci_templates_security_sast_iac
- aggregation: weekly
-- name: p_ci_templates_security_dependency_scanning
- aggregation: weekly
-- name: p_ci_templates_security_dast_api_latest
- aggregation: weekly
-- name: p_ci_templates_security_container_scanning_latest
- aggregation: weekly
-- name: p_ci_templates_security_api_fuzzing
- aggregation: weekly
-- name: p_ci_templates_security_dast
- aggregation: weekly
-- name: p_ci_templates_security_api_discovery
- aggregation: weekly
-- name: p_ci_templates_security_fortify_fod_sast
- aggregation: weekly
-- name: p_ci_templates_security_sast_iac_latest
- aggregation: weekly
-- name: p_ci_templates_security_bas_latest
- aggregation: weekly
-- name: p_ci_templates_qualys_iac_security
- aggregation: weekly
-- name: p_ci_templates_ios_fastlane
- aggregation: weekly
-- name: p_ci_templates_composer
- aggregation: weekly
-- name: p_ci_templates_c
- aggregation: weekly
-- name: p_ci_templates_python
- aggregation: weekly
-- name: p_ci_templates_android_fastlane
- aggregation: weekly
-- name: p_ci_templates_android_latest
- aggregation: weekly
-- name: p_ci_templates_django
- aggregation: weekly
-- name: p_ci_templates_maven
- aggregation: weekly
-- name: p_ci_templates_liquibase
- aggregation: weekly
-- name: p_ci_templates_flutter
- aggregation: weekly
-- name: p_ci_templates_workflows_branch_pipelines
- aggregation: weekly
-- name: p_ci_templates_workflows_mergerequest_pipelines
- aggregation: weekly
-- name: p_ci_templates_laravel
- aggregation: weekly
-- name: p_ci_templates_kaniko
- aggregation: weekly
-- name: p_ci_templates_php
- aggregation: weekly
-- name: p_ci_templates_packer
- aggregation: weekly
-- name: p_ci_templates_themekit
- aggregation: weekly
-- name: p_ci_templates_terraform
- aggregation: weekly
-- name: p_ci_templates_katalon
- aggregation: weekly
-- name: p_ci_templates_mono
- aggregation: weekly
-- name: p_ci_templates_go
- aggregation: weekly
-- name: p_ci_templates_scala
- aggregation: weekly
-- name: p_ci_templates_latex
- aggregation: weekly
-- name: p_ci_templates_android
- aggregation: weekly
-- name: p_ci_templates_indeni_cloudrail
- aggregation: weekly
-- name: p_ci_templates_matlab
- aggregation: weekly
-- name: p_ci_templates_deploy_ecs
- aggregation: weekly
-- name: p_ci_templates_aws_cf_provision_and_deploy_ec2
- aggregation: weekly
-- name: p_ci_templates_aws_deploy_ecs
- aggregation: weekly
-- name: p_ci_templates_gradle
- aggregation: weekly
-- name: p_ci_templates_chef
- aggregation: weekly
-- name: p_ci_templates_jobs_dast_default_branch_deploy
- aggregation: weekly
-- name: p_ci_templates_jobs_load_performance_testing
- aggregation: weekly
-- name: p_ci_templates_jobs_helm_2to3
- aggregation: weekly
-- name: p_ci_templates_jobs_sast
- aggregation: weekly
-- name: p_ci_templates_jobs_secret_detection
- aggregation: weekly
-- name: p_ci_templates_jobs_license_scanning
- aggregation: weekly
-- name: p_ci_templates_jobs_code_intelligence
- aggregation: weekly
-- name: p_ci_templates_jobs_code_quality
- aggregation: weekly
-- name: p_ci_templates_jobs_deploy_ecs
- aggregation: weekly
-- name: p_ci_templates_jobs_deploy_ec2
- aggregation: weekly
-- name: p_ci_templates_jobs_license_scanning_latest
- aggregation: weekly
-- name: p_ci_templates_jobs_deploy
- aggregation: weekly
-- name: p_ci_templates_jobs_build
- aggregation: weekly
-- name: p_ci_templates_jobs_browser_performance_testing
- aggregation: weekly
-- name: p_ci_templates_jobs_container_scanning
- aggregation: weekly
-- name: p_ci_templates_jobs_container_scanning_latest
- aggregation: weekly
-- name: p_ci_templates_jobs_dependency_scanning_latest
- aggregation: weekly
-- name: p_ci_templates_jobs_test
- aggregation: weekly
-- name: p_ci_templates_jobs_sast_latest
- aggregation: weekly
-- name: p_ci_templates_jobs_sast_iac
- aggregation: weekly
-- name: p_ci_templates_jobs_secret_detection_latest
- aggregation: weekly
-- name: p_ci_templates_jobs_dependency_scanning
- aggregation: weekly
-- name: p_ci_templates_jobs_deploy_latest
- aggregation: weekly
-- name: p_ci_templates_jobs_browser_performance_testing_latest
- aggregation: weekly
-- name: p_ci_templates_jobs_cf_provision
- aggregation: weekly
-- name: p_ci_templates_jobs_build_latest
- aggregation: weekly
-- name: p_ci_templates_jobs_sast_iac_latest
- aggregation: weekly
-- name: p_ci_templates_terraform_latest
- aggregation: weekly
-- name: p_ci_templates_swift
- aggregation: weekly
-- name: p_ci_templates_pages_jekyll
- aggregation: weekly
-- name: p_ci_templates_pages_harp
- aggregation: weekly
-- name: p_ci_templates_pages_octopress
- aggregation: weekly
-- name: p_ci_templates_pages_brunch
- aggregation: weekly
-- name: p_ci_templates_pages_doxygen
- aggregation: weekly
-- name: p_ci_templates_pages_hyde
- aggregation: weekly
-- name: p_ci_templates_pages_lektor
- aggregation: weekly
-- name: p_ci_templates_pages_jbake
- aggregation: weekly
-- name: p_ci_templates_pages_hexo
- aggregation: weekly
-- name: p_ci_templates_pages_middleman
- aggregation: weekly
-- name: p_ci_templates_pages_hugo
- aggregation: weekly
-- name: p_ci_templates_pages_pelican
- aggregation: weekly
-- name: p_ci_templates_pages_nanoc
- aggregation: weekly
-- name: p_ci_templates_pages_swaggerui
- aggregation: weekly
-- name: p_ci_templates_pages_jigsaw
- aggregation: weekly
-- name: p_ci_templates_pages_metalsmith
- aggregation: weekly
-- name: p_ci_templates_pages_gatsby
- aggregation: weekly
-- name: p_ci_templates_pages_html
- aggregation: weekly
-- name: p_ci_templates_dart
- aggregation: weekly
-- name: p_ci_templates_docker
- aggregation: weekly
-- name: p_ci_templates_julia
- aggregation: weekly
-- name: p_ci_templates_npm
- aggregation: weekly
-- name: p_ci_templates_dotnet_core
- aggregation: weekly
-- name: p_ci_templates_5_minute_production_app
- aggregation: weekly
-- name: p_ci_templates_ruby
- aggregation: weekly
-- name: p_ci_templates_implicit_auto_devops
- aggregation: weekly
-- name: p_ci_templates_implicit_jobs_browser_performance_testing
- aggregation: weekly
-- name: p_ci_templates_implicit_jobs_build
- aggregation: weekly
-- name: p_ci_templates_implicit_jobs_code_intelligence
- aggregation: weekly
-- name: p_ci_templates_implicit_jobs_code_quality
- aggregation: weekly
-- name: p_ci_templates_implicit_jobs_container_scanning
- aggregation: weekly
-- name: p_ci_templates_implicit_jobs_dast_default_branch_deploy
- aggregation: weekly
-- name: p_ci_templates_implicit_jobs_dependency_scanning
- aggregation: weekly
-- name: p_ci_templates_implicit_jobs_deploy
- aggregation: weekly
-- name: p_ci_templates_implicit_jobs_deploy_ec2
- aggregation: weekly
-- name: p_ci_templates_implicit_jobs_deploy_ecs
- aggregation: weekly
-- name: p_ci_templates_implicit_jobs_helm_2to3
- aggregation: weekly
-- name: p_ci_templates_implicit_jobs_license_scanning
- aggregation: weekly
-- name: p_ci_templates_implicit_jobs_sast
- aggregation: weekly
-- name: p_ci_templates_implicit_jobs_secret_detection
- aggregation: weekly
-- name: p_ci_templates_implicit_jobs_test
- aggregation: weekly
-- name: p_ci_templates_implicit_security_container_scanning
- aggregation: weekly
-- name: p_ci_templates_implicit_security_dast
- aggregation: weekly
-- name: p_ci_templates_implicit_security_dependency_scanning
- aggregation: weekly
-- name: p_ci_templates_implicit_security_license_scanning
- aggregation: weekly
-- name: p_ci_templates_implicit_security_sast
- aggregation: weekly
-- name: p_ci_templates_implicit_security_secret_detection
- aggregation: weekly
-- name: p_ci_templates_terraform_module_base
- aggregation: weekly
-- name: p_ci_templates_terraform_module
- aggregation: weekly
-- name: p_ci_templates_pages_zola
- aggregation: weekly
diff --git a/lib/gitlab/usage_data_counters/known_events/ci_users.yml b/lib/gitlab/usage_data_counters/known_events/ci_users.yml
deleted file mode 100644
index 49757c6e672..00000000000
--- a/lib/gitlab/usage_data_counters/known_events/ci_users.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-- name: ci_users_executing_deployment_job
- aggregation: weekly
-- name: ci_users_executing_verify_environment_job
- aggregation: weekly
diff --git a/lib/gitlab/usage_data_counters/known_events/code_review_events.yml b/lib/gitlab/usage_data_counters/known_events/code_review_events.yml
deleted file mode 100644
index bd8c79f4801..00000000000
--- a/lib/gitlab/usage_data_counters/known_events/code_review_events.yml
+++ /dev/null
@@ -1,235 +0,0 @@
----
-- name: i_code_review_create_note_in_ipynb_diff
- aggregation: weekly
-- name: i_code_review_create_note_in_ipynb_diff_mr
- aggregation: weekly
-- name: i_code_review_create_note_in_ipynb_diff_commit
- aggregation: weekly
-- name: i_code_review_user_create_note_in_ipynb_diff
- aggregation: weekly
-- name: i_code_review_user_create_note_in_ipynb_diff_mr
- aggregation: weekly
-- name: i_code_review_user_create_note_in_ipynb_diff_commit
- aggregation: weekly
-- name: i_code_review_mr_diffs
- aggregation: weekly
-- name: i_code_review_user_single_file_diffs
- aggregation: weekly
-- name: i_code_review_mr_single_file_diffs
- aggregation: weekly
-- name: i_code_review_user_toggled_task_item_status
- aggregation: weekly
-- name: i_code_review_create_mr
- aggregation: weekly
-- name: i_code_review_user_create_mr
- aggregation: weekly
-- name: i_code_review_user_close_mr
- aggregation: weekly
-- name: i_code_review_user_reopen_mr
- aggregation: weekly
-- name: i_code_review_user_approve_mr
- aggregation: weekly
-- name: i_code_review_user_unapprove_mr
- aggregation: weekly
-- name: i_code_review_user_resolve_thread
- aggregation: weekly
-- name: i_code_review_user_unresolve_thread
- aggregation: weekly
-- name: i_code_review_edit_mr_title
- aggregation: weekly
-- name: i_code_review_edit_mr_desc
- aggregation: weekly
-- name: i_code_review_user_merge_mr
- aggregation: weekly
-- name: i_code_review_user_create_mr_comment
- aggregation: weekly
-- name: i_code_review_user_edit_mr_comment
- aggregation: weekly
-- name: i_code_review_user_remove_mr_comment
- aggregation: weekly
-- name: i_code_review_user_create_review_note
- aggregation: weekly
-- name: i_code_review_user_publish_review
- aggregation: weekly
-- name: i_code_review_user_create_multiline_mr_comment
- aggregation: weekly
-- name: i_code_review_user_edit_multiline_mr_comment
- aggregation: weekly
-- name: i_code_review_user_remove_multiline_mr_comment
- aggregation: weekly
-- name: i_code_review_user_add_suggestion
- aggregation: weekly
-- name: i_code_review_user_apply_suggestion
- aggregation: weekly
-- name: i_code_review_user_assigned
- aggregation: weekly
-- name: i_code_review_user_marked_as_draft
- aggregation: weekly
-- name: i_code_review_user_unmarked_as_draft
- aggregation: weekly
-- name: i_code_review_user_review_requested
- aggregation: weekly
-- name: i_code_review_user_approval_rule_added
- aggregation: weekly
-- name: i_code_review_user_approval_rule_deleted
- aggregation: weekly
-- name: i_code_review_user_approval_rule_edited
- aggregation: weekly
-- name: i_code_review_user_vs_code_api_request
- aggregation: weekly
-- name: i_code_review_user_jetbrains_api_request
- aggregation: weekly
-- name: i_editor_extensions_user_jetbrains_bundled_api_request
- aggregation: weekly
-- name: i_code_review_user_gitlab_cli_api_request
- aggregation: weekly
-- name: i_code_review_user_create_mr_from_issue
- aggregation: weekly
-- name: i_code_review_user_mr_discussion_locked
- aggregation: weekly
-- name: i_code_review_user_mr_discussion_unlocked
- aggregation: weekly
-- name: i_code_review_user_time_estimate_changed
- aggregation: weekly
-- name: i_code_review_user_time_spent_changed
- aggregation: weekly
-- name: i_code_review_user_assignees_changed
- aggregation: weekly
-- name: i_code_review_user_reviewers_changed
- aggregation: weekly
-- name: i_code_review_user_milestone_changed
- aggregation: weekly
-- name: i_code_review_user_labels_changed
- aggregation: weekly
-# Diff settings events
-- name: i_code_review_click_diff_view_setting
- aggregation: weekly
-- name: i_code_review_click_single_file_mode_setting
- aggregation: weekly
-- name: i_code_review_click_file_browser_setting
- aggregation: weekly
-- name: i_code_review_click_whitespace_setting
- aggregation: weekly
-- name: i_code_review_diff_view_inline
- aggregation: weekly
-- name: i_code_review_diff_view_parallel
- aggregation: weekly
-- name: i_code_review_file_browser_tree_view
- aggregation: weekly
-- name: i_code_review_file_browser_list_view
- aggregation: weekly
-- name: i_code_review_diff_show_whitespace
- aggregation: weekly
-- name: i_code_review_diff_hide_whitespace
- aggregation: weekly
-- name: i_code_review_diff_single_file
- aggregation: weekly
-- name: i_code_review_diff_multiple_files
- aggregation: weekly
-- name: i_code_review_user_load_conflict_ui
- aggregation: weekly
-- name: i_code_review_user_resolve_conflict
- aggregation: weekly
-- name: i_code_review_user_searches_diff
- aggregation: weekly
-- name: i_code_review_total_suggestions_applied
- aggregation: weekly
-- name: i_code_review_total_suggestions_added
- aggregation: weekly
-- name: i_code_review_user_resolve_thread_in_issue
- aggregation: weekly
-- name: i_code_review_widget_nothing_merge_click_new_file
- aggregation: weekly
-- name: i_code_review_post_merge_delete_branch
- aggregation: weekly
-- name: i_code_review_post_merge_click_revert
- aggregation: weekly
-- name: i_code_review_post_merge_click_cherry_pick
- aggregation: weekly
-- name: i_code_review_post_merge_submit_revert_modal
- aggregation: weekly
-- name: i_code_review_post_merge_submit_cherry_pick_modal
- aggregation: weekly
-# MR Widget Extensions
-## Test Summary
-- name: i_code_review_merge_request_widget_test_summary_view
- aggregation: weekly
-- name: i_code_review_merge_request_widget_test_summary_full_report_clicked
- aggregation: weekly
-- name: i_code_review_merge_request_widget_test_summary_expand
- aggregation: weekly
-- name: i_code_review_merge_request_widget_test_summary_expand_success
- aggregation: weekly
-- name: i_code_review_merge_request_widget_test_summary_expand_warning
- aggregation: weekly
-- name: i_code_review_merge_request_widget_test_summary_expand_failed
- aggregation: weekly
-## Accessibility
-- name: i_code_review_merge_request_widget_accessibility_view
- aggregation: weekly
-- name: i_code_review_merge_request_widget_accessibility_full_report_clicked
- aggregation: weekly
-- name: i_code_review_merge_request_widget_accessibility_expand
- aggregation: weekly
-- name: i_code_review_merge_request_widget_accessibility_expand_success
- aggregation: weekly
-- name: i_code_review_merge_request_widget_accessibility_expand_warning
- aggregation: weekly
-- name: i_code_review_merge_request_widget_accessibility_expand_failed
- aggregation: weekly
-## Code Quality
-- name: i_code_review_merge_request_widget_code_quality_view
- aggregation: weekly
-- name: i_code_review_merge_request_widget_code_quality_full_report_clicked
- aggregation: weekly
-- name: i_code_review_merge_request_widget_code_quality_expand
- aggregation: weekly
-- name: i_code_review_merge_request_widget_code_quality_expand_success
- aggregation: weekly
-- name: i_code_review_merge_request_widget_code_quality_expand_warning
- aggregation: weekly
-- name: i_code_review_merge_request_widget_code_quality_expand_failed
- aggregation: weekly
-## Terraform
-- name: i_code_review_merge_request_widget_terraform_view
- aggregation: weekly
-- name: i_code_review_merge_request_widget_terraform_full_report_clicked
- aggregation: weekly
-- name: i_code_review_merge_request_widget_terraform_expand
- aggregation: weekly
-- name: i_code_review_merge_request_widget_terraform_expand_success
- aggregation: weekly
-- name: i_code_review_merge_request_widget_terraform_expand_warning
- aggregation: weekly
-- name: i_code_review_merge_request_widget_terraform_expand_failed
- aggregation: weekly
-- name: i_code_review_submit_review_approve
- aggregation: weekly
-- name: i_code_review_submit_review_comment
- aggregation: weekly
-## License Compliance
-- name: i_code_review_merge_request_widget_license_compliance_view
- aggregation: weekly
-- name: i_code_review_merge_request_widget_license_compliance_full_report_clicked
- aggregation: weekly
-- name: i_code_review_merge_request_widget_license_compliance_expand
- aggregation: weekly
-- name: i_code_review_merge_request_widget_license_compliance_expand_success
- aggregation: weekly
-- name: i_code_review_merge_request_widget_license_compliance_expand_warning
- aggregation: weekly
-- name: i_code_review_merge_request_widget_license_compliance_expand_failed
- aggregation: weekly
-## Security Reports
-- name: i_code_review_merge_request_widget_security_reports_view
- aggregation: weekly
-- name: i_code_review_merge_request_widget_security_reports_full_report_clicked
- aggregation: weekly
-- name: i_code_review_merge_request_widget_security_reports_expand
- aggregation: weekly
-- name: i_code_review_merge_request_widget_security_reports_expand_success
- aggregation: weekly
-- name: i_code_review_merge_request_widget_security_reports_expand_warning
- aggregation: weekly
-- name: i_code_review_merge_request_widget_security_reports_expand_failed
- aggregation: weekly
diff --git a/lib/gitlab/usage_data_counters/known_events/common.yml b/lib/gitlab/usage_data_counters/known_events/common.yml
deleted file mode 100644
index 0583d85c3cc..00000000000
--- a/lib/gitlab/usage_data_counters/known_events/common.yml
+++ /dev/null
@@ -1,167 +0,0 @@
----
-# Compliance category
-- name: g_edit_by_web_ide
- aggregation: daily
-- name: g_edit_by_sfe
- aggregation: daily
-- name: g_edit_by_snippet_ide
- aggregation: daily
-- name: g_edit_by_live_preview
- aggregation: daily
-- name: i_search_total
- aggregation: weekly
-- name: wiki_action
- aggregation: daily
-- name: design_action
- aggregation: daily
-- name: project_action
- aggregation: daily
-- name: git_write_action
- aggregation: daily
-- name: merge_request_action
- aggregation: daily
-- name: i_source_code_code_intelligence
- aggregation: daily
-# Incident management
-- name: incident_management_alert_status_changed
- aggregation: weekly
-- name: incident_management_alert_assigned
- aggregation: weekly
-- name: incident_management_alert_todo
- aggregation: weekly
-- name: incident_management_incident_created
- aggregation: weekly
-- name: incident_management_incident_reopened
- aggregation: weekly
-- name: incident_management_incident_closed
- aggregation: weekly
-- name: incident_management_incident_assigned
- aggregation: weekly
-- name: incident_management_incident_todo
- aggregation: weekly
-- name: incident_management_incident_comment
- aggregation: weekly
-- name: incident_management_incident_zoom_meeting
- aggregation: weekly
-- name: incident_management_incident_relate
- aggregation: weekly
-- name: incident_management_incident_unrelate
- aggregation: weekly
-- name: incident_management_incident_change_confidential
- aggregation: weekly
-# Incident management timeline events
-- name: incident_management_timeline_event_created
- aggregation: weekly
-- name: incident_management_timeline_event_edited
- aggregation: weekly
-- name: incident_management_timeline_event_deleted
- aggregation: weekly
-# Incident management alerts
-- name: incident_management_alert_create_incident
- aggregation: weekly
-# Testing category
-- name: i_testing_test_case_parsed
- aggregation: weekly
-- name: i_testing_test_report_uploaded
- aggregation: weekly
-- name: i_testing_coverage_report_uploaded
- aggregation: weekly
-# Project Management group
-- name: g_project_management_issue_title_changed
- aggregation: daily
-- name: g_project_management_issue_description_changed
- aggregation: daily
-- name: g_project_management_issue_assignee_changed
- aggregation: daily
-- name: g_project_management_issue_made_confidential
- aggregation: daily
-- name: g_project_management_issue_made_visible
- aggregation: daily
-- name: g_project_management_issue_created
- aggregation: daily
-- name: g_project_management_issue_closed
- aggregation: daily
-- name: g_project_management_issue_reopened
- aggregation: daily
-- name: g_project_management_issue_label_changed
- aggregation: daily
-- name: g_project_management_issue_milestone_changed
- aggregation: daily
-- name: g_project_management_issue_cross_referenced
- aggregation: daily
-- name: g_project_management_issue_moved
- aggregation: daily
-- name: g_project_management_issue_related
- aggregation: daily
-- name: g_project_management_issue_unrelated
- aggregation: daily
-- name: g_project_management_issue_marked_as_duplicate
- aggregation: daily
-- name: g_project_management_issue_locked
- aggregation: daily
-- name: g_project_management_issue_unlocked
- aggregation: daily
-- name: g_project_management_issue_designs_added
- aggregation: daily
-- name: g_project_management_issue_designs_modified
- aggregation: daily
-- name: g_project_management_issue_designs_removed
- aggregation: daily
-- name: g_project_management_issue_due_date_changed
- aggregation: daily
-- name: g_project_management_issue_design_comments_removed
- aggregation: daily
-- name: g_project_management_issue_time_estimate_changed
- aggregation: daily
-- name: g_project_management_issue_time_spent_changed
- aggregation: daily
-- name: g_project_management_issue_comment_added
- aggregation: daily
-- name: g_project_management_issue_comment_edited
- aggregation: daily
-- name: g_project_management_issue_comment_removed
- aggregation: daily
-- name: g_project_management_issue_cloned
- aggregation: daily
-# Runner group
-- name: g_runner_fleet_read_jobs_statistics
- aggregation: weekly
-# Secrets Management
-- name: i_snippets_show
- aggregation: weekly
-# Terraform
-- name: p_terraform_state_api_unique_users
- aggregation: weekly
-# Pipeline Authoring group
-- name: ci_interpolation_users
- aggregation: weekly
-- name: o_pipeline_authoring_unique_users_committing_ciconfigfile
- aggregation: weekly
-- name: o_pipeline_authoring_unique_users_pushing_mr_ciconfigfile
- aggregation: weekly
-- name: i_ci_secrets_management_id_tokens_build_created
- aggregation: weekly
-# Merge request widgets
-- name: users_expanding_secure_security_report
- aggregation: weekly
-- name: users_expanding_testing_code_quality_report
- aggregation: weekly
-- name: users_expanding_testing_accessibility_report
- aggregation: weekly
-- name: users_expanding_testing_license_compliance_report
- aggregation: weekly
-- name: users_visiting_testing_license_compliance_full_report
- aggregation: weekly
-- name: users_visiting_testing_manage_license_compliance
- aggregation: weekly
-- name: users_clicking_license_testing_visiting_external_website
- aggregation: weekly
-# Geo group
-- name: g_geo_proxied_requests
- aggregation: daily
-# Manage
-- name: unique_active_user
- aggregation: weekly
-# Environments page
-- name: users_visiting_environments_pages
- aggregation: weekly
diff --git a/lib/gitlab/usage_data_counters/known_events/container_registry_events.yml b/lib/gitlab/usage_data_counters/known_events/container_registry_events.yml
deleted file mode 100644
index aa0f9965fa7..00000000000
--- a/lib/gitlab/usage_data_counters/known_events/container_registry_events.yml
+++ /dev/null
@@ -1,11 +0,0 @@
----
-- name: i_container_registry_push_tag_user
- aggregation: weekly
-- name: i_container_registry_delete_tag_user
- aggregation: weekly
-- name: i_container_registry_push_repository_user
- aggregation: weekly
-- name: i_container_registry_delete_repository_user
- aggregation: weekly
-- name: i_container_registry_create_repository_user
- aggregation: weekly
diff --git a/lib/gitlab/usage_data_counters/known_events/ecosystem.yml b/lib/gitlab/usage_data_counters/known_events/ecosystem.yml
deleted file mode 100644
index 6e4a893d19a..00000000000
--- a/lib/gitlab/usage_data_counters/known_events/ecosystem.yml
+++ /dev/null
@@ -1,24 +0,0 @@
----
-# Ecosystem category
-- name: i_ecosystem_jira_service_close_issue
- aggregation: weekly
-- name: i_ecosystem_jira_service_cross_reference
- aggregation: weekly
-- name: i_ecosystem_slack_service_issue_notification
- aggregation: weekly
-- name: i_ecosystem_slack_service_push_notification
- aggregation: weekly
-- name: i_ecosystem_slack_service_deployment_notification
- aggregation: weekly
-- name: i_ecosystem_slack_service_wiki_page_notification
- aggregation: weekly
-- name: i_ecosystem_slack_service_merge_request_notification
- aggregation: weekly
-- name: i_ecosystem_slack_service_note_notification
- aggregation: weekly
-- name: i_ecosystem_slack_service_tag_push_notification
- aggregation: weekly
-- name: i_ecosystem_slack_service_confidential_note_notification
- aggregation: weekly
-- name: i_ecosystem_slack_service_confidential_issue_notification
- aggregation: weekly
diff --git a/lib/gitlab/usage_data_counters/known_events/error_tracking.yml b/lib/gitlab/usage_data_counters/known_events/error_tracking.yml
deleted file mode 100644
index ebfd1b274f9..00000000000
--- a/lib/gitlab/usage_data_counters/known_events/error_tracking.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-- name: error_tracking_view_details
- aggregation: weekly
-- name: error_tracking_view_list
- aggregation: weekly
diff --git a/lib/gitlab/usage_data_counters/known_events/importer_events.yml b/lib/gitlab/usage_data_counters/known_events/importer_events.yml
deleted file mode 100644
index abbd83a012b..00000000000
--- a/lib/gitlab/usage_data_counters/known_events/importer_events.yml
+++ /dev/null
@@ -1,13 +0,0 @@
----
-# Importer events
-- name: github_import_project_start
- aggregation: weekly
-- name: github_import_project_success
- aggregation: weekly
-- name: github_import_project_failure
- aggregation: weekly
-- name: github_import_project_cancelled
- aggregation: weekly
-- name: github_import_project_partially_completed
- aggregation: weekly
-
diff --git a/lib/gitlab/usage_data_counters/known_events/integrations.yml b/lib/gitlab/usage_data_counters/known_events/integrations.yml
deleted file mode 100644
index 4a83581e9f0..00000000000
--- a/lib/gitlab/usage_data_counters/known_events/integrations.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-- name: i_integrations_gitlab_for_slack_app_issue_notification
- aggregation: weekly
-- name: i_integrations_gitlab_for_slack_app_push_notification
- aggregation: weekly
-- name: i_integrations_gitlab_for_slack_app_deployment_notification
- aggregation: weekly
-- name: i_integrations_gitlab_for_slack_app_wiki_page_notification
- aggregation: weekly
-- name: i_integrations_gitlab_for_slack_app_merge_request_notification
- aggregation: weekly
-- name: i_integrations_gitlab_for_slack_app_note_notification
- aggregation: weekly
-- name: i_integrations_gitlab_for_slack_app_tag_push_notification
- aggregation: weekly
-- name: i_integrations_gitlab_for_slack_app_confidential_note_notification
- aggregation: weekly
-- name: i_integrations_gitlab_for_slack_app_confidential_issue_notification
- aggregation: weekly
diff --git a/lib/gitlab/usage_data_counters/known_events/kubernetes_agent.yml b/lib/gitlab/usage_data_counters/known_events/kubernetes_agent.yml
deleted file mode 100644
index fe779a9a25f..00000000000
--- a/lib/gitlab/usage_data_counters/known_events/kubernetes_agent.yml
+++ /dev/null
@@ -1,22 +0,0 @@
-- name: agent_users_using_ci_tunnel
- aggregation: weekly
-- name: k8s_api_proxy_requests_unique_users_via_ci_access
- aggregation: weekly
-- name: k8s_api_proxy_requests_unique_users_via_ci_access
- aggregation: monthly
-- name: k8s_api_proxy_requests_unique_agents_via_ci_access
- aggregation: weekly
-- name: k8s_api_proxy_requests_unique_agents_via_ci_access
- aggregation: monthly
-- name: k8s_api_proxy_requests_unique_users_via_user_access
- aggregation: weekly
-- name: k8s_api_proxy_requests_unique_users_via_user_access
- aggregation: monthly
-- name: k8s_api_proxy_requests_unique_agents_via_user_access
- aggregation: weekly
-- name: k8s_api_proxy_requests_unique_agents_via_user_access
- aggregation: monthly
-- name: flux_git_push_notified_unique_projects
- aggregation: weekly
-- name: flux_git_push_notified_unique_projects
- aggregation: monthly
diff --git a/lib/gitlab/usage_data_counters/known_events/package_events.yml b/lib/gitlab/usage_data_counters/known_events/package_events.yml
deleted file mode 100644
index fa99798cde0..00000000000
--- a/lib/gitlab/usage_data_counters/known_events/package_events.yml
+++ /dev/null
@@ -1,49 +0,0 @@
----
-- name: i_package_composer_deploy_token
- aggregation: weekly
-- name: i_package_composer_user
- aggregation: weekly
-- name: i_package_conan_deploy_token
- aggregation: weekly
-- name: i_package_conan_user
- aggregation: weekly
-- name: i_package_debian_deploy_token
- aggregation: weekly
-- name: i_package_debian_user
- aggregation: weekly
-- name: i_package_generic_deploy_token
- aggregation: weekly
-- name: i_package_generic_user
- aggregation: weekly
-- name: i_package_helm_deploy_token
- aggregation: weekly
-- name: i_package_helm_user
- aggregation: weekly
-- name: i_package_maven_deploy_token
- aggregation: weekly
-- name: i_package_maven_user
- aggregation: weekly
-- name: i_package_npm_deploy_token
- aggregation: weekly
-- name: i_package_npm_user
- aggregation: weekly
-- name: i_package_nuget_deploy_token
- aggregation: weekly
-- name: i_package_nuget_user
- aggregation: weekly
-- name: i_package_pypi_deploy_token
- aggregation: weekly
-- name: i_package_pypi_user
- aggregation: weekly
-- name: i_package_rubygems_deploy_token
- aggregation: weekly
-- name: i_package_rubygems_user
- aggregation: weekly
-- name: i_package_terraform_module_deploy_token
- aggregation: weekly
-- name: i_package_terraform_module_user
- aggregation: weekly
-- name: i_package_rpm_user
- aggregation: weekly
-- name: i_package_rpm_deploy_token
- aggregation: weekly
diff --git a/lib/gitlab/usage_data_counters/known_events/product_analytics.yml b/lib/gitlab/usage_data_counters/known_events/product_analytics.yml
deleted file mode 100644
index c43bf9040dd..00000000000
--- a/lib/gitlab/usage_data_counters/known_events/product_analytics.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-- name: project_created_analytics_dashboard
- aggregation: weekly
-- name: project_initialized_product_analytics
- aggregation: weekly
-- name: user_created_analytics_dashboard
- aggregation: weekly
-- name: user_visited_dashboard
- aggregation: weekly
diff --git a/lib/gitlab/usage_data_counters/known_events/quickactions.yml b/lib/gitlab/usage_data_counters/known_events/quickactions.yml
deleted file mode 100644
index 69f92ac5c0a..00000000000
--- a/lib/gitlab/usage_data_counters/known_events/quickactions.yml
+++ /dev/null
@@ -1,139 +0,0 @@
----
-- name: i_quickactions_assign_multiple
- aggregation: weekly
-- name: i_quickactions_approve
- aggregation: weekly
-- name: i_quickactions_unapprove
- aggregation: weekly
-- name: i_quickactions_assign_single
- aggregation: weekly
-- name: i_quickactions_assign_self
- aggregation: weekly
-- name: i_quickactions_assign_reviewer
- aggregation: weekly
-- name: i_quickactions_award
- aggregation: weekly
-- name: i_quickactions_board_move
- aggregation: weekly
-- name: i_quickactions_clone
- aggregation: weekly
-- name: i_quickactions_close
- aggregation: weekly
-- name: i_quickactions_confidential
- aggregation: weekly
-- name: i_quickactions_copy_metadata_merge_request
- aggregation: weekly
-- name: i_quickactions_copy_metadata_issue
- aggregation: weekly
-- name: i_quickactions_create_merge_request
- aggregation: weekly
-- name: i_quickactions_done
- aggregation: weekly
-- name: i_quickactions_draft
- aggregation: weekly
-- name: i_quickactions_due
- aggregation: weekly
-- name: i_quickactions_duplicate
- aggregation: weekly
-- name: i_quickactions_estimate
- aggregation: weekly
-- name: i_quickactions_label
- aggregation: weekly
-- name: i_quickactions_lock
- aggregation: weekly
-- name: i_quickactions_merge
- aggregation: weekly
-- name: i_quickactions_milestone
- aggregation: weekly
-- name: i_quickactions_move
- aggregation: weekly
-- name: i_quickactions_promote_to_incident
- aggregation: weekly
-- name: i_quickactions_timeline
- aggregation: weekly
-- name: i_quickactions_ready
- aggregation: weekly
-- name: i_quickactions_reassign
- aggregation: weekly
-- name: i_quickactions_reassign_reviewer
- aggregation: weekly
-- name: i_quickactions_rebase
- aggregation: weekly
-- name: i_quickactions_relabel
- aggregation: weekly
-- name: i_quickactions_relate
- aggregation: weekly
-- name: i_quickactions_remove_due_date
- aggregation: weekly
-- name: i_quickactions_remove_estimate
- aggregation: weekly
-- name: i_quickactions_remove_milestone
- aggregation: weekly
-- name: i_quickactions_remove_time_spent
- aggregation: weekly
-- name: i_quickactions_remove_zoom
- aggregation: weekly
-- name: i_quickactions_reopen
- aggregation: weekly
-- name: i_quickactions_severity
- aggregation: weekly
-- name: i_quickactions_shrug
- aggregation: weekly
-- name: i_quickactions_spend_subtract
- aggregation: weekly
-- name: i_quickactions_spend_add
- aggregation: weekly
-- name: i_quickactions_submit_review
- aggregation: weekly
-- name: i_quickactions_subscribe
- aggregation: weekly
-- name: i_quickactions_summarize_diff
- aggregation: weekly
-- name: i_quickactions_tableflip
- aggregation: weekly
-- name: i_quickactions_tag
- aggregation: weekly
-- name: i_quickactions_target_branch
- aggregation: weekly
-- name: i_quickactions_title
- aggregation: weekly
-- name: i_quickactions_todo
- aggregation: weekly
-- name: i_quickactions_unassign_specific
- aggregation: weekly
-- name: i_quickactions_unassign_all
- aggregation: weekly
-- name: i_quickactions_unassign_reviewer
- aggregation: weekly
-- name: i_quickactions_unlabel_specific
- aggregation: weekly
-- name: i_quickactions_unlabel_all
- aggregation: weekly
-- name: i_quickactions_unlock
- aggregation: weekly
-- name: i_quickactions_unsubscribe
- aggregation: weekly
-- name: i_quickactions_wip
- aggregation: weekly
-- name: i_quickactions_zoom
- aggregation: weekly
-- name: i_quickactions_link
- aggregation: weekly
-- name: i_quickactions_invite_email_single
- aggregation: weekly
-- name: i_quickactions_invite_email_multiple
- aggregation: weekly
-- name: i_quickactions_add_contacts
- aggregation: weekly
-- name: i_quickactions_remove_contacts
- aggregation: weekly
-- name: i_quickactions_type
- aggregation: weekly
-- name: i_quickactions_blocked_by
- aggregation: weekly
-- name: i_quickactions_blocks
- aggregation: weekly
-- name: i_quickactions_unlink
- aggregation: weekly
-- name: i_quickactions_promote_to
- aggregation: weekly
diff --git a/lib/gitlab/usage_data_counters/known_events/work_items.yml b/lib/gitlab/usage_data_counters/known_events/work_items.yml
deleted file mode 100644
index a6e5b9e1af5..00000000000
--- a/lib/gitlab/usage_data_counters/known_events/work_items.yml
+++ /dev/null
@@ -1,21 +0,0 @@
----
-- name: users_updating_work_item_title
- aggregation: weekly
-- name: users_creating_work_items
- aggregation: weekly
-- name: users_updating_work_item_dates
- aggregation: weekly
-- name: users_updating_work_item_labels
- aggregation: weekly
-- name: users_updating_work_item_milestone
- aggregation: weekly
-- name: users_updating_work_item_iteration
- # The event tracks an EE feature.
- # It's added here so it can be aggregated into the CE/EE 'OR' aggregate metrics.
- # It will report 0 for CE instances and should not be used with 'AND' aggregators.
- aggregation: weekly
-- name: users_updating_weight_estimate
- # The event tracks an EE feature.
- # It's added here so it can be aggregated into the CE/EE 'OR' aggregate metrics.
- # It will report 0 for CE instances and should not be used with 'AND' aggregators.
- aggregation: weekly
diff --git a/lib/gitlab/usage_data_counters/known_events/workspaces.yml b/lib/gitlab/usage_data_counters/known_events/workspaces.yml
deleted file mode 100644
index 8a96524b167..00000000000
--- a/lib/gitlab/usage_data_counters/known_events/workspaces.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-- name: users_updating_workspaces
- aggregation: weekly
-
-- name: users_creating_workspaces
- aggregation: weekly
diff --git a/lib/gitlab/usage_data_counters/neovim_plugin_activity_unique_counter.rb b/lib/gitlab/usage_data_counters/neovim_plugin_activity_unique_counter.rb
new file mode 100644
index 00000000000..7cf89a96e5d
--- /dev/null
+++ b/lib/gitlab/usage_data_counters/neovim_plugin_activity_unique_counter.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module UsageDataCounters
+ module NeovimPluginActivityUniqueCounter
+ NEOVIM_PLUGIN_API_REQUEST_ACTION = 'i_editor_extensions_user_neovim_plugin_api_request'
+ NEOVIM_PLUGIN_USER_AGENT_REGEX = /gitlab.vim/
+
+ class << self
+ def track_api_request_when_trackable(user_agent:, user:)
+ user_agent&.match?(NEOVIM_PLUGIN_USER_AGENT_REGEX) &&
+ track_unique_action_by_user(NEOVIM_PLUGIN_API_REQUEST_ACTION, user)
+ end
+
+ private
+
+ def track_unique_action_by_user(action, user)
+ return unless user
+
+ track_unique_action(action, user.id)
+ end
+
+ def track_unique_action(action, value)
+ Gitlab::UsageDataCounters::HLLRedisCounter.track_usage_event(action, value)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/usage_data_counters/visual_studio_extension_activity_unique_counter.rb b/lib/gitlab/usage_data_counters/visual_studio_extension_activity_unique_counter.rb
new file mode 100644
index 00000000000..771e466f5f9
--- /dev/null
+++ b/lib/gitlab/usage_data_counters/visual_studio_extension_activity_unique_counter.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module UsageDataCounters
+ module VisualStudioExtensionActivityUniqueCounter
+ VISUAL_STUDIO_EXTENSION_API_REQUEST_ACTION = 'i_editor_extensions_user_visual_studio_api_request'
+ VISUAL_STUDIO_EXTENSION_USER_AGENT_REGEX = /gl-visual-studio-extension/
+
+ class << self
+ def track_api_request_when_trackable(user_agent:, user:)
+ user_agent&.match?(VISUAL_STUDIO_EXTENSION_USER_AGENT_REGEX) &&
+ track_unique_action_by_user(VISUAL_STUDIO_EXTENSION_API_REQUEST_ACTION, user)
+ end
+
+ private
+
+ def track_unique_action_by_user(action, user)
+ return unless user
+
+ track_unique_action(action, user.id)
+ end
+
+ def track_unique_action(action, value)
+ Gitlab::UsageDataCounters::HLLRedisCounter.track_usage_event(action, value)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/usage_data_counters/work_item_activity_unique_counter.rb b/lib/gitlab/usage_data_counters/work_item_activity_unique_counter.rb
index 9de575d8567..b99c9ebb24f 100644
--- a/lib/gitlab/usage_data_counters/work_item_activity_unique_counter.rb
+++ b/lib/gitlab/usage_data_counters/work_item_activity_unique_counter.rb
@@ -33,7 +33,7 @@ module Gitlab
private
def track_unique_action(action, author)
- return unless author && Feature.enabled?(:track_work_items_activity)
+ return unless author
Gitlab::UsageDataCounters::HLLRedisCounter.track_event(action, values: author.id)
end
diff --git a/lib/gitlab/utils/markdown.rb b/lib/gitlab/utils/markdown.rb
index c95398a15df..c041953e7c8 100644
--- a/lib/gitlab/utils/markdown.rb
+++ b/lib/gitlab/utils/markdown.rb
@@ -4,7 +4,7 @@ module Gitlab
module Utils
module Markdown
PUNCTUATION_REGEXP = /[^\p{Word}\- ]/u.freeze
- PRODUCT_SUFFIX = /\s*\**\((core|starter|premium|ultimate|free|bronze|silver|gold)(\s+(only|self|saas))?\)\**/.freeze
+ PRODUCT_SUFFIX = /\s*\**\((core|starter|premium|ultimate|free|bronze|silver|gold)(\s+(all|only|self|saas))?\)\**/.freeze
def string_to_anchor(string)
string
diff --git a/lib/gitlab/web_hooks/logger.rb b/lib/gitlab/web_hooks/logger.rb
new file mode 100644
index 00000000000..010e40a3dab
--- /dev/null
+++ b/lib/gitlab/web_hooks/logger.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module WebHooks
+ class Logger < ::Gitlab::JsonLogger
+ def self.file_name_noext
+ 'web_hooks'
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/with_request_store.rb b/lib/gitlab/with_request_store.rb
deleted file mode 100644
index d13cd9a72f7..00000000000
--- a/lib/gitlab/with_request_store.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module WithRequestStore
- def with_request_store(&block)
- # Skip enabling the request store if it was already active. Whatever
- # instantiated the request store first is responsible for clearing it
- return yield if RequestStore.active?
-
- enabling_request_store(&block)
- end
-
- private
-
- def enabling_request_store
- RequestStore.begin!
- yield
- ensure
- RequestStore.end!
- RequestStore.clear!
- end
-
- extend self
- end
-end
diff --git a/lib/gitlab/x509/signature.rb b/lib/gitlab/x509/signature.rb
index d101a6d2522..649e5379927 100644
--- a/lib/gitlab/x509/signature.rb
+++ b/lib/gitlab/x509/signature.rb
@@ -42,11 +42,13 @@ module Gitlab
!verified_signature ||
signed_by_user.nil?
- if signed_by_user.verified_emails.include?(@email.downcase) && certificate_email.casecmp?(@email)
- :verified
- else
- :unverified
+ if signed_by_user.verified_emails.include?(@email.downcase)
+ return :verified if certificate_emails.find do |ce|
+ ce.casecmp?(@email)
+ end
end
+
+ :unverified
end
private
@@ -173,18 +175,13 @@ module Gitlab
end
def certificate_email
- email = nil
+ certificate_emails.first
+ end
- get_certificate_extension('subjectAltName').split(',').each do |item|
- if item.strip.start_with?("email")
- email = item.split('email:')[1]
- break
- end
+ def certificate_emails
+ get_certificate_extension('subjectAltName').split(',').each.with_object([]) do |item, emails|
+ emails << item.split('email:')[1] if item.strip.start_with?("email")
end
-
- return if email.nil?
-
- email
end
def x509_issuer
@@ -206,6 +203,7 @@ module Gitlab
subject_key_identifier: certificate_subject_key_identifier,
subject: certificate_subject,
email: certificate_email,
+ emails: certificate_emails,
serial_number: cert.serial.to_i,
x509_issuer_id: x509_issuer.id
}
diff --git a/lib/peek/views/click_house.rb b/lib/peek/views/click_house.rb
new file mode 100644
index 00000000000..cc109ccea51
--- /dev/null
+++ b/lib/peek/views/click_house.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module Peek
+ module Views
+ class ClickHouse < DetailedView
+ DEFAULT_THRESHOLDS = {
+ calls: 5,
+ duration: 1000,
+ individual_call: 1000
+ }.freeze
+
+ THRESHOLDS = {
+ production: {
+ calls: 5,
+ duration: 1000,
+ individual_call: 1000
+ }
+ }.freeze
+
+ def key
+ 'ch'
+ end
+
+ def self.thresholds
+ @thresholds ||= THRESHOLDS.fetch(Rails.env.to_sym, DEFAULT_THRESHOLDS)
+ end
+
+ private
+
+ def setup_subscribers
+ super
+
+ subscribe('sql.click_house') do |_, start, finish, _, data|
+ detail_store << generate_detail(start, finish, data) if Gitlab::PerformanceBar.enabled_for_request?
+ end
+ end
+
+ def generate_detail(start, finish, data)
+ {
+ start: start,
+ duration: finish - start,
+ sql: data[:query].strip,
+ backtrace: Gitlab::BacktraceCleaner.clean_backtrace(caller),
+ database: "database: #{data[:database]}",
+ statistics: "query stats: #{data[:statistics]}"
+ }
+ end
+ end
+ end
+end
diff --git a/lib/product_analytics/settings.rb b/lib/product_analytics/settings.rb
index 1c5f25c36b9..ad03c34cdd2 100644
--- a/lib/product_analytics/settings.rb
+++ b/lib/product_analytics/settings.rb
@@ -4,13 +4,10 @@ module ProductAnalytics
class Settings
BASE_CONFIG_KEYS = %w[product_analytics_data_collector_host cube_api_base_url cube_api_key].freeze
- JITSU_CONFIG_KEYS = (%w[jitsu_host jitsu_project_xid jitsu_administrator_email jitsu_administrator_password] +
- %w[product_analytics_clickhouse_connection_string] + BASE_CONFIG_KEYS).freeze
-
SNOWPLOW_CONFIG_KEYS = (%w[product_analytics_configurator_connection_string] +
BASE_CONFIG_KEYS).freeze
- ALL_CONFIG_KEYS = (ProductAnalytics::Settings::BASE_CONFIG_KEYS + ProductAnalytics::Settings::JITSU_CONFIG_KEYS +
+ ALL_CONFIG_KEYS = (ProductAnalytics::Settings::BASE_CONFIG_KEYS +
ProductAnalytics::Settings::SNOWPLOW_CONFIG_KEYS).freeze
def initialize(project:)
@@ -22,15 +19,7 @@ module ProductAnalytics
end
def configured?
- return configured_snowplow? if Feature.enabled?(:product_analytics_snowplow_support, @project)
-
- JITSU_CONFIG_KEYS.all? do |key|
- get_setting_value(key).present?
- end
- end
-
- def configured_snowplow?
- SNOWPLOW_CONFIG_KEYS.all? do |key|
+ ALL_CONFIG_KEYS.all? do |key|
get_setting_value(key).present?
end
end
diff --git a/lib/sbom/package_url/encoder.rb b/lib/sbom/package_url/encoder.rb
index 9cf05095571..e251d5fedcb 100644
--- a/lib/sbom/package_url/encoder.rb
+++ b/lib/sbom/package_url/encoder.rb
@@ -83,13 +83,14 @@ module Sbom
# - Apply type-specific normalization to the name if needed
# - UTF-8-encode the name if needed in your programming language
# - Append the percent-encoded name to the purl
- if @namespace.nil?
- io.write(URI.encode_www_form_component(@name, Encoding::UTF_8))
- else
- io.write(encode_segments(@namespace, &:empty?))
+ if @namespace.present?
+ normalized_namespace = Normalizer.new(type: @type, text: @namespace).normalize_namespace
+ io.write(encode_segments(normalized_namespace, &:empty?))
io.write('/')
- io.write(URI.encode_www_form_component(strip(@name, '/'), Encoding::UTF_8))
end
+
+ normalized_name = Normalizer.new(type: @type, text: strip(@name, '/')).normalize_name
+ io.write(URI.encode_www_form_component(normalized_name, Encoding::UTF_8))
end
def encode_version!
diff --git a/lib/sidebars/groups/menus/packages_registries_menu.rb b/lib/sidebars/groups/menus/packages_registries_menu.rb
index bd1cca6473a..b2d6aec1a78 100644
--- a/lib/sidebars/groups/menus/packages_registries_menu.rb
+++ b/lib/sidebars/groups/menus/packages_registries_menu.rb
@@ -50,7 +50,7 @@ module Sidebars
::Sidebars::MenuItem.new(
title: _('Container Registry'),
link: group_container_registries_path(context.group),
- super_sidebar_parent: ::Sidebars::Groups::SuperSidebarMenus::OperationsMenu,
+ super_sidebar_parent: ::Sidebars::Groups::SuperSidebarMenus::DeployMenu,
active_routes: { controller: 'groups/registry/repositories' },
item_id: :container_registry
)
diff --git a/lib/sidebars/groups/super_sidebar_menus/deploy_menu.rb b/lib/sidebars/groups/super_sidebar_menus/deploy_menu.rb
index fe9cc5280c7..b58ad0ee361 100644
--- a/lib/sidebars/groups/super_sidebar_menus/deploy_menu.rb
+++ b/lib/sidebars/groups/super_sidebar_menus/deploy_menu.rb
@@ -17,7 +17,8 @@ module Sidebars
override :configure_menu_items
def configure_menu_items
[
- :packages_registry
+ :packages_registry,
+ :container_registry
].each { |id| add_item(::Sidebars::NilMenuItem.new(item_id: id)) }
end
end
diff --git a/lib/sidebars/groups/super_sidebar_menus/operations_menu.rb b/lib/sidebars/groups/super_sidebar_menus/operations_menu.rb
index e716801486e..8988ffc9283 100644
--- a/lib/sidebars/groups/super_sidebar_menus/operations_menu.rb
+++ b/lib/sidebars/groups/super_sidebar_menus/operations_menu.rb
@@ -18,7 +18,6 @@ module Sidebars
def configure_menu_items
[
:dependency_proxy,
- :container_registry,
:group_kubernetes_clusters
].each { |id| add_item(::Sidebars::NilMenuItem.new(item_id: id)) }
end
diff --git a/lib/sidebars/menu.rb b/lib/sidebars/menu.rb
index 5f9255c06d0..73d6f733da5 100644
--- a/lib/sidebars/menu.rb
+++ b/lib/sidebars/menu.rb
@@ -123,6 +123,10 @@ module Sidebars
insert_element_after(@items, after_item, new_item)
end
+ def remove_item(item)
+ remove_element(@items, item.item_id)
+ end
+
def replace_placeholder(item)
idx = @items.index { |e| e.item_id == item.item_id && e.is_a?(::Sidebars::NilMenuItem) }
if idx.nil?
diff --git a/lib/sidebars/projects/menus/settings_menu.rb b/lib/sidebars/projects/menus/settings_menu.rb
index 9219312ede8..142d803037b 100644
--- a/lib/sidebars/projects/menus/settings_menu.rb
+++ b/lib/sidebars/projects/menus/settings_menu.rb
@@ -162,3 +162,5 @@ module Sidebars
end
end
end
+
+Sidebars::Projects::Menus::SettingsMenu.prepend_mod_with('Sidebars::Projects::Menus::SettingsMenu')
diff --git a/lib/slack_markdown_sanitizer.rb b/lib/slack_markdown_sanitizer.rb
index df3bec1a3c8..f26d9aeb688 100644
--- a/lib/slack_markdown_sanitizer.rb
+++ b/lib/slack_markdown_sanitizer.rb
@@ -8,4 +8,8 @@ module SlackMarkdownSanitizer
def self.sanitize(string)
string&.delete(UNSAFE_MARKUP_CHARACTERS)
end
+
+ def self.sanitize_slack_link(string)
+ string.gsub(Gitlab::Regex.slack_link_regex) { |m| m.gsub("<", "&lt;").gsub(">", "&gt;") }
+ end
end
diff --git a/lib/system_check/app/ruby_version_check.rb b/lib/system_check/app/ruby_version_check.rb
index 135413c528d..5d3d28baf89 100644
--- a/lib/system_check/app/ruby_version_check.rb
+++ b/lib/system_check/app/ruby_version_check.rb
@@ -7,7 +7,7 @@ module SystemCheck
set_check_pass -> { "yes (#{self.current_version})" }
def self.required_version
- @required_version ||= Gitlab::VersionInfo.new(2, 7, 2)
+ @required_version ||= Gitlab::VersionInfo.new(3, 0, 6)
end
def self.current_version
diff --git a/lib/tasks/gems.rake b/lib/tasks/gems.rake
index fc70048ea6d..0c4cbbfe3f8 100644
--- a/lib/tasks/gems.rake
+++ b/lib/tasks/gems.rake
@@ -29,7 +29,7 @@ namespace :gems do
end
def root_directory
- File.expand_path('../../vendor/gems', __dir__)
+ File.expand_path('../../gems', __dir__)
end
def generate_gem(vendor_gem_dir:, api_url:, gem_name:, module_name:, docker_image:)
@@ -53,14 +53,18 @@ namespace :gems do
write_file(gem_dir / 'LICENSE', license)
write_file(gem_dir / "#{gem_name}.gemspec") do |content|
replace_string(content, 'Unlicense', 'MIT')
+ replace_string(content, /.*add_development_dependency 'rspec'.*/, '')
replace_string(content, /(\.files\s*=).*/, '\1 Dir.glob("lib/**/*")')
replace_string(content, /(\.test_files\s*=).*/, '\1 []')
end
+ # This is gem is supposed to be generated. No developer should change code.
remove_entry_secure(gem_dir / 'Gemfile')
+ # The generated code doesn't align well with `gitlab-styles` configuration.
remove_entry_secure(gem_dir / '.rubocop.yml')
remove_entry_secure(gem_dir / '.travis.yml')
remove_entry_secure(gem_dir / 'git_push.sh')
+ # The RSpec examples are stubs and have no value.
remove_entry_secure(gem_dir / 'spec')
remove_entry_secure(gem_dir / '.rspec')
end
@@ -78,14 +82,16 @@ namespace :gems do
end
def readme_banner(task)
- # rubocop:disable Rails/TimeZone
<<~BANNER
- # Generated by `rake #{task.name}` on #{Time.now.strftime('%Y-%m-%d')}
+ # #{generated_by(task)}
See https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/development/rake_tasks.md#update-openapi-client-for-error-tracking-feature
BANNER
- # rubocop:enable Rails/TimeZone
+ end
+
+ def generated_by(task)
+ "Generated by `rake #{task.name}` on #{Time.now.strftime('%Y-%m-%d')}" # rubocop:disable Rails/TimeZone
end
def license
diff --git a/lib/tasks/gitlab/audit_event_types/audit_event_types.rake b/lib/tasks/gitlab/audit_event_types/audit_event_types.rake
new file mode 100644
index 00000000000..289f79568a9
--- /dev/null
+++ b/lib/tasks/gitlab/audit_event_types/audit_event_types.rake
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+return if Rails.env.production?
+
+namespace :gitlab do
+ namespace :audit_event_types do
+ event_types_dir = Rails.root.join("doc/administration/audit_event_streaming")
+ event_types_doc_file = Rails.root.join(event_types_dir, 'audit_event_types.md')
+ template_directory = 'tooling/audit_events/docs/templates/'
+ template_erb_file_path = Rails.root.join(template_directory, 'audit_event_types.md.erb')
+
+ desc 'GitLab | Audit event types | Generate audit event types docs'
+ task compile_docs: :environment do
+ require_relative './compile_docs_task'
+
+ Tasks::Gitlab::AuditEventTypes::CompileDocsTask
+ .new(event_types_dir, event_types_doc_file, template_erb_file_path).run
+ end
+
+ desc 'GitLab | Audit event types | Check if Audit event types docs are up to date'
+ task check_docs: :environment do
+ require_relative './check_docs_task'
+
+ Tasks::Gitlab::AuditEventTypes::CheckDocsTask
+ .new(event_types_dir, event_types_doc_file, template_erb_file_path).run
+ end
+ end
+end
diff --git a/lib/tasks/gitlab/audit_event_types/check_docs_task.rb b/lib/tasks/gitlab/audit_event_types/check_docs_task.rb
new file mode 100644
index 00000000000..f62dd116ed1
--- /dev/null
+++ b/lib/tasks/gitlab/audit_event_types/check_docs_task.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Tasks
+ module Gitlab
+ module AuditEventTypes
+ class CheckDocsTask
+ def initialize(docs_dir, docs_path, template_erb_path)
+ @event_types_dir = docs_dir
+ @audit_event_types_doc_file = docs_path
+ @event_type_erb_template = ERB.new(File.read(template_erb_path), trim_mode: '<>')
+ end
+
+ def run
+ doc = File.read(@audit_event_types_doc_file)
+
+ if doc == @event_type_erb_template.result
+ puts "Audit event types documentation is up to date."
+ else
+ error_message = "Audit event types documentation is outdated! Please update it by running " \
+ "`bundle exec rake gitlab:audit_event_types:compile_docs`."
+ heading = '#' * 10
+ puts heading
+ puts '#'
+ puts "# #{error_message}"
+ puts '#'
+ puts heading
+
+ abort
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/tasks/gitlab/audit_event_types/compile_docs_task.rb b/lib/tasks/gitlab/audit_event_types/compile_docs_task.rb
new file mode 100644
index 00000000000..ffa4f6d3514
--- /dev/null
+++ b/lib/tasks/gitlab/audit_event_types/compile_docs_task.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Tasks
+ module Gitlab
+ module AuditEventTypes
+ class CompileDocsTask
+ def initialize(docs_dir, docs_path, template_erb_path)
+ @event_types_dir = docs_dir
+ @audit_event_types_doc_file = docs_path
+ @event_type_erb_template = ERB.new(File.read(template_erb_path), trim_mode: '<>')
+ end
+
+ def run
+ FileUtils.mkdir_p(@event_types_dir)
+ File.write(@audit_event_types_doc_file, @event_type_erb_template.result)
+
+ puts "Documentation compiled."
+ end
+ end
+ end
+ end
+end
diff --git a/lib/tasks/gitlab/db/cells/bump_cell_sequences.rake b/lib/tasks/gitlab/db/cells/bump_cell_sequences.rake
new file mode 100644
index 00000000000..04d91c96ebe
--- /dev/null
+++ b/lib/tasks/gitlab/db/cells/bump_cell_sequences.rake
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+namespace :gitlab do
+ namespace :db do
+ namespace :cells do
+ desc 'Bump sequences for cell-local tables on the cells database'
+ task :bump_cell_sequences, [:increase_by] => :environment do |_t, args|
+ # We do not want to run this on production environment, even accidentally.
+ unless Gitlab.dev_or_test_env?
+ puts 'This rake task cannot be run in production environment'.color(:red)
+ exit 1
+ end
+
+ increase_by = args.increase_by.to_i
+ if increase_by < 1
+ puts 'Please specify a positive integer `increase_by` value'.color(:red)
+ puts 'Example: rake gitlab:db:cells:bump_cell_sequences[100000]'.color(:green)
+ exit 1
+ end
+
+ Gitlab::Database::BumpSequences.new(:gitlab_main_cell, increase_by).execute
+ end
+ end
+ end
+end
diff --git a/lib/tasks/gitlab/db/decomposition/rollback/bump_ci_sequences.rake b/lib/tasks/gitlab/db/decomposition/rollback/bump_ci_sequences.rake
index 4d78acb3011..fac4c68b0a6 100644
--- a/lib/tasks/gitlab/db/decomposition/rollback/bump_ci_sequences.rake
+++ b/lib/tasks/gitlab/db/decomposition/rollback/bump_ci_sequences.rake
@@ -4,8 +4,6 @@ namespace :gitlab do
namespace :db do
namespace :decomposition do
namespace :rollback do
- SEQUENCE_NAME_MATCHER = /nextval\('([a-z_]+)'::regclass\)/.freeze
-
desc 'Bump all the CI tables sequences on the Main Database'
task :bump_ci_sequences, [:increase_by] => :environment do |_t, args|
increase_by = args.increase_by.to_i
@@ -15,54 +13,9 @@ namespace :gitlab do
exit 1
end
- sequences_by_gitlab_schema(ApplicationRecord, :gitlab_ci).each do |sequence_name|
- increment_sequence_by(ApplicationRecord.connection, sequence_name, increase_by)
- end
+ Gitlab::Database::BumpSequences.new(:gitlab_ci, increase_by).execute
end
end
end
end
end
-
-# base_model is to choose which connection to use to query the tables
-# gitlab_schema, can be 'gitlab_main', 'gitlab_ci', 'gitlab_shared'
-def sequences_by_gitlab_schema(base_model, gitlab_schema)
- tables = Gitlab::Database::GitlabSchema.tables_to_schema.select do |_table_name, schema_name|
- schema_name == gitlab_schema
- end.keys
-
- models = tables.map do |table|
- model = Class.new(base_model)
- model.table_name = table
- model
- end
-
- sequences = []
- models.each do |model|
- model.columns.each do |column|
- match_result = column.default_function&.match(SEQUENCE_NAME_MATCHER)
- next unless match_result
-
- sequences << match_result[1]
- end
- end
-
- sequences
-end
-
-# This method is going to increase the sequence next_value by:
-# - increment_by + 1 if the sequence has the attribute is_called = True (which is the common case)
-# - increment_by if the sequence has the attribute is_called = False (for example, a newly created sequence)
-# It uses ALTER SEQUENCE as a safety mechanism to avoid that no concurrent insertions
-# will cause conflicts on the sequence.
-# This is because ALTER SEQUENCE blocks concurrent nextval, currval, lastval, and setval calls.
-def increment_sequence_by(connection, sequence_name, increment_by)
- connection.transaction do
- # The first call is to make sure that the sequence's is_called value is set to `true`
- # This guarantees that the next call to `nextval` will increase the sequence by `increment_by`
- connection.select_value("SELECT nextval($1)", nil, [sequence_name])
- connection.execute("ALTER SEQUENCE #{sequence_name} INCREMENT BY #{increment_by}")
- connection.select_value("select nextval($1)", nil, [sequence_name])
- connection.execute("ALTER SEQUENCE #{sequence_name} INCREMENT BY 1")
- end
-end
diff --git a/lib/tasks/gitlab/db/migration_squash.rake b/lib/tasks/gitlab/db/migration_squash.rake
new file mode 100644
index 00000000000..7ddd6c41d12
--- /dev/null
+++ b/lib/tasks/gitlab/db/migration_squash.rake
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+namespace :gitlab do
+ namespace :db do
+ desc "GitLab | DB | squash | squash as of a version"
+ task :squash, [:version] => :environment do |_t, args|
+ require 'git'
+ git = ::Git.open(Dir.pwd)
+
+ squasher = Gitlab::Database::Migrations::Squasher.new(
+ `git ls-tree --name-only -r #{args[:version]} -- db/migrate db/post_migrate`
+ )
+
+ new_init_structure_sql = git.show(args[:version], 'db/structure.sql')
+ # Delete relevant migrations and specs
+ squasher.files_to_delete.each do |filename|
+ git.remove filename
+ puts "\tDeleting #{filename} from repo".red
+ rescue Git::GitExecuteError
+ puts "#{filename} is not in the current branch"
+ end
+ puts "\tOverwriting init_structure.sql..."
+ File.write('db/init_structure.sql', new_init_structure_sql)
+ git.add('db/init_structure.sql')
+ puts "\tDone!".white
+ end
+ end
+end
diff --git a/lib/tasks/gitlab/info.rake b/lib/tasks/gitlab/info.rake
index 4f7053b7629..26ffe2c3f7b 100644
--- a/lib/tasks/gitlab/info.rake
+++ b/lib/tasks/gitlab/info.rake
@@ -90,6 +90,19 @@ namespace :gitlab do
puts "- #{name}: \t#{repository_storage.gitaly_address}"
end
puts "GitLab Shell path:\t\t#{Gitlab.config.gitlab_shell.path}"
+
+ # check Gitaly version
+ puts ""
+ puts "Gitaly".color(:yellow)
+ Gitlab.config.repositories.storages.each do |storage_name, storage|
+ gitaly_server_service = Gitlab::GitalyClient::ServerService.new(storage_name)
+ gitaly_server_info = gitaly_server_service.info
+ puts "- #{storage_name} Address: \t#{storage.gitaly_address}"
+ puts "- #{storage_name} Version: \t#{gitaly_server_info.server_version}"
+ puts "- #{storage_name} Git Version: \t#{gitaly_server_info.git_version}"
+ rescue GRPC::DeadlineExceeded
+ puts "Unable to reach storage #{storage_name}".color(red)
+ end
end
end
end
diff --git a/lib/tasks/gitlab/tw/codeowners.rake b/lib/tasks/gitlab/tw/codeowners.rake
index afe2c564247..cea66125fd0 100644
--- a/lib/tasks/gitlab/tw/codeowners.rake
+++ b/lib/tasks/gitlab/tw/codeowners.rake
@@ -21,7 +21,8 @@ namespace :tw do
CODE_OWNER_RULES = [
# CodeOwnerRule.new('Activation', ''),
# CodeOwnerRule.new('Acquisition', ''),
- # CodeOwnerRule.new('AI Assisted', ''),
+ CodeOwnerRule.new('AI Framework', '@sselhorn'),
+ CodeOwnerRule.new('AI Model Validation', '@sselhorn'),
CodeOwnerRule.new('Analytics Instrumentation', '@lciutacu'),
CodeOwnerRule.new('Anti-Abuse', '@phillipwells'),
CodeOwnerRule.new('Application Performance', '@jglassman1'),
@@ -34,13 +35,14 @@ namespace :tw do
CodeOwnerRule.new('Container Registry', '@marcel.amirault'),
CodeOwnerRule.new('Contributor Experience', '@eread'),
CodeOwnerRule.new('Database', '@aqualls'),
- # CodeOwnerRule.new('DataOps', ''),
+ CodeOwnerRule.new('DataOps', '@sselhorn'),
# CodeOwnerRule.new('Delivery', ''),
CodeOwnerRule.new('Development', '@sselhorn'),
CodeOwnerRule.new('Distribution', '@axil'),
CodeOwnerRule.new('Distribution (Charts)', '@axil'),
CodeOwnerRule.new('Distribution (Omnibus)', '@eread'),
CodeOwnerRule.new('Documentation Guidelines', '@sselhorn'),
+ CodeOwnerRule.new('Duo Chat', '@sselhorn'),
CodeOwnerRule.new('Dynamic Analysis', '@rdickenson'),
CodeOwnerRule.new('IDE', '@ashrafkhamis'),
CodeOwnerRule.new('Foundations', '@sselhorn'),
@@ -53,7 +55,7 @@ namespace :tw do
CodeOwnerRule.new('Import and Integrate', '@eread @ashrafkhamis'),
CodeOwnerRule.new('Infrastructure', '@sselhorn'),
# CodeOwnerRule.new('Knowledge', ''),
- # CodeOwnerRule.new('MLOps', '')
+ CodeOwnerRule.new('MLOps', '@sselhorn'),
# CodeOwnerRule.new('Observability', ''),
CodeOwnerRule.new('Optimize', '@lciutacu'),
CodeOwnerRule.new('Organization', '@lciutacu'),
@@ -71,7 +73,7 @@ namespace :tw do
CodeOwnerRule.new('Runner', '@fneill'),
CodeOwnerRule.new('Runner SaaS', '@fneill'),
CodeOwnerRule.new('Security Policies', '@rdickenson'),
- CodeOwnerRule.new('Source Code', '@aqualls @msedlakjakubowski'),
+ CodeOwnerRule.new('Source Code', ->(path) { path.start_with?('/doc/user') ? '@aqualls' : '@msedlakjakubowski' }),
CodeOwnerRule.new('Static Analysis', '@rdickenson'),
CodeOwnerRule.new('Style Guide', '@sselhorn'),
CodeOwnerRule.new('Tenant Scale', '@lciutacu'),
@@ -100,8 +102,14 @@ namespace :tw do
end
end
- def self.writer_for_group(category)
- CODE_OWNER_RULES.find { |rule| rule.category == category }&.writer
+ def self.writer_for_group(category, path)
+ writer = CODE_OWNER_RULES.find { |rule| rule.category == category }&.writer
+
+ if writer.is_a?(String) || writer.nil?
+ writer
+ else
+ writer.call(path)
+ end
end
errors = []
@@ -118,7 +126,7 @@ namespace :tw do
next
end
- writer = writer_for_group(document.group)
+ writer = writer_for_group(document.group, relative_file)
next unless writer
mappings << DocumentOwnerMapping.new(relative_file, writer) if document.has_a_valid_group?
diff --git a/lib/tasks/gitlab/user_management.rake b/lib/tasks/gitlab/user_management.rake
index 29f2360f64a..dbadc7a2f7a 100644
--- a/lib/tasks/gitlab/user_management.rake
+++ b/lib/tasks/gitlab/user_management.rake
@@ -5,11 +5,18 @@ namespace :gitlab do
desc "GitLab | User management | Update all users of a group with personal project limit to 0 and can_create_group to false"
task :disable_project_and_group_creation, [:group_id] => :environment do |t, args|
group = Group.find(args.group_id)
+ user_ids = Member.from_union([
+ group.hierarchy_members_with_inactive.select(:user_id),
+ group.descendant_project_members_with_inactive.select(:user_id)
+ ], remove_duplicates: false).distinct.pluck(:user_id)
- result = User.where(id: group.direct_and_indirect_users_with_inactive.select(:id)).update_all(projects_limit: 0, can_create_group: false)
- ids_count = group.direct_and_indirect_users_with_inactive.count
- puts "Done".color(:green) if result == ids_count
- puts "Something went wrong".color(:red) if result != ids_count
+ result = User.where(id: user_ids).update_all(projects_limit: 0, can_create_group: false)
+
+ if result == user_ids.count
+ puts "Done".color(:green)
+ else
+ puts "Something went wrong".color(:red)
+ end
end
end
end