summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-12-20 13:37:47 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-12-20 13:37:47 +0000
commitaee0a117a889461ce8ced6fcf73207fe017f1d99 (patch)
tree891d9ef189227a8445d83f35c1b0fc99573f4380 /lib
parent8d46af3258650d305f53b819eabf7ab18d22f59e (diff)
downloadgitlab-ce-aee0a117a889461ce8ced6fcf73207fe017f1d99.tar.gz
Add latest changes from gitlab-org/gitlab@14-6-stable-eev14.6.0-rc42
Diffstat (limited to 'lib')
-rw-r--r--lib/after_commit_queue.rb50
-rw-r--r--lib/api/admin/plan_limits.rb1
-rw-r--r--lib/api/ci/helpers/runner.rb16
-rw-r--r--lib/api/ci/pipelines.rb2
-rw-r--r--lib/api/ci/runner.rb4
-rw-r--r--lib/api/commit_statuses.rb5
-rw-r--r--lib/api/composer_packages.rb10
-rw-r--r--lib/api/concerns/packages/conan_endpoints.rb17
-rw-r--r--lib/api/concerns/packages/npm_endpoints.rb4
-rw-r--r--lib/api/dependency_proxy.rb12
-rw-r--r--lib/api/discussions.rb7
-rw-r--r--lib/api/entities/changelog.rb9
-rw-r--r--lib/api/entities/ci/job_basic.rb1
-rw-r--r--lib/api/entities/ci/pipeline.rb4
-rw-r--r--lib/api/entities/ci/pipeline_basic.rb2
-rw-r--r--lib/api/entities/ci/runner.rb2
-rw-r--r--lib/api/entities/commit_signature.rb4
-rw-r--r--lib/api/entities/issue_basic.rb2
-rw-r--r--lib/api/entities/personal_access_token.rb2
-rw-r--r--lib/api/entities/plan_limit.rb1
-rw-r--r--lib/api/entities/project.rb1
-rw-r--r--lib/api/entities/project_import_failed_relation.rb1
-rw-r--r--lib/api/entities/project_import_status.rb7
-rw-r--r--lib/api/group_export.rb4
-rw-r--r--lib/api/groups.rb24
-rw-r--r--lib/api/helpers/label_helpers.rb6
-rw-r--r--lib/api/helpers/members_helpers.rb3
-rw-r--r--lib/api/helpers/packages/conan/api_helpers.rb15
-rw-r--r--lib/api/helpers/projects_helpers.rb7
-rw-r--r--lib/api/helpers/rate_limiter.rb27
-rw-r--r--lib/api/invitations.rb1
-rw-r--r--lib/api/issues.rb8
-rw-r--r--lib/api/lint.rb4
-rw-r--r--lib/api/members.rb2
-rw-r--r--lib/api/merge_request_diffs.rb2
-rw-r--r--lib/api/merge_requests.rb30
-rw-r--r--lib/api/notes.rb7
-rw-r--r--lib/api/project_export.rb4
-rw-r--r--lib/api/project_import.rb6
-rw-r--r--lib/api/projects.rb2
-rw-r--r--lib/api/repositories.rb75
-rw-r--r--lib/api/resource_label_events.rb2
-rw-r--r--lib/api/resource_milestone_events.rb2
-rw-r--r--lib/api/resource_state_events.rb2
-rw-r--r--lib/api/search.rb4
-rw-r--r--lib/api/settings.rb1
-rw-r--r--lib/api/suggestions.rb4
-rw-r--r--lib/api/terraform/state.rb7
-rw-r--r--lib/api/topics.rb2
-rw-r--r--lib/api/v3/github.rb2
-rw-r--r--lib/api/validations/types/workhorse_file.rb1
-rw-r--r--lib/banzai/filter/base_sanitization_filter.rb6
-rw-r--r--lib/banzai/filter/external_link_filter.rb2
-rw-r--r--lib/banzai/filter/footnote_filter.rb37
-rw-r--r--lib/banzai/filter/issuable_reference_expansion_filter.rb92
-rw-r--r--lib/banzai/filter/issuable_state_filter.rb66
-rw-r--r--lib/banzai/filter/markdown_engines/common_mark.rb8
-rw-r--r--lib/banzai/filter/markdown_post_escape_filter.rb2
-rw-r--r--lib/banzai/filter/math_filter.rb2
-rw-r--r--lib/banzai/filter/plantuml_filter.rb2
-rw-r--r--lib/banzai/filter/references/abstract_reference_filter.rb2
-rw-r--r--lib/banzai/filter/references/reference_cache.rb14
-rw-r--r--lib/banzai/filter/references/reference_filter.rb4
-rw-r--r--lib/banzai/filter/repository_link_filter.rb2
-rw-r--r--lib/banzai/filter/sanitization_filter.rb9
-rw-r--r--lib/banzai/filter/syntax_highlight_filter.rb8
-rw-r--r--lib/banzai/pipeline/post_process_pipeline.rb2
-rw-r--r--lib/bulk_imports/clients/http.rb2
-rw-r--r--lib/bulk_imports/common/pipelines/badges_pipeline.rb44
-rw-r--r--lib/bulk_imports/common/pipelines/uploads_pipeline.rb29
-rw-r--r--lib/bulk_imports/common/pipelines/wiki_pipeline.rb21
-rw-r--r--lib/bulk_imports/common/rest/get_badges_query.rb (renamed from lib/bulk_imports/groups/rest/get_badges_query.rb)5
-rw-r--r--lib/bulk_imports/groups/pipelines/badges_pipeline.rb32
-rw-r--r--lib/bulk_imports/groups/pipelines/group_avatar_pipeline.rb49
-rw-r--r--lib/bulk_imports/groups/stage.rb10
-rw-r--r--lib/bulk_imports/ndjson_pipeline.rb9
-rw-r--r--lib/bulk_imports/projects/graphql/get_project_query.rb17
-rw-r--r--lib/bulk_imports/projects/graphql/get_repository_query.rb17
-rw-r--r--lib/bulk_imports/projects/graphql/get_snippet_repository_query.rb48
-rw-r--r--lib/bulk_imports/projects/graphql/queryable.rb25
-rw-r--r--lib/bulk_imports/projects/pipelines/auto_devops_pipeline.rb15
-rw-r--r--lib/bulk_imports/projects/pipelines/ci_pipelines_pipeline.rb15
-rw-r--r--lib/bulk_imports/projects/pipelines/container_expiration_policy_pipeline.rb15
-rw-r--r--lib/bulk_imports/projects/pipelines/pipeline_schedules_pipeline.rb15
-rw-r--r--lib/bulk_imports/projects/pipelines/project_attributes_pipeline.rb85
-rw-r--r--lib/bulk_imports/projects/pipelines/project_feature_pipeline.rb15
-rw-r--r--lib/bulk_imports/projects/pipelines/repository_pipeline.rb2
-rw-r--r--lib/bulk_imports/projects/pipelines/service_desk_setting_pipeline.rb15
-rw-r--r--lib/bulk_imports/projects/pipelines/snippets_pipeline.rb15
-rw-r--r--lib/bulk_imports/projects/pipelines/snippets_repository_pipeline.rb69
-rw-r--r--lib/bulk_imports/projects/stage.rb40
-rw-r--r--lib/extracts_ref.rb2
-rw-r--r--lib/feature.rb19
-rw-r--r--lib/feature/definition.rb18
-rw-r--r--lib/feature/shared.rb1
-rw-r--r--lib/flowdock/git/builder.rb2
-rw-r--r--lib/generators/gitlab/usage_metric_definition/redis_hll_generator.rb4
-rw-r--r--lib/generators/gitlab/usage_metric_definition_generator.rb5
-rw-r--r--lib/generators/gitlab/usage_metric_generator.rb4
-rw-r--r--lib/gitlab/abuse.rb18
-rw-r--r--lib/gitlab/analytics/cycle_analytics/aggregated/data_collector.rb2
-rw-r--r--lib/gitlab/analytics/cycle_analytics/aggregated/median.rb2
-rw-r--r--lib/gitlab/analytics/cycle_analytics/aggregated/records_fetcher.rb2
-rw-r--r--lib/gitlab/analytics/cycle_analytics/aggregated/stage_query_helpers.rb8
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_events/stage_event.rb2
-rw-r--r--lib/gitlab/anonymous_session.rb16
-rw-r--r--lib/gitlab/application_context.rb7
-rw-r--r--lib/gitlab/application_rate_limiter.rb102
-rw-r--r--lib/gitlab/asciidoc/syntax_highlighter/html_pipeline_adapter.rb2
-rw-r--r--lib/gitlab/auth.rb4
-rw-r--r--lib/gitlab/auth/user_access_denied_reason.rb2
-rw-r--r--lib/gitlab/background_migration.rb14
-rw-r--r--lib/gitlab/background_migration/add_merge_request_diff_commits_count.rb25
-rw-r--r--lib/gitlab/background_migration/add_modified_to_approval_merge_request_rule.rb73
-rw-r--r--lib/gitlab/background_migration/backfill_deployment_clusters_from_deployments.rb19
-rw-r--r--lib/gitlab/background_migration/backfill_environment_id_deployment_merge_requests.rb47
-rw-r--r--lib/gitlab/background_migration/backfill_merge_request_cleanup_schedules.rb40
-rw-r--r--lib/gitlab/background_migration/backfill_namespace_settings.rb18
-rw-r--r--lib/gitlab/background_migration/backfill_project_settings.rb18
-rw-r--r--lib/gitlab/background_migration/backfill_push_rules_id_in_projects.rb27
-rw-r--r--lib/gitlab/background_migration/drop_invalid_remediations.rb14
-rw-r--r--lib/gitlab/background_migration/drop_invalid_security_findings.rb47
-rw-r--r--lib/gitlab/background_migration/fix_promoted_epics_discussion_ids.rb22
-rw-r--r--lib/gitlab/background_migration/fix_user_namespace_names.rb68
-rw-r--r--lib/gitlab/background_migration/fix_user_project_route_names.rb38
-rw-r--r--lib/gitlab/background_migration/job_coordinator.rb38
-rw-r--r--lib/gitlab/background_migration/link_lfs_objects_projects.rb82
-rw-r--r--lib/gitlab/background_migration/migrate_fingerprint_sha256_within_keys.rb61
-rw-r--r--lib/gitlab/background_migration/migrate_pages_metadata.rb38
-rw-r--r--lib/gitlab/background_migration/migrate_to_hashed_storage.rb61
-rw-r--r--lib/gitlab/background_migration/move_epic_issues_after_epics.rb13
-rw-r--r--lib/gitlab/background_migration/populate_any_approval_rule_for_merge_requests.rb14
-rw-r--r--lib/gitlab/background_migration/populate_any_approval_rule_for_projects.rb14
-rw-r--r--lib/gitlab/background_migration/populate_canonical_emails.rb28
-rw-r--r--lib/gitlab/background_migration/populate_dismissed_state_for_vulnerabilities.rb17
-rw-r--r--lib/gitlab/background_migration/populate_has_vulnerabilities.rb64
-rw-r--r--lib/gitlab/background_migration/populate_merge_request_assignees_table.rb44
-rw-r--r--lib/gitlab/background_migration/populate_missing_vulnerability_dismissal_information.rb89
-rw-r--r--lib/gitlab/background_migration/populate_personal_snippet_statistics.rb49
-rw-r--r--lib/gitlab/background_migration/populate_project_snippet_statistics.rb61
-rw-r--r--lib/gitlab/background_migration/populate_vulnerability_feedback_pipeline_id.rb13
-rw-r--r--lib/gitlab/background_migration/populate_vulnerability_historical_statistics.rb14
-rw-r--r--lib/gitlab/background_migration/prune_orphaned_geo_events.rb17
-rw-r--r--lib/gitlab/background_migration/recalculate_project_authorizations_with_min_max_user_id.rb38
-rw-r--r--lib/gitlab/background_migration/recalculate_vulnerability_finding_signatures_for_findings.rb (renamed from lib/gitlab/background_migration/migrate_security_scans.rb)4
-rw-r--r--lib/gitlab/background_migration/remove_duplicate_cs_findings.rb13
-rw-r--r--lib/gitlab/background_migration/remove_duplicated_cs_findings_without_vulnerability_id.rb13
-rw-r--r--lib/gitlab/background_migration/remove_inaccessible_epic_todos.rb13
-rw-r--r--lib/gitlab/background_migration/remove_occurrence_pipelines_and_duplicate_vulnerabilities_findings.rb59
-rw-r--r--lib/gitlab/background_migration/remove_undefined_vulnerability_confidence_level.rb13
-rw-r--r--lib/gitlab/background_migration/remove_vulnerability_finding_links.rb17
-rw-r--r--lib/gitlab/background_migration/replace_blocked_by_links.rb34
-rw-r--r--lib/gitlab/background_migration/reset_merge_status.rb17
-rw-r--r--lib/gitlab/background_migration/reset_shared_runners_for_transferred_projects.rb30
-rw-r--r--lib/gitlab/background_migration/set_merge_request_diff_files_count.rb33
-rw-r--r--lib/gitlab/background_migration/update_existing_subgroup_to_match_visibility_level_of_parent.rb21
-rw-r--r--lib/gitlab/background_migration/update_existing_users_that_require_two_factor_auth.rb110
-rw-r--r--lib/gitlab/background_migration/update_location_fingerprint_for_container_scanning_findings.rb13
-rw-r--r--lib/gitlab/background_migration/update_timelogs_null_spent_at.rb34
-rw-r--r--lib/gitlab/background_migration/update_vulnerabilities_from_dismissal_feedback.rb13
-rw-r--r--lib/gitlab/background_migration/user_mentions/create_resource_user_mention.rb36
-rw-r--r--lib/gitlab/background_migration/user_mentions/lib/banzai/reference_parser.rb25
-rw-r--r--lib/gitlab/background_migration/user_mentions/lib/banzai/reference_parser/isolated_mentioned_group_parser.rb25
-rw-r--r--lib/gitlab/background_migration/user_mentions/lib/banzai/reference_parser/isolated_mentioned_project_parser.rb25
-rw-r--r--lib/gitlab/background_migration/user_mentions/lib/banzai/reference_parser/isolated_mentioned_user_parser.rb25
-rw-r--r--lib/gitlab/background_migration/user_mentions/lib/gitlab/isolated_reference_extractor.rb30
-rw-r--r--lib/gitlab/background_migration/user_mentions/lib/gitlab/isolated_visibility_level.rb60
-rw-r--r--lib/gitlab/background_migration/user_mentions/models/commit.rb36
-rw-r--r--lib/gitlab/background_migration/user_mentions/models/commit_user_mention.rb19
-rw-r--r--lib/gitlab/background_migration/user_mentions/models/concerns/isolated_feature_gate.rb20
-rw-r--r--lib/gitlab/background_migration/user_mentions/models/concerns/isolated_mentionable.rb104
-rw-r--r--lib/gitlab/background_migration/user_mentions/models/concerns/mentionable_migration_methods.rb26
-rw-r--r--lib/gitlab/background_migration/user_mentions/models/concerns/namespace/recursive_traversal.rb74
-rw-r--r--lib/gitlab/background_migration/user_mentions/models/design_management/design.rb36
-rw-r--r--lib/gitlab/background_migration/user_mentions/models/design_user_mention.rb19
-rw-r--r--lib/gitlab/background_migration/user_mentions/models/epic.rb52
-rw-r--r--lib/gitlab/background_migration/user_mentions/models/epic_user_mention.rb19
-rw-r--r--lib/gitlab/background_migration/user_mentions/models/group.rb97
-rw-r--r--lib/gitlab/background_migration/user_mentions/models/merge_request.rb47
-rw-r--r--lib/gitlab/background_migration/user_mentions/models/merge_request_user_mention.rb19
-rw-r--r--lib/gitlab/background_migration/user_mentions/models/namespace.rb41
-rw-r--r--lib/gitlab/background_migration/user_mentions/models/note.rb72
-rw-r--r--lib/gitlab/background_migration/user_mentions/models/project.rb48
-rw-r--r--lib/gitlab/background_migration/user_mentions/models/user.rb37
-rw-r--r--lib/gitlab/bitbucket_server_import/importer.rb7
-rw-r--r--lib/gitlab/chat/output.rb4
-rw-r--r--lib/gitlab/ci/badge/coverage/report.rb2
-rw-r--r--lib/gitlab/ci/badge/metadata.rb3
-rw-r--r--lib/gitlab/ci/build/context/base.rb6
-rw-r--r--lib/gitlab/ci/build/policy/variables.rb2
-rw-r--r--lib/gitlab/ci/build/rules/rule/clause/changes.rb2
-rw-r--r--lib/gitlab/ci/build/rules/rule/clause/if.rb2
-rw-r--r--lib/gitlab/ci/config.rb56
-rw-r--r--lib/gitlab/ci/config/entry/processable.rb2
-rw-r--r--lib/gitlab/ci/config/entry/tags.rb2
-rw-r--r--lib/gitlab/ci/config/external/context.rb20
-rw-r--r--lib/gitlab/ci/config/external/mapper.rb56
-rw-r--r--lib/gitlab/ci/config/external/processor.rb11
-rw-r--r--lib/gitlab/ci/features.rb30
-rw-r--r--lib/gitlab/ci/parsers/security/validators/schema_validator.rb2
-rw-r--r--lib/gitlab/ci/parsers/terraform/tfplan.rb2
-rw-r--r--lib/gitlab/ci/pipeline/chain/base.rb2
-rw-r--r--lib/gitlab/ci/pipeline/chain/build.rb4
-rw-r--r--lib/gitlab/ci/pipeline/chain/command.rb13
-rw-r--r--lib/gitlab/ci/pipeline/chain/config/process.rb25
-rw-r--r--lib/gitlab/ci/pipeline/chain/create.rb43
-rw-r--r--lib/gitlab/ci/pipeline/chain/create_deployments.rb44
-rw-r--r--lib/gitlab/ci/pipeline/chain/ensure_environments.rb36
-rw-r--r--lib/gitlab/ci/pipeline/chain/ensure_resource_groups.rb34
-rw-r--r--lib/gitlab/ci/pipeline/chain/seed.rb29
-rw-r--r--lib/gitlab/ci/pipeline/chain/sequence.rb14
-rw-r--r--lib/gitlab/ci/pipeline/chain/validate/external.rb10
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexeme/variable.rb6
-rw-r--r--lib/gitlab/ci/pipeline/expression/statement.rb4
-rw-r--r--lib/gitlab/ci/pipeline/logger.rb103
-rw-r--r--lib/gitlab/ci/pipeline/seed/build.rb33
-rw-r--r--lib/gitlab/ci/reports/security/report.rb2
-rw-r--r--lib/gitlab/ci/status/bridge/common.rb6
-rw-r--r--lib/gitlab/ci/status/build/failed.rb3
-rw-r--r--lib/gitlab/ci/tags/bulk_insert.rb90
-rw-r--r--lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml3
-rw-r--r--lib/gitlab/ci/templates/Jobs/SAST-IaC.latest.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Pages/HTML.gitlab-ci.yml1
-rw-r--r--lib/gitlab/ci/templates/Python.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Scala.gitlab-ci.yml6
-rw-r--r--lib/gitlab/ci/templates/Security/API-Fuzzing.latest.gitlab-ci.yml4
-rw-r--r--lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml3
-rw-r--r--lib/gitlab/ci/templates/Security/DAST-API.latest.gitlab-ci.yml52
-rw-r--r--lib/gitlab/ci/templates/Verify/Accessibility.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/dotNET-Core.gitlab-ci.yml6
-rw-r--r--lib/gitlab/ci/variables/builder.rb2
-rw-r--r--lib/gitlab/ci/yaml_processor.rb2
-rw-r--r--lib/gitlab/ci/yaml_processor/result.rb1
-rw-r--r--lib/gitlab/config/entry/undefined.rb4
-rw-r--r--lib/gitlab/content_security_policy/config_loader.rb11
-rw-r--r--lib/gitlab/content_security_policy/directives.rb2
-rw-r--r--lib/gitlab/contributions_calendar.rb72
-rw-r--r--lib/gitlab/daemon.rb15
-rw-r--r--lib/gitlab/database.rb13
-rw-r--r--lib/gitlab/database/async_indexes/index_creator.rb4
-rw-r--r--lib/gitlab/database/background_migration/batched_job.rb1
-rw-r--r--lib/gitlab/database/background_migration/batched_migration.rb12
-rw-r--r--lib/gitlab/database/count/reltuples_count_strategy.rb39
-rw-r--r--lib/gitlab/database/count/tablesample_count_strategy.rb2
-rw-r--r--lib/gitlab/database/gitlab_loose_foreign_keys.yml69
-rw-r--r--lib/gitlab/database/gitlab_schemas.yml13
-rw-r--r--lib/gitlab/database/load_balancing.rb4
-rw-r--r--lib/gitlab/database/load_balancing/configuration.rb4
-rw-r--r--lib/gitlab/database/load_balancing/sidekiq_server_middleware.rb31
-rw-r--r--lib/gitlab/database/load_balancing/sticking.rb9
-rw-r--r--lib/gitlab/database/loose_foreign_keys.rb38
-rw-r--r--lib/gitlab/database/migration_helpers.rb1
-rw-r--r--lib/gitlab/database/migrations/background_migration_helpers.rb148
-rw-r--r--lib/gitlab/database/migrations/batched_background_migration_helpers.rb118
-rw-r--r--lib/gitlab/database/migrations/instrumentation.rb4
-rw-r--r--lib/gitlab/database/migrations/observers/migration_observer.rb4
-rw-r--r--lib/gitlab/database/migrations/runner.rb2
-rw-r--r--lib/gitlab/database/partitioning/detached_partition_dropper.rb2
-rw-r--r--lib/gitlab/database/partitioning/monthly_strategy.rb4
-rw-r--r--lib/gitlab/database/partitioning/partition_manager.rb8
-rw-r--r--lib/gitlab/database/partitioning/single_numeric_list_partition.rb76
-rw-r--r--lib/gitlab/database/partitioning/sliding_list_strategy.rb75
-rw-r--r--lib/gitlab/database/pg_class.rb2
-rw-r--r--lib/gitlab/database/postgres_hll/buckets.rb3
-rw-r--r--lib/gitlab/database/query_analyzer.rb12
-rw-r--r--lib/gitlab/database/query_analyzers/base.rb7
-rw-r--r--lib/gitlab/database/query_analyzers/prevent_cross_database_modification.rb67
-rw-r--r--lib/gitlab/database/reindexing.rb20
-rw-r--r--lib/gitlab/database/reindexing/coordinator.rb4
-rw-r--r--lib/gitlab/database/schema_cache_with_renamed_table.rb2
-rw-r--r--lib/gitlab/database/schema_helpers.rb1
-rw-r--r--lib/gitlab/database/shared_model.rb4
-rw-r--r--lib/gitlab/database/type/json_pg_safe.rb24
-rw-r--r--lib/gitlab/diff/custom_diff.rb58
-rw-r--r--lib/gitlab/diff/file.rb33
-rw-r--r--lib/gitlab/diff/file_collection/merge_request_diff_base.rb9
-rw-r--r--lib/gitlab/diff/highlight.rb3
-rw-r--r--lib/gitlab/diff/inline_diff.rb4
-rw-r--r--lib/gitlab/email/handler/create_issue_handler.rb2
-rw-r--r--lib/gitlab/email/handler/reply_processing.rb4
-rw-r--r--lib/gitlab/email/handler/service_desk_handler.rb54
-rw-r--r--lib/gitlab/email/hook/smime_signature_interceptor.rb1
-rw-r--r--lib/gitlab/email/message/in_product_marketing/admin_verify.rb2
-rw-r--r--lib/gitlab/email/message/in_product_marketing/base.rb33
-rw-r--r--lib/gitlab/email/message/in_product_marketing/create.rb2
-rw-r--r--lib/gitlab/email/message/in_product_marketing/helper.rb23
-rw-r--r--lib/gitlab/email/message/in_product_marketing/verify.rb2
-rw-r--r--lib/gitlab/email/receiver.rb14
-rw-r--r--lib/gitlab/email/service_desk_receiver.rb17
-rw-r--r--lib/gitlab/emoji.rb2
-rw-r--r--lib/gitlab/empty_search_results.rb28
-rw-r--r--lib/gitlab/error_tracking.rb9
-rw-r--r--lib/gitlab/etag_caching/router/graphql.rb5
-rw-r--r--lib/gitlab/etag_caching/store.rb12
-rw-r--r--lib/gitlab/exception_log_formatter.rb43
-rw-r--r--lib/gitlab/experimentation.rb3
-rw-r--r--lib/gitlab/experimentation/controller_concern.rb2
-rw-r--r--lib/gitlab/gfm/reference_rewriter.rb2
-rw-r--r--lib/gitlab/git/blob.rb3
-rw-r--r--lib/gitlab/git/diff_collection.rb6
-rw-r--r--lib/gitlab/git/diff_stats_collection.rb4
-rw-r--r--lib/gitlab/git/repository.rb2
-rw-r--r--lib/gitlab/gitaly_client/commit_service.rb13
-rw-r--r--lib/gitlab/github_import/client.rb26
-rw-r--r--lib/gitlab/github_import/importer/diff_note_importer.rb4
-rw-r--r--lib/gitlab/github_import/importer/note_importer.rb1
-rw-r--r--lib/gitlab/github_import/parallel_importer.rb2
-rw-r--r--lib/gitlab/github_import/representation/diff_note.rb21
-rw-r--r--lib/gitlab/github_import/representation/note.rb8
-rw-r--r--lib/gitlab/gon_helper.rb1
-rw-r--r--lib/gitlab/gpg.rb2
-rw-r--r--lib/gitlab/gpg/commit.rb6
-rw-r--r--lib/gitlab/gpg/invalid_gpg_signature_updater.rb4
-rw-r--r--lib/gitlab/graphql/tracers/logger_tracer.rb13
-rw-r--r--lib/gitlab/graphql/tracers/timer_tracer.rb10
-rw-r--r--lib/gitlab/hook_data/merge_request_builder.rb4
-rw-r--r--lib/gitlab/i18n.rb18
-rw-r--r--lib/gitlab/import/import_failure_service.rb27
-rw-r--r--lib/gitlab/import/set_async_jid.rb2
-rw-r--r--lib/gitlab/import_export/group/relation_tree_restorer.rb26
-rw-r--r--lib/gitlab/import_export/json/streaming_serializer.rb13
-rw-r--r--lib/gitlab/import_export/project/import_export.yml77
-rw-r--r--lib/gitlab/import_export/project/relation_factory.rb4
-rw-r--r--lib/gitlab/lograge/custom_options.rb4
-rw-r--r--lib/gitlab/merge_requests/commit_message_generator.rb (renamed from lib/gitlab/merge_requests/merge_commit_message.rb)48
-rw-r--r--lib/gitlab/merge_requests/mergeability/redis_interface.rb4
-rw-r--r--lib/gitlab/metrics/exporter/base_exporter.rb12
-rw-r--r--lib/gitlab/metrics/exporter/sidekiq_exporter.rb27
-rw-r--r--lib/gitlab/metrics/exporter/web_exporter.rb6
-rw-r--r--lib/gitlab/metrics/samplers/database_sampler.rb32
-rw-r--r--lib/gitlab/metrics/subscribers/active_record.rb32
-rw-r--r--lib/gitlab/pagination/keyset/iterator.rb2
-rw-r--r--lib/gitlab/pagination/keyset/order.rb2
-rw-r--r--lib/gitlab/pagination/offset_pagination.rb10
-rw-r--r--lib/gitlab/patch/legacy_database_config.rb44
-rw-r--r--lib/gitlab/patch/sidekiq_client.rb22
-rw-r--r--lib/gitlab/patch/sidekiq_poller.rb (renamed from lib/gitlab/patch/sidekiq_cron_poller.rb)2
-rw-r--r--lib/gitlab/process_management.rb85
-rw-r--r--lib/gitlab/quick_actions/issue_actions.rb34
-rw-r--r--lib/gitlab/rack_attack/request.rb2
-rw-r--r--lib/gitlab/rate_limit_helpers.rb35
-rw-r--r--lib/gitlab/redis/multi_store.rb52
-rw-r--r--lib/gitlab/redis/sessions.rb42
-rw-r--r--lib/gitlab/redis/sessions_store_helper.rb27
-rw-r--r--lib/gitlab/redis/shared_state.rb4
-rw-r--r--lib/gitlab/redis/wrapper.rb12
-rw-r--r--lib/gitlab/regex.rb21
-rw-r--r--lib/gitlab/relative_positioning/item_context.rb22
-rw-r--r--lib/gitlab/repository_archive_rate_limiter.rb13
-rw-r--r--lib/gitlab/saas.rb4
-rw-r--r--lib/gitlab/search/abuse_detection.rb81
-rw-r--r--lib/gitlab/search/abuse_validators/no_abusive_coercion_from_string_validator.rb15
-rw-r--r--lib/gitlab/search/abuse_validators/no_abusive_term_length_validator.rb36
-rw-r--r--lib/gitlab/search/params.rb89
-rw-r--r--lib/gitlab/search/query.rb2
-rw-r--r--lib/gitlab/security/scan_configuration.rb47
-rw-r--r--lib/gitlab/sherlock/line_profiler.rb2
-rw-r--r--lib/gitlab/sidekiq_config.rb11
-rw-r--r--lib/gitlab/sidekiq_enq.rb111
-rw-r--r--lib/gitlab/sidekiq_status.rb22
-rw-r--r--lib/gitlab/sidekiq_status/client_middleware.rb3
-rw-r--r--lib/gitlab/spamcheck/client.rb18
-rw-r--r--lib/gitlab/string_range_marker.rb2
-rw-r--r--lib/gitlab/subscription_portal.rb14
-rw-r--r--lib/gitlab/task_helpers.rb3
-rw-r--r--lib/gitlab/tracking.rb12
-rw-r--r--lib/gitlab/tracking/destinations/snowplow.rb14
-rw-r--r--lib/gitlab/tracking/destinations/snowplow_micro.rb11
-rw-r--r--lib/gitlab/usage/metrics/instrumentations/snowplow_configured_to_gitlab_collector_metric.rb17
-rw-r--r--lib/gitlab/usage/metrics/instrumentations/snowplow_enabled_metric.rb15
-rw-r--r--lib/gitlab/usage/metrics/names_suggestions/generator.rb4
-rw-r--r--lib/gitlab/usage_data.rb26
-rw-r--r--lib/gitlab/usage_data_counters/known_events/analytics.yml12
-rw-r--r--lib/gitlab/usage_data_counters/known_events/ci_templates.yml8
-rw-r--r--lib/gitlab/usage_data_counters/known_events/common.yml13
-rw-r--r--lib/gitlab/usage_data_counters/known_events/importer_events.yml3
-rw-r--r--lib/gitlab/usage_data_counters/known_events/quickactions.yml8
-rw-r--r--lib/gitlab/usage_data_non_sql_metrics.rb13
-rw-r--r--lib/gitlab/usage_data_queries.rb13
-rw-r--r--lib/gitlab/utils.rb16
-rw-r--r--lib/gitlab/utils/nokogiri.rb2
-rw-r--r--lib/gitlab/utils/strong_memoize.rb2
-rw-r--r--lib/gitlab/utils/usage_data.rb20
-rw-r--r--lib/gitlab/x509/commit.rb6
-rw-r--r--lib/google_api/cloud_platform/client.rb47
-rw-r--r--lib/safe_zip/extract.rb2
-rw-r--r--lib/sidebars/concerns/container_with_html_options.rb2
-rw-r--r--lib/sidebars/concerns/link_with_html_options.rb14
-rw-r--r--lib/sidebars/concerns/positionable_list.rb8
-rw-r--r--lib/sidebars/groups/menus/customer_relations_menu.rb4
-rw-r--r--lib/sidebars/groups/menus/packages_registries_menu.rb18
-rw-r--r--lib/sidebars/menu_item.rb2
-rw-r--r--lib/sidebars/panel.rb4
-rw-r--r--lib/sidebars/projects/menus/analytics_menu.rb2
-rw-r--r--lib/sidebars/projects/menus/infrastructure_menu.rb10
-rw-r--r--lib/sidebars/projects/menus/shimo_menu.rb41
-rw-r--r--lib/sidebars/projects/panel.rb15
-rw-r--r--lib/tasks/gettext.rake13
-rw-r--r--lib/tasks/gitlab/background_migrations.rake19
-rw-r--r--lib/tasks/gitlab/cleanup.rake29
-rw-r--r--lib/tasks/gitlab/db.rake49
-rw-r--r--lib/tasks/gitlab/gitaly.rake9
-rw-r--r--lib/tasks/gitlab/seed/group_seed.rake2
-rw-r--r--lib/tasks/gitlab/sidekiq.rake5
-rw-r--r--lib/tasks/gitlab/update_templates.rake2
-rw-r--r--lib/tasks/gitlab/x509/update.rake4
-rw-r--r--lib/version_check.rb44
407 files changed, 4148 insertions, 4020 deletions
diff --git a/lib/after_commit_queue.rb b/lib/after_commit_queue.rb
deleted file mode 100644
index cbeaea97951..00000000000
--- a/lib/after_commit_queue.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-# frozen_string_literal: true
-
-module AfterCommitQueue
- extend ActiveSupport::Concern
-
- included do
- after_commit :_run_after_commit_queue
- after_rollback :_clear_after_commit_queue
- end
-
- def run_after_commit(&block)
- _after_commit_queue << block if block
-
- true
- end
-
- def run_after_commit_or_now(&block)
- if ApplicationRecord.inside_transaction?
- if ActiveRecord::Base.connection.current_transaction.records&.include?(self)
- run_after_commit(&block)
- else
- # If the current transaction does not include this record, we can run
- # the block now, even if it queues a Sidekiq job.
- Sidekiq::Worker.skipping_transaction_check do
- instance_eval(&block)
- end
- end
- else
- instance_eval(&block)
- end
-
- true
- end
-
- protected
-
- def _run_after_commit_queue
- while action = _after_commit_queue.pop
- self.instance_eval(&action)
- end
- end
-
- def _after_commit_queue
- @after_commit_queue ||= []
- end
-
- def _clear_after_commit_queue
- _after_commit_queue.clear
- end
-end
diff --git a/lib/api/admin/plan_limits.rb b/lib/api/admin/plan_limits.rb
index ab6a4e4a04a..d595b5b2e09 100644
--- a/lib/api/admin/plan_limits.rb
+++ b/lib/api/admin/plan_limits.rb
@@ -37,6 +37,7 @@ module API
optional :conan_max_file_size, type: Integer, desc: 'Maximum Conan package file size in bytes'
optional :generic_packages_max_file_size, type: Integer, desc: 'Maximum generic package file size in bytes'
+ optional :helm_max_file_size, type: Integer, desc: 'Maximum Helm chart file size in bytes'
optional :maven_max_file_size, type: Integer, desc: 'Maximum Maven package file size in bytes'
optional :npm_max_file_size, type: Integer, desc: 'Maximum NPM package file size in bytes'
optional :nuget_max_file_size, type: Integer, desc: 'Maximum NuGet package file size in bytes'
diff --git a/lib/api/ci/helpers/runner.rb b/lib/api/ci/helpers/runner.rb
index dabb6c7ab3a..72c388160b4 100644
--- a/lib/api/ci/helpers/runner.rb
+++ b/lib/api/ci/helpers/runner.rb
@@ -29,7 +29,7 @@ module API
def get_runner_details_from_request
return get_runner_ip unless params['info'].present?
- attributes_for_keys(%w(name version revision platform architecture), params['info'])
+ attributes_for_keys(%w(name version revision platform architecture executor), params['info'])
.merge(get_runner_config_from_request)
.merge(get_runner_ip)
end
@@ -52,7 +52,7 @@ module API
# HTTP status codes to terminate the job on GitLab Runner:
# - 403
- def authenticate_job!(require_running: true)
+ def authenticate_job!(require_running: true, heartbeat_runner: false)
job = current_job
# 404 is not returned here because we want to terminate the job if it's
@@ -70,7 +70,17 @@ module API
job_forbidden!(job, 'Job is not running') unless job.running?
end
- job.runner&.heartbeat(get_runner_ip)
+ # Only some requests (like updating the job or patching the trace) should trigger
+ # runner heartbeat. Operations like artifacts uploading are executed in context of
+ # the running job and in the job environment, which in many cases will cause the IP
+ # to be updated to not the expected value. And operations like artifacts downloads can
+ # be done even after the job is finished and from totally different runners - while
+ # they would then update the connection status of not the runner that they should.
+ # Runner requests done in context of job authentication should explicitly define when
+ # the heartbeat should be triggered.
+ if heartbeat_runner
+ job.runner&.heartbeat(get_runner_ip)
+ end
job
end
diff --git a/lib/api/ci/pipelines.rb b/lib/api/ci/pipelines.rb
index 03b59e7e6ad..4e5d6c264bf 100644
--- a/lib/api/ci/pipelines.rb
+++ b/lib/api/ci/pipelines.rb
@@ -166,7 +166,7 @@ module API
params do
requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
end
- get ':id/pipelines/:pipeline_id/variables', feature_category: :pipeline_authoring do
+ get ':id/pipelines/:pipeline_id/variables', feature_category: :pipeline_authoring, urgency: :low do
authorize! :read_pipeline_variable, pipeline
present pipeline.variables, with: Entities::Ci::Variable
diff --git a/lib/api/ci/runner.rb b/lib/api/ci/runner.rb
index aabcf34952c..4317789f7aa 100644
--- a/lib/api/ci/runner.rb
+++ b/lib/api/ci/runner.rb
@@ -176,7 +176,7 @@ module API
optional :exit_code, type: Integer, desc: %q(Job's exit code)
end
put '/:id', feature_category: :continuous_integration do
- job = authenticate_job!
+ job = authenticate_job!(heartbeat_runner: true)
Gitlab::Metrics.add_event(:update_build)
@@ -203,7 +203,7 @@ module API
optional :token, type: String, desc: %q(Job's authentication token)
end
patch '/:id/trace', feature_category: :continuous_integration do
- job = authenticate_job!
+ job = authenticate_job!(heartbeat_runner: true)
error!('400 Missing header Content-Range', 400) unless request.headers.key?('Content-Range')
content_range = request.headers['Content-Range']
diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb
index 1785362656e..c89abf72e2d 100644
--- a/lib/api/commit_statuses.rb
+++ b/lib/api/commit_statuses.rb
@@ -78,13 +78,16 @@ module API
name = params[:name] || params[:context] || 'default'
- pipeline ||= user_project.ci_pipelines.create!(
+ pipeline ||= user_project.ci_pipelines.build(
source: :external,
sha: commit.sha,
ref: ref,
user: current_user,
protected: user_project.protected_for?(ref))
+ pipeline.ensure_project_iid!
+ pipeline.save!
+
authorize! :update_pipeline, pipeline
status = GenericCommitStatus.running_or_pending.find_or_initialize_by(
diff --git a/lib/api/composer_packages.rb b/lib/api/composer_packages.rb
index 94cad7e6c65..0e6e04d2645 100644
--- a/lib/api/composer_packages.rb
+++ b/lib/api/composer_packages.rb
@@ -70,7 +70,7 @@ module API
end
desc 'Composer packages endpoint at group level'
- route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
+ route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true
get ':id/-/packages/composer/packages' do
presenter.root
end
@@ -79,7 +79,7 @@ module API
params do
requires :sha, type: String, desc: 'Shasum of current json'
end
- route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
+ route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true
get ':id/-/packages/composer/p/:sha' do
presenter.provider
end
@@ -88,7 +88,7 @@ module API
params do
requires :package_name, type: String, file_path: true, desc: 'The Composer package name'
end
- route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
+ route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true
get ':id/-/packages/composer/p2/*package_name', requirements: COMPOSER_ENDPOINT_REQUIREMENTS, file_path: true do
not_found! if packages.empty?
@@ -99,7 +99,7 @@ module API
params do
requires :package_name, type: String, file_path: true, desc: 'The Composer package name'
end
- route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
+ route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true
get ':id/-/packages/composer/*package_name', requirements: COMPOSER_ENDPOINT_REQUIREMENTS, file_path: true do
not_found! if packages.empty?
not_found! if params[:sha].blank?
@@ -119,7 +119,7 @@ module API
desc 'Composer packages endpoint for registering packages'
namespace ':id/packages/composer' do
- route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
+ route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true
params do
optional :branch, type: String, desc: 'The name of the branch'
diff --git a/lib/api/concerns/packages/conan_endpoints.rb b/lib/api/concerns/packages/conan_endpoints.rb
index 3194cdebde8..edf20b6aebe 100644
--- a/lib/api/concerns/packages/conan_endpoints.rb
+++ b/lib/api/concerns/packages/conan_endpoints.rb
@@ -27,6 +27,7 @@ module API
PACKAGE_COMPONENT_REGEX = Gitlab::Regex.conan_recipe_component_regex
CONAN_REVISION_REGEX = Gitlab::Regex.conan_revision_regex
+ CONAN_REVISION_USER_CHANNEL_REGEX = Gitlab::Regex.conan_recipe_user_channel_regex
CONAN_FILES = (Gitlab::Regex::Packages::CONAN_RECIPE_FILES + Gitlab::Regex::Packages::CONAN_PACKAGE_FILES).freeze
@@ -105,10 +106,14 @@ module API
params do
requires :package_name, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package name'
requires :package_version, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package version'
- requires :package_username, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package username'
- requires :package_channel, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package channel'
+ requires :package_username, type: String, regexp: CONAN_REVISION_USER_CHANNEL_REGEX, desc: 'Package username'
+ requires :package_channel, type: String, regexp: CONAN_REVISION_USER_CHANNEL_REGEX, desc: 'Package channel'
end
namespace 'conans/:package_name/:package_version/:package_username/:package_channel', requirements: PACKAGE_REQUIREMENTS do
+ after_validation do
+ check_username_channel
+ end
+
# Get the snapshot
#
# the snapshot is a hash of { filename: md5 hash }
@@ -264,8 +269,8 @@ module API
params do
requires :package_name, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package name'
requires :package_version, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package version'
- requires :package_username, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package username'
- requires :package_channel, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package channel'
+ requires :package_username, type: String, regexp: CONAN_REVISION_USER_CHANNEL_REGEX, desc: 'Package username'
+ requires :package_channel, type: String, regexp: CONAN_REVISION_USER_CHANNEL_REGEX, desc: 'Package channel'
requires :recipe_revision, type: String, regexp: CONAN_REVISION_REGEX, desc: 'Conan Recipe Revision'
end
namespace 'files/:package_name/:package_version/:package_username/:package_channel/:recipe_revision', requirements: PACKAGE_REQUIREMENTS do
@@ -273,6 +278,10 @@ module API
authenticate_non_get!
end
+ after_validation do
+ check_username_channel
+ end
+
params do
requires :file_name, type: String, desc: 'Package file name', values: CONAN_FILES
end
diff --git a/lib/api/concerns/packages/npm_endpoints.rb b/lib/api/concerns/packages/npm_endpoints.rb
index 7a657be5bf3..d6e006df976 100644
--- a/lib/api/concerns/packages/npm_endpoints.rb
+++ b/lib/api/concerns/packages/npm_endpoints.rb
@@ -121,9 +121,7 @@ module API
not_found!('Packages') if packages.empty?
- include_metadata = Feature.enabled?(:packages_npm_abbreviated_metadata, project, default_enabled: :yaml)
-
- present ::Packages::Npm::PackagePresenter.new(package_name, packages, include_metadata: include_metadata),
+ present ::Packages::Npm::PackagePresenter.new(package_name, packages),
with: ::API::Entities::NpmPackage
end
end
diff --git a/lib/api/dependency_proxy.rb b/lib/api/dependency_proxy.rb
index 185b8d5a15d..9d0b1bf4423 100644
--- a/lib/api/dependency_proxy.rb
+++ b/lib/api/dependency_proxy.rb
@@ -6,15 +6,6 @@ module API
feature_category :dependency_proxy
- helpers do
- def obtain_new_purge_cache_lease
- Gitlab::ExclusiveLease
- .new("dependency_proxy:delete_group_blobs:#{user_group.id}",
- timeout: 1.hour)
- .try_obtain
- end
- end
-
after_validation do
authorize! :admin_group, user_group
end
@@ -29,9 +20,6 @@ module API
delete ':id/dependency_proxy/cache' do
not_found! unless user_group.dependency_proxy_feature_available?
- message = 'This request has already been made. It may take some time to purge the cache. You can run this at most once an hour for a given group'
- render_api_error!(message, 409) unless obtain_new_purge_cache_lease
-
# rubocop:disable CodeReuse/Worker
PurgeDependencyProxyCacheWorker.perform_async(current_user.id, user_group.id)
# rubocop:enable CodeReuse/Worker
diff --git a/lib/api/discussions.rb b/lib/api/discussions.rb
index cf4b2348458..0709a8c2036 100644
--- a/lib/api/discussions.rb
+++ b/lib/api/discussions.rb
@@ -8,6 +8,13 @@ module API
before { authenticate! }
+ urgency :low, [
+ '/projects/:id/merge_requests/:noteable_id/discussions',
+ '/projects/:id/merge_requests/:noteable_id/discussions/:discussion_id',
+ '/projects/:id/merge_requests/:noteable_id/discussions/:discussion_id/notes',
+ '/projects/:id/merge_requests/:noteable_id/discussions/:discussion_id/notes/:note_id'
+ ]
+
Helpers::DiscussionsHelpers.feature_category_per_noteable_type.each do |noteable_type, feature_category|
parent_type = noteable_type.parent_class.to_s.underscore
noteables_str = noteable_type.to_s.underscore.pluralize
diff --git a/lib/api/entities/changelog.rb b/lib/api/entities/changelog.rb
new file mode 100644
index 00000000000..f8ca5826418
--- /dev/null
+++ b/lib/api/entities/changelog.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class Changelog < Grape::Entity
+ expose :to_s, as: :notes
+ end
+ end
+end
diff --git a/lib/api/entities/ci/job_basic.rb b/lib/api/entities/ci/job_basic.rb
index c31340f1ff0..0badde4089e 100644
--- a/lib/api/entities/ci/job_basic.rb
+++ b/lib/api/entities/ci/job_basic.rb
@@ -13,6 +13,7 @@ module API
expose :user, with: ::API::Entities::User
expose :commit, with: ::API::Entities::Commit
expose :pipeline, with: ::API::Entities::Ci::PipelineBasic
+ expose :failure_reason, if: -> (job) { job.failed? }
expose :web_url do |job, _options|
Gitlab::Routing.url_helpers.project_job_url(job.project, job)
diff --git a/lib/api/entities/ci/pipeline.rb b/lib/api/entities/ci/pipeline.rb
index 11336ae070d..a8033a21044 100644
--- a/lib/api/entities/ci/pipeline.rb
+++ b/lib/api/entities/ci/pipeline.rb
@@ -10,7 +10,9 @@ module API
expose :created_at, :updated_at, :started_at, :finished_at, :committed_at
expose :duration
expose :queued_duration
- expose :coverage
+ expose :coverage do |pipeline|
+ pipeline.present.coverage
+ end
expose :detailed_status, using: DetailedStatusEntity do |pipeline, options|
pipeline.detailed_status(options[:current_user])
end
diff --git a/lib/api/entities/ci/pipeline_basic.rb b/lib/api/entities/ci/pipeline_basic.rb
index 4d56176bdb3..a2a5a98920a 100644
--- a/lib/api/entities/ci/pipeline_basic.rb
+++ b/lib/api/entities/ci/pipeline_basic.rb
@@ -4,7 +4,7 @@ module API
module Entities
module Ci
class PipelineBasic < Grape::Entity
- expose :id, :project_id, :sha, :ref, :status, :source
+ expose :id, :iid, :project_id, :sha, :ref, :status, :source
expose :created_at, :updated_at
expose :web_url do |pipeline, _options|
diff --git a/lib/api/entities/ci/runner.rb b/lib/api/entities/ci/runner.rb
index 60193fe1df4..c17ff513479 100644
--- a/lib/api/entities/ci/runner.rb
+++ b/lib/api/entities/ci/runner.rb
@@ -14,7 +14,7 @@ module API
expose :online?, as: :online
# DEPRECATED
# TODO Remove in %15.0 in favor of `status` for REST calls, see https://gitlab.com/gitlab-org/gitlab/-/issues/344648
- expose :status, as: :deprecated_rest_status
+ expose :deprecated_rest_status, as: :status
end
end
end
diff --git a/lib/api/entities/commit_signature.rb b/lib/api/entities/commit_signature.rb
index 505ce462edf..0d8e977a9f5 100644
--- a/lib/api/entities/commit_signature.rb
+++ b/lib/api/entities/commit_signature.rb
@@ -6,9 +6,9 @@ module API
expose :signature_type
expose :signature, merge: true do |commit, options|
- if commit.signature.is_a?(GpgSignature) || commit.raw_commit_from_rugged?
+ if commit.signature.is_a?(::CommitSignatures::GpgSignature) || commit.raw_commit_from_rugged?
::API::Entities::GpgCommitSignature.represent commit_signature(commit), options
- elsif commit.signature.is_a?(X509CommitSignature)
+ elsif commit.signature.is_a?(::CommitSignatures::X509CommitSignature)
::API::Entities::X509Signature.represent commit.signature, options
end
end
diff --git a/lib/api/entities/issue_basic.rb b/lib/api/entities/issue_basic.rb
index ab248523028..6125dc05a6e 100644
--- a/lib/api/entities/issue_basic.rb
+++ b/lib/api/entities/issue_basic.rb
@@ -23,7 +23,7 @@ module API
expose :issue_type,
as: :type,
format_with: :upcase,
- documentation: { type: "String", desc: "One of #{::WorkItem::Type.base_types.keys.map(&:upcase)}" }
+ documentation: { type: "String", desc: "One of #{::WorkItem::Type.allowed_types_for_issues.map(&:upcase)}" }
expose :assignee, using: ::API::Entities::UserBasic do |issue|
issue.assignees.first
diff --git a/lib/api/entities/personal_access_token.rb b/lib/api/entities/personal_access_token.rb
index 3846929c903..55764daef9d 100644
--- a/lib/api/entities/personal_access_token.rb
+++ b/lib/api/entities/personal_access_token.rb
@@ -3,7 +3,7 @@
module API
module Entities
class PersonalAccessToken < Grape::Entity
- expose :id, :name, :revoked, :created_at, :scopes, :user_id
+ expose :id, :name, :revoked, :created_at, :scopes, :user_id, :last_used_at
expose :active?, as: :active
expose :expires_at do |personal_access_token|
personal_access_token.expires_at ? personal_access_token.expires_at.strftime("%Y-%m-%d") : nil
diff --git a/lib/api/entities/plan_limit.rb b/lib/api/entities/plan_limit.rb
index 04ec44b5167..9f4d1635998 100644
--- a/lib/api/entities/plan_limit.rb
+++ b/lib/api/entities/plan_limit.rb
@@ -5,6 +5,7 @@ module API
class PlanLimit < Grape::Entity
expose :conan_max_file_size
expose :generic_packages_max_file_size
+ expose :helm_max_file_size
expose :maven_max_file_size
expose :npm_max_file_size
expose :nuget_max_file_size
diff --git a/lib/api/entities/project.rb b/lib/api/entities/project.rb
index 662ca59852e..1b9299ed17e 100644
--- a/lib/api/entities/project.rb
+++ b/lib/api/entities/project.rb
@@ -117,6 +117,7 @@ module API
expose :squash_option
expose :suggestion_commit_message
expose :merge_commit_template
+ expose :squash_commit_template
expose :statistics, using: 'API::Entities::ProjectStatistics', if: -> (project, options) {
options[:statistics] && Ability.allowed?(options[:current_user], :read_statistics, project)
}
diff --git a/lib/api/entities/project_import_failed_relation.rb b/lib/api/entities/project_import_failed_relation.rb
index b8f842c1646..26cfae7260c 100644
--- a/lib/api/entities/project_import_failed_relation.rb
+++ b/lib/api/entities/project_import_failed_relation.rb
@@ -10,6 +10,7 @@ module API
end
expose :relation_key, as: :relation_name
+ expose :relation_index, as: :line_number
end
end
end
diff --git a/lib/api/entities/project_import_status.rb b/lib/api/entities/project_import_status.rb
index e79c1cdf1a2..5daae4a70f2 100644
--- a/lib/api/entities/project_import_status.rb
+++ b/lib/api/entities/project_import_status.rb
@@ -4,6 +4,7 @@ module API
module Entities
class ProjectImportStatus < ProjectIdentity
expose :import_status
+ expose :import_type
expose :correlation_id do |project, _options|
project.import_state&.correlation_id
end
@@ -15,6 +16,12 @@ module API
expose :import_error do |project, _options|
project.import_state&.last_error
end
+
+ expose :stats do |project, _options|
+ if project.github_import?
+ ::Gitlab::GithubImport::ObjectCounter.summary(project)
+ end
+ end
end
end
end
diff --git a/lib/api/group_export.rb b/lib/api/group_export.rb
index 25cc4e53bd2..f0c0182a02f 100644
--- a/lib/api/group_export.rb
+++ b/lib/api/group_export.rb
@@ -18,7 +18,7 @@ module API
detail 'This feature was introduced in GitLab 12.5.'
end
get ':id/export/download' do
- check_rate_limit! :group_download_export, [current_user, user_group]
+ check_rate_limit! :group_download_export, scope: [current_user, user_group]
if user_group.export_file_exists?
if user_group.export_archive_exists?
@@ -35,7 +35,7 @@ module API
detail 'This feature was introduced in GitLab 12.5.'
end
post ':id/export' do
- check_rate_limit! :group_export, [current_user]
+ check_rate_limit! :group_export, scope: current_user
export_service = ::Groups::ImportExport::ExportService.new(group: user_group, user: current_user)
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 680e3a6e994..d3d1f03585b 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -109,7 +109,7 @@ module API
end
def present_groups_with_pagination_strategies(params, groups)
- return present_groups(params, groups) if current_user.present? || Feature.disabled?(:keyset_pagination_for_groups_api)
+ return present_groups(params, groups) if current_user.present?
options = {
with: Entities::Group,
@@ -382,6 +382,28 @@ module API
end
end
+ desc 'Transfer a group to a new parent group or promote a subgroup to a root group'
+ params do
+ optional :group_id, type: Integer,
+ desc: 'The ID of the target group to which the group needs to be transferred to.'\
+ 'If not provided, the source group will be promoted to a root group.'
+ end
+ post ':id/transfer' do
+ group = find_group!(params[:id])
+ authorize! :admin_group, group
+
+ new_parent_group = find_group!(params[:group_id]) if params[:group_id].present?
+
+ service = ::Groups::TransferService.new(group, current_user)
+
+ if service.execute(new_parent_group)
+ group.preload_shared_group_links
+ present group, with: Entities::GroupDetail, current_user: current_user
+ else
+ render_api_error!(service.error, 400)
+ end
+ end
+
desc 'Share a group with a group' do
success Entities::GroupDetail
end
diff --git a/lib/api/helpers/label_helpers.rb b/lib/api/helpers/label_helpers.rb
index da0ee8f207e..02613cbf9b9 100644
--- a/lib/api/helpers/label_helpers.rb
+++ b/lib/api/helpers/label_helpers.rb
@@ -105,7 +105,11 @@ module API
end
def promote_label(parent)
- authorize! :admin_label, parent
+ unless parent.group
+ render_api_error!('Failed to promote project label to group label', 400)
+ end
+
+ authorize! :admin_label, parent.group
label = find_label(parent, params[:name], include_ancestor_groups: false)
diff --git a/lib/api/helpers/members_helpers.rb b/lib/api/helpers/members_helpers.rb
index 1e89f9f97a2..c2710be6c03 100644
--- a/lib/api/helpers/members_helpers.rb
+++ b/lib/api/helpers/members_helpers.rb
@@ -8,6 +8,9 @@ module API
params :optional_filter_params_ee do
end
+ params :optional_state_filter_ee do
+ end
+
def find_source(source_type, id)
public_send("find_#{source_type}!", id) # rubocop:disable GitlabSecurity/PublicSend
end
diff --git a/lib/api/helpers/packages/conan/api_helpers.rb b/lib/api/helpers/packages/conan/api_helpers.rb
index 4b6dac39348..031c29e7472 100644
--- a/lib/api/helpers/packages/conan/api_helpers.rb
+++ b/lib/api/helpers/packages/conan/api_helpers.rb
@@ -7,6 +7,21 @@ module API
module ApiHelpers
include Gitlab::Utils::StrongMemoize
+ def check_username_channel
+ username = declared(params)[:package_username]
+ channel = declared(params)[:package_channel]
+
+ if username == ::Packages::Conan::Metadatum::NONE_VALUE && package_scope == :instance
+ # at the instance level, username must not be empty (naming convention)
+ # don't try to process the empty username and eagerly return not found.
+ not_found!
+ end
+
+ ::Packages::Conan::Metadatum.validate_username_and_channel(username, channel) do |none_field|
+ bad_request!("#{none_field} can't be solely blank")
+ end
+ end
+
def present_download_urls(entity)
authorize!(:read_package, project)
diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb
index 42d1c40dd11..d7de8bd8b8b 100644
--- a/lib/api/helpers/projects_helpers.rb
+++ b/lib/api/helpers/projects_helpers.rb
@@ -62,6 +62,7 @@ module API
optional :merge_method, type: String, values: %w(ff rebase_merge merge), desc: 'The merge method used when merging merge requests'
optional :suggestion_commit_message, type: String, desc: 'The commit message used to apply merge request suggestions'
optional :merge_commit_template, type: String, desc: 'Template used to create merge commit message'
+ optional :squash_commit_template, type: String, desc: 'Template used to create squash commit message'
optional :initialize_with_readme, type: Boolean, desc: "Initialize a project with a README.md"
optional :ci_default_git_depth, type: Integer, desc: 'Default number of revisions for shallow cloning'
optional :auto_devops_enabled, type: Boolean, desc: 'Flag indication if Auto DevOps is enabled'
@@ -162,6 +163,7 @@ module API
:avatar,
:suggestion_commit_message,
:merge_commit_template,
+ :squash_commit_template,
:repository_storage,
:compliance_framework_setting,
:packages_enabled,
@@ -181,9 +183,10 @@ module API
def filter_attributes_using_license!(attrs)
end
- def validate_git_import_url!(import_url, import_enabled: true)
+ def validate_git_import_url!(import_url)
return if import_url.blank?
- return unless import_enabled
+
+ yield if block_given?
result = Import::ValidateRemoteGitEndpointService.new(url: import_url).execute # network call
diff --git a/lib/api/helpers/rate_limiter.rb b/lib/api/helpers/rate_limiter.rb
index 3a16aef6a74..7d87c74097d 100644
--- a/lib/api/helpers/rate_limiter.rb
+++ b/lib/api/helpers/rate_limiter.rb
@@ -2,26 +2,27 @@
module API
module Helpers
+ # == RateLimiter
+ #
+ # Helper that checks if the rate limit for a given endpoint is throttled by calling the
+ # Gitlab::ApplicationRateLimiter class. If the action is throttled for the current user, the request
+ # will be logged and an error message will be rendered with a Too Many Requests response status.
+ # See app/controllers/concerns/check_rate_limit.rb for Rails controllers version
module RateLimiter
- def check_rate_limit!(key, scope, users_allowlist = nil)
- if rate_limiter.throttled?(key, scope: scope, users_allowlist: users_allowlist)
- log_request(key)
- render_exceeded_limit_error!
- end
- end
+ def check_rate_limit!(key, scope:, **options)
+ return unless rate_limiter.throttled?(key, scope: scope, **options)
- private
+ rate_limiter.log_request(request, "#{key}_request_limit".to_sym, current_user)
- def rate_limiter
- ::Gitlab::ApplicationRateLimiter
- end
+ return yield if block_given?
- def render_exceeded_limit_error!
render_api_error!({ error: _('This endpoint has been requested too many times. Try again later.') }, 429)
end
- def log_request(key)
- rate_limiter.log_request(request, "#{key}_request_limit".to_sym, current_user)
+ private
+
+ def rate_limiter
+ ::Gitlab::ApplicationRateLimiter
end
end
end
diff --git a/lib/api/invitations.rb b/lib/api/invitations.rb
index f7f5af07378..d78576b5d5b 100644
--- a/lib/api/invitations.rb
+++ b/lib/api/invitations.rb
@@ -24,7 +24,6 @@ module API
requires :access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'A valid access level (defaults: `30`, developer access level)'
optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY'
optional :invite_source, type: String, desc: 'Source that triggered the member creation process', default: 'invitations-api'
- optional :areas_of_focus, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Areas the inviter wants the member to focus upon'
optional :tasks_to_be_done, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Tasks the inviter wants the member to do'
optional :tasks_project_id, type: Integer, desc: 'The project ID in which to create the task issues'
end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 9958526fa7f..4d67cbd1272 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -82,7 +82,7 @@ module API
desc: 'Return issues sorted in `asc` or `desc` order.'
optional :due_date, type: String, values: %w[0 overdue week month next_month_and_previous_two_weeks] << '',
desc: 'Return issues that have no due date (`0`), or whose due date is this week, this month, between two weeks ago and next month, or which are overdue. Accepts: `overdue`, `week`, `month`, `next_month_and_previous_two_weeks`, `0`'
- optional :issue_type, type: String, values: WorkItem::Type.base_types.keys, desc: "The type of the issue. Accepts: #{WorkItem::Type.base_types.keys.join(', ')}"
+ optional :issue_type, type: String, values: WorkItem::Type.allowed_types_for_issues, desc: "The type of the issue. Accepts: #{WorkItem::Type.allowed_types_for_issues.join(', ')}"
use :issues_stats_params
use :pagination
@@ -99,7 +99,7 @@ module API
optional :due_date, type: String, desc: 'Date string in the format YEAR-MONTH-DAY'
optional :confidential, type: Boolean, desc: 'Boolean parameter if the issue should be confidential'
optional :discussion_locked, type: Boolean, desc: " Boolean parameter indicating if the issue's discussion is locked"
- optional :issue_type, type: String, values: WorkItem::Type.base_types.keys, desc: "The type of the issue. Accepts: #{WorkItem::Type.base_types.keys.join(', ')}"
+ optional :issue_type, type: String, values: WorkItem::Type.allowed_types_for_issues, desc: "The type of the issue. Accepts: #{WorkItem::Type.allowed_types_for_issues.join(', ')}"
use :optional_issue_params_ee
end
@@ -262,7 +262,7 @@ module API
post ':id/issues' do
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/21140')
- check_rate_limit! :issues_create, [current_user] if Feature.disabled?("rate_limited_service_issues_create", user_project, default_enabled: :yaml)
+ check_rate_limit!(:issues_create, scope: current_user) if Feature.disabled?("rate_limited_service_issues_create", user_project, default_enabled: :yaml)
authorize! :create_issue, user_project
@@ -472,7 +472,7 @@ module API
end
get ':id/issues/:issue_iid/participants' do
issue = find_project_issue(params[:issue_iid])
- participants = ::Kaminari.paginate_array(issue.participants)
+ participants = ::Kaminari.paginate_array(issue.visible_participants(current_user))
present paginate(participants), with: Entities::UserBasic, current_user: current_user, project: user_project
end
diff --git a/lib/api/lint.rb b/lib/api/lint.rb
index 3655cb56564..bfd457a3092 100644
--- a/lib/api/lint.rb
+++ b/lib/api/lint.rb
@@ -43,7 +43,7 @@ module API
optional :dry_run, type: Boolean, default: false, desc: 'Run pipeline creation simulation, or only do static check.'
optional :include_jobs, type: Boolean, desc: 'Whether or not to include CI jobs in the response'
end
- get ':id/ci/lint' do
+ get ':id/ci/lint', urgency: :low do
authorize! :download_code, user_project
content = user_project.repository.gitlab_ci_yml_for(user_project.commit.id, user_project.ci_config_path_or_default)
@@ -64,7 +64,7 @@ module API
optional :dry_run, type: Boolean, default: false, desc: 'Run pipeline creation simulation, or only do static check.'
optional :include_jobs, type: Boolean, desc: 'Whether or not to include CI jobs in the response'
end
- post ':id/ci/lint' do
+ post ':id/ci/lint', urgency: :low do
authorize! :create_pipeline, user_project
result = Gitlab::Ci::Lint
diff --git a/lib/api/members.rb b/lib/api/members.rb
index f488c8c26fc..4798edc4ddf 100644
--- a/lib/api/members.rb
+++ b/lib/api/members.rb
@@ -41,6 +41,7 @@ module API
optional :query, type: String, desc: 'A query string to search for members'
optional :user_ids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'Array of user ids to look up for membership'
optional :show_seat_info, type: Boolean, desc: 'Show seat information for members'
+ use :optional_state_filter_ee
use :pagination
end
@@ -94,7 +95,6 @@ module API
requires :user_id, types: [Integer, String], desc: 'The user ID of the new member or multiple IDs separated by commas.'
optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY'
optional :invite_source, type: String, desc: 'Source that triggered the member creation process', default: 'members-api'
- optional :areas_of_focus, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Areas the inviter wants the member to focus upon'
optional :tasks_to_be_done, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Tasks the inviter wants the member to do'
optional :tasks_project_id, type: Integer, desc: 'The project ID in which to create the task issues'
end
diff --git a/lib/api/merge_request_diffs.rb b/lib/api/merge_request_diffs.rb
index 8fa7138af42..87623568a04 100644
--- a/lib/api/merge_request_diffs.rb
+++ b/lib/api/merge_request_diffs.rb
@@ -38,7 +38,7 @@ module API
requires :version_id, type: Integer, desc: 'The ID of a merge request diff version'
end
- get ":id/merge_requests/:merge_request_iid/versions/:version_id" do
+ get ":id/merge_requests/:merge_request_iid/versions/:version_id", urgency: :low do
merge_request = find_merge_request_with_access(params[:merge_request_iid])
present_cached merge_request.merge_request_diffs.find(params[:version_id]), with: Entities::MergeRequestDiffFull, cache_context: nil
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 96d1a69c03a..3f39af7f909 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -134,7 +134,7 @@ module API
use :merge_requests_params
use :optional_scope_param
end
- get feature_category: :code_review do
+ get feature_category: :code_review, urgency: :low do
authenticate! unless params[:scope] == 'all'
validate_anonymous_search_access! if params[:search].present?
merge_requests = find_merge_requests
@@ -155,7 +155,7 @@ module API
optional :non_archived, type: Boolean, desc: 'Return merge requests from non archived projects',
default: true
end
- get ":id/merge_requests", feature_category: :code_review do
+ get ":id/merge_requests", feature_category: :code_review, urgency: :low do
validate_anonymous_search_access! if declared_params[:search].present?
merge_requests = find_merge_requests(group_id: user_group.id, include_subgroups: true)
@@ -195,7 +195,7 @@ module API
use :merge_requests_params
optional :iids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'The IID array of merge requests'
end
- get ":id/merge_requests", feature_category: :code_review do
+ get ":id/merge_requests", feature_category: :code_review, urgency: :low do
authorize! :read_merge_request, user_project
validate_anonymous_search_access! if declared_params[:search].present?
@@ -222,7 +222,7 @@ module API
desc: 'The target project of the merge request defaults to the :id of the project'
use :optional_params
end
- post ":id/merge_requests", feature_category: :code_review do
+ post ":id/merge_requests", feature_category: :code_review, urgency: :low do
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20770')
authorize! :create_merge_request_from, user_project
@@ -244,7 +244,7 @@ module API
params do
requires :merge_request_iid, type: Integer, desc: 'The IID of a merge request'
end
- delete ":id/merge_requests/:merge_request_iid", feature_category: :code_review do
+ delete ":id/merge_requests/:merge_request_iid", feature_category: :code_review, urgency: :low do
merge_request = find_project_merge_request(params[:merge_request_iid])
authorize!(:destroy_merge_request, merge_request)
@@ -263,7 +263,7 @@ module API
desc 'Get a single merge request' do
success Entities::MergeRequest
end
- get ':id/merge_requests/:merge_request_iid', feature_category: :code_review do
+ get ':id/merge_requests/:merge_request_iid', feature_category: :code_review, urgency: :low do
merge_request = find_merge_request_with_access(params[:merge_request_iid])
present merge_request,
@@ -279,10 +279,10 @@ module API
desc 'Get the participants of a merge request' do
success Entities::UserBasic
end
- get ':id/merge_requests/:merge_request_iid/participants', feature_category: :code_review do
+ get ':id/merge_requests/:merge_request_iid/participants', feature_category: :code_review, urgency: :low do
merge_request = find_merge_request_with_access(params[:merge_request_iid])
- participants = ::Kaminari.paginate_array(merge_request.participants)
+ participants = ::Kaminari.paginate_array(merge_request.visible_participants(current_user))
present paginate(participants), with: Entities::UserBasic
end
@@ -290,7 +290,7 @@ module API
desc 'Get the commits of a merge request' do
success Entities::Commit
end
- get ':id/merge_requests/:merge_request_iid/commits', feature_category: :code_review do
+ get ':id/merge_requests/:merge_request_iid/commits', feature_category: :code_review, urgency: :low do
merge_request = find_merge_request_with_access(params[:merge_request_iid])
commits =
@@ -303,7 +303,7 @@ module API
desc 'Get the context commits of a merge request' do
success Entities::Commit
end
- get ':id/merge_requests/:merge_request_iid/context_commits', feature_category: :code_review do
+ get ':id/merge_requests/:merge_request_iid/context_commits', feature_category: :code_review, urgency: :high do
merge_request = find_merge_request_with_access(params[:merge_request_iid])
project = merge_request.project
@@ -371,7 +371,7 @@ module API
desc 'Show the merge request changes' do
success Entities::MergeRequestChanges
end
- get ':id/merge_requests/:merge_request_iid/changes', feature_category: :code_review do
+ get ':id/merge_requests/:merge_request_iid/changes', feature_category: :code_review, urgency: :low do
merge_request = find_merge_request_with_access(params[:merge_request_iid])
present merge_request,
@@ -422,7 +422,7 @@ module API
use :optional_params
at_least_one_of(*::API::MergeRequests.update_params_at_least_one_of)
end
- put ':id/merge_requests/:merge_request_iid', feature_category: :code_review do
+ put ':id/merge_requests/:merge_request_iid', feature_category: :code_review, urgency: :low do
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20772')
merge_request = find_merge_request_with_access(params.delete(:merge_request_iid), :update_merge_request)
@@ -454,7 +454,7 @@ module API
optional :sha, type: String, desc: 'When present, must have the HEAD SHA of the source branch'
optional :squash, type: Grape::API::Boolean, desc: 'When true, the commits will be squashed into a single commit on merge'
end
- put ':id/merge_requests/:merge_request_iid/merge', feature_category: :code_review do
+ put ':id/merge_requests/:merge_request_iid/merge', feature_category: :code_review, urgency: :low do
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/4796')
merge_request = find_project_merge_request(params[:merge_request_iid])
@@ -524,7 +524,7 @@ module API
params do
optional :skip_ci, type: Boolean, desc: 'Do not create CI pipeline'
end
- put ':id/merge_requests/:merge_request_iid/rebase', feature_category: :code_review do
+ put ':id/merge_requests/:merge_request_iid/rebase', feature_category: :code_review, urgency: :low do
merge_request = find_project_merge_request(params[:merge_request_iid])
authorize_push_to_merge_request!(merge_request)
@@ -543,7 +543,7 @@ module API
params do
use :pagination
end
- get ':id/merge_requests/:merge_request_iid/closes_issues', feature_category: :code_review do
+ get ':id/merge_requests/:merge_request_iid/closes_issues', feature_category: :code_review, urgency: :low do
merge_request = find_merge_request_with_access(params[:merge_request_iid])
issues = ::Kaminari.paginate_array(merge_request.visible_closing_issues_for(current_user))
issues = paginate(issues)
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index 656eaa2b2bb..93ef77d5a62 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -7,6 +7,11 @@ module API
before { authenticate! }
+ urgency :low, [
+ '/projects/:id/merge_requests/:noteable_id/notes',
+ '/projects/:id/merge_requests/:noteable_id/notes/:note_id'
+ ]
+
Helpers::NotesHelpers.feature_category_per_noteable_type.each do |noteable_type, feature_category|
parent_type = noteable_type.parent_class.to_s.underscore
noteables_str = noteable_type.to_s.underscore.pluralize
@@ -74,7 +79,7 @@ module API
post ":id/#{noteables_str}/:noteable_id/notes", feature_category: feature_category do
allowlist =
Gitlab::CurrentSettings.current_application_settings.notes_create_limit_allowlist
- check_rate_limit! :notes_create, [current_user], allowlist
+ check_rate_limit! :notes_create, scope: current_user, users_allowlist: allowlist
noteable = find_noteable(noteable_type, params[:noteable_id])
opts = {
diff --git a/lib/api/project_export.rb b/lib/api/project_export.rb
index e01c195dbc4..843f72c0e1d 100644
--- a/lib/api/project_export.rb
+++ b/lib/api/project_export.rb
@@ -25,7 +25,7 @@ module API
detail 'This feature was introduced in GitLab 10.6.'
end
get ':id/export/download' do
- check_rate_limit! :project_download_export, [current_user, user_project]
+ check_rate_limit! :project_download_export, scope: [current_user, user_project]
if user_project.export_file_exists?
if user_project.export_archive_exists?
@@ -49,7 +49,7 @@ module API
end
end
post ':id/export' do
- check_rate_limit! :project_export, [current_user]
+ check_rate_limit! :project_export, scope: current_user
user_project.remove_exports
diff --git a/lib/api/project_import.rb b/lib/api/project_import.rb
index e7c532e2483..7bdcaa5a26f 100644
--- a/lib/api/project_import.rb
+++ b/lib/api/project_import.rb
@@ -81,7 +81,7 @@ module API
post 'import' do
require_gitlab_workhorse!
- check_rate_limit! :project_import, [current_user, :project_import]
+ check_rate_limit! :project_import, scope: [current_user, :project_import]
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/21041')
@@ -107,7 +107,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- desc 'Get a project export status' do
+ desc 'Get a project import status' do
detail 'This feature was introduced in GitLab 10.6.'
success Entities::ProjectImportStatus
end
@@ -135,7 +135,7 @@ module API
post 'remote-import' do
not_found! unless ::Feature.enabled?(:import_project_from_remote_file)
- check_rate_limit! :project_import, [current_user, :project_import]
+ check_rate_limit! :project_import, scope: [current_user, :project_import]
response = ::Import::GitlabProjects::CreateProjectFromRemoteFileService.new(
current_user,
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 9f0077d23d8..67f0b7af7a9 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -270,7 +270,7 @@ module API
attrs = translate_params_for_compatibility(attrs)
filter_attributes_using_license!(attrs)
- validate_git_import_url!(params[:import_url], import_enabled: check_import_by_url_is_enabled)
+ validate_git_import_url!(params[:import_url]) { check_import_by_url_is_enabled }
project = ::Projects::CreateService.new(current_user, attrs).execute
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index 2dd0e40afba..fc976c23726 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -10,6 +10,32 @@ module API
helpers ::API::Helpers::HeadersHelpers
+ helpers do
+ params :release_params do
+ requires :version,
+ type: String,
+ regexp: Gitlab::Regex.unbounded_semver_regex,
+ desc: 'The version of the release, using the semantic versioning format'
+
+ optional :from,
+ type: String,
+ desc: 'The first commit in the range of commits to use for the changelog'
+
+ optional :to,
+ type: String,
+ desc: 'The last commit in the range of commits to use for the changelog'
+
+ optional :date,
+ type: DateTime,
+ desc: 'The date and time of the release'
+
+ optional :trailer,
+ type: String,
+ desc: 'The Git trailer to use for determining if commits are to be included in the changelog',
+ default: ::Repositories::ChangelogService::DEFAULT_TRAILER
+ end
+ end
+
before { authorize! :download_code, user_project }
feature_category :source_code_management
@@ -19,7 +45,7 @@ module API
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
helpers do
- include ::Gitlab::RateLimitHelpers
+ include Gitlab::RepositoryArchiveRateLimiter
def handle_project_member_errors(errors)
if errors[:project_access].any?
@@ -124,8 +150,8 @@ module API
optional :path, type: String, desc: 'Subfolder of the repository to be downloaded'
end
get ':id/repository/archive', requirements: { format: Gitlab::PathRegex.archive_formats_regex } do
- if archive_rate_limit_reached?(current_user, user_project)
- render_api_error!({ error: ::Gitlab::RateLimitHelpers::ARCHIVE_RATE_LIMIT_REACHED_MESSAGE }, 429)
+ check_archive_rate_limit!(current_user, user_project) do
+ render_api_error!({ error: _('This archive has been requested too many times. Try again later.') }, 429)
end
not_acceptable! if Gitlab::HotlinkingDetector.intercept_hotlinking?(request)
@@ -208,36 +234,33 @@ module API
end
end
- desc 'Generates a changelog section for a release' do
- detail 'This feature was introduced in GitLab 13.9'
+ desc 'Generates a changelog section for a release and returns it' do
+ detail 'This feature was introduced in GitLab 14.6'
end
params do
- requires :version,
- type: String,
- regexp: Gitlab::Regex.unbounded_semver_regex,
- desc: 'The version of the release, using the semantic versioning format'
-
- optional :from,
- type: String,
- desc: 'The first commit in the range of commits to use for the changelog'
+ use :release_params
+ end
+ get ':id/repository/changelog' do
+ service = ::Repositories::ChangelogService.new(
+ user_project,
+ current_user,
+ **declared_params(include_missing: false)
+ )
+ changelog = service.execute(commit_to_changelog: false)
- optional :to,
- type: String,
- desc: 'The last commit in the range of commits to use for the changelog'
+ present changelog, with: Entities::Changelog
+ end
- optional :date,
- type: DateTime,
- desc: 'The date and time of the release'
+ desc 'Generates a changelog section for a release and commits it in a changelog file' do
+ detail 'This feature was introduced in GitLab 13.9'
+ end
+ params do
+ use :release_params
optional :branch,
type: String,
desc: 'The branch to commit the changelog changes to'
- optional :trailer,
- type: String,
- desc: 'The Git trailer to use for determining if commits are to be included in the changelog',
- default: ::Repositories::ChangelogService::DEFAULT_TRAILER
-
optional :file,
type: String,
desc: 'The file to commit the changelog changes to',
@@ -261,7 +284,7 @@ module API
**declared_params(include_missing: false)
)
- service.execute
+ service.execute(commit_to_changelog: true)
status(200)
rescue Gitlab::Changelog::Error => ex
render_api_error!("Failed to generate the changelog: #{ex.message}", 422)
@@ -269,3 +292,5 @@ module API
end
end
end
+
+API::Repositories.prepend_mod
diff --git a/lib/api/resource_label_events.rb b/lib/api/resource_label_events.rb
index 33589f6c393..cd56809f45a 100644
--- a/lib/api/resource_label_events.rb
+++ b/lib/api/resource_label_events.rb
@@ -24,7 +24,7 @@ module API
use :pagination
end
- get ":id/#{eventables_str}/:eventable_id/resource_label_events", feature_category: feature_category do
+ get ":id/#{eventables_str}/:eventable_id/resource_label_events", feature_category: feature_category, urgency: :low do
eventable = find_noteable(eventable_type, params[:eventable_id])
events = eventable.resource_label_events.inc_relations
diff --git a/lib/api/resource_milestone_events.rb b/lib/api/resource_milestone_events.rb
index c0483ca59c2..04d71faa56a 100644
--- a/lib/api/resource_milestone_events.rb
+++ b/lib/api/resource_milestone_events.rb
@@ -26,7 +26,7 @@ module API
use :pagination
end
- get ":id/#{eventables_str}/:eventable_id/resource_milestone_events", feature_category: feature_category do
+ get ":id/#{eventables_str}/:eventable_id/resource_milestone_events", feature_category: feature_category, urgency: :low do
eventable = find_noteable(eventable_type, params[:eventable_id])
events = ResourceMilestoneEventFinder.new(current_user, eventable).execute
diff --git a/lib/api/resource_state_events.rb b/lib/api/resource_state_events.rb
index 9b6f6a954b4..4b92f320d6f 100644
--- a/lib/api/resource_state_events.rb
+++ b/lib/api/resource_state_events.rb
@@ -25,7 +25,7 @@ module API
use :pagination
end
- get ":id/#{eventable_name.pluralize}/:eventable_iid/resource_state_events", feature_category: feature_category do
+ get ":id/#{eventable_name.pluralize}/:eventable_iid/resource_state_events", feature_category: feature_category, urgency: :low do
eventable = find_noteable(eventable_class, params[:eventable_iid])
events = ResourceStateEventFinder.new(current_user, eventable).execute
diff --git a/lib/api/search.rb b/lib/api/search.rb
index 3c5801366a8..fbdbe3476db 100644
--- a/lib/api/search.rb
+++ b/lib/api/search.rb
@@ -8,6 +8,10 @@ module API
feature_category :global_search
+ rescue_from ActiveRecord::QueryCanceled do |e|
+ render_api_error!({ error: 'Request timed out' }, 408)
+ end
+
helpers do
SCOPE_ENTITY = {
merge_requests: Entities::MergeRequestBasic,
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index 12e1d21a00d..508ccdb4b33 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -154,7 +154,6 @@ module API
optional :spam_check_endpoint_enabled, type: Boolean, desc: 'Enable Spam Check via external API endpoint'
given spam_check_endpoint_enabled: ->(val) { val } do
requires :spam_check_endpoint_url, type: String, desc: 'The URL of the external Spam Check service endpoint'
- requires :spam_check_api_key, type: String, desc: 'The API key used by GitLab for accessing the Spam Check service endpoint'
end
optional :terminal_max_session_time, type: Integer, desc: 'Maximum time for web terminal websocket connection (in seconds). Set to 0 for unlimited time.'
optional :usage_ping_enabled, type: Boolean, desc: 'Every week GitLab will report license usage back to GitLab, Inc.'
diff --git a/lib/api/suggestions.rb b/lib/api/suggestions.rb
index 7921700e365..0697169b49a 100644
--- a/lib/api/suggestions.rb
+++ b/lib/api/suggestions.rb
@@ -14,7 +14,7 @@ module API
requires :id, type: String, desc: 'The suggestion ID'
optional :commit_message, type: String, desc: "A custom commit message to use instead of the default generated message or the project's default message"
end
- put ':id/apply' do
+ put ':id/apply', urgency: :low do
suggestion = Suggestion.find_by_id(params[:id])
if suggestion
@@ -31,7 +31,7 @@ module API
requires :ids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: "An array of suggestion ID's"
optional :commit_message, type: String, desc: "A custom commit message to use instead of the default generated message or the project's default message"
end
- put 'batch_apply' do
+ put 'batch_apply', urgency: :low do
ids = params[:ids]
suggestions = Suggestion.id_in(ids)
diff --git a/lib/api/terraform/state.rb b/lib/api/terraform/state.rb
index f6dfbcafbb6..29e71611092 100644
--- a/lib/api/terraform/state.rb
+++ b/lib/api/terraform/state.rb
@@ -11,6 +11,13 @@ module API
default_format :json
+ rescue_from(
+ ::ActiveRecord::RecordNotUnique,
+ ::PG::UniqueViolation
+ ) do |e|
+ render_api_error!(e.message, 422)
+ end
+
before do
authenticate!
authorize! :read_terraform_state, user_project
diff --git a/lib/api/topics.rb b/lib/api/topics.rb
index bd28ebe58a9..b9c2bcc2da8 100644
--- a/lib/api/topics.rb
+++ b/lib/api/topics.rb
@@ -69,6 +69,8 @@ module API
topic = ::Projects::Topic.find(params[:id])
+ topic.remove_avatar! if params.key?(:avatar) && params[:avatar].nil?
+
if topic.update(declared_params(include_missing: false))
present topic, with: Entities::Projects::Topic
else
diff --git a/lib/api/v3/github.rb b/lib/api/v3/github.rb
index 677d0840208..d6c026963e1 100644
--- a/lib/api/v3/github.rb
+++ b/lib/api/v3/github.rb
@@ -101,8 +101,6 @@ module API
# of time after a Gitaly timeout, to mitigate frequent Gitaly timeouts
# for some Commit diffs.
def diff_files(commit)
- return commit.diffs.diff_files unless Feature.enabled?(:api_v3_commits_skip_diff_files, commit.project, default_enabled: :yaml)
-
cache_key = [
GITALY_TIMEOUT_CACHE_KEY,
commit.project.id,
diff --git a/lib/api/validations/types/workhorse_file.rb b/lib/api/validations/types/workhorse_file.rb
index e65e94fc8db..23f402596d5 100644
--- a/lib/api/validations/types/workhorse_file.rb
+++ b/lib/api/validations/types/workhorse_file.rb
@@ -5,6 +5,7 @@ module API
module Types
class WorkhorseFile
def self.parse(value)
+ return if value.blank?
raise "#{value.class} is not an UploadedFile type" unless parsed?(value)
value
diff --git a/lib/banzai/filter/base_sanitization_filter.rb b/lib/banzai/filter/base_sanitization_filter.rb
index c63453f94ca..7ea32c4b1e7 100644
--- a/lib/banzai/filter/base_sanitization_filter.rb
+++ b/lib/banzai/filter/base_sanitization_filter.rb
@@ -59,7 +59,11 @@ module Banzai
def remove_rel
lambda do |env|
if env[:node_name] == 'a'
- env[:node].remove_attribute('rel')
+ # we allow rel="license" to support the Rel-license microformat
+ # http://microformats.org/wiki/rel-license
+ unless env[:node].attribute('rel')&.value == 'license'
+ env[:node].remove_attribute('rel')
+ end
end
end
end
diff --git a/lib/banzai/filter/external_link_filter.rb b/lib/banzai/filter/external_link_filter.rb
index 67019454e44..dc65e2abb46 100644
--- a/lib/banzai/filter/external_link_filter.rb
+++ b/lib/banzai/filter/external_link_filter.rb
@@ -112,7 +112,9 @@ module Banzai
def add_nofollow!(uri, node)
if SCHEMES.include?(uri&.scheme)
+ license = true if node.attribute('rel')&.value == 'license'
node.set_attribute('rel', 'nofollow noreferrer noopener')
+ node.kwattr_append('rel', 'license') if license
node.set_attribute('target', '_blank')
end
end
diff --git a/lib/banzai/filter/footnote_filter.rb b/lib/banzai/filter/footnote_filter.rb
index 39c42ceaf9b..00a38f02141 100644
--- a/lib/banzai/filter/footnote_filter.rb
+++ b/lib/banzai/filter/footnote_filter.rb
@@ -21,9 +21,9 @@ module Banzai
FOOTNOTE_LI_REFERENCE_PATTERN = /\A#{FOOTNOTE_ID_PREFIX}.+\z/.freeze
FOOTNOTE_LINK_REFERENCE_PATTERN = /\A#{FOOTNOTE_LINK_ID_PREFIX}.+\z/.freeze
- CSS_SECTION = "ol > li a[href^=\"\##{FOOTNOTE_LINK_ID_PREFIX}\"]"
+ CSS_SECTION = "section[data-footnotes]"
XPATH_SECTION = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_SECTION).freeze
- CSS_FOOTNOTE = 'sup > a[id]'
+ CSS_FOOTNOTE = 'sup > a[data-footnote-ref]'
XPATH_FOOTNOTE = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_FOOTNOTE).freeze
# only needed when feature flag use_cmark_renderer is turned off
@@ -37,39 +37,47 @@ module Banzai
XPATH_SECTION_OLD = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_SECTION_OLD).freeze
def call
- xpath_section = Feature.enabled?(:use_cmark_renderer) ? XPATH_SECTION : XPATH_SECTION_OLD
- return doc unless first_footnote = doc.at_xpath(xpath_section)
+ if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
+ # Sanitization stripped off the section class - add it back in
+ return doc unless section_node = doc.at_xpath(XPATH_SECTION)
- # Sanitization stripped off the section wrapper - add it back in
- if Feature.enabled?(:use_cmark_renderer)
- first_footnote.parent.parent.parent.wrap('<section class="footnotes" data-footnotes>')
+ section_node.append_class('footnotes')
else
+ return doc unless first_footnote = doc.at_xpath(XPATH_SECTION_OLD)
+ return doc unless first_footnote.parent
+
first_footnote.parent.wrap('<section class="footnotes">')
end
rand_suffix = "-#{random_number}"
modified_footnotes = {}
- doc.xpath(XPATH_FOOTNOTE).each do |link_node|
- if Feature.enabled?(:use_cmark_renderer)
+ xpath_footnote = if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
+ XPATH_FOOTNOTE
+ else
+ Gitlab::Utils::Nokogiri.css_to_xpath('sup > a[id]')
+ end
+
+ doc.xpath(xpath_footnote).each do |link_node|
+ if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
ref_num = link_node[:id].delete_prefix(FOOTNOTE_LINK_ID_PREFIX)
ref_num.gsub!(/[[:punct:]]/, '\\\\\&')
else
ref_num = link_node[:id].delete_prefix(FOOTNOTE_LINK_ID_PREFIX_OLD)
end
- node_xpath = Gitlab::Utils::Nokogiri.css_to_xpath("li[id=#{fn_id(ref_num)}]")
+ css = Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml) ? "section[data-footnotes] li[id=#{fn_id(ref_num)}]" : "li[id=#{fn_id(ref_num)}]"
+ node_xpath = Gitlab::Utils::Nokogiri.css_to_xpath(css)
footnote_node = doc.at_xpath(node_xpath)
if footnote_node || modified_footnotes[ref_num]
- next if Feature.disabled?(:use_cmark_renderer) && !INTEGER_PATTERN.match?(ref_num)
+ next if Feature.disabled?(:use_cmark_renderer, default_enabled: :yaml) && !INTEGER_PATTERN.match?(ref_num)
link_node[:href] += rand_suffix
link_node[:id] += rand_suffix
# Sanitization stripped off class - add it back in
link_node.parent.append_class('footnote-ref')
- link_node['data-footnote-ref'] = nil if Feature.enabled?(:use_cmark_renderer)
unless modified_footnotes[ref_num]
footnote_node[:id] += rand_suffix
@@ -78,7 +86,6 @@ module Banzai
if backref_node
backref_node[:href] += rand_suffix
backref_node.append_class('footnote-backref')
- backref_node['data-footnote-backref'] = nil if Feature.enabled?(:use_cmark_renderer)
end
modified_footnotes[ref_num] = true
@@ -96,12 +103,12 @@ module Banzai
end
def fn_id(num)
- prefix = Feature.enabled?(:use_cmark_renderer) ? FOOTNOTE_ID_PREFIX : FOOTNOTE_ID_PREFIX_OLD
+ prefix = Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml) ? FOOTNOTE_ID_PREFIX : FOOTNOTE_ID_PREFIX_OLD
"#{prefix}#{num}"
end
def fnref_id(num)
- prefix = Feature.enabled?(:use_cmark_renderer) ? FOOTNOTE_LINK_ID_PREFIX : FOOTNOTE_LINK_ID_PREFIX_OLD
+ prefix = Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml) ? FOOTNOTE_LINK_ID_PREFIX : FOOTNOTE_LINK_ID_PREFIX_OLD
"#{prefix}#{num}"
end
end
diff --git a/lib/banzai/filter/issuable_reference_expansion_filter.rb b/lib/banzai/filter/issuable_reference_expansion_filter.rb
new file mode 100644
index 00000000000..6822e36c9be
--- /dev/null
+++ b/lib/banzai/filter/issuable_reference_expansion_filter.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+module Banzai
+ module Filter
+ # HTML filter that appends extra information to issuable links.
+ # Runs as a post-process filter as issuable might change while
+ # Markdown is in the cache.
+ #
+ # This filter supports cross-project references.
+ class IssuableReferenceExpansionFilter < HTML::Pipeline::Filter
+ include Gitlab::Utils::StrongMemoize
+
+ VISIBLE_STATES = %w(closed merged).freeze
+
+ def call
+ return doc unless context[:issuable_reference_expansion_enabled]
+
+ context = RenderContext.new(project, current_user)
+ extractor = Banzai::IssuableExtractor.new(context)
+ issuables = extractor.extract([doc])
+
+ issuables.each do |node, issuable|
+ next if !can_read_cross_project? && cross_referenced?(issuable)
+ next unless should_expand?(node, issuable)
+
+ case node.attr('data-reference-format')
+ when '+'
+ expand_reference_with_title_and_state(node, issuable)
+ else
+ expand_reference_with_state(node, issuable)
+ end
+ end
+
+ doc
+ end
+
+ private
+
+ # Example: Issue Title (#123 - closed)
+ def expand_reference_with_title_and_state(node, issuable)
+ node.content = "#{issuable.title.truncate(50)} (#{node.content}"
+ node.content += " - #{issuable_state_text(issuable)}" if VISIBLE_STATES.include?(issuable.state)
+ node.content += ')'
+ end
+
+ # Example: #123 (closed)
+ def expand_reference_with_state(node, issuable)
+ node.content += " (#{issuable_state_text(issuable)})"
+ end
+
+ def issuable_state_text(issuable)
+ moved_issue?(issuable) ? s_("IssuableStatus|moved") : issuable.state
+ end
+
+ def moved_issue?(issuable)
+ issuable.instance_of?(Issue) && issuable.moved?
+ end
+
+ def should_expand?(node, issuable)
+ # We add this extra check to avoid unescaping HTML and generating reference link text for every reference
+ return unless node.attr('data-reference-format').present? || VISIBLE_STATES.include?(issuable.state)
+
+ CGI.unescapeHTML(node.inner_html) == issuable.reference_link_text(project || group)
+ end
+
+ def cross_referenced?(issuable)
+ return true if issuable.project != project
+ return true if issuable.respond_to?(:group) && issuable.group != group
+
+ false
+ end
+
+ def can_read_cross_project?
+ strong_memoize(:can_read_cross_project) do
+ Ability.allowed?(current_user, :read_cross_project)
+ end
+ end
+
+ def current_user
+ context[:current_user]
+ end
+
+ def project
+ context[:project]
+ end
+
+ def group
+ context[:group]
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/issuable_state_filter.rb b/lib/banzai/filter/issuable_state_filter.rb
deleted file mode 100644
index a88629ac105..00000000000
--- a/lib/banzai/filter/issuable_state_filter.rb
+++ /dev/null
@@ -1,66 +0,0 @@
-# frozen_string_literal: true
-
-module Banzai
- module Filter
- # HTML filter that appends state information to issuable links.
- # Runs as a post-process filter as issuable state might change while
- # Markdown is in the cache.
- #
- # This filter supports cross-project references.
- class IssuableStateFilter < HTML::Pipeline::Filter
- VISIBLE_STATES = %w(closed merged).freeze
-
- def call
- return doc unless context[:issuable_state_filter_enabled]
-
- context = RenderContext.new(project, current_user)
- extractor = Banzai::IssuableExtractor.new(context)
- issuables = extractor.extract([doc])
-
- issuables.each do |node, issuable|
- next if !can_read_cross_project? && cross_referenced?(issuable)
-
- if VISIBLE_STATES.include?(issuable.state) && issuable_reference?(node.inner_html, issuable)
- state = moved_issue?(issuable) ? s_("IssuableStatus|moved") : issuable.state
- node.content += " (#{state})"
- end
- end
-
- doc
- end
-
- private
-
- def moved_issue?(issuable)
- issuable.instance_of?(Issue) && issuable.moved?
- end
-
- def issuable_reference?(text, issuable)
- CGI.unescapeHTML(text) == issuable.reference_link_text(project || group)
- end
-
- def cross_referenced?(issuable)
- return true if issuable.project != project
- return true if issuable.respond_to?(:group) && issuable.group != group
-
- false
- end
-
- def can_read_cross_project?
- Ability.allowed?(current_user, :read_cross_project)
- end
-
- def current_user
- context[:current_user]
- end
-
- def project
- context[:project]
- end
-
- def group
- context[:group]
- end
- end
- end
-end
diff --git a/lib/banzai/filter/markdown_engines/common_mark.rb b/lib/banzai/filter/markdown_engines/common_mark.rb
index a25ebedf029..dc94e3c925a 100644
--- a/lib/banzai/filter/markdown_engines/common_mark.rb
+++ b/lib/banzai/filter/markdown_engines/common_mark.rb
@@ -42,11 +42,11 @@ module Banzai
def initialize(context)
@context = context
- @renderer = Banzai::Renderer::CommonMark::HTML.new(options: render_options) if Feature.disabled?(:use_cmark_renderer)
+ @renderer = Banzai::Renderer::CommonMark::HTML.new(options: render_options) if Feature.disabled?(:use_cmark_renderer, default_enabled: :yaml)
end
def render(text)
- if Feature.enabled?(:use_cmark_renderer)
+ if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
CommonMarker.render_html(text, render_options, extensions)
else
doc = CommonMarker.render_doc(text, PARSE_OPTIONS, extensions)
@@ -58,7 +58,7 @@ module Banzai
private
def extensions
- if Feature.enabled?(:use_cmark_renderer)
+ if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
EXTENSIONS
else
EXTENSIONS + [
@@ -72,7 +72,7 @@ module Banzai
end
def render_options_no_sourcepos
- Feature.enabled?(:use_cmark_renderer) ? RENDER_OPTIONS_C : RENDER_OPTIONS_RUBY
+ Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml) ? RENDER_OPTIONS_C : RENDER_OPTIONS_RUBY
end
def render_options_sourcepos
diff --git a/lib/banzai/filter/markdown_post_escape_filter.rb b/lib/banzai/filter/markdown_post_escape_filter.rb
index ccffe1bfbb1..b979b7573ae 100644
--- a/lib/banzai/filter/markdown_post_escape_filter.rb
+++ b/lib/banzai/filter/markdown_post_escape_filter.rb
@@ -42,7 +42,7 @@ module Banzai
private
def lang_tag
- if Feature.enabled?(:use_cmark_renderer)
+ if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
Gitlab::Utils::Nokogiri.css_to_xpath('pre')
else
Gitlab::Utils::Nokogiri.css_to_xpath('code')
diff --git a/lib/banzai/filter/math_filter.rb b/lib/banzai/filter/math_filter.rb
index 53dafe45fb3..6859d67c9d8 100644
--- a/lib/banzai/filter/math_filter.rb
+++ b/lib/banzai/filter/math_filter.rb
@@ -39,7 +39,7 @@ module Banzai
code[:class] = INLINE_CLASSES
code[STYLE_ATTRIBUTE] = 'inline'
- closing.content = closing.content[1..-1]
+ closing.content = closing.content[1..]
opening.content = opening.content[0..-2]
end
end
diff --git a/lib/banzai/filter/plantuml_filter.rb b/lib/banzai/filter/plantuml_filter.rb
index e67cdc7df12..3f160960d23 100644
--- a/lib/banzai/filter/plantuml_filter.rb
+++ b/lib/banzai/filter/plantuml_filter.rb
@@ -26,7 +26,7 @@ module Banzai
def lang_tag
@lang_tag ||=
- if Feature.enabled?(:use_cmark_renderer)
+ if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
Gitlab::Utils::Nokogiri.css_to_xpath('pre[lang="plantuml"] > code').freeze
else
Gitlab::Utils::Nokogiri.css_to_xpath('pre > code[lang="plantuml"]').freeze
diff --git a/lib/banzai/filter/references/abstract_reference_filter.rb b/lib/banzai/filter/references/abstract_reference_filter.rb
index cae0a8b424a..7a23326bafa 100644
--- a/lib/banzai/filter/references/abstract_reference_filter.rb
+++ b/lib/banzai/filter/references/abstract_reference_filter.rb
@@ -205,6 +205,8 @@ module Banzai
data_attributes = data_attributes_for(link_content || match, parent, object,
link_content: !!link_content,
link_reference: link_reference)
+ data_attributes[:reference_format] = matches[:format] if matches.names.include?("format")
+
data = data_attribute(data_attributes)
url =
diff --git a/lib/banzai/filter/references/reference_cache.rb b/lib/banzai/filter/references/reference_cache.rb
index 259958f1598..c8370d5f9c1 100644
--- a/lib/banzai/filter/references/reference_cache.rb
+++ b/lib/banzai/filter/references/reference_cache.rb
@@ -29,7 +29,7 @@ module Banzai
@references_per_parent[parent_type] ||= begin
refs = Hash.new { |hash, key| hash[key] = Set.new }
- prepare_doc_for_scan(filter.doc).to_enum(:scan, regex).each do
+ prepare_doc_for_scan.to_enum(:scan, regex).each do
parent_path = if parent_type == :project
full_project_path($~[:namespace], $~[:project])
else
@@ -184,14 +184,12 @@ module Banzai
Gitlab::SafeRequestStore["banzai_#{parent_type}_refs".to_sym] ||= {}
end
- def prepare_doc_for_scan(doc)
- html = if Feature.enabled?(:reference_cache_memoization, project, default_enabled: :yaml)
- result[:rendered_html] ||= doc.to_html
- else
- doc.to_html
- end
+ def prepare_doc_for_scan
+ filter.requires_unescaping? ? unescape_html_entities(html_content) : html_content
+ end
- filter.requires_unescaping? ? unescape_html_entities(html) : html
+ def html_content
+ result[:rendered_html] ||= filter.doc.to_html
end
def unescape_html_entities(text)
diff --git a/lib/banzai/filter/references/reference_filter.rb b/lib/banzai/filter/references/reference_filter.rb
index 6c2c993cc01..97ef71036a2 100644
--- a/lib/banzai/filter/references/reference_filter.rb
+++ b/lib/banzai/filter/references/reference_filter.rb
@@ -184,7 +184,9 @@ module Banzai
end
def unescape_link(href)
- CGI.unescape(href)
+ # We cannot use CGI.unescape here because it also converts `+` to spaces.
+ # We need to keep the `+` for expanded reference formats.
+ Addressable::URI.unescape(href)
end
def unescape_html_entities(text)
diff --git a/lib/banzai/filter/repository_link_filter.rb b/lib/banzai/filter/repository_link_filter.rb
index 04bbcabd93f..408e6dc685d 100644
--- a/lib/banzai/filter/repository_link_filter.rb
+++ b/lib/banzai/filter/repository_link_filter.rb
@@ -174,7 +174,7 @@ module Banzai
def build_relative_path(path, request_path)
return request_path if path.empty?
return path unless request_path
- return path[1..-1] if path.start_with?('/')
+ return path[1..] if path.start_with?('/')
parts = request_path.split('/')
diff --git a/lib/banzai/filter/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb
index 7afbc1a1c9c..d5f45ff7689 100644
--- a/lib/banzai/filter/sanitization_filter.rb
+++ b/lib/banzai/filter/sanitization_filter.rb
@@ -28,6 +28,13 @@ module Banzai
allowlist[:attributes]['li'] = %w[id]
allowlist[:transformers].push(self.class.remove_non_footnote_ids)
+ if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
+ # Allow section elements with data-footnotes attribute
+ allowlist[:elements].push('section')
+ allowlist[:attributes]['section'] = %w(data-footnotes)
+ allowlist[:attributes]['a'].push('data-footnote-ref', 'data-footnote-backref')
+ end
+
allowlist
end
@@ -54,7 +61,7 @@ module Banzai
return unless node.name == 'a' || node.name == 'li'
return unless node.has_attribute?('id')
- if Feature.enabled?(:use_cmark_renderer)
+ if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
return if node.name == 'a' && node['id'] =~ Banzai::Filter::FootnoteFilter::FOOTNOTE_LINK_REFERENCE_PATTERN
return if node.name == 'li' && node['id'] =~ Banzai::Filter::FootnoteFilter::FOOTNOTE_LI_REFERENCE_PATTERN
else
diff --git a/lib/banzai/filter/syntax_highlight_filter.rb b/lib/banzai/filter/syntax_highlight_filter.rb
index 66bd86c5bb4..9fcfcf4acc4 100644
--- a/lib/banzai/filter/syntax_highlight_filter.rb
+++ b/lib/banzai/filter/syntax_highlight_filter.rb
@@ -58,10 +58,10 @@ module Banzai
sourcepos_attr = sourcepos ? "data-sourcepos=\"#{sourcepos}\"" : ''
- highlighted = %(<pre #{sourcepos_attr} class="#{css_classes}"
+ highlighted = %(<div class="gl-relative markdown-code-block js-markdown-code"><pre #{sourcepos_attr} class="#{css_classes}"
lang="#{language}"
#{lang_params}
- v-pre="true"><code>#{code}</code></pre>)
+ v-pre="true"><code>#{code}</code></pre><copy-code></copy-code></div>)
# Extracted to a method to measure it
replace_parent_pre_element(node, highlighted)
@@ -70,7 +70,7 @@ module Banzai
private
def parse_lang_params(node)
- node = node.parent if Feature.enabled?(:use_cmark_renderer)
+ node = node.parent if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
# Commonmarker's FULL_INFO_STRING render option works with the space delimiter.
# But the current behavior of GitLab's markdown renderer is different - it grabs everything as the single
@@ -92,7 +92,7 @@ module Banzai
language, language_params = language.split(LANG_PARAMS_DELIMITER, 2)
- if Feature.enabled?(:use_cmark_renderer)
+ if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
language_params = [node.attr('data-meta'), language_params].compact.join(' ')
end
diff --git a/lib/banzai/pipeline/post_process_pipeline.rb b/lib/banzai/pipeline/post_process_pipeline.rb
index 889574cf6bf..da2262cdf83 100644
--- a/lib/banzai/pipeline/post_process_pipeline.rb
+++ b/lib/banzai/pipeline/post_process_pipeline.rb
@@ -19,7 +19,7 @@ module Banzai
# prevent unnecessary Gitaly calls from being made.
Filter::UploadLinkFilter,
Filter::RepositoryLinkFilter,
- Filter::IssuableStateFilter,
+ Filter::IssuableReferenceExpansionFilter,
Filter::SuggestionFilter
]
end
diff --git a/lib/bulk_imports/clients/http.rb b/lib/bulk_imports/clients/http.rb
index 90414a875c6..eb3d551d1d7 100644
--- a/lib/bulk_imports/clients/http.rb
+++ b/lib/bulk_imports/clients/http.rb
@@ -123,7 +123,7 @@ module BulkImports
def with_error_handling
response = yield
- raise ::BulkImports::NetworkError.new(response: response) unless response.success?
+ raise ::BulkImports::NetworkError.new("Unsuccessful response #{response.code} from #{response.request.path.path}", response: response) unless response.success?
response
rescue *Gitlab::HTTP::HTTP_ERRORS => e
diff --git a/lib/bulk_imports/common/pipelines/badges_pipeline.rb b/lib/bulk_imports/common/pipelines/badges_pipeline.rb
new file mode 100644
index 00000000000..33a24e61a3f
--- /dev/null
+++ b/lib/bulk_imports/common/pipelines/badges_pipeline.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module BulkImports
+ module Common
+ module Pipelines
+ class BadgesPipeline
+ include Pipeline
+
+ extractor BulkImports::Common::Extractors::RestExtractor,
+ query: BulkImports::Common::Rest::GetBadgesQuery
+
+ transformer Common::Transformers::ProhibitedAttributesTransformer
+
+ def transform(context, data)
+ return if data.blank?
+ # Project badges API returns badges of both group and project kind. To avoid creation of duplicates for the group we skip group badges when it's a project.
+ return if context.entity.project? && group_badge?(data)
+
+ {
+ name: data['name'],
+ link_url: data['link_url'],
+ image_url: data['image_url']
+ }
+ end
+
+ def load(context, data)
+ return if data.blank?
+
+ if context.entity.project?
+ context.portable.project_badges.create!(data)
+ else
+ context.portable.badges.create!(data)
+ end
+ end
+
+ private
+
+ def group_badge?(data)
+ data['kind'] == 'group'
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bulk_imports/common/pipelines/uploads_pipeline.rb b/lib/bulk_imports/common/pipelines/uploads_pipeline.rb
index 15e126e1646..49c16209661 100644
--- a/lib/bulk_imports/common/pipelines/uploads_pipeline.rb
+++ b/lib/bulk_imports/common/pipelines/uploads_pipeline.rb
@@ -8,6 +8,9 @@ module BulkImports
include Gitlab::ImportExport::CommandLineUtil
FILENAME = 'uploads.tar.gz'
+ AVATAR_PATTERN = %r{.*\/#{BulkImports::UploadsExportService::AVATAR_PATH}\/(?<identifier>.*)}.freeze
+
+ AvatarLoadingError = Class.new(StandardError)
def extract(context)
download_service(tmp_dir, context).execute
@@ -18,14 +21,18 @@ module BulkImports
end
def load(context, file_path)
- dynamic_path = FileUploader.extract_dynamic_path(file_path)
+ avatar_path = AVATAR_PATTERN.match(file_path)
+
+ return save_avatar(file_path) if avatar_path
+
+ dynamic_path = file_uploader.extract_dynamic_path(file_path)
return unless dynamic_path
return if File.directory?(file_path)
named_captures = dynamic_path.named_captures.symbolize_keys
- UploadService.new(context.portable, File.open(file_path, 'r'), FileUploader, **named_captures).execute
+ UploadService.new(context.portable, File.open(file_path, 'r'), file_uploader, **named_captures).execute
end
def after_run(_)
@@ -46,6 +53,24 @@ module BulkImports
def tmp_dir
@tmp_dir ||= Dir.mktmpdir('bulk_imports')
end
+
+ def file_uploader
+ @file_uploader ||= if context.entity.group?
+ NamespaceFileUploader
+ else
+ FileUploader
+ end
+ end
+
+ def save_avatar(file_path)
+ File.open(file_path) do |avatar|
+ service = context.entity.update_service.new(portable, current_user, avatar: avatar)
+
+ unless service.execute
+ raise AvatarLoadingError, portable.errors.full_messages.to_sentence
+ end
+ end
+ end
end
end
end
diff --git a/lib/bulk_imports/common/pipelines/wiki_pipeline.rb b/lib/bulk_imports/common/pipelines/wiki_pipeline.rb
index ccab0b979b2..6900835b14d 100644
--- a/lib/bulk_imports/common/pipelines/wiki_pipeline.rb
+++ b/lib/bulk_imports/common/pipelines/wiki_pipeline.rb
@@ -7,7 +7,9 @@ module BulkImports
include Pipeline
def extract(*)
- BulkImports::Pipeline::ExtractedData.new(data: { url: url_from_parent_path(context.entity.source_full_path) })
+ url = url_from_parent_path(context.entity.source_full_path) if source_wiki_exists?
+
+ BulkImports::Pipeline::ExtractedData.new(data: { url: url })
end
def transform(_, data)
@@ -15,14 +17,15 @@ module BulkImports
end
def load(context, data)
- return unless context.portable.wiki
+ return unless data&.dig(:url)
+ wiki = context.portable.wiki
url = data[:url].sub("://", "://oauth2:#{context.configuration.access_token}@")
Gitlab::UrlBlocker.validate!(url, allow_local_network: allow_local_requests?, allow_localhost: allow_local_requests?)
- context.portable.wiki.ensure_repository
- context.portable.wiki.repository.fetch_as_mirror(url)
+ wiki.ensure_repository
+ wiki.repository.fetch_as_mirror(url)
end
private
@@ -36,6 +39,16 @@ module BulkImports
def allow_local_requests?
Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
end
+
+ def source_wiki_exists?
+ wikis = client.get(context.entity.wikis_url_path).parsed_response
+
+ wikis.any?
+ end
+
+ def client
+ BulkImports::Clients::HTTP.new(url: context.configuration.url, token: context.configuration.access_token)
+ end
end
end
end
diff --git a/lib/bulk_imports/groups/rest/get_badges_query.rb b/lib/bulk_imports/common/rest/get_badges_query.rb
index 79ffdd9a1f6..60b2ebcc552 100644
--- a/lib/bulk_imports/groups/rest/get_badges_query.rb
+++ b/lib/bulk_imports/common/rest/get_badges_query.rb
@@ -1,16 +1,17 @@
# frozen_string_literal: true
module BulkImports
- module Groups
+ module Common
module Rest
module GetBadgesQuery
extend self
def to_h(context)
+ resource = context.entity.pluralized_name
encoded_full_path = ERB::Util.url_encode(context.entity.source_full_path)
{
- resource: ['groups', encoded_full_path, 'badges'].join('/'),
+ resource: [resource, encoded_full_path, 'badges'].join('/'),
query: {
page: context.tracker.next_page
}
diff --git a/lib/bulk_imports/groups/pipelines/badges_pipeline.rb b/lib/bulk_imports/groups/pipelines/badges_pipeline.rb
deleted file mode 100644
index 8569ff3f77a..00000000000
--- a/lib/bulk_imports/groups/pipelines/badges_pipeline.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-# frozen_string_literal: true
-
-module BulkImports
- module Groups
- module Pipelines
- class BadgesPipeline
- include Pipeline
-
- extractor BulkImports::Common::Extractors::RestExtractor,
- query: BulkImports::Groups::Rest::GetBadgesQuery
-
- transformer Common::Transformers::ProhibitedAttributesTransformer
-
- def transform(_, data)
- return if data.blank?
-
- {
- name: data['name'],
- link_url: data['link_url'],
- image_url: data['image_url']
- }
- end
-
- def load(context, data)
- return if data.blank?
-
- context.group.badges.create!(data)
- end
- end
- end
- end
-end
diff --git a/lib/bulk_imports/groups/pipelines/group_avatar_pipeline.rb b/lib/bulk_imports/groups/pipelines/group_avatar_pipeline.rb
deleted file mode 100644
index 6de8bbbc910..00000000000
--- a/lib/bulk_imports/groups/pipelines/group_avatar_pipeline.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-# frozen_string_literal: true
-
-module BulkImports
- module Groups
- module Pipelines
- class GroupAvatarPipeline
- include Pipeline
-
- ALLOWED_AVATAR_DOWNLOAD_TYPES = (AvatarUploader::MIME_WHITELIST + %w(application/octet-stream)).freeze
-
- GroupAvatarLoadingError = Class.new(StandardError)
-
- def extract(context)
- context.extra[:tmpdir] = Dir.mktmpdir
-
- filepath = BulkImports::FileDownloadService.new(
- configuration: context.configuration,
- relative_url: "/groups/#{context.entity.encoded_source_full_path}/avatar",
- dir: context.extra[:tmpdir],
- file_size_limit: Avatarable::MAXIMUM_FILE_SIZE,
- allowed_content_types: ALLOWED_AVATAR_DOWNLOAD_TYPES
- ).execute
-
- BulkImports::Pipeline::ExtractedData.new(data: { filepath: filepath })
- end
-
- def load(context, data)
- return if data.blank?
-
- File.open(data[:filepath]) do |avatar|
- service = ::Groups::UpdateService.new(
- portable,
- current_user,
- avatar: avatar
- )
-
- unless service.execute
- raise GroupAvatarLoadingError, portable.errors.full_messages.first
- end
- end
- end
-
- def after_run(_)
- FileUtils.remove_entry(context.extra[:tmpdir]) if context.extra[:tmpdir].present?
- end
- end
- end
- end
-end
diff --git a/lib/bulk_imports/groups/stage.rb b/lib/bulk_imports/groups/stage.rb
index 241dd428dd5..1a3babe1679 100644
--- a/lib/bulk_imports/groups/stage.rb
+++ b/lib/bulk_imports/groups/stage.rb
@@ -11,10 +11,6 @@ module BulkImports
pipeline: BulkImports::Groups::Pipelines::GroupPipeline,
stage: 0
},
- avatar: {
- pipeline: BulkImports::Groups::Pipelines::GroupAvatarPipeline,
- stage: 1
- },
subgroups: {
pipeline: BulkImports::Groups::Pipelines::SubgroupEntitiesPipeline,
stage: 1
@@ -32,13 +28,17 @@ module BulkImports
stage: 1
},
badges: {
- pipeline: BulkImports::Groups::Pipelines::BadgesPipeline,
+ pipeline: BulkImports::Common::Pipelines::BadgesPipeline,
stage: 1
},
boards: {
pipeline: BulkImports::Common::Pipelines::BoardsPipeline,
stage: 2
},
+ uploads: {
+ pipeline: BulkImports::Common::Pipelines::UploadsPipeline,
+ stage: 2
+ },
finisher: {
pipeline: BulkImports::Common::Pipelines::EntityFinisher,
stage: 3
diff --git a/lib/bulk_imports/ndjson_pipeline.rb b/lib/bulk_imports/ndjson_pipeline.rb
index 6cc29d63919..d5475a8b324 100644
--- a/lib/bulk_imports/ndjson_pipeline.rb
+++ b/lib/bulk_imports/ndjson_pipeline.rb
@@ -10,7 +10,12 @@ module BulkImports
ndjson_pipeline!
def transform(context, data)
+ return unless data
+
relation_hash, relation_index = data
+
+ return unless relation_hash
+
relation_definition = import_export_config.top_relation_tree(relation)
relation_object = deep_transform_relation!(relation_hash, relation, relation_definition) do |key, hash|
@@ -31,9 +36,7 @@ module BulkImports
end
def load(_, object)
- return unless object
-
- object.save! unless object.persisted?
+ object&.save!
end
def deep_transform_relation!(relation_hash, relation_key, relation_definition, &block)
diff --git a/lib/bulk_imports/projects/graphql/get_project_query.rb b/lib/bulk_imports/projects/graphql/get_project_query.rb
index 2aec496880f..04ac0916bbc 100644
--- a/lib/bulk_imports/projects/graphql/get_project_query.rb
+++ b/lib/bulk_imports/projects/graphql/get_project_query.rb
@@ -4,6 +4,7 @@ module BulkImports
module Projects
module Graphql
module GetProjectQuery
+ extend Queryable
extend self
def to_s
@@ -28,22 +29,6 @@ module BulkImports
}
GRAPHQL
end
-
- def variables(context)
- { full_path: context.entity.source_full_path }
- end
-
- def base_path
- %w[data project]
- end
-
- def data_path
- base_path
- end
-
- def page_info_path
- base_path << 'page_info'
- end
end
end
end
diff --git a/lib/bulk_imports/projects/graphql/get_repository_query.rb b/lib/bulk_imports/projects/graphql/get_repository_query.rb
index d3e377c1175..24efce9e276 100644
--- a/lib/bulk_imports/projects/graphql/get_repository_query.rb
+++ b/lib/bulk_imports/projects/graphql/get_repository_query.rb
@@ -4,6 +4,7 @@ module BulkImports
module Projects
module Graphql
module GetRepositoryQuery
+ extend Queryable
extend self
def to_s
@@ -15,22 +16,6 @@ module BulkImports
}
GRAPHQL
end
-
- def variables(context)
- { full_path: context.entity.source_full_path }
- end
-
- def base_path
- %w[data project]
- end
-
- def data_path
- base_path
- end
-
- def page_info_path
- base_path << 'page_info'
- end
end
end
end
diff --git a/lib/bulk_imports/projects/graphql/get_snippet_repository_query.rb b/lib/bulk_imports/projects/graphql/get_snippet_repository_query.rb
new file mode 100644
index 00000000000..1ba57789612
--- /dev/null
+++ b/lib/bulk_imports/projects/graphql/get_snippet_repository_query.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module BulkImports
+ module Projects
+ module Graphql
+ module GetSnippetRepositoryQuery
+ extend Queryable
+ extend self
+
+ def to_s
+ <<-'GRAPHQL'
+ query($full_path: ID!) {
+ project(fullPath: $full_path) {
+ snippets {
+ page_info: pageInfo {
+ next_page: endCursor
+ has_next_page: hasNextPage
+ }
+ nodes {
+ title
+ createdAt
+ httpUrlToRepo
+ }
+ }
+ }
+ }
+ GRAPHQL
+ end
+
+ def variables(context)
+ {
+ full_path: context.entity.source_full_path,
+ cursor: context.tracker.next_page,
+ per_page: ::BulkImports::Tracker::DEFAULT_PAGE_SIZE
+ }
+ end
+
+ def base_path
+ %w[data project snippets]
+ end
+
+ def data_path
+ base_path << 'nodes'
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bulk_imports/projects/graphql/queryable.rb b/lib/bulk_imports/projects/graphql/queryable.rb
new file mode 100644
index 00000000000..a897632dff3
--- /dev/null
+++ b/lib/bulk_imports/projects/graphql/queryable.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module BulkImports
+ module Projects
+ module Graphql
+ module Queryable
+ def variables(context)
+ { full_path: context.entity.source_full_path }
+ end
+
+ def base_path
+ %w[data project]
+ end
+
+ def data_path
+ base_path
+ end
+
+ def page_info_path
+ base_path << 'page_info'
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bulk_imports/projects/pipelines/auto_devops_pipeline.rb b/lib/bulk_imports/projects/pipelines/auto_devops_pipeline.rb
new file mode 100644
index 00000000000..1e54ca7017d
--- /dev/null
+++ b/lib/bulk_imports/projects/pipelines/auto_devops_pipeline.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module BulkImports
+ module Projects
+ module Pipelines
+ class AutoDevopsPipeline
+ include NdjsonPipeline
+
+ relation_name 'auto_devops'
+
+ extractor ::BulkImports::Common::Extractors::NdjsonExtractor, relation: relation
+ end
+ end
+ end
+end
diff --git a/lib/bulk_imports/projects/pipelines/ci_pipelines_pipeline.rb b/lib/bulk_imports/projects/pipelines/ci_pipelines_pipeline.rb
new file mode 100644
index 00000000000..4487835b88e
--- /dev/null
+++ b/lib/bulk_imports/projects/pipelines/ci_pipelines_pipeline.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module BulkImports
+ module Projects
+ module Pipelines
+ class CiPipelinesPipeline
+ include NdjsonPipeline
+
+ relation_name 'ci_pipelines'
+
+ extractor ::BulkImports::Common::Extractors::NdjsonExtractor, relation: relation
+ end
+ end
+ end
+end
diff --git a/lib/bulk_imports/projects/pipelines/container_expiration_policy_pipeline.rb b/lib/bulk_imports/projects/pipelines/container_expiration_policy_pipeline.rb
new file mode 100644
index 00000000000..796e2bd5293
--- /dev/null
+++ b/lib/bulk_imports/projects/pipelines/container_expiration_policy_pipeline.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module BulkImports
+ module Projects
+ module Pipelines
+ class ContainerExpirationPolicyPipeline
+ include NdjsonPipeline
+
+ relation_name 'container_expiration_policy'
+
+ extractor ::BulkImports::Common::Extractors::NdjsonExtractor, relation: relation
+ end
+ end
+ end
+end
diff --git a/lib/bulk_imports/projects/pipelines/pipeline_schedules_pipeline.rb b/lib/bulk_imports/projects/pipelines/pipeline_schedules_pipeline.rb
new file mode 100644
index 00000000000..67053f4e0d4
--- /dev/null
+++ b/lib/bulk_imports/projects/pipelines/pipeline_schedules_pipeline.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module BulkImports
+ module Projects
+ module Pipelines
+ class PipelineSchedulesPipeline
+ include NdjsonPipeline
+
+ relation_name 'pipeline_schedules'
+
+ extractor ::BulkImports::Common::Extractors::NdjsonExtractor, relation: relation
+ end
+ end
+ end
+end
diff --git a/lib/bulk_imports/projects/pipelines/project_attributes_pipeline.rb b/lib/bulk_imports/projects/pipelines/project_attributes_pipeline.rb
new file mode 100644
index 00000000000..4d742225ff7
--- /dev/null
+++ b/lib/bulk_imports/projects/pipelines/project_attributes_pipeline.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+module BulkImports
+ module Projects
+ module Pipelines
+ class ProjectAttributesPipeline
+ include Pipeline
+
+ transformer ::BulkImports::Common::Transformers::ProhibitedAttributesTransformer
+
+ def extract(context)
+ download_service(tmp_dir, context).execute
+ decompression_service(tmp_dir).execute
+ project_attributes = json_decode(json_attributes)
+
+ BulkImports::Pipeline::ExtractedData.new(data: project_attributes)
+ end
+
+ def transform(_, data)
+ subrelations = config.portable_relations_tree.keys.map(&:to_s)
+
+ Gitlab::ImportExport::AttributeCleaner.clean(
+ relation_hash: data,
+ relation_class: Project,
+ excluded_keys: config.relation_excluded_keys(:project)
+ ).except(*subrelations)
+ end
+
+ def load(_, data)
+ portable.assign_attributes(data)
+ portable.reconcile_shared_runners_setting!
+ portable.drop_visibility_level!
+ portable.save!
+ end
+
+ def after_run(_)
+ FileUtils.remove_entry(tmp_dir)
+ end
+
+ def json_attributes
+ @json_attributes ||= File.read(File.join(tmp_dir, filename))
+ end
+
+ private
+
+ def tmp_dir
+ @tmp_dir ||= Dir.mktmpdir
+ end
+
+ def config
+ @config ||= BulkImports::FileTransfer.config_for(portable)
+ end
+
+ def download_service(tmp_dir, context)
+ @download_service ||= BulkImports::FileDownloadService.new(
+ configuration: context.configuration,
+ relative_url: context.entity.relation_download_url_path(BulkImports::FileTransfer::BaseConfig::SELF_RELATION),
+ dir: tmp_dir,
+ filename: compressed_filename
+ )
+ end
+
+ def decompression_service(tmp_dir)
+ @decompression_service ||= BulkImports::FileDecompressionService.new(dir: tmp_dir, filename: compressed_filename)
+ end
+
+ def compressed_filename
+ "#{filename}.gz"
+ end
+
+ def filename
+ "#{BulkImports::FileTransfer::BaseConfig::SELF_RELATION}.json"
+ end
+
+ def json_decode(string)
+ Gitlab::Json.parse(string)
+ rescue JSON::ParserError => e
+ Gitlab::ErrorTracking.log_exception(e)
+
+ raise BulkImports::Error, 'Incorrect JSON format'
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bulk_imports/projects/pipelines/project_feature_pipeline.rb b/lib/bulk_imports/projects/pipelines/project_feature_pipeline.rb
new file mode 100644
index 00000000000..ff5437efeef
--- /dev/null
+++ b/lib/bulk_imports/projects/pipelines/project_feature_pipeline.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module BulkImports
+ module Projects
+ module Pipelines
+ class ProjectFeaturePipeline
+ include NdjsonPipeline
+
+ relation_name 'project_feature'
+
+ extractor ::BulkImports::Common::Extractors::NdjsonExtractor, relation: relation
+ end
+ end
+ end
+end
diff --git a/lib/bulk_imports/projects/pipelines/repository_pipeline.rb b/lib/bulk_imports/projects/pipelines/repository_pipeline.rb
index 6bbd4d0688b..f5ccc1dd922 100644
--- a/lib/bulk_imports/projects/pipelines/repository_pipeline.rb
+++ b/lib/bulk_imports/projects/pipelines/repository_pipeline.rb
@@ -16,6 +16,8 @@ module BulkImports
def load(context, data)
url = data['httpUrlToRepo']
+ return unless url.present?
+
url = url.sub("://", "://oauth2:#{context.configuration.access_token}@")
project = context.portable
diff --git a/lib/bulk_imports/projects/pipelines/service_desk_setting_pipeline.rb b/lib/bulk_imports/projects/pipelines/service_desk_setting_pipeline.rb
new file mode 100644
index 00000000000..a50b5423366
--- /dev/null
+++ b/lib/bulk_imports/projects/pipelines/service_desk_setting_pipeline.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module BulkImports
+ module Projects
+ module Pipelines
+ class ServiceDeskSettingPipeline
+ include NdjsonPipeline
+
+ relation_name 'service_desk_setting'
+
+ extractor ::BulkImports::Common::Extractors::NdjsonExtractor, relation: relation
+ end
+ end
+ end
+end
diff --git a/lib/bulk_imports/projects/pipelines/snippets_pipeline.rb b/lib/bulk_imports/projects/pipelines/snippets_pipeline.rb
new file mode 100644
index 00000000000..d543bcec383
--- /dev/null
+++ b/lib/bulk_imports/projects/pipelines/snippets_pipeline.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module BulkImports
+ module Projects
+ module Pipelines
+ class SnippetsPipeline
+ include NdjsonPipeline
+
+ relation_name 'snippets'
+
+ extractor ::BulkImports::Common::Extractors::NdjsonExtractor, relation: relation
+ end
+ end
+ end
+end
diff --git a/lib/bulk_imports/projects/pipelines/snippets_repository_pipeline.rb b/lib/bulk_imports/projects/pipelines/snippets_repository_pipeline.rb
new file mode 100644
index 00000000000..6d423717a51
--- /dev/null
+++ b/lib/bulk_imports/projects/pipelines/snippets_repository_pipeline.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+module BulkImports
+ module Projects
+ module Pipelines
+ class SnippetsRepositoryPipeline
+ include Pipeline
+
+ extractor Common::Extractors::GraphqlExtractor, query: Graphql::GetSnippetRepositoryQuery
+
+ def transform(_context, data)
+ data.tap do |d|
+ d['createdAt'] = DateTime.parse(data['createdAt'])
+ end
+ end
+
+ def load(context, data)
+ return unless data['httpUrlToRepo'].present?
+
+ oauth2_url = oauth2(data['httpUrlToRepo'])
+ validate_url(oauth2_url)
+
+ matched_snippet = find_matched_snippet(data)
+ # Skip snippets that we couldn't find a match. Probably because more snippets were
+ # added after the migration had already started, namely after the SnippetsPipeline
+ # has already run.
+ return unless matched_snippet
+
+ matched_snippet.create_repository
+ matched_snippet.repository.fetch_as_mirror(oauth2_url)
+ response = Snippets::RepositoryValidationService.new(nil, matched_snippet).execute
+
+ # skips matched_snippet repository creation if repository is invalid
+ return cleanup_snippet_repository(matched_snippet) if response.error?
+
+ Snippets::UpdateStatisticsService.new(matched_snippet).execute
+ end
+
+ private
+
+ def find_matched_snippet(data)
+ Snippet.find_by_project_title_trunc_created_at(
+ context.portable, data['title'], data['createdAt'])
+ end
+
+ def allow_local_requests?
+ Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
+ end
+
+ def oauth2(url)
+ url.sub("://", "://oauth2:#{context.configuration.access_token}@")
+ end
+
+ def validate_url(url)
+ Gitlab::UrlBlocker.validate!(
+ url,
+ allow_local_network: allow_local_requests?,
+ allow_localhost: allow_local_requests?)
+ end
+
+ def cleanup_snippet_repository(snippet)
+ snippet.repository.remove
+ snippet.snippet_repository.delete
+ snippet.repository.expire_exists_cache
+ end
+ end
+ end
+ end
+end
diff --git a/lib/bulk_imports/projects/stage.rb b/lib/bulk_imports/projects/stage.rb
index 9ccc9efff1d..0556395ca66 100644
--- a/lib/bulk_imports/projects/stage.rb
+++ b/lib/bulk_imports/projects/stage.rb
@@ -15,6 +15,10 @@ module BulkImports
pipeline: BulkImports::Projects::Pipelines::RepositoryPipeline,
stage: 1
},
+ project_attributes: {
+ pipeline: BulkImports::Projects::Pipelines::ProjectAttributesPipeline,
+ stage: 1
+ },
labels: {
pipeline: BulkImports::Common::Pipelines::LabelsPipeline,
stage: 2
@@ -23,10 +27,22 @@ module BulkImports
pipeline: BulkImports::Common::Pipelines::MilestonesPipeline,
stage: 2
},
+ badges: {
+ pipeline: BulkImports::Common::Pipelines::BadgesPipeline,
+ stage: 2
+ },
issues: {
pipeline: BulkImports::Projects::Pipelines::IssuesPipeline,
stage: 3
},
+ snippets: {
+ pipeline: BulkImports::Projects::Pipelines::SnippetsPipeline,
+ stage: 3
+ },
+ snippets_repository: {
+ pipeline: BulkImports::Projects::Pipelines::SnippetsRepositoryPipeline,
+ stage: 4
+ },
boards: {
pipeline: BulkImports::Common::Pipelines::BoardsPipeline,
stage: 4
@@ -43,6 +59,22 @@ module BulkImports
pipeline: BulkImports::Projects::Pipelines::ProtectedBranchesPipeline,
stage: 4
},
+ ci_pipelines: {
+ pipeline: BulkImports::Projects::Pipelines::CiPipelinesPipeline,
+ stage: 4
+ },
+ project_feature: {
+ pipeline: BulkImports::Projects::Pipelines::ProjectFeaturePipeline,
+ stage: 4
+ },
+ container_expiration_policy: {
+ pipeline: BulkImports::Projects::Pipelines::ContainerExpirationPolicyPipeline,
+ stage: 4
+ },
+ service_desk_setting: {
+ pipeline: BulkImports::Projects::Pipelines::ServiceDeskSettingPipeline,
+ stage: 4
+ },
wiki: {
pipeline: BulkImports::Common::Pipelines::WikiPipeline,
stage: 5
@@ -51,6 +83,14 @@ module BulkImports
pipeline: BulkImports::Common::Pipelines::UploadsPipeline,
stage: 5
},
+ auto_devops: {
+ pipeline: BulkImports::Projects::Pipelines::AutoDevopsPipeline,
+ stage: 5
+ },
+ pipeline_schedules: {
+ pipeline: BulkImports::Projects::Pipelines::PipelineSchedulesPipeline,
+ stage: 5
+ },
finisher: {
pipeline: BulkImports::Common::Pipelines::EntityFinisher,
stage: 6
diff --git a/lib/extracts_ref.rb b/lib/extracts_ref.rb
index d130a9d6f82..daba0452318 100644
--- a/lib/extracts_ref.rb
+++ b/lib/extracts_ref.rb
@@ -113,7 +113,7 @@ module ExtractsRef
best_match = valid_refs.max_by(&:length)
# Partition the string into the ref and the path, ignoring the empty first value
- id.partition(best_match)[1..-1]
+ id.partition(best_match)[1..]
end
def use_first_path_segment?(ref)
diff --git a/lib/feature.rb b/lib/feature.rb
index 8186fbc40fa..f301f206b46 100644
--- a/lib/feature.rb
+++ b/lib/feature.rb
@@ -83,7 +83,12 @@ class Feature
# `persisted?` can potentially generate DB queries and also checks for inclusion
# in an array of feature names (177 at last count), possibly reducing performance by half.
# So we only perform the `persisted` check if `default_enabled: true`
- !default_enabled || Feature.persisted_name?(feature.name) ? feature.enabled?(thing) : true
+ feature_value = !default_enabled || Feature.persisted_name?(feature.name) ? feature.enabled?(thing) : true
+
+ # If we don't filter out this flag here we will enter an infinite loop
+ log_feature_flag_state(key, feature_value) if log_feature_flag_states?(key)
+
+ feature_value
end
def disabled?(key, thing = nil, type: :development, default_enabled: false)
@@ -153,6 +158,18 @@ class Feature
@logger ||= Feature::Logger.build
end
+ def log_feature_flag_states?(key)
+ Feature::Definition.log_states?(key)
+ end
+
+ def log_feature_flag_state(key, feature_value)
+ logged_states[key] ||= feature_value
+ end
+
+ def logged_states
+ RequestStore.fetch(:feature_flag_events) { {} }
+ end
+
private
def flipper
diff --git a/lib/feature/definition.rb b/lib/feature/definition.rb
index cd2f5cb07a2..61f7e395769 100644
--- a/lib/feature/definition.rb
+++ b/lib/feature/definition.rb
@@ -82,6 +82,16 @@ class Feature
attributes
end
+ def for_upcoming_milestone?
+ return false unless milestone
+
+ Gitlab::VersionInfo.parse(milestone + '.999') >= Gitlab.version_info
+ end
+
+ def force_log_state_changes?
+ attributes[:log_state_changes]
+ end
+
class << self
def paths
@paths ||= [Rails.root.join('config', 'feature_flags', '**', '*.yml')]
@@ -106,6 +116,14 @@ class Feature
definitions.has_key?(key.to_sym)
end
+ def log_states?(key)
+ return false if key == :feature_flag_state_logs
+ return false if Feature.disabled?(:feature_flag_state_logs, type: :ops)
+ return false unless (feature = get(key))
+
+ feature.force_log_state_changes? || feature.for_upcoming_milestone?
+ end
+
def valid_usage!(key, type:, default_enabled:)
if definition = get(key)
definition.valid_usage!(type_in_code: type, default_enabled_in_code: default_enabled)
diff --git a/lib/feature/shared.rb b/lib/feature/shared.rb
index 70e5b523adf..2ce078b2f02 100644
--- a/lib/feature/shared.rb
+++ b/lib/feature/shared.rb
@@ -58,6 +58,7 @@ class Feature
introduced_by_url
rollout_issue_url
milestone
+ log_state_changes
type
group
default_enabled
diff --git a/lib/flowdock/git/builder.rb b/lib/flowdock/git/builder.rb
index 6f4428d1f42..88d9814950a 100644
--- a/lib/flowdock/git/builder.rb
+++ b/lib/flowdock/git/builder.rb
@@ -51,7 +51,7 @@ module Flowdock
end
def body
- content = @commit[:message][first_line.size..-1]
+ content = @commit[:message][first_line.size..]
content.strip! if content
"<pre>#{content}</pre>" unless content.empty?
end
diff --git a/lib/generators/gitlab/usage_metric_definition/redis_hll_generator.rb b/lib/generators/gitlab/usage_metric_definition/redis_hll_generator.rb
index e343e2dcf91..58d3257d07e 100644
--- a/lib/generators/gitlab/usage_metric_definition/redis_hll_generator.rb
+++ b/lib/generators/gitlab/usage_metric_definition/redis_hll_generator.rb
@@ -12,11 +12,11 @@ module Gitlab
class_option :ee, type: :boolean, optional: true, default: false, desc: 'Indicates if metric is for ee'
def create_metrics
- weekly_params = ["#{key_path}_weekly", '--dir', '7d']
+ weekly_params = ["#{key_path}_weekly", '--dir', '7d', '--class_name', 'RedisHLLMetric']
weekly_params << '--ee' if ee?
Gitlab::UsageMetricDefinitionGenerator.start(weekly_params)
- monthly_params = ["#{key_path}_monthly", '--dir', '28d']
+ monthly_params = ["#{key_path}_monthly", '--dir', '28d', '--class_name', 'RedisHLLMetric']
monthly_params << '--ee' if ee?
Gitlab::UsageMetricDefinitionGenerator.start(monthly_params)
end
diff --git a/lib/generators/gitlab/usage_metric_definition_generator.rb b/lib/generators/gitlab/usage_metric_definition_generator.rb
index bd34ab0a16f..4ddbe8b9f09 100644
--- a/lib/generators/gitlab/usage_metric_definition_generator.rb
+++ b/lib/generators/gitlab/usage_metric_definition_generator.rb
@@ -35,6 +35,7 @@ module Gitlab
class_option :ee, type: :boolean, optional: true, default: false, desc: 'Indicates if metric is for ee'
class_option :dir,
type: :string, desc: "Indicates the metric location. It must be one of: #{VALID_INPUT_DIRS.join(', ')}"
+ class_option :class_name, type: :string, optional: true, desc: 'Instrumentation class name, e.g.: CountIssues'
argument :key_paths, type: :array, desc: 'Unique JSON key paths for the metrics'
@@ -66,6 +67,10 @@ module Gitlab
Gitlab::VERSION.match('(\d+\.\d+)').captures.first
end
+ def class_name
+ options[:class_name]
+ end
+
private
def metric_name_suggestion(key_path)
diff --git a/lib/generators/gitlab/usage_metric_generator.rb b/lib/generators/gitlab/usage_metric_generator.rb
index c0fdcf21f20..0656dfbc312 100644
--- a/lib/generators/gitlab/usage_metric_generator.rb
+++ b/lib/generators/gitlab/usage_metric_generator.rb
@@ -5,9 +5,9 @@ require 'rails/generators'
module Gitlab
class UsageMetricGenerator < Rails::Generators::Base
CE_DIR = 'lib/gitlab/usage/metrics/instrumentations'
- EE_DIR = 'ee/lib/ee/gitlab/usage/metrics/instrumentations'
+ EE_DIR = 'ee/lib/gitlab/usage/metrics/instrumentations'
SPEC_CE_DIR = 'spec/lib/gitlab/usage/metrics/instrumentations'
- SPEC_EE_DIR = 'ee/spec/lib/ee/gitlab/usage/metrics/instrumentations'
+ SPEC_EE_DIR = 'ee/spec/lib/gitlab/usage/metrics/instrumentations'
ALLOWED_SUPERCLASSES = {
generic: 'Generic',
diff --git a/lib/gitlab/abuse.rb b/lib/gitlab/abuse.rb
new file mode 100644
index 00000000000..cc95d3c1e0c
--- /dev/null
+++ b/lib/gitlab/abuse.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Abuse
+ CONFIDENCE_LEVELS = {
+ certain: 1.0,
+ likely: 0.8,
+ uncertain: 0.5,
+ unknown: 0.0
+ }.freeze
+
+ class << self
+ def confidence(rating)
+ CONFIDENCE_LEVELS.fetch(rating.to_sym)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/analytics/cycle_analytics/aggregated/data_collector.rb b/lib/gitlab/analytics/cycle_analytics/aggregated/data_collector.rb
index c8b11ecb4a8..2441b7a7497 100644
--- a/lib/gitlab/analytics/cycle_analytics/aggregated/data_collector.rb
+++ b/lib/gitlab/analytics/cycle_analytics/aggregated/data_collector.rb
@@ -52,3 +52,5 @@ module Gitlab
end
end
end
+
+Gitlab::Analytics::CycleAnalytics::Aggregated::DataCollector.prepend_mod_with('Gitlab::Analytics::CycleAnalytics::Aggregated::DataCollector')
diff --git a/lib/gitlab/analytics/cycle_analytics/aggregated/median.rb b/lib/gitlab/analytics/cycle_analytics/aggregated/median.rb
index 181ee20948b..bf8f9f4dcc7 100644
--- a/lib/gitlab/analytics/cycle_analytics/aggregated/median.rb
+++ b/lib/gitlab/analytics/cycle_analytics/aggregated/median.rb
@@ -15,7 +15,7 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord
def seconds
- @query = @query.select(median_duration_in_seconds.as('median')).reorder(nil)
+ @query = @query.select(duration_in_seconds(percentile_cont).as('median')).reorder(nil)
result = @query.take || {}
result['median'] || nil
diff --git a/lib/gitlab/analytics/cycle_analytics/aggregated/records_fetcher.rb b/lib/gitlab/analytics/cycle_analytics/aggregated/records_fetcher.rb
index 7dce757cdc8..1742d396c10 100644
--- a/lib/gitlab/analytics/cycle_analytics/aggregated/records_fetcher.rb
+++ b/lib/gitlab/analytics/cycle_analytics/aggregated/records_fetcher.rb
@@ -36,7 +36,7 @@ module Gitlab
def serialized_records
strong_memoize(:serialized_records) do
- records = ordered_and_limited_query.select(stage_event_model.arel_table[Arel.star], duration.as('total_time'))
+ records = ordered_and_limited_query.select(stage_event_model.arel_table[Arel.star], duration_in_seconds.as('total_time'))
yield records if block_given?
issuables_and_records = load_issuables(records)
diff --git a/lib/gitlab/analytics/cycle_analytics/aggregated/stage_query_helpers.rb b/lib/gitlab/analytics/cycle_analytics/aggregated/stage_query_helpers.rb
index f23d1832df9..b00925495f2 100644
--- a/lib/gitlab/analytics/cycle_analytics/aggregated/stage_query_helpers.rb
+++ b/lib/gitlab/analytics/cycle_analytics/aggregated/stage_query_helpers.rb
@@ -27,13 +27,13 @@ module Gitlab
end
end
- def median_duration_in_seconds
- Arel::Nodes::Extract.new(percentile_cont, :epoch)
- end
-
def in_progress?
params[:end_event_filter] == :in_progress
end
+
+ def duration_in_seconds(duration_expression = duration)
+ Arel::Nodes::Extract.new(duration_expression, :epoch)
+ end
end
end
end
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event.rb
index 945cecfcf8c..e99ad42b0b2 100644
--- a/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event.rb
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event.rb
@@ -19,7 +19,7 @@ module Gitlab
raise NotImplementedError
end
- def markdown_description
+ def html_description(options = {})
self.class.name
end
diff --git a/lib/gitlab/anonymous_session.rb b/lib/gitlab/anonymous_session.rb
index 911825eef3a..e58240e16b4 100644
--- a/lib/gitlab/anonymous_session.rb
+++ b/lib/gitlab/anonymous_session.rb
@@ -2,27 +2,29 @@
module Gitlab
class AnonymousSession
+ include ::Gitlab::Redis::SessionsStoreHelper
+
def initialize(remote_ip)
@remote_ip = remote_ip
end
def count_session_ip
- Gitlab::Redis::SharedState.with do |redis|
- redis.pipelined do
- redis.incr(session_lookup_name)
- redis.expire(session_lookup_name, 24.hours)
+ redis_store_class.with do |redis|
+ redis.pipelined do |pipeline|
+ pipeline.incr(session_lookup_name)
+ pipeline.expire(session_lookup_name, 24.hours)
end
end
end
def session_count
- Gitlab::Redis::SharedState.with do |redis|
+ redis_store_class.with do |redis|
redis.get(session_lookup_name).to_i
end
end
def cleanup_session_per_ip_count
- Gitlab::Redis::SharedState.with do |redis|
+ redis_store_class.with do |redis|
redis.del(session_lookup_name)
end
end
@@ -32,7 +34,7 @@ module Gitlab
attr_reader :remote_ip
def session_lookup_name
- @session_lookup_name ||= "#{Gitlab::Redis::SharedState::IP_SESSIONS_LOOKUP_NAMESPACE}:#{remote_ip}"
+ @session_lookup_name ||= "#{Gitlab::Redis::Sessions::IP_SESSIONS_LOOKUP_NAMESPACE}:#{remote_ip}"
end
end
end
diff --git a/lib/gitlab/application_context.rb b/lib/gitlab/application_context.rb
index aa33f56582b..c3415c45b28 100644
--- a/lib/gitlab/application_context.rb
+++ b/lib/gitlab/application_context.rb
@@ -124,11 +124,8 @@ module Gitlab
strong_memoize(:runner_project) do
next unless runner&.project_type?
- projects = ::Gitlab::Database.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/342147') do
- runner.projects.take(2) # rubocop: disable CodeReuse/ActiveRecord
- end
-
- projects.first if projects.one?
+ runner_projects = runner.runner_projects.take(2) # rubocop: disable CodeReuse/ActiveRecord
+ runner_projects.first.project if runner_projects.one?
end
end
diff --git a/lib/gitlab/application_rate_limiter.rb b/lib/gitlab/application_rate_limiter.rb
index 3db2f1295f9..fb90ad9e275 100644
--- a/lib/gitlab/application_rate_limiter.rb
+++ b/lib/gitlab/application_rate_limiter.rb
@@ -4,12 +4,7 @@ module Gitlab
# This class implements a simple rate limiter that can be used to throttle
# certain actions. Unlike Rack Attack and Rack::Throttle, which operate at
# the middleware level, this can be used at the controller or API level.
- #
- # @example
- # if Gitlab::ApplicationRateLimiter.throttled?(:project_export, scope: [@project, @current_user])
- # flash[:alert] = 'error!'
- # redirect_to(edit_project_path(@project), status: :too_many_requests)
- # end
+ # See CheckRateLimit concern for usage.
class ApplicationRateLimiter
InvalidKeyError = Class.new(StandardError)
@@ -47,7 +42,7 @@ module Gitlab
project_import: { threshold: -> { application_settings.project_import_limit }, interval: 1.minute },
project_testing_hook: { threshold: 5, interval: 1.minute },
play_pipeline_schedule: { threshold: 1, interval: 1.minute },
- show_raw_controller: { threshold: -> { application_settings.raw_blob_request_limit }, interval: 1.minute },
+ raw_blob: { threshold: -> { application_settings.raw_blob_request_limit }, interval: 1.minute },
group_export: { threshold: -> { application_settings.group_export_limit }, interval: 1.minute },
group_download_export: { threshold: -> { application_settings.group_download_export_limit }, interval: 1.minute },
group_import: { threshold: -> { application_settings.group_import_limit }, interval: 1.minute },
@@ -64,45 +59,47 @@ module Gitlab
# be throttled.
#
# @param key [Symbol] Key attribute registered in `.rate_limits`
- # @option scope [Array<ActiveRecord>] Array of ActiveRecord models to scope throttling to a specific request (e.g. per user per project)
- # @option threshold [Integer] Optional threshold value to override default one registered in `.rate_limits`
- # @option users_allowlist [Array<String>] Optional list of usernames to exclude from the limit. This param will only be functional if Scope includes a current user.
+ # @param scope [Array<ActiveRecord>] Array of ActiveRecord models to scope throttling to a specific request (e.g. per user per project)
+ # @param threshold [Integer] Optional threshold value to override default one registered in `.rate_limits`
+ # @param users_allowlist [Array<String>] Optional list of usernames to exclude from the limit. This param will only be functional if Scope includes a current user.
+ # @param peek [Boolean] Optional. When true the key will not be incremented but the current throttled state will be returned.
#
# @return [Boolean] Whether or not a request should be throttled
- def throttled?(key, **options)
+ def throttled?(key, scope:, threshold: nil, users_allowlist: nil, peek: false)
raise InvalidKeyError unless rate_limits[key]
- return if scoped_user_in_allowlist?(options)
+ return false if scoped_user_in_allowlist?(scope, users_allowlist)
- threshold_value = options[:threshold] || threshold(key)
- threshold_value > 0 &&
- increment(key, options[:scope]) > threshold_value
- end
+ threshold_value = threshold || threshold(key)
- # Increments a cache key that is based on the current time and interval.
- # So that when time passes to the next interval, the key changes and the count starts again from 0.
- #
- # Based on https://github.com/rack/rack-attack/blob/886ba3a18d13c6484cd511a4dc9b76c0d14e5e96/lib/rack/attack/cache.rb#L63-L68
- #
- # @param key [Symbol] Key attribute registered in `.rate_limits`
- # @option scope [Array<ActiveRecord>] Array of ActiveRecord models to scope throttling to a specific request (e.g. per user per project)
- #
- # @return [Integer] incremented value
- def increment(key, scope)
- interval_value = interval(key)
+ return false if threshold_value == 0
+ interval_value = interval(key)
+ # `period_key` is based on the current time and interval so when time passes to the next interval
+ # the key changes and the rate limit count starts again from 0.
+ # Based on https://github.com/rack/rack-attack/blob/886ba3a18d13c6484cd511a4dc9b76c0d14e5e96/lib/rack/attack/cache.rb#L63-L68
period_key, time_elapsed_in_period = Time.now.to_i.divmod(interval_value)
+ cache_key = cache_key(key, scope, period_key)
- cache_key = "#{action_key(key, scope)}:#{period_key}"
- # We add a 1 second buffer to avoid timing issues when we're at the end of a period
- expiry = interval_value - time_elapsed_in_period + 1
+ value = if peek
+ read(cache_key)
+ else
+ increment(cache_key, interval_value, time_elapsed_in_period)
+ end
- ::Gitlab::Redis::RateLimiting.with do |redis|
- redis.pipelined do
- redis.incr(cache_key)
- redis.expire(cache_key, expiry)
- end.first
- end
+ value > threshold_value
+ end
+
+ # Returns the current rate limited state without incrementing the count.
+ #
+ # @param key [Symbol] Key attribute registered in `.rate_limits`
+ # @param scope [Array<ActiveRecord>] Array of ActiveRecord models to scope throttling to a specific request (e.g. per user per project)
+ # @param threshold [Integer] Optional threshold value to override default one registered in `.rate_limits`
+ # @param users_allowlist [Array<String>] Optional list of usernames to exclude from the limit. This param will only be functional if Scope includes a current user.
+ #
+ # @return [Boolean] Whether or not a request is currently throttled
+ def peek(key, scope:, threshold: nil, users_allowlist: nil)
+ throttled?(key, peek: true, scope: scope, threshold: threshold, users_allowlist: users_allowlist)
end
# Logs request using provided logger
@@ -150,7 +147,28 @@ module Gitlab
action[setting] if action
end
- def action_key(key, scope)
+ # Increments the rate limit count and returns the new count value.
+ def increment(cache_key, interval_value, time_elapsed_in_period)
+ # We add a 1 second buffer to avoid timing issues when we're at the end of a period
+ expiry = interval_value - time_elapsed_in_period + 1
+
+ ::Gitlab::Redis::RateLimiting.with do |redis|
+ redis.pipelined do
+ redis.incr(cache_key)
+ redis.expire(cache_key, expiry)
+ end.first
+ end
+ end
+
+ # Returns the rate limit count.
+ # Will be 0 if there is no data in the cache.
+ def read(cache_key)
+ ::Gitlab::Redis::RateLimiting.with do |redis|
+ redis.get(cache_key).to_i
+ end
+ end
+
+ def cache_key(key, scope, period_key)
composed_key = [key, scope].flatten.compact
serialized = composed_key.map do |obj|
@@ -161,20 +179,20 @@ module Gitlab
end
end.join(":")
- "application_rate_limiter:#{serialized}"
+ "application_rate_limiter:#{serialized}:#{period_key}"
end
def application_settings
Gitlab::CurrentSettings.current_application_settings
end
- def scoped_user_in_allowlist?(options)
- return unless options[:users_allowlist].present?
+ def scoped_user_in_allowlist?(scope, users_allowlist)
+ return unless users_allowlist.present?
- scoped_user = [options[:scope]].flatten.find { |s| s.is_a?(User) }
+ scoped_user = [scope].flatten.find { |s| s.is_a?(User) }
return unless scoped_user
- scoped_user.username.downcase.in?(options[:users_allowlist])
+ scoped_user.username.downcase.in?(users_allowlist)
end
end
diff --git a/lib/gitlab/asciidoc/syntax_highlighter/html_pipeline_adapter.rb b/lib/gitlab/asciidoc/syntax_highlighter/html_pipeline_adapter.rb
index 6dbe6f691f6..3ada3f947ee 100644
--- a/lib/gitlab/asciidoc/syntax_highlighter/html_pipeline_adapter.rb
+++ b/lib/gitlab/asciidoc/syntax_highlighter/html_pipeline_adapter.rb
@@ -7,7 +7,7 @@ module Gitlab
register_for 'gitlab-html-pipeline'
def format(node, lang, opts)
- if Feature.enabled?(:use_cmark_renderer)
+ if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
%(<pre #{lang ? %[lang="#{lang}"] : ''}><code>#{node.content}</code></pre>)
else
%(<pre><code #{lang ? %[ lang="#{lang}"] : ''}>#{node.content}</code></pre>)
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 0970b92723b..3e982168339 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -189,7 +189,7 @@ module Gitlab
user = User.id_in(token.resource_owner_id).first
return unless user && can_user_login_with_non_expired_password?(user)
- Gitlab::Auth::Result.new(user, nil, :oauth, full_authentication_abilities)
+ Gitlab::Auth::Result.new(user, nil, :oauth, abilities_for_scopes(token.scopes))
end
end
end
@@ -230,7 +230,7 @@ module Gitlab
# rubocop: enable CodeReuse/ActiveRecord
def valid_oauth_token?(token)
- token && token.accessible? && valid_scoped_token?(token, [:api])
+ token && token.accessible? && valid_scoped_token?(token, Doorkeeper.configuration.scopes)
end
def valid_scoped_token?(token, scopes)
diff --git a/lib/gitlab/auth/user_access_denied_reason.rb b/lib/gitlab/auth/user_access_denied_reason.rb
index 904759919ae..ff6dc7313bb 100644
--- a/lib/gitlab/auth/user_access_denied_reason.rb
+++ b/lib/gitlab/auth/user_access_denied_reason.rb
@@ -15,7 +15,7 @@ module Gitlab
"Your account is pending approval from your administrator and hence blocked."
when :terms_not_accepted
"You (#{@user.to_reference}) must accept the Terms of Service in order to perform this action. "\
- "Please access GitLab from a web browser to accept these terms."
+ "To accept these terms, please access GitLab from a web browser at #{Gitlab.config.gitlab.url}."
when :deactivated
"Your account has been deactivated by your administrator. "\
"Please log back in from a web browser to reactivate your account at #{Gitlab.config.gitlab.url}"
diff --git a/lib/gitlab/background_migration.rb b/lib/gitlab/background_migration.rb
index 22b4b685f81..ed58508182a 100644
--- a/lib/gitlab/background_migration.rb
+++ b/lib/gitlab/background_migration.rb
@@ -2,11 +2,13 @@
module Gitlab
module BackgroundMigration
+ DEFAULT_TRACKING_DATABASE = Gitlab::Database::MAIN_DATABASE_NAME
+
def self.coordinator_for_database(database)
- JobCoordinator.for_database(database)
+ JobCoordinator.for_tracking_database(database)
end
- def self.queue(database: :main)
+ def self.queue(database: DEFAULT_TRACKING_DATABASE)
coordinator_for_database(database).queue
end
@@ -22,7 +24,7 @@ module Gitlab
# steal_class - The name of the class for which to steal jobs.
# retry_dead_jobs - Flag to control whether jobs in Sidekiq::RetrySet or Sidekiq::DeadSet are retried.
# database - tracking database this migration executes against
- def self.steal(steal_class, retry_dead_jobs: false, database: :main, &block)
+ def self.steal(steal_class, retry_dead_jobs: false, database: DEFAULT_TRACKING_DATABASE, &block)
coordinator_for_database(database).steal(steal_class, retry_dead_jobs: retry_dead_jobs, &block)
end
@@ -35,15 +37,15 @@ module Gitlab
# arguments - The arguments to pass to the background migration's "perform"
# method.
# database - tracking database this migration executes against
- def self.perform(class_name, arguments, database: :main)
+ def self.perform(class_name, arguments, database: DEFAULT_TRACKING_DATABASE)
coordinator_for_database(database).perform(class_name, arguments)
end
- def self.exists?(migration_class, additional_queues = [], database: :main)
+ def self.exists?(migration_class, additional_queues = [], database: DEFAULT_TRACKING_DATABASE)
coordinator_for_database(database).exists?(migration_class, additional_queues) # rubocop:disable CodeReuse/ActiveRecord
end
- def self.remaining(database: :main)
+ def self.remaining(database: DEFAULT_TRACKING_DATABASE)
coordinator_for_database(database).remaining
end
end
diff --git a/lib/gitlab/background_migration/add_merge_request_diff_commits_count.rb b/lib/gitlab/background_migration/add_merge_request_diff_commits_count.rb
deleted file mode 100644
index 5b9ee8a0ee2..00000000000
--- a/lib/gitlab/background_migration/add_merge_request_diff_commits_count.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-# frozen_string_literal: true
-# rubocop:disable Style/Documentation
-
-module Gitlab
- module BackgroundMigration
- class AddMergeRequestDiffCommitsCount
- class MergeRequestDiff < ActiveRecord::Base
- self.table_name = 'merge_request_diffs'
- end
-
- def perform(start_id, stop_id)
- Gitlab::AppLogger.info("Setting commits_count for merge request diffs: #{start_id} - #{stop_id}")
-
- update = '
- commits_count = (
- SELECT count(*)
- FROM merge_request_diff_commits
- WHERE merge_request_diffs.id = merge_request_diff_commits.merge_request_diff_id
- )'.squish
-
- MergeRequestDiff.where(id: start_id..stop_id).where(commits_count: nil).update_all(update)
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/add_modified_to_approval_merge_request_rule.rb b/lib/gitlab/background_migration/add_modified_to_approval_merge_request_rule.rb
deleted file mode 100644
index 2148e96f6b4..00000000000
--- a/lib/gitlab/background_migration/add_modified_to_approval_merge_request_rule.rb
+++ /dev/null
@@ -1,73 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # Compare all current rules to project rules
- class AddModifiedToApprovalMergeRequestRule
- # Stubbed class to access the Group table
- class Group < ActiveRecord::Base
- self.table_name = 'namespaces'
- self.inheritance_column = :_type_disabled
- end
-
- # Stubbed class to access the ApprovalMergeRequestRule table
- class ApprovalMergeRequestRule < ActiveRecord::Base
- self.table_name = 'approval_merge_request_rules'
-
- has_one :approval_merge_request_rule_source, class_name: 'AddModifiedToApprovalMergeRequestRule::ApprovalMergeRequestRuleSource'
- has_one :approval_project_rule, through: :approval_merge_request_rule_source
- has_and_belongs_to_many :groups,
- class_name: 'AddModifiedToApprovalMergeRequestRule::Group', join_table: "#{self.table_name}_groups"
- end
-
- # Stubbed class to access the ApprovalProjectRule table
- class ApprovalProjectRule < ActiveRecord::Base
- self.table_name = 'approval_project_rules'
-
- has_many :approval_merge_request_rule_sources, class_name: 'AddModifiedToApprovalMergeRequestRule::ApprovalMergeRequestRuleSource'
- has_and_belongs_to_many :groups,
- class_name: 'AddModifiedToApprovalMergeRequestRule::Group', join_table: "#{self.table_name}_groups"
- end
-
- # Stubbed class to access the ApprovalMergeRequestRuleSource table
- class ApprovalMergeRequestRuleSource < ActiveRecord::Base
- self.table_name = 'approval_merge_request_rule_sources'
-
- belongs_to :approval_merge_request_rule, class_name: 'AddModifiedToApprovalMergeRequestRule::ApprovalMergeRequestRule'
- belongs_to :approval_project_rule, class_name: 'AddModifiedToApprovalMergeRequestRule::ApprovalProjectRule'
- end
-
- def perform(start_id, stop_id)
- approval_merge_requests_rules = ApprovalMergeRequestRule
- .joins(:groups, :approval_merge_request_rule_source)
- .where(id: start_id..stop_id)
- .pluck(
- 'approval_merge_request_rule_sources.id as ars_id',
- 'approval_merge_request_rules_groups.id as amrg_id'
- )
-
- approval_project_rules = ApprovalProjectRule
- .joins(:groups, approval_merge_request_rule_sources: :approval_merge_request_rule)
- .where(approval_merge_request_rules: { id: start_id..stop_id })
- .pluck(
- 'approval_merge_request_rule_sources.id as ars_id',
- 'approval_project_rules_groups.id as apg_id'
- )
-
- different_names_or_approval_sources = ApprovalMergeRequestRule.joins(:approval_project_rule, :approval_merge_request_rule_source)
- .where(id: start_id..stop_id)
- .where('approval_merge_request_rules.name != approval_project_rules.name OR ' \
- 'approval_merge_request_rules.approvals_required != approval_project_rules.approvals_required')
- .pluck('approval_merge_request_rule_sources.id as ars_id')
-
- intersected_set = approval_merge_requests_rules.to_set ^ approval_project_rules.to_set
- source_ids = intersected_set.collect { |rule| rule[0] }.uniq
-
- rule_sources = ApprovalMergeRequestRuleSource.where(id: source_ids + different_names_or_approval_sources)
- changed_merge_request_rules = ApprovalMergeRequestRule.where(id: rule_sources.select(:approval_merge_request_rule_id))
-
- changed_merge_request_rules.update_all(modified_from_project_rule: true)
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/backfill_deployment_clusters_from_deployments.rb b/lib/gitlab/background_migration/backfill_deployment_clusters_from_deployments.rb
deleted file mode 100644
index 9778f360e87..00000000000
--- a/lib/gitlab/background_migration/backfill_deployment_clusters_from_deployments.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # Backfill deployment_clusters for a range of deployments
- class BackfillDeploymentClustersFromDeployments
- def perform(start_id, end_id)
- ActiveRecord::Base.connection.execute <<~SQL
- INSERT INTO deployment_clusters (deployment_id, cluster_id)
- SELECT deployments.id, deployments.cluster_id
- FROM deployments
- WHERE deployments.cluster_id IS NOT NULL
- AND deployments.id BETWEEN #{start_id} AND #{end_id}
- ON CONFLICT DO NOTHING
- SQL
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/backfill_environment_id_deployment_merge_requests.rb b/lib/gitlab/background_migration/backfill_environment_id_deployment_merge_requests.rb
deleted file mode 100644
index 4fd3b81fda3..00000000000
--- a/lib/gitlab/background_migration/backfill_environment_id_deployment_merge_requests.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # BackfillEnvironmentIdDeploymentMergeRequests deletes duplicates
- # from deployment_merge_requests table and backfills environment_id
- class BackfillEnvironmentIdDeploymentMergeRequests
- def perform(_start_mr_id, _stop_mr_id)
- # no-op
-
- # Background migration removed due to
- # https://gitlab.com/gitlab-org/gitlab/-/issues/217191
- end
-
- def backfill_range(start_mr_id, stop_mr_id)
- start_mr_id = Integer(start_mr_id)
- stop_mr_id = Integer(stop_mr_id)
-
- ActiveRecord::Base.connection.execute(<<~SQL)
- DELETE FROM deployment_merge_requests
- WHERE (deployment_id, merge_request_id) in (
- SELECT t.deployment_id, t.merge_request_id FROM (
- SELECT mrd.merge_request_id, mrd.deployment_id, ROW_NUMBER() OVER w AS rnum
- FROM deployment_merge_requests as mrd
- INNER JOIN "deployments" ON "deployments"."id" = "mrd"."deployment_id"
- WHERE mrd.merge_request_id BETWEEN #{start_mr_id} AND #{stop_mr_id}
- WINDOW w AS (
- PARTITION BY merge_request_id, deployments.environment_id
- ORDER BY deployments.id
- )
- ) t
- WHERE t.rnum > 1
- );
- SQL
-
- ActiveRecord::Base.connection.execute(<<~SQL)
- UPDATE deployment_merge_requests
- SET environment_id = deployments.environment_id
- FROM deployments
- WHERE deployments.id = "deployment_merge_requests".deployment_id
- AND "deployment_merge_requests".environment_id IS NULL
- AND "deployment_merge_requests".merge_request_id BETWEEN #{start_mr_id} AND #{stop_mr_id}
- SQL
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/backfill_merge_request_cleanup_schedules.rb b/lib/gitlab/background_migration/backfill_merge_request_cleanup_schedules.rb
deleted file mode 100644
index 8a58cf9b302..00000000000
--- a/lib/gitlab/background_migration/backfill_merge_request_cleanup_schedules.rb
+++ /dev/null
@@ -1,40 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # Backfill merge request cleanup schedules of closed/merged merge requests
- # without any corresponding records.
- class BackfillMergeRequestCleanupSchedules
- # Model used for migration added in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46782.
- class MergeRequest < ActiveRecord::Base
- include EachBatch
-
- self.table_name = 'merge_requests'
-
- def self.eligible
- where('merge_requests.state_id IN (2, 3)')
- end
- end
-
- def perform(start_id, end_id)
- eligible_mrs = MergeRequest.eligible.where(id: start_id..end_id)
- scheduled_at_column = "COALESCE(metrics.merged_at, COALESCE(metrics.latest_closed_at, merge_requests.updated_at)) + interval '14 days'"
- query =
- eligible_mrs
- .select("merge_requests.id, #{scheduled_at_column}, NOW(), NOW()")
- .joins('LEFT JOIN merge_request_metrics metrics ON metrics.merge_request_id = merge_requests.id')
-
- result = ActiveRecord::Base.connection.execute <<~SQL
- INSERT INTO merge_request_cleanup_schedules (merge_request_id, scheduled_at, created_at, updated_at)
- #{query.to_sql}
- ON CONFLICT (merge_request_id) DO NOTHING;
- SQL
-
- ::Gitlab::BackgroundMigration::Logger.info(
- message: 'Backfilled merge_request_cleanup_schedules records',
- count: result.cmd_tuples
- )
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/backfill_namespace_settings.rb b/lib/gitlab/background_migration/backfill_namespace_settings.rb
deleted file mode 100644
index a391d5f4ebe..00000000000
--- a/lib/gitlab/background_migration/backfill_namespace_settings.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # Backfillnamespace_settings for a range of namespaces
- class BackfillNamespaceSettings
- def perform(start_id, end_id)
- ActiveRecord::Base.connection.execute <<~SQL
- INSERT INTO namespace_settings (namespace_id, created_at, updated_at)
- SELECT namespaces.id, now(), now()
- FROM namespaces
- WHERE namespaces.id BETWEEN #{start_id} AND #{end_id}
- ON CONFLICT (namespace_id) DO NOTHING;
- SQL
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/backfill_project_settings.rb b/lib/gitlab/background_migration/backfill_project_settings.rb
deleted file mode 100644
index 7d7f19e1fda..00000000000
--- a/lib/gitlab/background_migration/backfill_project_settings.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # Backfill project_settings for a range of projects
- class BackfillProjectSettings
- def perform(start_id, end_id)
- ActiveRecord::Base.connection.execute <<~SQL
- INSERT INTO project_settings (project_id, created_at, updated_at)
- SELECT projects.id, now(), now()
- FROM projects
- WHERE projects.id BETWEEN #{start_id} AND #{end_id}
- ON CONFLICT (project_id) DO NOTHING;
- SQL
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/backfill_push_rules_id_in_projects.rb b/lib/gitlab/background_migration/backfill_push_rules_id_in_projects.rb
deleted file mode 100644
index 9b9ef70424a..00000000000
--- a/lib/gitlab/background_migration/backfill_push_rules_id_in_projects.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # Class that will insert record into project_push_rules
- # for each existing push_rule
- class BackfillPushRulesIdInProjects
- # Temporary AR table for push rules
- class ProjectSetting < ActiveRecord::Base
- self.table_name = 'project_settings'
- end
-
- def perform(start_id, stop_id)
- ProjectSetting.connection.execute(<<~SQL)
- UPDATE project_settings ps1
- SET push_rule_id = pr.id
- FROM project_settings ps2
- INNER JOIN push_rules pr
- ON ps2.project_id = pr.project_id
- WHERE pr.is_sample = false
- AND pr.id BETWEEN #{start_id} AND #{stop_id}
- AND ps1.project_id = ps2.project_id
- SQL
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/drop_invalid_remediations.rb b/lib/gitlab/background_migration/drop_invalid_remediations.rb
new file mode 100644
index 00000000000..f0a0de586f5
--- /dev/null
+++ b/lib/gitlab/background_migration/drop_invalid_remediations.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # rubocop: disable Style/Documentation
+ class DropInvalidRemediations
+ def perform(start_id, stop_id)
+ end
+ end
+ # rubocop: enable Style/Documentation
+ end
+end
+
+Gitlab::BackgroundMigration::DropInvalidRemediations.prepend_mod_with('Gitlab::BackgroundMigration::DropInvalidRemediations')
diff --git a/lib/gitlab/background_migration/drop_invalid_security_findings.rb b/lib/gitlab/background_migration/drop_invalid_security_findings.rb
new file mode 100644
index 00000000000..87551bb1b1e
--- /dev/null
+++ b/lib/gitlab/background_migration/drop_invalid_security_findings.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+module Gitlab
+ module BackgroundMigration
+ # Drop rows from security_findings where the uuid is NULL
+ class DropInvalidSecurityFindings
+ # rubocop:disable Style/Documentation
+ class SecurityFinding < ActiveRecord::Base
+ include ::EachBatch
+ self.table_name = 'security_findings'
+ scope :no_uuid, -> { where(uuid: nil) }
+ end
+ # rubocop:enable Style/Documentation
+
+ PAUSE_SECONDS = 0.1
+
+ def perform(start_id, end_id, sub_batch_size)
+ ranged_query = SecurityFinding
+ .where(id: start_id..end_id)
+ .no_uuid
+
+ ranged_query.each_batch(of: sub_batch_size) do |sub_batch|
+ first, last = sub_batch.pluck(Arel.sql('min(id), max(id)')).first
+
+ # The query need to be reconstructed because .each_batch modifies the default scope
+ # See: https://gitlab.com/gitlab-org/gitlab/-/issues/330510
+ SecurityFinding.unscoped
+ .where(id: first..last)
+ .no_uuid
+ .delete_all
+
+ sleep PAUSE_SECONDS
+ end
+
+ mark_job_as_succeeded(start_id, end_id, sub_batch_size)
+ end
+
+ private
+
+ def mark_job_as_succeeded(*arguments)
+ Gitlab::Database::BackgroundMigrationJob.mark_all_as_succeeded(
+ self.class.name.demodulize,
+ arguments
+ )
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/fix_promoted_epics_discussion_ids.rb b/lib/gitlab/background_migration/fix_promoted_epics_discussion_ids.rb
deleted file mode 100644
index 1a80ccdee92..00000000000
--- a/lib/gitlab/background_migration/fix_promoted_epics_discussion_ids.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # This migration updates discussion ids for epics that were promoted from issue so that the discussion id on epics
- # is different from discussion id on issue, which was causing problems when replying to epic discussions as it would
- # identify the discussion as related to an issue and complaint about missing project_id
- class FixPromotedEpicsDiscussionIds
- # notes model to iterate through the notes to be updated
- class Note < ActiveRecord::Base
- self.table_name = 'notes'
- self.inheritance_column = :_type_disabled
- end
-
- def perform(discussion_ids)
- Note.where(noteable_type: 'Epic')
- .where(discussion_id: discussion_ids)
- .update_all("discussion_id=MD5(discussion_id)||substring(discussion_id from 1 for 8)")
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/fix_user_namespace_names.rb b/lib/gitlab/background_migration/fix_user_namespace_names.rb
deleted file mode 100644
index cd5b4ab103d..00000000000
--- a/lib/gitlab/background_migration/fix_user_namespace_names.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # This migration fixes the namespaces.name for all user-namespaces that have names
- # that aren't equal to the users name.
- # Then it uses the updated names of the namespaces to update the associated routes
- # For more info see https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/23272
- class FixUserNamespaceNames
- def perform(from_id, to_id)
- fix_namespace_names(from_id, to_id)
- fix_namespace_route_names(from_id, to_id)
- end
-
- def fix_namespace_names(from_id, to_id)
- ActiveRecord::Base.connection.execute <<~UPDATE_NAMESPACES
- WITH namespaces_to_update AS #{Gitlab::Database::AsWithMaterialized.materialized_if_supported} (
- SELECT
- namespaces.id,
- users.name AS correct_name
- FROM
- namespaces
- INNER JOIN users ON namespaces.owner_id = users.id
- WHERE
- namespaces.type IS NULL
- AND namespaces.id BETWEEN #{from_id} AND #{to_id}
- AND namespaces.name != users.name
- )
- UPDATE
- namespaces
- SET
- name = correct_name
- FROM
- namespaces_to_update
- WHERE
- namespaces.id = namespaces_to_update.id
- UPDATE_NAMESPACES
- end
-
- def fix_namespace_route_names(from_id, to_id)
- ActiveRecord::Base.connection.execute <<~ROUTES_UPDATE
- WITH routes_to_update AS #{Gitlab::Database::AsWithMaterialized.materialized_if_supported} (
- SELECT
- routes.id,
- users.name AS correct_name
- FROM
- routes
- INNER JOIN namespaces ON routes.source_id = namespaces.id
- INNER JOIN users ON namespaces.owner_id = users.id
- WHERE
- namespaces.type IS NULL
- AND routes.source_type = 'Namespace'
- AND namespaces.id BETWEEN #{from_id} AND #{to_id}
- AND (routes.name != users.name OR routes.name IS NULL)
- )
- UPDATE
- routes
- SET
- name = correct_name
- FROM
- routes_to_update
- WHERE
- routes_to_update.id = routes.id
- ROUTES_UPDATE
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/fix_user_project_route_names.rb b/lib/gitlab/background_migration/fix_user_project_route_names.rb
deleted file mode 100644
index e534f2449aa..00000000000
--- a/lib/gitlab/background_migration/fix_user_project_route_names.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # This migration fixes the routes.name for all user-projects that have names
- # that don't start with the users name.
- # For more info see https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/23272
- class FixUserProjectRouteNames
- def perform(from_id, to_id)
- ActiveRecord::Base.connection.execute <<~ROUTES_UPDATE
- WITH routes_to_update AS #{Gitlab::Database::AsWithMaterialized.materialized_if_supported} (
- SELECT
- routes.id,
- users.name || ' / ' || projects.name AS correct_name
- FROM
- routes
- INNER JOIN projects ON routes.source_id = projects.id
- INNER JOIN namespaces ON projects.namespace_id = namespaces.id
- INNER JOIN users ON namespaces.owner_id = users.id
- WHERE
- routes.source_type = 'Project'
- AND routes.id BETWEEN #{from_id} AND #{to_id}
- AND namespaces.type IS NULL
- AND (routes.name NOT LIKE users.name || '%' OR routes.name IS NULL)
- )
- UPDATE
- routes
- SET
- name = routes_to_update.correct_name
- FROM
- routes_to_update
- WHERE
- routes_to_update.id = routes.id
- ROUTES_UPDATE
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/job_coordinator.rb b/lib/gitlab/background_migration/job_coordinator.rb
index 1c8819eaa62..cfbe7167677 100644
--- a/lib/gitlab/background_migration/job_coordinator.rb
+++ b/lib/gitlab/background_migration/job_coordinator.rb
@@ -8,24 +8,33 @@ module Gitlab
# convention of how the queues and worker classes are setup for each database.
#
# Also provides a database connection to the correct tracking database.
- class JobCoordinator
- VALID_DATABASES = %i[main].freeze
- WORKER_CLASS_NAME = 'BackgroundMigrationWorker'
-
- def self.for_database(database)
- database = database.to_sym
+ class JobCoordinator # rubocop:disable Metrics/ClassLength
+ class << self
+ def worker_classes
+ @worker_classes ||= [
+ BackgroundMigrationWorker
+ ].freeze
+ end
- unless VALID_DATABASES.include?(database)
- raise ArgumentError, "database must be one of [#{VALID_DATABASES.join(', ')}], got '#{database}'"
+ def worker_for_tracking_database
+ @worker_for_tracking_database ||= worker_classes
+ .index_by(&:tracking_database)
+ .with_indifferent_access
+ .freeze
end
- namespace = database.to_s.capitalize unless database == :main
- namespaced_worker_class = [namespace, WORKER_CLASS_NAME].compact.join('::')
+ def for_tracking_database(tracking_database)
+ worker_class = worker_for_tracking_database[tracking_database]
- new(database, "::#{namespaced_worker_class}".constantize)
+ if worker_class.nil?
+ raise ArgumentError, "tracking_database must be one of [#{worker_for_tracking_database.keys.join(', ')}]"
+ end
+
+ new(worker_class)
+ end
end
- attr_reader :database, :worker_class
+ attr_reader :worker_class
def queue
@queue ||= worker_class.sidekiq_options['queue']
@@ -118,15 +127,14 @@ module Gitlab
private
- def initialize(database, worker_class)
- @database = database
+ def initialize(worker_class)
@worker_class = worker_class
end
def connection
@connection ||= Gitlab::Database
.database_base_models
- .fetch(database, Gitlab::Database::PRIMARY_DATABASE_NAME)
+ .fetch(worker_class.tracking_database, Gitlab::Database::PRIMARY_DATABASE_NAME)
.connection
end
end
diff --git a/lib/gitlab/background_migration/link_lfs_objects_projects.rb b/lib/gitlab/background_migration/link_lfs_objects_projects.rb
deleted file mode 100644
index 983470c5121..00000000000
--- a/lib/gitlab/background_migration/link_lfs_objects_projects.rb
+++ /dev/null
@@ -1,82 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # Create missing LfsObjectsProject records for forks
- class LinkLfsObjectsProjects
- # Model specifically used for migration.
- class LfsObjectsProject < ActiveRecord::Base
- include EachBatch
-
- self.table_name = 'lfs_objects_projects'
-
- def self.linkable
- where(
- <<~SQL
- lfs_objects_projects.project_id IN (
- SELECT fork_network_members.forked_from_project_id
- FROM fork_network_members
- WHERE fork_network_members.forked_from_project_id IS NOT NULL
- )
- SQL
- )
- end
- end
-
- # Model specifically used for migration.
- class ForkNetworkMember < ActiveRecord::Base
- include EachBatch
-
- self.table_name = 'fork_network_members'
-
- def self.without_lfs_object(lfs_object_id)
- where(
- <<~SQL
- fork_network_members.project_id NOT IN (
- SELECT lop.project_id
- FROM lfs_objects_projects lop
- WHERE lop.lfs_object_id = #{lfs_object_id}
- )
- SQL
- )
- end
- end
-
- BATCH_SIZE = 1000
-
- def perform(start_id, end_id)
- lfs_objects_projects =
- Gitlab::BackgroundMigration::LinkLfsObjectsProjects::LfsObjectsProject
- .linkable
- .where(id: start_id..end_id)
-
- return if lfs_objects_projects.empty?
-
- lfs_objects_projects.find_each do |lop|
- ForkNetworkMember
- .select("#{lop.lfs_object_id}, fork_network_members.project_id, NOW(), NOW()")
- .without_lfs_object(lop.lfs_object_id)
- .where(forked_from_project_id: lop.project_id)
- .each_batch(of: BATCH_SIZE) do |batch, index|
- execute <<~SQL
- INSERT INTO lfs_objects_projects (lfs_object_id, project_id, created_at, updated_at)
- #{batch.to_sql}
- SQL
-
- logger.info(message: "LinkLfsObjectsProjects: created missing LfsObjectsProject records for LfsObject #{lop.lfs_object_id}")
- end
- end
- end
-
- private
-
- def execute(sql)
- ::ActiveRecord::Base.connection.execute(sql)
- end
-
- def logger
- @logger ||= Gitlab::BackgroundMigration::Logger.build
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/migrate_fingerprint_sha256_within_keys.rb b/lib/gitlab/background_migration/migrate_fingerprint_sha256_within_keys.rb
deleted file mode 100644
index 36a339c6b80..00000000000
--- a/lib/gitlab/background_migration/migrate_fingerprint_sha256_within_keys.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # This class is responsible to update all sha256 fingerprints within the keys table
- class MigrateFingerprintSha256WithinKeys
- # Temporary AR table for keys
- class Key < ActiveRecord::Base
- include EachBatch
-
- self.table_name = 'keys'
- self.inheritance_column = :_type_disabled
- end
-
- TEMP_TABLE = 'tmp_fingerprint_sha256_migration'
-
- def perform(start_id, stop_id)
- ActiveRecord::Base.transaction do
- execute(<<~SQL)
- CREATE TEMPORARY TABLE #{TEMP_TABLE}
- (id bigint primary key, fingerprint_sha256 bytea not null)
- ON COMMIT DROP
- SQL
-
- fingerprints = []
- Key.where(id: start_id..stop_id, fingerprint_sha256: nil).find_each do |regular_key|
- if fingerprint = generate_ssh_public_key(regular_key.key)
- bytea = ActiveRecord::Base.connection.escape_bytea(Base64.decode64(fingerprint))
-
- fingerprints << {
- id: regular_key.id,
- fingerprint_sha256: bytea
- }
- end
- end
-
- ApplicationRecord.legacy_bulk_insert(TEMP_TABLE, fingerprints) # rubocop:disable Gitlab/BulkInsert
-
- execute("ANALYZE #{TEMP_TABLE}")
-
- execute(<<~SQL)
- UPDATE keys
- SET fingerprint_sha256 = t.fingerprint_sha256
- FROM #{TEMP_TABLE} t
- WHERE keys.id = t.id
- SQL
- end
- end
-
- private
-
- def generate_ssh_public_key(regular_key)
- Gitlab::SSHPublicKey.new(regular_key).fingerprint("SHA256")&.gsub("SHA256:", "")
- end
-
- def execute(query)
- ActiveRecord::Base.connection.execute(query)
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/migrate_pages_metadata.rb b/lib/gitlab/background_migration/migrate_pages_metadata.rb
deleted file mode 100644
index 68fd0c17d29..00000000000
--- a/lib/gitlab/background_migration/migrate_pages_metadata.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # Class that will insert record into project_pages_metadata
- # for each existing project
- class MigratePagesMetadata
- def perform(start_id, stop_id)
- perform_on_relation(Project.where(id: start_id..stop_id))
- end
-
- def perform_on_relation(relation)
- successful_pages_deploy = <<~SQL
- SELECT TRUE
- FROM ci_builds
- WHERE ci_builds.type = 'GenericCommitStatus'
- AND ci_builds.status = 'success'
- AND ci_builds.stage = 'deploy'
- AND ci_builds.name = 'pages:deploy'
- AND ci_builds.project_id = projects.id
- LIMIT 1
- SQL
-
- select_from = relation
- .select("projects.id", "COALESCE((#{successful_pages_deploy}), FALSE)")
- .to_sql
-
- ActiveRecord::Base.connection_pool.with_connection do |connection|
- connection.execute <<~SQL
- INSERT INTO project_pages_metadata (project_id, deployed)
- #{select_from}
- ON CONFLICT (project_id) DO NOTHING
- SQL
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/migrate_to_hashed_storage.rb b/lib/gitlab/background_migration/migrate_to_hashed_storage.rb
deleted file mode 100644
index 4054db4fb87..00000000000
--- a/lib/gitlab/background_migration/migrate_to_hashed_storage.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # Background migration to move any legacy project to Hashed Storage
- class MigrateToHashedStorage
- def perform
- batch_size = helper.batch_size
- legacy_projects_count = Project.with_unmigrated_storage.count
-
- if storage_migrator.rollback_pending?
- logger.warn(
- migrator: 'MigrateToHashedStorage',
- message: 'Aborting an storage rollback operation currently in progress'
- )
-
- storage_migrator.abort_rollback!
- end
-
- if legacy_projects_count == 0
- logger.info(
- migrator: 'MigrateToHashedStorage',
- message: 'There are no projects requiring migration to Hashed Storage'
- )
-
- return
- end
-
- logger.info(
- migrator: 'MigrateToHashedStorage',
- message: "Enqueuing migration of #{legacy_projects_count} projects in batches of #{batch_size}"
- )
-
- helper.project_id_batches_migration do |start, finish|
- storage_migrator.bulk_schedule_migration(start: start, finish: finish)
-
- logger.info(
- migrator: 'MigrateToHashedStorage',
- message: "Enqueuing migration of projects in batches of #{batch_size} from ID=#{start} to ID=#{finish}",
- batch_from: start,
- batch_to: finish
- )
- end
- end
-
- private
-
- def helper
- Gitlab::HashedStorage::RakeHelper
- end
-
- def storage_migrator
- @storage_migrator ||= Gitlab::HashedStorage::Migrator.new
- end
-
- def logger
- @logger ||= ::Gitlab::BackgroundMigration::Logger.build
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/move_epic_issues_after_epics.rb b/lib/gitlab/background_migration/move_epic_issues_after_epics.rb
deleted file mode 100644
index 174994c7862..00000000000
--- a/lib/gitlab/background_migration/move_epic_issues_after_epics.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # rubocop: disable Style/Documentation
- class MoveEpicIssuesAfterEpics
- def perform(start_id, stop_id)
- end
- end
- end
-end
-
-Gitlab::BackgroundMigration::MoveEpicIssuesAfterEpics.prepend_mod_with('Gitlab::BackgroundMigration::MoveEpicIssuesAfterEpics')
diff --git a/lib/gitlab/background_migration/populate_any_approval_rule_for_merge_requests.rb b/lib/gitlab/background_migration/populate_any_approval_rule_for_merge_requests.rb
deleted file mode 100644
index 890a43800c9..00000000000
--- a/lib/gitlab/background_migration/populate_any_approval_rule_for_merge_requests.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # This background migration creates any approver rule records according
- # to the given merge request IDs range. A _single_ INSERT is issued for the given range.
- class PopulateAnyApprovalRuleForMergeRequests
- def perform(from_id, to_id)
- end
- end
- end
-end
-
-Gitlab::BackgroundMigration::PopulateAnyApprovalRuleForMergeRequests.prepend_mod_with('Gitlab::BackgroundMigration::PopulateAnyApprovalRuleForMergeRequests')
diff --git a/lib/gitlab/background_migration/populate_any_approval_rule_for_projects.rb b/lib/gitlab/background_migration/populate_any_approval_rule_for_projects.rb
deleted file mode 100644
index ac7ed18ba14..00000000000
--- a/lib/gitlab/background_migration/populate_any_approval_rule_for_projects.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # This background migration creates any approver rule records according
- # to the given project IDs range. A _single_ INSERT is issued for the given range.
- class PopulateAnyApprovalRuleForProjects
- def perform(from_id, to_id)
- end
- end
- end
-end
-
-Gitlab::BackgroundMigration::PopulateAnyApprovalRuleForProjects.prepend_mod_with('Gitlab::BackgroundMigration::PopulateAnyApprovalRuleForProjects')
diff --git a/lib/gitlab/background_migration/populate_canonical_emails.rb b/lib/gitlab/background_migration/populate_canonical_emails.rb
deleted file mode 100644
index 052e75c5655..00000000000
--- a/lib/gitlab/background_migration/populate_canonical_emails.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # Class to populate new rows of UserCanonicalEmail based on existing email addresses
- class PopulateCanonicalEmails
- def perform(start_id, stop_id)
- ActiveRecord::Base.connection.execute <<~SQL
- INSERT INTO
- user_canonical_emails (
- user_id,
- canonical_email,
- created_at,
- updated_at
- )
- SELECT users.id AS user_id,
- concat(translate(split_part(split_part(users.email, '@', 1), '+', 1), '.', ''), '@gmail.com') AS canonical_email,
- NOW() AS created_at,
- NOW() AS updated_at
- FROM users
- WHERE users.email ILIKE '%@gmail.com'
- AND users.id BETWEEN #{start_id} AND #{stop_id}
- ON CONFLICT DO NOTHING;
- SQL
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/populate_dismissed_state_for_vulnerabilities.rb b/lib/gitlab/background_migration/populate_dismissed_state_for_vulnerabilities.rb
deleted file mode 100644
index 68c91650d93..00000000000
--- a/lib/gitlab/background_migration/populate_dismissed_state_for_vulnerabilities.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # This class updates vulnerabilities entities with state dismissed
- class PopulateDismissedStateForVulnerabilities
- class Vulnerability < ActiveRecord::Base # rubocop:disable Style/Documentation
- self.table_name = 'vulnerabilities'
- end
-
- def perform(*vulnerability_ids)
- Vulnerability.where(id: vulnerability_ids).update_all(state: 2)
- PopulateMissingVulnerabilityDismissalInformation.new.perform(*vulnerability_ids)
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/populate_has_vulnerabilities.rb b/lib/gitlab/background_migration/populate_has_vulnerabilities.rb
deleted file mode 100644
index 28ff2070209..00000000000
--- a/lib/gitlab/background_migration/populate_has_vulnerabilities.rb
+++ /dev/null
@@ -1,64 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # This class populates missing dismissal information for
- # vulnerability entries.
- class PopulateHasVulnerabilities
- class ProjectSetting < ActiveRecord::Base # rubocop:disable Style/Documentation
- self.table_name = 'project_settings'
-
- def self.upsert_for(project_ids)
- connection.execute(upsert_sql % { project_ids: project_ids.join(', ') })
- end
-
- def self.upsert_sql
- <<~SQL
- WITH upsert_data (project_id, has_vulnerabilities, created_at, updated_at) AS #{Gitlab::Database::AsWithMaterialized.materialized_if_supported} (
- SELECT projects.id, true, current_timestamp, current_timestamp FROM projects WHERE projects.id IN (%{project_ids})
- )
- INSERT INTO project_settings
- (project_id, has_vulnerabilities, created_at, updated_at)
- (SELECT * FROM upsert_data)
- ON CONFLICT (project_id)
- DO UPDATE SET
- has_vulnerabilities = true,
- updated_at = EXCLUDED.updated_at
- SQL
- end
- end
-
- class Vulnerability < ActiveRecord::Base # rubocop:disable Style/Documentation
- include EachBatch
-
- self.table_name = 'vulnerabilities'
- end
-
- def perform(*project_ids)
- ProjectSetting.upsert_for(project_ids)
- rescue StandardError => e
- log_error(e, project_ids)
- ensure
- log_info(project_ids)
- end
-
- private
-
- def log_error(error, project_ids)
- ::Gitlab::BackgroundMigration::Logger.error(
- migrator: self.class.name,
- message: error.message,
- project_ids: project_ids
- )
- end
-
- def log_info(project_ids)
- ::Gitlab::BackgroundMigration::Logger.info(
- migrator: self.class.name,
- message: 'Projects has been processed to populate `has_vulnerabilities` information',
- count: project_ids.length
- )
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/populate_merge_request_assignees_table.rb b/lib/gitlab/background_migration/populate_merge_request_assignees_table.rb
deleted file mode 100644
index 28cc4a5e3fa..00000000000
--- a/lib/gitlab/background_migration/populate_merge_request_assignees_table.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # This background migration creates records on merge_request_assignees according
- # to the given merge request IDs range. A _single_ INSERT is issued for the given range.
- # This is required for supporting multiple assignees on merge requests.
- class PopulateMergeRequestAssigneesTable
- def perform(from_id, to_id)
- select_sql =
- MergeRequest
- .where(merge_request_assignees_not_exists_clause)
- .where(id: from_id..to_id)
- .where.not(assignee_id: nil)
- .select(:id, :assignee_id)
- .to_sql
-
- execute("INSERT INTO merge_request_assignees (merge_request_id, user_id) #{select_sql}")
- end
-
- def perform_all_sync(batch_size:)
- MergeRequest.each_batch(of: batch_size) do |batch|
- range = batch.pluck('MIN(id)', 'MAX(id)').first
-
- perform(*range)
- end
- end
-
- private
-
- def merge_request_assignees_not_exists_clause
- <<~SQL
- NOT EXISTS (SELECT 1 FROM merge_request_assignees
- WHERE merge_request_assignees.merge_request_id = merge_requests.id)
- SQL
- end
-
- def execute(sql)
- @connection ||= ActiveRecord::Base.connection
- @connection.execute(sql)
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/populate_missing_vulnerability_dismissal_information.rb b/lib/gitlab/background_migration/populate_missing_vulnerability_dismissal_information.rb
deleted file mode 100644
index 04342fdabd4..00000000000
--- a/lib/gitlab/background_migration/populate_missing_vulnerability_dismissal_information.rb
+++ /dev/null
@@ -1,89 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # This class populates missing dismissal information for
- # vulnerability entries.
- class PopulateMissingVulnerabilityDismissalInformation
- class Vulnerability < ActiveRecord::Base # rubocop:disable Style/Documentation
- include EachBatch
-
- self.table_name = 'vulnerabilities'
-
- has_one :finding, class_name: '::Gitlab::BackgroundMigration::PopulateMissingVulnerabilityDismissalInformation::Finding'
-
- scope :broken, -> { where('state = 2 AND (dismissed_at IS NULL OR dismissed_by_id IS NULL)') }
-
- def copy_dismissal_information
- return unless finding&.dismissal_feedback
-
- update_columns(
- dismissed_at: finding.dismissal_feedback.created_at,
- dismissed_by_id: finding.dismissal_feedback.author_id
- )
- end
- end
-
- class Finding < ActiveRecord::Base # rubocop:disable Style/Documentation
- include ShaAttribute
- include ::Gitlab::Utils::StrongMemoize
-
- self.table_name = 'vulnerability_occurrences'
-
- sha_attribute :project_fingerprint
-
- def dismissal_feedback
- strong_memoize(:dismissal_feedback) do
- Feedback.dismissal.where(category: report_type, project_fingerprint: project_fingerprint, project_id: project_id).first
- end
- end
- end
-
- class Feedback < ActiveRecord::Base # rubocop:disable Style/Documentation
- DISMISSAL_TYPE = 0
-
- self.table_name = 'vulnerability_feedback'
-
- scope :dismissal, -> { where(feedback_type: DISMISSAL_TYPE) }
- end
-
- def perform(*vulnerability_ids)
- Vulnerability.includes(:finding).where(id: vulnerability_ids).each { |vulnerability| populate_for(vulnerability) }
-
- log_info(vulnerability_ids)
- end
-
- private
-
- def populate_for(vulnerability)
- log_warning(vulnerability) unless vulnerability.copy_dismissal_information
- rescue StandardError => error
- log_error(error, vulnerability)
- end
-
- def log_info(vulnerability_ids)
- ::Gitlab::BackgroundMigration::Logger.info(
- migrator: self.class.name,
- message: 'Dismissal information has been copied',
- count: vulnerability_ids.length
- )
- end
-
- def log_warning(vulnerability)
- ::Gitlab::BackgroundMigration::Logger.warn(
- migrator: self.class.name,
- message: 'Could not update vulnerability!',
- vulnerability_id: vulnerability.id
- )
- end
-
- def log_error(error, vulnerability)
- ::Gitlab::BackgroundMigration::Logger.error(
- migrator: self.class.name,
- message: error.message,
- vulnerability_id: vulnerability.id
- )
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/populate_personal_snippet_statistics.rb b/lib/gitlab/background_migration/populate_personal_snippet_statistics.rb
deleted file mode 100644
index ed7ffce8018..00000000000
--- a/lib/gitlab/background_migration/populate_personal_snippet_statistics.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # This class creates/updates those personal snippets statistics
- # that haven't been created nor initialized.
- # It also updates the related root storage namespace stats
- class PopulatePersonalSnippetStatistics
- def perform(snippet_ids)
- personal_snippets(snippet_ids).group_by(&:author).each do |author, author_snippets|
- upsert_snippet_statistics(author_snippets)
- update_namespace_statistics(author.namespace)
- end
- end
-
- private
-
- def personal_snippets(snippet_ids)
- PersonalSnippet
- .where(id: snippet_ids)
- .includes(author: :namespace)
- .includes(:statistics)
- .includes(snippet_repository: :shard)
- end
-
- def upsert_snippet_statistics(snippets)
- snippets.each do |snippet|
- response = Snippets::UpdateStatisticsService.new(snippet).execute
-
- error_message("#{response.message} snippet: #{snippet.id}") if response.error?
- end
- end
-
- def update_namespace_statistics(namespace)
- Namespaces::StatisticsRefresherService.new.execute(namespace)
- rescue StandardError => e
- error_message("Error updating statistics for namespace #{namespace.id}: #{e.message}")
- end
-
- def logger
- @logger ||= Gitlab::BackgroundMigration::Logger.build
- end
-
- def error_message(message)
- logger.error(message: "Snippet Statistics Migration: #{message}")
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/populate_project_snippet_statistics.rb b/lib/gitlab/background_migration/populate_project_snippet_statistics.rb
deleted file mode 100644
index 37af320f044..00000000000
--- a/lib/gitlab/background_migration/populate_project_snippet_statistics.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # This class creates/updates those project snippets statistics
- # that haven't been created nor initialized.
- # It also updates the related project statistics and its root storage namespace stats
- class PopulateProjectSnippetStatistics
- def perform(snippet_ids)
- project_snippets(snippet_ids).group_by(&:namespace_id).each do |namespace_id, namespace_snippets|
- namespace_snippets.group_by(&:project).each do |project, snippets|
- upsert_snippet_statistics(snippets)
- update_project_statistics(project)
- rescue StandardError
- error_message("Error updating statistics for project #{project.id}")
- end
-
- update_namespace_statistics(namespace_snippets.first.project.root_namespace)
- rescue StandardError => e
- error_message("Error updating statistics for namespace #{namespace_id}: #{e.message}")
- end
- end
-
- private
-
- def project_snippets(snippet_ids)
- ProjectSnippet
- .select('snippets.*, projects.namespace_id')
- .where(id: snippet_ids)
- .joins(:project)
- .includes(:statistics)
- .includes(snippet_repository: :shard)
- .includes(project: [:route, :statistics, :namespace])
- end
-
- def upsert_snippet_statistics(snippets)
- snippets.each do |snippet|
- response = Snippets::UpdateStatisticsService.new(snippet).execute
-
- error_message("#{response.message} snippet: #{snippet.id}") if response.error?
- end
- end
-
- def logger
- @logger ||= Gitlab::BackgroundMigration::Logger.build
- end
-
- def error_message(message)
- logger.error(message: "Snippet Statistics Migration: #{message}")
- end
-
- def update_project_statistics(project)
- project.statistics&.refresh!(only: [:snippets_size])
- end
-
- def update_namespace_statistics(namespace)
- Namespaces::StatisticsRefresherService.new.execute(namespace)
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/populate_vulnerability_feedback_pipeline_id.rb b/lib/gitlab/background_migration/populate_vulnerability_feedback_pipeline_id.rb
deleted file mode 100644
index 8241fea66db..00000000000
--- a/lib/gitlab/background_migration/populate_vulnerability_feedback_pipeline_id.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # This class updates vulnerability feedback entities with no pipeline id assigned.
- class PopulateVulnerabilityFeedbackPipelineId
- def perform(project_ids)
- end
- end
- end
-end
-
-Gitlab::BackgroundMigration::PopulateVulnerabilityFeedbackPipelineId.prepend_mod_with('Gitlab::BackgroundMigration::PopulateVulnerabilityFeedbackPipelineId')
diff --git a/lib/gitlab/background_migration/populate_vulnerability_historical_statistics.rb b/lib/gitlab/background_migration/populate_vulnerability_historical_statistics.rb
deleted file mode 100644
index 9a9f23e29ea..00000000000
--- a/lib/gitlab/background_migration/populate_vulnerability_historical_statistics.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # This class creates/updates those project historical vulnerability statistics
- # that haven't been created nor initialized. It should only be executed in EE.
- class PopulateVulnerabilityHistoricalStatistics
- def perform(project_ids, retention_period = 90)
- end
- end
- end
-end
-
-Gitlab::BackgroundMigration::PopulateVulnerabilityHistoricalStatistics.prepend_mod_with('Gitlab::BackgroundMigration::PopulateVulnerabilityHistoricalStatistics')
diff --git a/lib/gitlab/background_migration/prune_orphaned_geo_events.rb b/lib/gitlab/background_migration/prune_orphaned_geo_events.rb
deleted file mode 100644
index 0efbe72775c..00000000000
--- a/lib/gitlab/background_migration/prune_orphaned_geo_events.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-#
-# rubocop:disable Style/Documentation
-
-# This job is added to fix https://gitlab.com/gitlab-org/gitlab/issues/30229
-# It's not used anywhere else.
-# Can be removed in GitLab 13.*
-module Gitlab
- module BackgroundMigration
- class PruneOrphanedGeoEvents
- def perform(table_name)
- end
- end
- end
-end
-
-Gitlab::BackgroundMigration::PruneOrphanedGeoEvents.prepend_mod_with('Gitlab::BackgroundMigration::PruneOrphanedGeoEvents')
diff --git a/lib/gitlab/background_migration/recalculate_project_authorizations_with_min_max_user_id.rb b/lib/gitlab/background_migration/recalculate_project_authorizations_with_min_max_user_id.rb
deleted file mode 100644
index b66fdfd5c65..00000000000
--- a/lib/gitlab/background_migration/recalculate_project_authorizations_with_min_max_user_id.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # rubocop:disable Style/Documentation
- class RecalculateProjectAuthorizationsWithMinMaxUserId
- def perform(min_user_id, max_user_id)
- User.where(id: min_user_id..max_user_id).find_each do |user|
- service = Users::RefreshAuthorizedProjectsService.new(
- user,
- incorrect_auth_found_callback:
- ->(project_id, access_level) do
- logger.info(message: 'Removing ProjectAuthorizations',
- user_id: user.id,
- project_id: project_id,
- access_level: access_level)
- end,
- missing_auth_found_callback:
- ->(project_id, access_level) do
- logger.info(message: 'Creating ProjectAuthorizations',
- user_id: user.id,
- project_id: project_id,
- access_level: access_level)
- end
- )
-
- service.execute
- end
- end
-
- private
-
- def logger
- @logger ||= Gitlab::BackgroundMigration::Logger.build
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/migrate_security_scans.rb b/lib/gitlab/background_migration/recalculate_vulnerability_finding_signatures_for_findings.rb
index 0ae984f2dbc..20200a1d508 100644
--- a/lib/gitlab/background_migration/migrate_security_scans.rb
+++ b/lib/gitlab/background_migration/recalculate_vulnerability_finding_signatures_for_findings.rb
@@ -3,11 +3,11 @@
module Gitlab
module BackgroundMigration
# rubocop: disable Style/Documentation
- class MigrateSecurityScans
+ class RecalculateVulnerabilityFindingSignaturesForFindings
def perform(start_id, stop_id)
end
end
end
end
-Gitlab::BackgroundMigration::MigrateSecurityScans.prepend_mod_with('Gitlab::BackgroundMigration::MigrateSecurityScans')
+Gitlab::BackgroundMigration::RecalculateVulnerabilityFindingSignaturesForFindings.prepend_mod
diff --git a/lib/gitlab/background_migration/remove_duplicate_cs_findings.rb b/lib/gitlab/background_migration/remove_duplicate_cs_findings.rb
deleted file mode 100644
index 17ef6dec4c0..00000000000
--- a/lib/gitlab/background_migration/remove_duplicate_cs_findings.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-# rubocop:disable Style/Documentation
-
-module Gitlab
- module BackgroundMigration
- class RemoveDuplicateCsFindings
- def perform(start_id, stop_id)
- end
- end
- end
-end
-
-Gitlab::BackgroundMigration::RemoveDuplicateCsFindings.prepend_mod_with('Gitlab::BackgroundMigration::RemoveDuplicateCsFindings')
diff --git a/lib/gitlab/background_migration/remove_duplicated_cs_findings_without_vulnerability_id.rb b/lib/gitlab/background_migration/remove_duplicated_cs_findings_without_vulnerability_id.rb
deleted file mode 100644
index e5772fc7375..00000000000
--- a/lib/gitlab/background_migration/remove_duplicated_cs_findings_without_vulnerability_id.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-# rubocop:disable Style/Documentation
-
-module Gitlab
- module BackgroundMigration
- class RemoveDuplicatedCsFindingsWithoutVulnerabilityId
- def perform(start_id, stop_id)
- end
- end
- end
-end
-
-Gitlab::BackgroundMigration::RemoveDuplicatedCsFindingsWithoutVulnerabilityId.prepend_mod_with('Gitlab::BackgroundMigration::RemoveDuplicatedCsFindingsWithoutVulnerabilityId')
diff --git a/lib/gitlab/background_migration/remove_inaccessible_epic_todos.rb b/lib/gitlab/background_migration/remove_inaccessible_epic_todos.rb
deleted file mode 100644
index cb6a600a525..00000000000
--- a/lib/gitlab/background_migration/remove_inaccessible_epic_todos.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # rubocop:disable Style/Documentation
- class RemoveInaccessibleEpicTodos
- def perform(start_id, stop_id)
- end
- end
- end
-end
-
-Gitlab::BackgroundMigration::RemoveInaccessibleEpicTodos.prepend_mod_with('Gitlab::BackgroundMigration::RemoveInaccessibleEpicTodos')
diff --git a/lib/gitlab/background_migration/remove_occurrence_pipelines_and_duplicate_vulnerabilities_findings.rb b/lib/gitlab/background_migration/remove_occurrence_pipelines_and_duplicate_vulnerabilities_findings.rb
new file mode 100644
index 00000000000..7fe5a427d10
--- /dev/null
+++ b/lib/gitlab/background_migration/remove_occurrence_pipelines_and_duplicate_vulnerabilities_findings.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+# This migration will look for Vulnerabilities::Finding objects that would have a duplicate UUIDv5 if the UUID was
+# recalculated. Then it removes Vulnerabilities::FindingPipeline objects associated with those Findings.
+# We can't just drop those Findings directly since the cascade drop will timeout if any given Finding has too many
+# associated FindingPipelines
+class Gitlab::BackgroundMigration::RemoveOccurrencePipelinesAndDuplicateVulnerabilitiesFindings
+ # rubocop:disable Gitlab/NamespacedClass, Style/Documentation
+ class VulnerabilitiesFinding < ActiveRecord::Base
+ self.table_name = "vulnerability_occurrences"
+ end
+
+ class VulnerabilitiesFindingPipeline < ActiveRecord::Base
+ include EachBatch
+ self.table_name = "vulnerability_occurrence_pipelines"
+ end
+ # rubocop:enable Gitlab/NamespacedClass, Style/Documentation
+
+ def perform(start_id, end_id)
+ ids_to_look_for = findings_that_would_produce_duplicate_uuids(start_id, end_id)
+
+ ids_to_look_for.each do |finding_id|
+ VulnerabilitiesFindingPipeline.where(occurrence_id: finding_id).each_batch(of: 1000) do |pipelines|
+ pipelines.delete_all
+ end
+ end
+
+ VulnerabilitiesFinding.where(id: ids_to_look_for).delete_all
+
+ mark_job_as_succeeded(start_id, end_id)
+ end
+
+ private
+
+ def findings_that_would_produce_duplicate_uuids(start_id, end_id)
+ VulnerabilitiesFinding
+ .from("vulnerability_occurrences to_delete")
+ .where("to_delete.id BETWEEN ? AND ?", start_id, end_id)
+ .where(
+ "EXISTS (
+ SELECT 1
+ FROM vulnerability_occurrences duplicates
+ WHERE duplicates.report_type = to_delete.report_type
+ AND duplicates.location_fingerprint = to_delete.location_fingerprint
+ AND duplicates.primary_identifier_id = to_delete.primary_identifier_id
+ AND duplicates.project_id = to_delete.project_id
+ AND duplicates.id > to_delete.id
+ )"
+ )
+ .pluck("to_delete.id")
+ end
+
+ def mark_job_as_succeeded(*arguments)
+ Gitlab::Database::BackgroundMigrationJob.mark_all_as_succeeded(
+ self.class.name.demodulize,
+ arguments
+ )
+ end
+end
diff --git a/lib/gitlab/background_migration/remove_undefined_vulnerability_confidence_level.rb b/lib/gitlab/background_migration/remove_undefined_vulnerability_confidence_level.rb
deleted file mode 100644
index 4be61bfb689..00000000000
--- a/lib/gitlab/background_migration/remove_undefined_vulnerability_confidence_level.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-# rubocop:disable Style/Documentation
-
-module Gitlab
- module BackgroundMigration
- class RemoveUndefinedVulnerabilityConfidenceLevel
- def perform(start_id, stop_id)
- end
- end
- end
-end
-
-Gitlab::BackgroundMigration::RemoveUndefinedVulnerabilityConfidenceLevel.prepend_mod_with('Gitlab::BackgroundMigration::RemoveUndefinedVulnerabilityConfidenceLevel')
diff --git a/lib/gitlab/background_migration/remove_vulnerability_finding_links.rb b/lib/gitlab/background_migration/remove_vulnerability_finding_links.rb
new file mode 100644
index 00000000000..31fb5e97c5d
--- /dev/null
+++ b/lib/gitlab/background_migration/remove_vulnerability_finding_links.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # Remove vulnerability finding link records
+ # The records will be repopulated from the `raw_metadata`
+ # column of `vulnerability_occurrences` once the unique
+ # index is in place.
+ class RemoveVulnerabilityFindingLinks
+ include Gitlab::Database::DynamicModelHelpers
+
+ def perform(start_id, stop_id)
+ define_batchable_model('vulnerability_finding_links').where(id: start_id..stop_id).delete_all
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/replace_blocked_by_links.rb b/lib/gitlab/background_migration/replace_blocked_by_links.rb
deleted file mode 100644
index 0c29887bb00..00000000000
--- a/lib/gitlab/background_migration/replace_blocked_by_links.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-# frozen_string_literal: true
-# rubocop:disable Style/Documentation
-
-module Gitlab
- module BackgroundMigration
- class ReplaceBlockedByLinks
- class IssueLink < ActiveRecord::Base
- self.table_name = 'issue_links'
- end
-
- def perform(start_id, stop_id)
- blocked_by_links = IssueLink.where(id: start_id..stop_id).where(link_type: 2)
-
- ActiveRecord::Base.transaction do
- # There could be two edge cases:
- # 1) issue1 is blocked by issue2 AND issue2 blocks issue1 (type 1)
- # 2) issue1 is blocked by issue2 AND issue2 is related to issue1 (type 0)
- # In both cases cases we couldn't convert blocked by relation to
- # `issue2 blocks issue` because there is already a link with the same
- # source/target id. To avoid these conflicts, we first delete any
- # "opposite" links before we update `blocked by` relation. This
- # should be rare as we have a pre-create check which checks if issues
- # are already linked
- opposite_ids = blocked_by_links
- .select('opposite_links.id')
- .joins('INNER JOIN issue_links as opposite_links ON issue_links.source_id = opposite_links.target_id AND issue_links.target_id = opposite_links.source_id')
- IssueLink.where(id: opposite_ids).delete_all
-
- blocked_by_links.update_all('source_id=target_id,target_id=source_id,link_type=1')
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/reset_merge_status.rb b/lib/gitlab/background_migration/reset_merge_status.rb
deleted file mode 100644
index d040b4931be..00000000000
--- a/lib/gitlab/background_migration/reset_merge_status.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # Updates the range of given MRs to merge_status "unchecked", if they're opened
- # and mergeable.
- class ResetMergeStatus
- def perform(from_id, to_id)
- relation = MergeRequest.where(id: from_id..to_id,
- state_id: 1, # opened
- merge_status: 'can_be_merged')
-
- relation.update_all(merge_status: 'unchecked')
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/reset_shared_runners_for_transferred_projects.rb b/lib/gitlab/background_migration/reset_shared_runners_for_transferred_projects.rb
deleted file mode 100644
index 0053cafb4ac..00000000000
--- a/lib/gitlab/background_migration/reset_shared_runners_for_transferred_projects.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # Resets inconsistent state of shared_runners_enabled for projects that have been transferred
- class ResetSharedRunnersForTransferredProjects
- # Model specifically used for migration.
- class Namespace < ActiveRecord::Base
- include EachBatch
-
- self.table_name = 'namespaces'
- end
-
- # Model specifically used for migration.
- class Project < ActiveRecord::Base
- self.table_name = 'projects'
- end
-
- def perform(start_id, stop_id)
- Project.reset_column_information
-
- Namespace.where(id: start_id..stop_id).each_batch(of: 1_000) do |relation|
- ids = relation.where(shared_runners_enabled: false, allow_descendants_override_disabled_shared_runners: false).select(:id)
-
- Project.where(namespace_id: ids).update_all(shared_runners_enabled: false)
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/set_merge_request_diff_files_count.rb b/lib/gitlab/background_migration/set_merge_request_diff_files_count.rb
deleted file mode 100644
index 527dd2a0a83..00000000000
--- a/lib/gitlab/background_migration/set_merge_request_diff_files_count.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # Sets the MergeRequestDiff#files_count value for old rows
- class SetMergeRequestDiffFilesCount
- # Some historic data has a *lot* of files. Apply a sentinel to these cases
- FILES_COUNT_SENTINEL = 2**15 - 1
-
- def self.count_subquery
- <<~SQL
- files_count = (
- SELECT LEAST(#{FILES_COUNT_SENTINEL}, count(*))
- FROM merge_request_diff_files
- WHERE merge_request_diff_files.merge_request_diff_id = merge_request_diffs.id
- )
- SQL
- end
-
- class MergeRequestDiff < ActiveRecord::Base # rubocop:disable Style/Documentation
- include EachBatch
-
- self.table_name = 'merge_request_diffs'
- end
-
- def perform(start_id, end_id)
- MergeRequestDiff.where(id: start_id..end_id).each_batch do |relation|
- relation.update_all(self.class.count_subquery)
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/update_existing_subgroup_to_match_visibility_level_of_parent.rb b/lib/gitlab/background_migration/update_existing_subgroup_to_match_visibility_level_of_parent.rb
deleted file mode 100644
index 9e330f7c008..00000000000
--- a/lib/gitlab/background_migration/update_existing_subgroup_to_match_visibility_level_of_parent.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # This background migration updates children of group to match visibility of a parent
- class UpdateExistingSubgroupToMatchVisibilityLevelOfParent
- def perform(parents_groups_ids, level)
- groups_ids = Gitlab::ObjectHierarchy.new(Group.where(id: parents_groups_ids))
- .base_and_descendants
- .where("visibility_level > ?", level)
- .select(:id)
-
- return if groups_ids.empty?
-
- Group
- .where(id: groups_ids)
- .update_all(visibility_level: level)
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/update_existing_users_that_require_two_factor_auth.rb b/lib/gitlab/background_migration/update_existing_users_that_require_two_factor_auth.rb
deleted file mode 100644
index d97765cd398..00000000000
--- a/lib/gitlab/background_migration/update_existing_users_that_require_two_factor_auth.rb
+++ /dev/null
@@ -1,110 +0,0 @@
-# frozen_string_literal: true
-# rubocop:disable Style/Documentation
-
-module Gitlab
- module BackgroundMigration
- class UpdateExistingUsersThatRequireTwoFactorAuth # rubocop:disable Metrics/ClassLength
- def perform(start_id, stop_id)
- ActiveRecord::Base.connection.execute <<~SQL
- UPDATE
- users
- SET
- require_two_factor_authentication_from_group = FALSE
- WHERE
- users.id BETWEEN #{start_id}
- AND #{stop_id}
- AND users.require_two_factor_authentication_from_group = TRUE
- AND users.id NOT IN ( SELECT DISTINCT
- users_groups_query.user_id
- FROM (
- SELECT
- users.id AS user_id,
- members.source_id AS group_ids
- FROM
- users
- LEFT JOIN members ON members.source_type = 'Namespace'
- AND members.requested_at IS NULL
- AND members.user_id = users.id
- AND members.type = 'GroupMember'
- WHERE
- users.require_two_factor_authentication_from_group = TRUE
- AND users.id BETWEEN #{start_id}
- AND #{stop_id}) AS users_groups_query
- INNER JOIN LATERAL ( WITH RECURSIVE "base_and_ancestors" AS (
- (
- SELECT
- "namespaces"."type",
- "namespaces"."id",
- "namespaces"."parent_id",
- "namespaces"."require_two_factor_authentication"
- FROM
- "namespaces"
- WHERE
- "namespaces"."type" = 'Group'
- AND "namespaces"."id" = users_groups_query.group_ids)
- UNION (
- SELECT
- "namespaces"."type",
- "namespaces"."id",
- "namespaces"."parent_id",
- "namespaces"."require_two_factor_authentication"
- FROM
- "namespaces",
- "base_and_ancestors"
- WHERE
- "namespaces"."type" = 'Group'
- AND "namespaces"."id" = "base_and_ancestors"."parent_id")),
- "base_and_descendants" AS (
- (
- SELECT
- "namespaces"."type",
- "namespaces"."id",
- "namespaces"."parent_id",
- "namespaces"."require_two_factor_authentication"
- FROM
- "namespaces"
- WHERE
- "namespaces"."type" = 'Group'
- AND "namespaces"."id" = users_groups_query.group_ids)
- UNION (
- SELECT
- "namespaces"."type",
- "namespaces"."id",
- "namespaces"."parent_id",
- "namespaces"."require_two_factor_authentication"
- FROM
- "namespaces",
- "base_and_descendants"
- WHERE
- "namespaces"."type" = 'Group'
- AND "namespaces"."parent_id" = "base_and_descendants"."id"))
- SELECT
- "namespaces".*
- FROM ((
- SELECT
- "namespaces"."type",
- "namespaces"."id",
- "namespaces"."parent_id",
- "namespaces"."require_two_factor_authentication"
- FROM
- "base_and_ancestors" AS "namespaces"
- WHERE
- "namespaces"."type" = 'Group')
- UNION (
- SELECT
- "namespaces"."type",
- "namespaces"."id",
- "namespaces"."parent_id",
- "namespaces"."require_two_factor_authentication"
- FROM
- "base_and_descendants" AS "namespaces"
- WHERE
- "namespaces"."type" = 'Group')) namespaces
- WHERE
- "namespaces"."type" = 'Group'
- AND "namespaces"."require_two_factor_authentication" = TRUE) AS hierarchy_tree ON TRUE);
- SQL
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/update_location_fingerprint_for_container_scanning_findings.rb b/lib/gitlab/background_migration/update_location_fingerprint_for_container_scanning_findings.rb
deleted file mode 100644
index 054b918dade..00000000000
--- a/lib/gitlab/background_migration/update_location_fingerprint_for_container_scanning_findings.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-# rubocop:disable Style/Documentation
-
-module Gitlab
- module BackgroundMigration
- class UpdateLocationFingerprintForContainerScanningFindings
- def perform(start_id, stop_id)
- end
- end
- end
-end
-
-Gitlab::BackgroundMigration::UpdateLocationFingerprintForContainerScanningFindings.prepend_mod_with('Gitlab::BackgroundMigration::UpdateLocationFingerprintForContainerScanningFindings')
diff --git a/lib/gitlab/background_migration/update_timelogs_null_spent_at.rb b/lib/gitlab/background_migration/update_timelogs_null_spent_at.rb
new file mode 100644
index 00000000000..c95ef9f5515
--- /dev/null
+++ b/lib/gitlab/background_migration/update_timelogs_null_spent_at.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # Class to populate spent_at for timelogs
+ class UpdateTimelogsNullSpentAt
+ include Gitlab::Database::DynamicModelHelpers
+
+ BATCH_SIZE = 100
+
+ def perform(start_id, stop_id)
+ define_batchable_model('timelogs').where(spent_at: nil, id: start_id..stop_id).each_batch(of: 100) do |subbatch|
+ batch_start, batch_end = subbatch.pluck('min(id), max(id)').first
+
+ update_timelogs(batch_start, batch_end)
+ end
+ end
+
+ def update_timelogs(batch_start, batch_stop)
+ execute(<<~SQL)
+ UPDATE timelogs
+ SET spent_at = created_at
+ WHERE spent_at IS NULL
+ AND timelogs.id BETWEEN #{batch_start} AND #{batch_stop};
+ SQL
+ end
+
+ def execute(sql)
+ @connection ||= ::ActiveRecord::Base.connection
+ @connection.execute(sql)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/update_vulnerabilities_from_dismissal_feedback.rb b/lib/gitlab/background_migration/update_vulnerabilities_from_dismissal_feedback.rb
deleted file mode 100644
index 1cc03f061fb..00000000000
--- a/lib/gitlab/background_migration/update_vulnerabilities_from_dismissal_feedback.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- # rubocop: disable Style/Documentation
- class UpdateVulnerabilitiesFromDismissalFeedback
- def perform(project_id)
- end
- end
- end
-end
-
-Gitlab::BackgroundMigration::UpdateVulnerabilitiesFromDismissalFeedback.prepend_mod_with('Gitlab::BackgroundMigration::UpdateVulnerabilitiesFromDismissalFeedback')
diff --git a/lib/gitlab/background_migration/user_mentions/create_resource_user_mention.rb b/lib/gitlab/background_migration/user_mentions/create_resource_user_mention.rb
deleted file mode 100644
index b3876018553..00000000000
--- a/lib/gitlab/background_migration/user_mentions/create_resource_user_mention.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# frozen_string_literal: true
-# rubocop:disable Style/Documentation
-
-module Gitlab
- module BackgroundMigration
- module UserMentions
- class CreateResourceUserMention
- # Resources that have mentions to be migrated:
- # issue, merge_request, epic, commit, snippet, design
-
- BULK_INSERT_SIZE = 1_000
- ISOLATION_MODULE = 'Gitlab::BackgroundMigration::UserMentions::Models'
-
- def perform(resource_model, join, conditions, with_notes, start_id, end_id)
- return unless Feature.enabled?(:migrate_user_mentions, default_enabled: true)
-
- resource_model = "#{ISOLATION_MODULE}::#{resource_model}".constantize if resource_model.is_a?(String)
- model = with_notes ? Gitlab::BackgroundMigration::UserMentions::Models::Note : resource_model
- resource_user_mention_model = resource_model.user_mention_model
-
- records = model.joins(join).where(conditions).where(id: start_id..end_id)
-
- records.each_batch(of: BULK_INSERT_SIZE) do |records|
- mentions = []
- records.each do |record|
- mention_record = record.build_mention_values(resource_user_mention_model.resource_foreign_key)
- mentions << mention_record unless mention_record.blank?
- end
-
- resource_user_mention_model.insert_all(mentions) unless mentions.empty?
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/user_mentions/lib/banzai/reference_parser.rb b/lib/gitlab/background_migration/user_mentions/lib/banzai/reference_parser.rb
deleted file mode 100644
index 3def5eb3369..00000000000
--- a/lib/gitlab/background_migration/user_mentions/lib/banzai/reference_parser.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- module UserMentions
- module Lib
- module Banzai
- # isolated Banzai::ReferenceParser
- module ReferenceParser
- # Returns the reference parser class for the given type
- #
- # Example:
- #
- # Banzai::ReferenceParser['isolated_mentioned_group']
- #
- # This would return the `::Gitlab::BackgroundMigration::UserMentions::Lib::Banzai::ReferenceParser::IsolatedMentionedGroupParser` class.
- def self.[](name)
- const_get("::Gitlab::BackgroundMigration::UserMentions::Lib::Banzai::ReferenceParser::#{name.to_s.camelize}Parser", false)
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/user_mentions/lib/banzai/reference_parser/isolated_mentioned_group_parser.rb b/lib/gitlab/background_migration/user_mentions/lib/banzai/reference_parser/isolated_mentioned_group_parser.rb
deleted file mode 100644
index d3d032ba433..00000000000
--- a/lib/gitlab/background_migration/user_mentions/lib/banzai/reference_parser/isolated_mentioned_group_parser.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- module UserMentions
- module Lib
- module Banzai
- module ReferenceParser
- # isolated Banzai::ReferenceParser::MentionedGroupParser
- class IsolatedMentionedGroupParser < ::Banzai::ReferenceParser::MentionedGroupParser
- extend ::Gitlab::Utils::Override
-
- self.reference_type = :user
-
- override :references_relation
- def references_relation
- ::Gitlab::BackgroundMigration::UserMentions::Models::Group
- end
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/user_mentions/lib/banzai/reference_parser/isolated_mentioned_project_parser.rb b/lib/gitlab/background_migration/user_mentions/lib/banzai/reference_parser/isolated_mentioned_project_parser.rb
deleted file mode 100644
index 5930d65bc2c..00000000000
--- a/lib/gitlab/background_migration/user_mentions/lib/banzai/reference_parser/isolated_mentioned_project_parser.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- module UserMentions
- module Lib
- module Banzai
- module ReferenceParser
- # isolated Banzai::ReferenceParser::MentionedGroupParser
- class IsolatedMentionedProjectParser < ::Banzai::ReferenceParser::MentionedProjectParser
- extend ::Gitlab::Utils::Override
-
- self.reference_type = :user
-
- override :references_relation
- def references_relation
- ::Gitlab::BackgroundMigration::UserMentions::Models::Project
- end
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/user_mentions/lib/banzai/reference_parser/isolated_mentioned_user_parser.rb b/lib/gitlab/background_migration/user_mentions/lib/banzai/reference_parser/isolated_mentioned_user_parser.rb
deleted file mode 100644
index f5f98517433..00000000000
--- a/lib/gitlab/background_migration/user_mentions/lib/banzai/reference_parser/isolated_mentioned_user_parser.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- module UserMentions
- module Lib
- module Banzai
- module ReferenceParser
- # isolated Banzai::ReferenceParser::MentionedGroupParser
- class IsolatedMentionedUserParser < ::Banzai::ReferenceParser::MentionedUserParser
- extend ::Gitlab::Utils::Override
-
- self.reference_type = :user
-
- override :references_relation
- def references_relation
- ::Gitlab::BackgroundMigration::UserMentions::Models::User
- end
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/user_mentions/lib/gitlab/isolated_reference_extractor.rb b/lib/gitlab/background_migration/user_mentions/lib/gitlab/isolated_reference_extractor.rb
deleted file mode 100644
index 8610129533d..00000000000
--- a/lib/gitlab/background_migration/user_mentions/lib/gitlab/isolated_reference_extractor.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- module UserMentions
- module Lib
- module Gitlab
- # Extract possible GFM references from an arbitrary String for further processing.
- class IsolatedReferenceExtractor < ::Gitlab::ReferenceExtractor
- REFERABLES = %i(isolated_mentioned_group isolated_mentioned_user isolated_mentioned_project).freeze
-
- REFERABLES.each do |type|
- define_method("#{type}s") do
- @references[type] ||= isolated_references(type)
- end
- end
-
- def isolated_references(type)
- context = ::Banzai::RenderContext.new(project, current_user)
- processor = ::Gitlab::BackgroundMigration::UserMentions::Lib::Banzai::ReferenceParser[type].new(context)
-
- refs = processor.process(html_documents)
- refs[:visible]
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/user_mentions/lib/gitlab/isolated_visibility_level.rb b/lib/gitlab/background_migration/user_mentions/lib/gitlab/isolated_visibility_level.rb
deleted file mode 100644
index 0334ea1dd08..00000000000
--- a/lib/gitlab/background_migration/user_mentions/lib/gitlab/isolated_visibility_level.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- module UserMentions
- module Lib
- module Gitlab
- # Gitlab::IsolatedVisibilityLevel module
- #
- # Define allowed public modes that can be used for
- # GitLab projects to determine project public mode
- #
- module IsolatedVisibilityLevel
- extend ::ActiveSupport::Concern
-
- included do
- scope :public_to_user, -> (user = nil) do
- where(visibility_level: IsolatedVisibilityLevel.levels_for_user(user))
- end
- end
-
- PRIVATE = 0 unless const_defined?(:PRIVATE)
- INTERNAL = 10 unless const_defined?(:INTERNAL)
- PUBLIC = 20 unless const_defined?(:PUBLIC)
-
- class << self
- def levels_for_user(user = nil)
- return [PUBLIC] unless user
-
- if user.can_read_all_resources?
- [PRIVATE, INTERNAL, PUBLIC]
- elsif user.external?
- [PUBLIC]
- else
- [INTERNAL, PUBLIC]
- end
- end
- end
-
- def private?
- visibility_level_value == PRIVATE
- end
-
- def internal?
- visibility_level_value == INTERNAL
- end
-
- def public?
- visibility_level_value == PUBLIC
- end
-
- def visibility_level_value
- self[visibility_level_field]
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/user_mentions/models/commit.rb b/lib/gitlab/background_migration/user_mentions/models/commit.rb
deleted file mode 100644
index 65f4a7a25b6..00000000000
--- a/lib/gitlab/background_migration/user_mentions/models/commit.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# frozen_string_literal: true
-# rubocop:disable Style/Documentation
-
-module Gitlab
- module BackgroundMigration
- module UserMentions
- module Models
- class Commit
- include EachBatch
- include Concerns::IsolatedMentionable
- include Concerns::MentionableMigrationMethods
-
- def self.user_mention_model
- Gitlab::BackgroundMigration::UserMentions::Models::CommitUserMention
- end
-
- def user_mention_model
- self.class.user_mention_model
- end
-
- def user_mention_resource_id
- id
- end
-
- def user_mention_note_id
- 'NULL'
- end
-
- def self.no_quote_columns
- [:note_id]
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/user_mentions/models/commit_user_mention.rb b/lib/gitlab/background_migration/user_mentions/models/commit_user_mention.rb
deleted file mode 100644
index f4cc96c8bc0..00000000000
--- a/lib/gitlab/background_migration/user_mentions/models/commit_user_mention.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-# rubocop:disable Style/Documentation
-
-module Gitlab
- module BackgroundMigration
- module UserMentions
- module Models
- class CommitUserMention < ActiveRecord::Base
- self.table_name = 'commit_user_mentions'
- self.inheritance_column = :_type_disabled
-
- def self.resource_foreign_key
- :commit_id
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/user_mentions/models/concerns/isolated_feature_gate.rb b/lib/gitlab/background_migration/user_mentions/models/concerns/isolated_feature_gate.rb
deleted file mode 100644
index ba6b783f9f1..00000000000
--- a/lib/gitlab/background_migration/user_mentions/models/concerns/isolated_feature_gate.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- module UserMentions
- module Models
- module Concerns
- # isolated FeatureGate module
- module IsolatedFeatureGate
- def flipper_id
- return if new_record?
-
- "#{self.class.name}:#{id}"
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/user_mentions/models/concerns/isolated_mentionable.rb b/lib/gitlab/background_migration/user_mentions/models/concerns/isolated_mentionable.rb
deleted file mode 100644
index f684f789ea9..00000000000
--- a/lib/gitlab/background_migration/user_mentions/models/concerns/isolated_mentionable.rb
+++ /dev/null
@@ -1,104 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- module UserMentions
- module Models
- module Concerns
- # == IsolatedMentionable concern
- #
- # Shortcutted for isolation version of Mentionable to be used in mentions migrations
- #
- module IsolatedMentionable
- extend ::ActiveSupport::Concern
-
- class_methods do
- # Indicate which attributes of the Mentionable to search for GFM references.
- def attr_mentionable(attr, options = {})
- attr = attr.to_s
- mentionable_attrs << [attr, options]
- end
- end
-
- included do
- # Accessor for attributes marked mentionable.
- cattr_accessor :mentionable_attrs, instance_accessor: false do
- []
- end
-
- if self < Participable
- participant -> (user, ext) { all_references(user, extractor: ext) }
- end
- end
-
- def all_references(current_user = nil, extractor: nil)
- # Use custom extractor if it's passed in the function parameters.
- if extractor
- extractors[current_user] = extractor
- else
- extractor = extractors[current_user] ||=
- Gitlab::BackgroundMigration::UserMentions::Lib::Gitlab::IsolatedReferenceExtractor.new(project, current_user)
-
- extractor.reset_memoized_values
- end
-
- self.class.mentionable_attrs.each do |attr, options|
- text = __send__(attr) # rubocop:disable GitlabSecurity/PublicSend
- options = options.merge(
- cache_key: [self, attr],
- author: author,
- skip_project_check: skip_project_check?
- ).merge(mentionable_params)
-
- cached_html = self.try(:updated_cached_html_for, attr.to_sym)
- options[:rendered] = cached_html if cached_html
-
- extractor.analyze(text, options)
- end
-
- extractor
- end
-
- def extractors
- @extractors ||= {}
- end
-
- def skip_project_check?
- false
- end
-
- def build_mention_values(resource_foreign_key)
- refs = all_references(author)
-
- mentioned_users_ids = array_to_sql(refs.isolated_mentioned_users.pluck(:id))
- mentioned_projects_ids = array_to_sql(refs.isolated_mentioned_projects.pluck(:id))
- mentioned_groups_ids = array_to_sql(refs.isolated_mentioned_groups.pluck(:id))
-
- return if mentioned_users_ids.blank? && mentioned_projects_ids.blank? && mentioned_groups_ids.blank?
-
- {
- "#{resource_foreign_key}": user_mention_resource_id,
- note_id: user_mention_note_id,
- mentioned_users_ids: mentioned_users_ids,
- mentioned_projects_ids: mentioned_projects_ids,
- mentioned_groups_ids: mentioned_groups_ids
- }
- end
-
- def array_to_sql(ids_array)
- return unless ids_array.present?
-
- '{' + ids_array.join(", ") + '}'
- end
-
- private
-
- def mentionable_params
- {}
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/user_mentions/models/concerns/mentionable_migration_methods.rb b/lib/gitlab/background_migration/user_mentions/models/concerns/mentionable_migration_methods.rb
deleted file mode 100644
index efb08d44100..00000000000
--- a/lib/gitlab/background_migration/user_mentions/models/concerns/mentionable_migration_methods.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- module UserMentions
- module Models
- module Concerns
- # Extract common no_quote_columns method used in determining the columns that do not need
- # to be quoted for corresponding models
- module MentionableMigrationMethods
- extend ::ActiveSupport::Concern
-
- class_methods do
- def no_quote_columns
- [
- :note_id,
- user_mention_model.resource_foreign_key
- ]
- end
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/user_mentions/models/concerns/namespace/recursive_traversal.rb b/lib/gitlab/background_migration/user_mentions/models/concerns/namespace/recursive_traversal.rb
deleted file mode 100644
index 75759ed0111..00000000000
--- a/lib/gitlab/background_migration/user_mentions/models/concerns/namespace/recursive_traversal.rb
+++ /dev/null
@@ -1,74 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- module UserMentions
- module Models
- module Concerns
- module Namespace
- # isolate recursive traversal code for namespace hierarchy
- module RecursiveTraversal
- extend ActiveSupport::Concern
-
- def root_ancestor
- return self if persisted? && parent_id.nil?
-
- strong_memoize(:root_ancestor) do
- Gitlab::ObjectHierarchy
- .new(self.class.where(id: id))
- .base_and_ancestors
- .reorder(nil)
- .find_by(parent_id: nil)
- end
- end
-
- # Returns all ancestors, self, and descendants of the current namespace.
- def self_and_hierarchy
- Gitlab::ObjectHierarchy
- .new(self.class.where(id: id))
- .all_objects
- end
-
- # Returns all the ancestors of the current namespaces.
- def ancestors
- return self.class.none unless parent_id
-
- Gitlab::ObjectHierarchy
- .new(self.class.where(id: parent_id))
- .base_and_ancestors
- end
-
- # returns all ancestors upto but excluding the given namespace
- # when no namespace is given, all ancestors upto the top are returned
- def ancestors_upto(top = nil, hierarchy_order: nil)
- Gitlab::ObjectHierarchy.new(self.class.where(id: id))
- .ancestors(upto: top, hierarchy_order: hierarchy_order)
- end
-
- def self_and_ancestors(hierarchy_order: nil)
- return self.class.where(id: id) unless parent_id
-
- Gitlab::ObjectHierarchy
- .new(self.class.where(id: id))
- .base_and_ancestors(hierarchy_order: hierarchy_order)
- end
-
- # Returns all the descendants of the current namespace.
- def descendants
- Gitlab::ObjectHierarchy
- .new(self.class.where(parent_id: id))
- .base_and_descendants
- end
-
- def self_and_descendants
- Gitlab::ObjectHierarchy
- .new(self.class.where(id: id))
- .base_and_descendants
- end
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/user_mentions/models/design_management/design.rb b/lib/gitlab/background_migration/user_mentions/models/design_management/design.rb
deleted file mode 100644
index d010d68600d..00000000000
--- a/lib/gitlab/background_migration/user_mentions/models/design_management/design.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# frozen_string_literal: true
-# rubocop:disable Style/Documentation
-
-module Gitlab
- module BackgroundMigration
- module UserMentions
- module Models
- module DesignManagement
- class Design < ActiveRecord::Base
- include EachBatch
- include Concerns::MentionableMigrationMethods
-
- self.table_name = 'design_management_designs'
- self.inheritance_column = :_type_disabled
-
- def self.user_mention_model
- Gitlab::BackgroundMigration::UserMentions::Models::DesignUserMention
- end
-
- def user_mention_model
- self.class.user_mention_model
- end
-
- def user_mention_resource_id
- id
- end
-
- def user_mention_note_id
- 'NULL'
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/user_mentions/models/design_user_mention.rb b/lib/gitlab/background_migration/user_mentions/models/design_user_mention.rb
deleted file mode 100644
index eb00f6cfa3f..00000000000
--- a/lib/gitlab/background_migration/user_mentions/models/design_user_mention.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-# rubocop:disable Style/Documentation
-
-module Gitlab
- module BackgroundMigration
- module UserMentions
- module Models
- class DesignUserMention < ActiveRecord::Base
- self.table_name = 'design_user_mentions'
- self.inheritance_column = :_type_disabled
-
- def self.resource_foreign_key
- :design_id
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/user_mentions/models/epic.rb b/lib/gitlab/background_migration/user_mentions/models/epic.rb
deleted file mode 100644
index cfd9a4faa9b..00000000000
--- a/lib/gitlab/background_migration/user_mentions/models/epic.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-# frozen_string_literal: true
-# rubocop:disable Style/Documentation
-
-module Gitlab
- module BackgroundMigration
- module UserMentions
- module Models
- class Epic < ActiveRecord::Base
- include EachBatch
- include Concerns::IsolatedMentionable
- include Concerns::MentionableMigrationMethods
- include CacheMarkdownField
-
- attr_mentionable :title, pipeline: :single_line
- attr_mentionable :description
- cache_markdown_field :title, pipeline: :single_line
- cache_markdown_field :description, issuable_state_filter_enabled: true
-
- self.table_name = 'epics'
- self.inheritance_column = :_type_disabled
-
- belongs_to :author, class_name: "::Gitlab::BackgroundMigration::UserMentions::Models::User"
- belongs_to :group, class_name: "::Gitlab::BackgroundMigration::UserMentions::Models::Group"
-
- def self.user_mention_model
- Gitlab::BackgroundMigration::UserMentions::Models::EpicUserMention
- end
-
- def user_mention_model
- self.class.user_mention_model
- end
-
- def project
- nil
- end
-
- def mentionable_params
- { group: group, label_url_method: :group_epics_url }
- end
-
- def user_mention_resource_id
- id
- end
-
- def user_mention_note_id
- 'NULL'
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/user_mentions/models/epic_user_mention.rb b/lib/gitlab/background_migration/user_mentions/models/epic_user_mention.rb
deleted file mode 100644
index 579e4d99612..00000000000
--- a/lib/gitlab/background_migration/user_mentions/models/epic_user_mention.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-# rubocop:disable Style/Documentation
-
-module Gitlab
- module BackgroundMigration
- module UserMentions
- module Models
- class EpicUserMention < ActiveRecord::Base
- self.table_name = 'epic_user_mentions'
- self.inheritance_column = :_type_disabled
-
- def self.resource_foreign_key
- :epic_id
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/user_mentions/models/group.rb b/lib/gitlab/background_migration/user_mentions/models/group.rb
deleted file mode 100644
index 310723570c2..00000000000
--- a/lib/gitlab/background_migration/user_mentions/models/group.rb
+++ /dev/null
@@ -1,97 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- module UserMentions
- module Models
- # isolated Group model
- class Group < ::Gitlab::BackgroundMigration::UserMentions::Models::Namespace
- self.store_full_sti_class = false
- self.inheritance_column = :_type_disabled
-
- has_one :saml_provider
-
- def root_saml_provider
- root_ancestor.saml_provider
- end
-
- def self.declarative_policy_class
- "GroupPolicy"
- end
-
- def max_member_access_for_user(user)
- return GroupMember::NO_ACCESS unless user
-
- return GroupMember::OWNER if user.admin?
-
- max_member_access = members_with_parents.where(user_id: user)
- .reorder(access_level: :desc)
- .first
- &.access_level
-
- max_member_access || GroupMember::NO_ACCESS
- end
-
- def members_with_parents
- # Avoids an unnecessary SELECT when the group has no parents
- source_ids =
- if has_parent?
- self_and_ancestors.reorder(nil).select(:id)
- else
- id
- end
-
- group_hierarchy_members = GroupMember.active_without_invites_and_requests
- .where(source_id: source_ids)
-
- GroupMember.from_union([group_hierarchy_members,
- members_from_self_and_ancestor_group_shares])
- end
-
- # rubocop: disable Metrics/AbcSize
- def members_from_self_and_ancestor_group_shares
- group_group_link_table = GroupGroupLink.arel_table
- group_member_table = GroupMember.arel_table
-
- source_ids =
- if has_parent?
- self_and_ancestors.reorder(nil).select(:id)
- else
- id
- end
-
- group_group_links_query = GroupGroupLink.where(shared_group_id: source_ids)
- cte = Gitlab::SQL::CTE.new(:group_group_links_cte, group_group_links_query)
- cte_alias = cte.table.alias(GroupGroupLink.table_name)
-
- # Instead of members.access_level, we need to maximize that access_level at
- # the respective group_group_links.group_access.
- member_columns = GroupMember.attribute_names.map do |column_name|
- if column_name == 'access_level'
- smallest_value_arel([cte_alias[:group_access], group_member_table[:access_level]],
- 'access_level')
- else
- group_member_table[column_name]
- end
- end
-
- GroupMember
- .with(cte.to_arel)
- .select(*member_columns)
- .from([group_member_table, cte.alias_to(group_group_link_table)])
- .where(group_member_table[:requested_at].eq(nil))
- .where(group_member_table[:source_id].eq(group_group_link_table[:shared_with_group_id]))
- .where(group_member_table[:source_type].eq('Namespace'))
- end
- # rubocop: enable Metrics/AbcSize
-
- def smallest_value_arel(args, column_alias)
- Arel::Nodes::As.new(
- Arel::Nodes::NamedFunction.new('LEAST', args),
- Arel::Nodes::SqlLiteral.new(column_alias))
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/user_mentions/models/merge_request.rb b/lib/gitlab/background_migration/user_mentions/models/merge_request.rb
deleted file mode 100644
index 13addcc3c55..00000000000
--- a/lib/gitlab/background_migration/user_mentions/models/merge_request.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-# frozen_string_literal: true
-# rubocop:disable Style/Documentation
-
-module Gitlab
- module BackgroundMigration
- module UserMentions
- module Models
- class MergeRequest < ActiveRecord::Base
- include EachBatch
- include Concerns::IsolatedMentionable
- include CacheMarkdownField
- include Concerns::MentionableMigrationMethods
-
- attr_mentionable :title, pipeline: :single_line
- attr_mentionable :description
- cache_markdown_field :title, pipeline: :single_line
- cache_markdown_field :description, issuable_state_filter_enabled: true
-
- self.table_name = 'merge_requests'
- self.inheritance_column = :_type_disabled
-
- belongs_to :author, class_name: "::Gitlab::BackgroundMigration::UserMentions::Models::User"
- belongs_to :target_project, class_name: "::Gitlab::BackgroundMigration::UserMentions::Models::Project"
- belongs_to :source_project, class_name: "::Gitlab::BackgroundMigration::UserMentions::Models::Project"
-
- alias_attribute :project, :target_project
-
- def self.user_mention_model
- Gitlab::BackgroundMigration::UserMentions::Models::MergeRequestUserMention
- end
-
- def user_mention_model
- self.class.user_mention_model
- end
-
- def user_mention_resource_id
- id
- end
-
- def user_mention_note_id
- 'NULL'
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/user_mentions/models/merge_request_user_mention.rb b/lib/gitlab/background_migration/user_mentions/models/merge_request_user_mention.rb
deleted file mode 100644
index 4a85892d7b8..00000000000
--- a/lib/gitlab/background_migration/user_mentions/models/merge_request_user_mention.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-# rubocop:disable Style/Documentation
-
-module Gitlab
- module BackgroundMigration
- module UserMentions
- module Models
- class MergeRequestUserMention < ActiveRecord::Base
- self.table_name = 'merge_request_user_mentions'
- self.inheritance_column = :_type_disabled
-
- def self.resource_foreign_key
- :merge_request_id
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/user_mentions/models/namespace.rb b/lib/gitlab/background_migration/user_mentions/models/namespace.rb
deleted file mode 100644
index d76d06606ee..00000000000
--- a/lib/gitlab/background_migration/user_mentions/models/namespace.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- module UserMentions
- module Models
- # isolated Namespace model
- class Namespace < ActiveRecord::Base
- self.inheritance_column = :_type_disabled
-
- include Concerns::IsolatedFeatureGate
- include Gitlab::BackgroundMigration::UserMentions::Lib::Gitlab::IsolatedVisibilityLevel
- include ::Gitlab::Utils::StrongMemoize
- include Gitlab::BackgroundMigration::UserMentions::Models::Concerns::Namespace::RecursiveTraversal
-
- belongs_to :parent, class_name: "::Gitlab::BackgroundMigration::UserMentions::Models::Namespace"
-
- def visibility_level_field
- :visibility_level
- end
-
- def has_parent?
- parent_id.present? || parent.present?
- end
-
- # Deprecated, use #licensed_feature_available? instead. Remove once Namespace#feature_available? isn't used anymore.
- def feature_available?(feature)
- licensed_feature_available?(feature)
- end
-
- # Overridden in EE::Namespace
- def licensed_feature_available?(_feature)
- false
- end
- end
- end
- end
- end
-end
-
-Namespace.prepend_mod_with('Namespace')
diff --git a/lib/gitlab/background_migration/user_mentions/models/note.rb b/lib/gitlab/background_migration/user_mentions/models/note.rb
deleted file mode 100644
index 4026a91903f..00000000000
--- a/lib/gitlab/background_migration/user_mentions/models/note.rb
+++ /dev/null
@@ -1,72 +0,0 @@
-# frozen_string_literal: true
-# rubocop:disable Style/Documentation
-
-module Gitlab
- module BackgroundMigration
- module UserMentions
- module Models
- class Note < ActiveRecord::Base
- include EachBatch
- include Concerns::IsolatedMentionable
- include CacheMarkdownField
-
- self.table_name = 'notes'
- self.inheritance_column = :_type_disabled
-
- attr_mentionable :note, pipeline: :note
- cache_markdown_field :note, pipeline: :note, issuable_state_filter_enabled: true
-
- belongs_to :author, class_name: "::Gitlab::BackgroundMigration::UserMentions::Models::User"
- belongs_to :noteable, polymorphic: true
- belongs_to :project, class_name: "::Gitlab::BackgroundMigration::UserMentions::Models::Project"
-
- def for_personal_snippet?
- noteable && noteable.instance_of?(PersonalSnippet)
- end
-
- def for_project_noteable?
- !for_personal_snippet? && !for_epic?
- end
-
- def skip_project_check?
- !for_project_noteable?
- end
-
- def for_epic?
- noteable && noteable_type == 'Epic'
- end
-
- def user_mention_resource_id
- noteable_id || commit_id
- end
-
- def user_mention_note_id
- id
- end
-
- def noteable
- super unless for_commit?
- end
-
- def for_commit?
- noteable_type == "Commit"
- end
-
- private
-
- def mentionable_params
- return super unless for_epic?
-
- super.merge(banzai_context_params)
- end
-
- def banzai_context_params
- return {} unless noteable
-
- { group: noteable.group, label_url_method: :group_epics_url }
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/user_mentions/models/project.rb b/lib/gitlab/background_migration/user_mentions/models/project.rb
deleted file mode 100644
index 4e02bf97d12..00000000000
--- a/lib/gitlab/background_migration/user_mentions/models/project.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- module UserMentions
- module Models
- # isolated Namespace model
- class Project < ActiveRecord::Base
- include Concerns::IsolatedFeatureGate
- include Gitlab::BackgroundMigration::UserMentions::Lib::Gitlab::IsolatedVisibilityLevel
-
- self.table_name = 'projects'
- self.inheritance_column = :_type_disabled
-
- belongs_to :group, -> { where(type: 'Group') }, foreign_key: 'namespace_id', class_name: "::Gitlab::BackgroundMigration::UserMentions::Models::Group"
- belongs_to :namespace, class_name: "::Gitlab::BackgroundMigration::UserMentions::Models::Namespace"
- alias_method :parent, :namespace
-
- # Returns a collection of projects that is either public or visible to the
- # logged in user.
- def self.public_or_visible_to_user(user = nil, min_access_level = nil)
- min_access_level = nil if user&.can_read_all_resources?
-
- return public_to_user unless user
-
- if user.is_a?(::Gitlab::BackgroundMigration::UserMentions::Models::User)
- where('EXISTS (?) OR projects.visibility_level IN (?)',
- user.authorizations_for_projects(min_access_level: min_access_level),
- levels_for_user(user))
- end
- end
-
- def grafana_integration
- nil
- end
-
- def default_issues_tracker?
- true # we do not care of the issue tracker type(internal or external) when parsing mentions
- end
-
- def visibility_level_field
- :visibility_level
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/background_migration/user_mentions/models/user.rb b/lib/gitlab/background_migration/user_mentions/models/user.rb
deleted file mode 100644
index a30220b6934..00000000000
--- a/lib/gitlab/background_migration/user_mentions/models/user.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module BackgroundMigration
- module UserMentions
- module Models
- # isolated Namespace model
- class User < ActiveRecord::Base
- include Concerns::IsolatedFeatureGate
-
- self.table_name = 'users'
- self.inheritance_column = :_type_disabled
-
- has_many :project_authorizations, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
-
- def authorizations_for_projects(min_access_level: nil, related_project_column: 'projects.id')
- authorizations = project_authorizations
- .select(1)
- .where("project_authorizations.project_id = #{related_project_column}")
-
- return authorizations unless min_access_level.present?
-
- authorizations.where('project_authorizations.access_level >= ?', min_access_level)
- end
-
- def can_read_all_resources?
- can?(:read_all_resources)
- end
-
- def can?(action, subject = :global)
- Ability.allowed?(self, action, subject)
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/bitbucket_server_import/importer.rb b/lib/gitlab/bitbucket_server_import/importer.rb
index 899e2e6c1c5..242979da367 100644
--- a/lib/gitlab/bitbucket_server_import/importer.rb
+++ b/lib/gitlab/bitbucket_server_import/importer.rb
@@ -19,7 +19,8 @@ module Gitlab
end
def self.refmap
- [:heads, :tags, '+refs/pull-requests/*/to:refs/merge-requests/*/head']
+ # We omit :heads and :tags since these are fetched in the import_repository
+ ['+refs/pull-requests/*/to:refs/merge-requests/*/head']
end
# Unlike GitHub, you can't grab the commit SHAs for pull requests that
@@ -140,11 +141,11 @@ module Gitlab
def import_repository
log_info(stage: 'import_repository', message: 'starting import')
- project.ensure_repository
+ project.repository.import_repository(project.import_url)
project.repository.fetch_as_mirror(project.import_url, refmap: self.class.refmap)
log_info(stage: 'import_repository', message: 'finished import')
- rescue Gitlab::Shell::Error => e
+ rescue ::Gitlab::Git::CommandError => e
Gitlab::ErrorTracking.log_exception(
e,
stage: 'import_repository', message: 'failed import', error: e.message
diff --git a/lib/gitlab/chat/output.rb b/lib/gitlab/chat/output.rb
index 4a55b81a9eb..b4ce05ca06d 100644
--- a/lib/gitlab/chat/output.rb
+++ b/lib/gitlab/chat/output.rb
@@ -48,10 +48,10 @@ module Gitlab
# follows it will produce a nil. For example:
#
# "\n".split("\n") # => []
- # "\n".split("\n")[1..-1] # => nil
+ # "\n".split("\n")[1..] # => nil
#
# To work around this we only "join" if we're given an Array.
- if (converted = output.split("\n")[1..-1])
+ if (converted = output.split("\n")[1..])
converted.join("\n")
else
''
diff --git a/lib/gitlab/ci/badge/coverage/report.rb b/lib/gitlab/ci/badge/coverage/report.rb
index 78b51dbdaf0..4d1193176ad 100644
--- a/lib/gitlab/ci/badge/coverage/report.rb
+++ b/lib/gitlab/ci/badge/coverage/report.rb
@@ -30,7 +30,7 @@ module Gitlab::Ci
@coverage ||= raw_coverage
return unless @coverage
- @coverage.to_f.round(2)
+ @coverage.round(2)
end
def metadata
diff --git a/lib/gitlab/ci/badge/metadata.rb b/lib/gitlab/ci/badge/metadata.rb
index eec9fedfaa9..244e3aff851 100644
--- a/lib/gitlab/ci/badge/metadata.rb
+++ b/lib/gitlab/ci/badge/metadata.rb
@@ -8,14 +8,13 @@ module Gitlab::Ci
class Metadata
include Gitlab::Routing
include ActionView::Helpers::AssetTagHelper
- include ActionView::Helpers::UrlHelper
def initialize(badge)
@badge = badge
end
def to_html
- link_to(image_tag(image_url, alt: title), link_url)
+ ApplicationController.helpers.link_to(image_tag(image_url, alt: title), link_url)
end
def to_markdown
diff --git a/lib/gitlab/ci/build/context/base.rb b/lib/gitlab/ci/build/context/base.rb
index c7ea7c78e2f..81f96e822f4 100644
--- a/lib/gitlab/ci/build/context/base.rb
+++ b/lib/gitlab/ci/build/context/base.rb
@@ -17,6 +17,12 @@ module Gitlab
raise NotImplementedError
end
+ def variables_hash
+ strong_memoize(:variables_hash) do
+ variables.to_hash
+ end
+ end
+
def project
pipeline.project
end
diff --git a/lib/gitlab/ci/build/policy/variables.rb b/lib/gitlab/ci/build/policy/variables.rb
index 7b1ce6330f0..810523052ae 100644
--- a/lib/gitlab/ci/build/policy/variables.rb
+++ b/lib/gitlab/ci/build/policy/variables.rb
@@ -10,7 +10,7 @@ module Gitlab
end
def satisfied_by?(pipeline, context)
- variables = context.variables
+ variables = context.variables_hash
statements = @expressions.map do |statement|
::Gitlab::Ci::Pipeline::Expression::Statement
diff --git a/lib/gitlab/ci/build/rules/rule/clause/changes.rb b/lib/gitlab/ci/build/rules/rule/clause/changes.rb
index 9c2f6eea1dd..82a59fdb4e1 100644
--- a/lib/gitlab/ci/build/rules/rule/clause/changes.rb
+++ b/lib/gitlab/ci/build/rules/rule/clause/changes.rb
@@ -23,7 +23,7 @@ module Gitlab
return @globs unless context
@globs.map do |glob|
- ExpandVariables.expand_existing(glob, context.variables)
+ ExpandVariables.expand_existing(glob, -> { context.variables_hash })
end
end
end
diff --git a/lib/gitlab/ci/build/rules/rule/clause/if.rb b/lib/gitlab/ci/build/rules/rule/clause/if.rb
index 6143a736ca6..499a265a1e2 100644
--- a/lib/gitlab/ci/build/rules/rule/clause/if.rb
+++ b/lib/gitlab/ci/build/rules/rule/clause/if.rb
@@ -10,7 +10,7 @@ module Gitlab
def satisfied_by?(pipeline, context)
::Gitlab::Ci::Pipeline::Expression::Statement.new(
- @expression, context.variables).truthful?
+ @expression, context.variables_hash).truthful?
end
end
end
diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb
index 6f149385969..42b487fdf81 100644
--- a/lib/gitlab/ci/config.rb
+++ b/lib/gitlab/ci/config.rb
@@ -17,21 +17,27 @@ module Gitlab
Config::Yaml::Tags::TagError
].freeze
- attr_reader :root, :context, :source_ref_path, :source
+ attr_reader :root, :context, :source_ref_path, :source, :logger
- def initialize(config, project: nil, pipeline: nil, sha: nil, user: nil, parent_pipeline: nil, source: nil)
+ def initialize(config, project: nil, pipeline: nil, sha: nil, user: nil, parent_pipeline: nil, source: nil, logger: nil)
+ @logger = logger || ::Gitlab::Ci::Pipeline::Logger.new(project: project)
@source_ref_path = pipeline&.source_ref_path
- @context = build_context(project: project, pipeline: pipeline, sha: sha, user: user, parent_pipeline: parent_pipeline)
+ @context = self.logger.instrument(:config_build_context) do
+ build_context(project: project, pipeline: pipeline, sha: sha, user: user, parent_pipeline: parent_pipeline)
+ end
+
@context.set_deadline(TIMEOUT_SECONDS)
@source = source
- @config = expand_config(config)
-
- @root = Entry::Root.new(@config)
- @root.compose!
+ @config = self.logger.instrument(:config_expand) do
+ expand_config(config)
+ end
+ @root = self.logger.instrument(:config_compose) do
+ Entry::Root.new(@config).tap(&:compose!)
+ end
rescue *rescue_errors => e
raise Config::ConfigError, e.message
end
@@ -94,11 +100,25 @@ module Gitlab
end
def build_config(config)
- initial_config = Config::Yaml.load!(config)
- initial_config = Config::External::Processor.new(initial_config, @context).perform
- initial_config = Config::Extendable.new(initial_config).to_hash
- initial_config = Config::Yaml::Tags::Resolver.new(initial_config).to_hash
- Config::EdgeStagesInjector.new(initial_config).to_hash
+ initial_config = logger.instrument(:config_yaml_load) do
+ Config::Yaml.load!(config)
+ end
+
+ initial_config = logger.instrument(:config_external_process) do
+ Config::External::Processor.new(initial_config, @context).perform
+ end
+
+ initial_config = logger.instrument(:config_yaml_extend) do
+ Config::Extendable.new(initial_config).to_hash
+ end
+
+ initial_config = logger.instrument(:config_tags_resolve) do
+ Config::Yaml::Tags::Resolver.new(initial_config).to_hash
+ end
+
+ logger.instrument(:config_stages_inject) do
+ Config::EdgeStagesInjector.new(initial_config).to_hash
+ end
end
def find_sha(project)
@@ -115,10 +135,20 @@ module Gitlab
sha: sha || find_sha(project),
user: user,
parent_pipeline: parent_pipeline,
- variables: build_variables(project: project, pipeline: pipeline))
+ variables: build_variables(project: project, pipeline: pipeline),
+ logger: logger)
end
def build_variables(project:, pipeline:)
+ logger.instrument(:config_build_variables) do
+ build_variables_without_instrumentation(
+ project: project,
+ pipeline: pipeline
+ )
+ end
+ end
+
+ def build_variables_without_instrumentation(project:, pipeline:)
Gitlab::Ci::Variables::Collection.new.tap do |variables|
break variables unless project
diff --git a/lib/gitlab/ci/config/entry/processable.rb b/lib/gitlab/ci/config/entry/processable.rb
index 520b1ce6119..43475742214 100644
--- a/lib/gitlab/ci/config/entry/processable.rb
+++ b/lib/gitlab/ci/config/entry/processable.rb
@@ -26,7 +26,7 @@ module Gitlab
validates :name, length: { maximum: 255 }, if: -> { ::Feature.enabled?(:ci_validate_job_length, default_enabled: :yaml) }
validates :config, disallowed_keys: {
- in: %i[only except when start_in],
+ in: %i[only except start_in],
message: 'key may not be used with `rules`'
},
if: :has_rules?
diff --git a/lib/gitlab/ci/config/entry/tags.rb b/lib/gitlab/ci/config/entry/tags.rb
index ca3b48372e2..6044cfddbdc 100644
--- a/lib/gitlab/ci/config/entry/tags.rb
+++ b/lib/gitlab/ci/config/entry/tags.rb
@@ -16,8 +16,6 @@ module Gitlab
validates :config, array_of_strings: true
validate do
- next unless ::Feature.enabled?(:ci_build_tags_limit, default_enabled: :yaml)
-
if config.is_a?(Array) && config.size >= TAGS_LIMIT
errors.add(:config, _("must be less than the limit of %{tag_limit} tags") % { tag_limit: TAGS_LIMIT })
end
diff --git a/lib/gitlab/ci/config/external/context.rb b/lib/gitlab/ci/config/external/context.rb
index 51624dc30ea..308414af47d 100644
--- a/lib/gitlab/ci/config/external/context.rb
+++ b/lib/gitlab/ci/config/external/context.rb
@@ -9,17 +9,22 @@ module Gitlab
TimeoutError = Class.new(StandardError)
+ include ::Gitlab::Utils::StrongMemoize
+
attr_reader :project, :sha, :user, :parent_pipeline, :variables
- attr_reader :expandset, :execution_deadline
+ attr_reader :expandset, :execution_deadline, :logger
+
+ delegate :instrument, to: :logger
- def initialize(project: nil, sha: nil, user: nil, parent_pipeline: nil, variables: [])
+ def initialize(project: nil, sha: nil, user: nil, parent_pipeline: nil, variables: nil, logger: nil)
@project = project
@sha = sha
@user = user
@parent_pipeline = parent_pipeline
- @variables = variables
+ @variables = variables || Ci::Variables::Collection.new
@expandset = Set.new
@execution_deadline = 0
+ @logger = logger || Gitlab::Ci::Pipeline::Logger.new(project: project)
yield self if block_given?
end
@@ -36,10 +41,17 @@ module Gitlab
end
end
+ def variables_hash
+ strong_memoize(:variables_hash) do
+ variables.to_hash
+ end
+ end
+
def mutate(attrs = {})
self.class.new(**attrs) do |ctx|
ctx.expandset = expandset
ctx.execution_deadline = execution_deadline
+ ctx.logger = logger
end
end
@@ -60,7 +72,7 @@ module Gitlab
protected
- attr_writer :expandset, :execution_deadline
+ attr_writer :expandset, :execution_deadline, :logger
private
diff --git a/lib/gitlab/ci/config/external/mapper.rb b/lib/gitlab/ci/config/external/mapper.rb
index 95f1a842c50..a5bf066c81f 100644
--- a/lib/gitlab/ci/config/external/mapper.rb
+++ b/lib/gitlab/ci/config/external/mapper.rb
@@ -30,6 +30,18 @@ module Gitlab
def process
return [] if locations.empty?
+ logger.instrument(:config_mapper_process) do
+ process_without_instrumentation
+ end
+ end
+
+ private
+
+ attr_reader :locations, :context
+
+ delegate :expandset, :logger, to: :context
+
+ def process_without_instrumentation
locations
.compact
.map(&method(:normalize_location))
@@ -41,14 +53,14 @@ module Gitlab
.map(&method(:select_first_matching))
end
- private
-
- attr_reader :locations, :context
-
- delegate :expandset, to: :context
+ def normalize_location(location)
+ logger.instrument(:config_mapper_normalize) do
+ normalize_location_without_instrumentation(location)
+ end
+ end
# convert location if String to canonical form
- def normalize_location(location)
+ def normalize_location_without_instrumentation(location)
if location.is_a?(String)
expanded_location = expand_variables(location)
normalize_location_string(expanded_location)
@@ -58,6 +70,12 @@ module Gitlab
end
def verify_rules(location)
+ logger.instrument(:config_mapper_rules) do
+ verify_rules_without_instrumentation(location)
+ end
+ end
+
+ def verify_rules_without_instrumentation(location)
return unless Rules.new(location[:rules]).evaluate(context).pass?
location
@@ -72,6 +90,12 @@ module Gitlab
end
def expand_wildcard_paths(location)
+ logger.instrument(:config_mapper_wildcards) do
+ expand_wildcard_paths_without_instrumentation(location)
+ end
+ end
+
+ def expand_wildcard_paths_without_instrumentation(location)
# We only support local files for wildcard paths
return location unless location[:local] && location[:local].include?('*')
@@ -89,6 +113,12 @@ module Gitlab
end
def verify_duplicates!(location)
+ logger.instrument(:config_mapper_verify) do
+ verify_duplicates_without_instrumentation!(location)
+ end
+ end
+
+ def verify_duplicates_without_instrumentation!(location)
if expandset.count >= MAX_INCLUDES
raise TooManyIncludesError, "Maximum of #{MAX_INCLUDES} nested includes are allowed!"
end
@@ -106,6 +136,12 @@ module Gitlab
end
def select_first_matching(location)
+ logger.instrument(:config_mapper_select) do
+ select_first_matching_without_instrumentation(location)
+ end
+ end
+
+ def select_first_matching_without_instrumentation(location)
matching = FILE_CLASSES.map do |file_class|
file_class.new(location, context)
end.select(&:matching?)
@@ -116,6 +152,12 @@ module Gitlab
end
def expand_variables(data)
+ logger.instrument(:config_mapper_variables) do
+ expand_variables_without_instrumentation(data)
+ end
+ end
+
+ def expand_variables_without_instrumentation(data)
if data.is_a?(String)
expand(data)
else
@@ -137,7 +179,7 @@ module Gitlab
end
def expand(data)
- ExpandVariables.expand(data, context.variables)
+ ExpandVariables.expand(data, -> { context.variables_hash })
end
end
end
diff --git a/lib/gitlab/ci/config/external/processor.rb b/lib/gitlab/ci/config/external/processor.rb
index de69a1b1e8f..6a4aee26d80 100644
--- a/lib/gitlab/ci/config/external/processor.rb
+++ b/lib/gitlab/ci/config/external/processor.rb
@@ -7,10 +7,13 @@ module Gitlab
class Processor
IncludeError = Class.new(StandardError)
+ attr_reader :context, :logger
+
def initialize(values, context)
@values = values
@external_files = External::Mapper.new(values, context).process
@content = {}
+ @logger = context.logger
rescue External::Mapper::Error,
OpenSSL::SSL::SSLError => e
raise IncludeError, e.message
@@ -29,13 +32,17 @@ module Gitlab
def validate_external_files!
@external_files.each do |file|
- raise IncludeError, file.error_message unless file.valid?
+ logger.instrument(:config_external_verify) do
+ raise IncludeError, file.error_message unless file.valid?
+ end
end
end
def merge_external_files!
@external_files.each do |file|
- @content.deep_merge!(file.to_hash)
+ logger.instrument(:config_external_merge) do
+ @content.deep_merge!(file.to_hash)
+ end
end
end
diff --git a/lib/gitlab/ci/features.rb b/lib/gitlab/ci/features.rb
deleted file mode 100644
index 51051b0490f..00000000000
--- a/lib/gitlab/ci/features.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Ci
- ##
- # Deprecated: Ci::Features is a class that aggregates all CI/CD feature flags in one place.
- #
- module Features
- # NOTE: The feature flag `disallow_to_create_merge_request_pipelines_in_target_project`
- # is a safe switch to disable the feature for a particular project when something went wrong,
- # therefore it's not supposed to be enabled by default.
- def self.disallow_to_create_merge_request_pipelines_in_target_project?(target_project)
- ::Feature.enabled?(:ci_disallow_to_create_merge_request_pipelines_in_target_project, target_project)
- end
-
- def self.accept_trace?(project)
- ::Feature.enabled?(:ci_enable_live_trace, project) &&
- ::Feature.enabled?(:ci_accept_trace, project, type: :ops, default_enabled: true)
- end
-
- def self.log_invalid_trace_chunks?(project)
- ::Feature.enabled?(:ci_trace_log_invalid_chunks, project, type: :ops, default_enabled: false)
- end
-
- def self.gldropdown_tags_enabled?
- ::Feature.enabled?(:gldropdown_tags, default_enabled: :yaml)
- end
- end
- end
-end
diff --git a/lib/gitlab/ci/parsers/security/validators/schema_validator.rb b/lib/gitlab/ci/parsers/security/validators/schema_validator.rb
index 73cfa02ce4b..651ed23eb25 100644
--- a/lib/gitlab/ci/parsers/security/validators/schema_validator.rb
+++ b/lib/gitlab/ci/parsers/security/validators/schema_validator.rb
@@ -34,7 +34,7 @@ module Gitlab
end
def file_name
- "#{report_type.to_s.dasherize}-report-format.json"
+ report_type == :api_fuzzing ? "dast-report-format.json" : "#{report_type.to_s.dasherize}-report-format.json"
end
end
diff --git a/lib/gitlab/ci/parsers/terraform/tfplan.rb b/lib/gitlab/ci/parsers/terraform/tfplan.rb
index f9afa58f915..041d73cd914 100644
--- a/lib/gitlab/ci/parsers/terraform/tfplan.rb
+++ b/lib/gitlab/ci/parsers/terraform/tfplan.rb
@@ -34,7 +34,7 @@ module Gitlab
def job_details(job)
{
'job_id' => job.id.to_s,
- 'job_name' => job.options.dig(:artifacts, :name).to_s,
+ 'job_name' => job.name,
'job_path' => Gitlab::Routing.url_helpers.project_job_path(job.project, job)
}
end
diff --git a/lib/gitlab/ci/pipeline/chain/base.rb b/lib/gitlab/ci/pipeline/chain/base.rb
index 9b494f3a7ec..28567437719 100644
--- a/lib/gitlab/ci/pipeline/chain/base.rb
+++ b/lib/gitlab/ci/pipeline/chain/base.rb
@@ -7,7 +7,7 @@ module Gitlab
class Base
attr_reader :pipeline, :command, :config
- delegate :project, :current_user, :parent_pipeline, to: :command
+ delegate :project, :current_user, :parent_pipeline, :logger, to: :command
def initialize(pipeline, command)
@pipeline = pipeline
diff --git a/lib/gitlab/ci/pipeline/chain/build.rb b/lib/gitlab/ci/pipeline/chain/build.rb
index 6feb693221b..bbdc6b65b96 100644
--- a/lib/gitlab/ci/pipeline/chain/build.rb
+++ b/lib/gitlab/ci/pipeline/chain/build.rb
@@ -21,6 +21,10 @@ module Gitlab
merge_request: @command.merge_request,
external_pull_request: @command.external_pull_request,
locked: @command.project.default_pipeline_lock)
+
+ # Initialize the feature flag at the beginning of the pipeline creation process
+ # so that the flag references in the latter chains return the same value.
+ @pipeline.create_deployment_in_separate_transaction?
end
def break?
diff --git a/lib/gitlab/ci/pipeline/chain/command.rb b/lib/gitlab/ci/pipeline/chain/command.rb
index beb8801096b..c466b8b36d0 100644
--- a/lib/gitlab/ci/pipeline/chain/command.rb
+++ b/lib/gitlab/ci/pipeline/chain/command.rb
@@ -11,7 +11,7 @@ module Gitlab
:trigger_request, :schedule, :merge_request, :external_pull_request,
:ignore_skip_ci, :save_incompleted,
:seeds_block, :variables_attributes, :push_options,
- :chat_data, :allow_mirror_update, :bridge, :content, :dry_run,
+ :chat_data, :allow_mirror_update, :bridge, :content, :dry_run, :logger,
# These attributes are set by Chains during processing:
:config_content, :yaml_processor_result, :workflow_rules_result, :pipeline_seed
) do
@@ -88,7 +88,14 @@ module Gitlab
@metrics ||= ::Gitlab::Ci::Pipeline::Metrics
end
+ def logger
+ self[:logger] ||= ::Gitlab::Ci::Pipeline::Logger.new(project: project)
+ end
+
def observe_step_duration(step_class, duration)
+ step = step_class.name.underscore.parameterize(separator: '_')
+ logger.observe("pipeline_step_#{step}_duration_s", duration)
+
if Feature.enabled?(:ci_pipeline_creation_step_duration_tracking, type: :ops, default_enabled: :yaml)
metrics.pipeline_creation_step_duration_histogram
.observe({ step: step_class.name }, duration.seconds)
@@ -96,11 +103,15 @@ module Gitlab
end
def observe_creation_duration(duration)
+ logger.observe(:pipeline_creation_duration_s, duration)
+
metrics.pipeline_creation_duration_histogram
.observe({}, duration.seconds)
end
def observe_pipeline_size(pipeline)
+ logger.observe(:pipeline_size_count, pipeline.total_size)
+
metrics.pipeline_size_histogram
.observe({ source: pipeline.source.to_s }, pipeline.total_size)
end
diff --git a/lib/gitlab/ci/pipeline/chain/config/process.rb b/lib/gitlab/ci/pipeline/chain/config/process.rb
index f3c937ddd28..64d1b001e3c 100644
--- a/lib/gitlab/ci/pipeline/chain/config/process.rb
+++ b/lib/gitlab/ci/pipeline/chain/config/process.rb
@@ -11,16 +11,21 @@ module Gitlab
def perform!
raise ArgumentError, 'missing config content' unless @command.config_content
- result = ::Gitlab::Ci::YamlProcessor.new(
- @command.config_content, {
- project: project,
- pipeline: @pipeline,
- sha: @pipeline.sha,
- source: @pipeline.source,
- user: current_user,
- parent_pipeline: parent_pipeline
- }
- ).execute
+ result = logger.instrument(:pipeline_config_process) do
+ processor = ::Gitlab::Ci::YamlProcessor.new(
+ @command.config_content, {
+ project: project,
+ pipeline: @pipeline,
+ sha: @pipeline.sha,
+ source: @pipeline.source,
+ user: current_user,
+ parent_pipeline: parent_pipeline,
+ logger: logger
+ }
+ )
+
+ processor.execute
+ end
add_warnings_to_pipeline(result.warnings)
diff --git a/lib/gitlab/ci/pipeline/chain/create.rb b/lib/gitlab/ci/pipeline/chain/create.rb
index 81ef3bb074d..15b0ff3c04d 100644
--- a/lib/gitlab/ci/pipeline/chain/create.rb
+++ b/lib/gitlab/ci/pipeline/chain/create.rb
@@ -6,10 +6,18 @@ module Gitlab
module Chain
class Create < Chain::Base
include Chain::Helpers
+ include Gitlab::Utils::StrongMemoize
def perform!
- BulkInsertableAssociations.with_bulk_insert do
- pipeline.save!
+ logger.instrument(:pipeline_save) do
+ BulkInsertableAssociations.with_bulk_insert do
+ tags = extract_tag_list_by_status
+
+ pipeline.transaction do
+ pipeline.save!
+ CommitStatus.bulk_insert_tags!(statuses, tags) if bulk_insert_tags?
+ end
+ end
end
rescue ActiveRecord::RecordInvalid => e
error("Failed to persist the pipeline: #{e}")
@@ -18,6 +26,37 @@ module Gitlab
def break?
!pipeline.persisted?
end
+
+ private
+
+ def statuses
+ strong_memoize(:statuses) do
+ pipeline.stages.flat_map(&:statuses)
+ end
+ end
+
+ # We call `job.tag_list=` to assign tags to the jobs from the
+ # Chain::Seed step which uses the `@tag_list` instance variable to
+ # store them on the record. We remove them here because we want to
+ # bulk insert them, otherwise they would be inserted and assigned one
+ # by one with callbacks. We must use `remove_instance_variable`
+ # because having the instance variable defined would still run the callbacks
+ def extract_tag_list_by_status
+ return {} unless bulk_insert_tags?
+
+ statuses.each.with_object({}) do |job, acc|
+ tag_list = job.clear_memoization(:tag_list)
+ next unless tag_list
+
+ acc[job.name] = tag_list
+ end
+ end
+
+ def bulk_insert_tags?
+ strong_memoize(:bulk_insert_tags) do
+ ::Feature.enabled?(:ci_bulk_insert_tags, project, default_enabled: :yaml)
+ end
+ end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/chain/create_deployments.rb b/lib/gitlab/ci/pipeline/chain/create_deployments.rb
new file mode 100644
index 00000000000..b92aa89d62d
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/chain/create_deployments.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Pipeline
+ module Chain
+ class CreateDeployments < Chain::Base
+ DeploymentCreationError = Class.new(StandardError)
+
+ def perform!
+ return unless pipeline.create_deployment_in_separate_transaction?
+
+ create_deployments!
+ end
+
+ def break?
+ false
+ end
+
+ private
+
+ def create_deployments!
+ pipeline.stages.map(&:statuses).flatten.map(&method(:create_deployment))
+ end
+
+ def create_deployment(build)
+ return unless build.instance_of?(::Ci::Build) && build.persisted_environment.present?
+
+ deployment = ::Gitlab::Ci::Pipeline::Seed::Deployment
+ .new(build, build.persisted_environment).to_resource
+
+ return unless deployment
+
+ deployment.deployable = build
+ deployment.save!
+ rescue ActiveRecord::RecordInvalid => e
+ Gitlab::ErrorTracking.track_and_raise_for_dev_exception(
+ DeploymentCreationError.new(e.message), build_id: build.id)
+ 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
new file mode 100644
index 00000000000..424e1d87fb4
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/chain/ensure_environments.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Pipeline
+ module Chain
+ class EnsureEnvironments < Chain::Base
+ def perform!
+ return unless pipeline.create_deployment_in_separate_transaction?
+
+ pipeline.stages.map(&:statuses).flatten.each(&method(:ensure_environment))
+ end
+
+ def break?
+ false
+ end
+
+ private
+
+ def ensure_environment(build)
+ return unless build.instance_of?(::Ci::Build) && build.has_environment?
+
+ environment = ::Gitlab::Ci::Pipeline::Seed::Environment.new(build).to_resource
+
+ if environment.persisted?
+ build.persisted_environment = environment
+ build.assign_attributes(metadata_attributes: { expanded_environment_name: environment.name })
+ else
+ build.assign_attributes(status: :failed, failure_reason: :environment_creation_failure)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/pipeline/chain/ensure_resource_groups.rb b/lib/gitlab/ci/pipeline/chain/ensure_resource_groups.rb
new file mode 100644
index 00000000000..f4e5e6e467a
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/chain/ensure_resource_groups.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Pipeline
+ module Chain
+ class EnsureResourceGroups < Chain::Base
+ def perform!
+ return unless pipeline.create_deployment_in_separate_transaction?
+
+ pipeline.stages.map(&:statuses).flatten.each(&method(:ensure_resource_group))
+ end
+
+ def break?
+ false
+ end
+
+ private
+
+ def ensure_resource_group(processable)
+ return unless processable.is_a?(::Ci::Processable)
+
+ key = processable.options.delete(:resource_group_key)
+
+ resource_group = ::Gitlab::Ci::Pipeline::Seed::Processable::ResourceGroup
+ .new(processable, key).to_resource
+
+ processable.resource_group = resource_group
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/pipeline/chain/seed.rb b/lib/gitlab/ci/pipeline/chain/seed.rb
index ef7447fa83d..356eeb76908 100644
--- a/lib/gitlab/ci/pipeline/chain/seed.rb
+++ b/lib/gitlab/ci/pipeline/chain/seed.rb
@@ -13,8 +13,10 @@ module Gitlab
raise ArgumentError, 'missing workflow rules result' unless @command.workflow_rules_result
# Allocate next IID. This operation must be outside of transactions of pipeline creations.
- pipeline.ensure_project_iid!
- pipeline.ensure_ci_ref!
+ logger.instrument(:pipeline_allocate_seed_attributes) do
+ pipeline.ensure_project_iid!
+ pipeline.ensure_ci_ref!
+ end
# Protect the pipeline. This is assigned in Populate instead of
# Build to prevent erroring out on ambiguous refs.
@@ -23,8 +25,12 @@ module Gitlab
##
# Gather all runtime build/stage errors
#
- if pipeline_seed.errors
- return error(pipeline_seed.errors.join("\n"), config_error: true)
+ seed_errors = logger.instrument(:pipeline_seed_evaluation) do
+ pipeline_seed.errors
+ end
+
+ if seed_errors
+ return error(seed_errors.join("\n"), config_error: true)
end
@command.pipeline_seed = pipeline_seed
@@ -38,8 +44,11 @@ module Gitlab
def pipeline_seed
strong_memoize(:pipeline_seed) do
- stages_attributes = @command.yaml_processor_result.stages_attributes
- Gitlab::Ci::Pipeline::Seed::Pipeline.new(context, stages_attributes)
+ logger.instrument(:pipeline_seed_initialization) do
+ stages_attributes = @command.yaml_processor_result.stages_attributes
+
+ Gitlab::Ci::Pipeline::Seed::Pipeline.new(context, stages_attributes)
+ end
end
end
@@ -48,9 +57,11 @@ module Gitlab
end
def root_variables
- ::Gitlab::Ci::Variables::Helpers.merge_variables(
- @command.yaml_processor_result.root_variables, @command.workflow_rules_result.variables
- )
+ logger.instrument(:pipeline_seed_merge_variables) do
+ ::Gitlab::Ci::Variables::Helpers.merge_variables(
+ @command.yaml_processor_result.root_variables, @command.workflow_rules_result.variables
+ )
+ end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/chain/sequence.rb b/lib/gitlab/ci/pipeline/chain/sequence.rb
index 845eb6c7a42..de147914850 100644
--- a/lib/gitlab/ci/pipeline/chain/sequence.rb
+++ b/lib/gitlab/ci/pipeline/chain/sequence.rb
@@ -9,30 +9,36 @@ module Gitlab
@pipeline = pipeline
@command = command
@sequence = sequence
- @start = Time.now
+ @start = current_monotonic_time
end
def build!
@sequence.each do |step_class|
- step_start = ::Gitlab::Metrics::System.monotonic_time
+ step_start = current_monotonic_time
step = step_class.new(@pipeline, @command)
step.perform!
@command.observe_step_duration(
step_class,
- ::Gitlab::Metrics::System.monotonic_time - step_start
+ current_monotonic_time - step_start
)
break if step.break?
end
- @command.observe_creation_duration(Time.now - @start)
+ @command.observe_creation_duration(current_monotonic_time - @start)
@command.observe_pipeline_size(@pipeline)
@command.observe_jobs_count_in_alive_pipelines
@pipeline
end
+
+ private
+
+ def current_monotonic_time
+ ::Gitlab::Metrics::System.monotonic_time
+ end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/chain/validate/external.rb b/lib/gitlab/ci/pipeline/chain/validate/external.rb
index 28ba1cd4d47..85bd5f0a7c1 100644
--- a/lib/gitlab/ci/pipeline/chain/validate/external.rb
+++ b/lib/gitlab/ci/pipeline/chain/validate/external.rb
@@ -113,7 +113,7 @@ module Gitlab
name: build[:name],
stage: build[:stage],
image: build.dig(:options, :image, :name),
- services: build.dig(:options, :services)&.map { |service| service[:name] },
+ services: service_names(build),
script: [
build.dig(:options, :before_script),
build.dig(:options, :script),
@@ -122,6 +122,14 @@ module Gitlab
}
end
+ def service_names(build)
+ services = build.dig(:options, :services)
+
+ return unless services
+
+ services.compact.map { |service| service[:name] }
+ end
+
def stages_attributes
command.yaml_processor_result.stages_attributes
end
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/variable.rb b/lib/gitlab/ci/pipeline/expression/lexeme/variable.rb
index 11d2010909f..6da88fd287e 100644
--- a/lib/gitlab/ci/pipeline/expression/lexeme/variable.rb
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/variable.rb
@@ -9,7 +9,11 @@ module Gitlab
PATTERN = /\$(?<name>\w+)/.freeze
def evaluate(variables = {})
- variables.with_indifferent_access.fetch(@value, nil)
+ unless variables.is_a?(ActiveSupport::HashWithIndifferentAccess)
+ variables = variables.with_indifferent_access
+ end
+
+ variables.fetch(@value, nil)
end
def inspect
diff --git a/lib/gitlab/ci/pipeline/expression/statement.rb b/lib/gitlab/ci/pipeline/expression/statement.rb
index 5f3310dd668..4b13cae792e 100644
--- a/lib/gitlab/ci/pipeline/expression/statement.rb
+++ b/lib/gitlab/ci/pipeline/expression/statement.rb
@@ -9,7 +9,7 @@ module Gitlab
def initialize(statement, variables = nil)
@lexer = Expression::Lexer.new(statement)
- @variables = variables&.to_hash
+ @variables = variables || {}
end
def parse_tree
@@ -19,7 +19,7 @@ module Gitlab
end
def evaluate
- parse_tree.evaluate(@variables.to_h)
+ parse_tree.evaluate(@variables)
end
def truthful?
diff --git a/lib/gitlab/ci/pipeline/logger.rb b/lib/gitlab/ci/pipeline/logger.rb
new file mode 100644
index 00000000000..97f7dddd09a
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/logger.rb
@@ -0,0 +1,103 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Pipeline
+ class Logger
+ include ::Gitlab::Utils::StrongMemoize
+
+ def self.current_monotonic_time
+ ::Gitlab::Metrics::System.monotonic_time
+ end
+
+ def initialize(project:, destination: Gitlab::AppJsonLogger)
+ @started_at = current_monotonic_time
+ @project = project
+ @destination = destination
+ @log_conditions = []
+
+ yield(self) if block_given?
+ end
+
+ def log_when(&block)
+ log_conditions.push(block)
+ end
+
+ def instrument(operation)
+ return yield unless enabled?
+
+ raise ArgumentError, 'block not given' unless block_given?
+
+ op_started_at = current_monotonic_time
+
+ result = yield
+
+ observe("#{operation}_duration_s", current_monotonic_time - op_started_at)
+
+ result
+ end
+
+ def observe(operation, value)
+ return unless enabled?
+
+ observations[operation.to_s].push(value)
+ end
+
+ def commit(pipeline:, caller:)
+ return unless log?
+
+ attributes = {
+ class: self.class.name.to_s,
+ pipeline_creation_caller: caller,
+ project_id: project.id,
+ pipeline_id: pipeline.id,
+ pipeline_persisted: pipeline.persisted?,
+ pipeline_source: pipeline.source,
+ pipeline_creation_service_duration_s: age
+ }.stringify_keys.merge(observations_hash)
+
+ destination.info(attributes)
+ end
+
+ def observations_hash
+ observations.transform_values do |values|
+ next if values.empty?
+
+ {
+ 'count' => values.size,
+ 'min' => values.min,
+ 'max' => values.max,
+ 'avg' => values.sum / values.size
+ }
+ end.compact
+ end
+
+ private
+
+ attr_reader :project, :destination, :started_at, :log_conditions
+ delegate :current_monotonic_time, to: :class
+
+ def age
+ current_monotonic_time - started_at
+ end
+
+ def log?
+ return false unless enabled?
+ return true if log_conditions.empty?
+
+ log_conditions.any? { |cond| cond.call(observations) }
+ end
+
+ def enabled?
+ strong_memoize(:enabled) do
+ ::Feature.enabled?(:ci_pipeline_creation_logger, project, type: :ops, default_enabled: :yaml)
+ end
+ end
+
+ def observations
+ @observations ||= Hash.new { |hash, key| hash[key] = [] }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/pipeline/seed/build.rb b/lib/gitlab/ci/pipeline/seed/build.rb
index 72837b8ec22..762292f0fa3 100644
--- a/lib/gitlab/ci/pipeline/seed/build.rb
+++ b/lib/gitlab/ci/pipeline/seed/build.rb
@@ -7,8 +7,6 @@ module Gitlab
class Build < Seed::Base
include Gitlab::Utils::StrongMemoize
- EnvironmentCreationFailure = Class.new(StandardError)
-
delegate :dig, to: :@seed_attributes
def initialize(context, attributes, stages_for_needs_lookup = [])
@@ -30,7 +28,7 @@ module Gitlab
@except = Gitlab::Ci::Build::Policy
.fabricate(attributes.delete(:except))
@rules = Gitlab::Ci::Build::Rules
- .new(attributes.delete(:rules), default_when: 'on_success')
+ .new(attributes.delete(:rules), default_when: attributes[:when])
@cache = Gitlab::Ci::Build::Cache
.new(attributes.delete(:cache), @pipeline)
@@ -80,7 +78,7 @@ module Gitlab
def to_resource
strong_memoize(:resource) do
processable = initialize_processable
- assign_resource_group(processable)
+ assign_resource_group(processable) unless @pipeline.create_deployment_in_separate_transaction?
processable
end
end
@@ -90,7 +88,9 @@ module Gitlab
::Ci::Bridge.new(attributes)
else
::Ci::Build.new(attributes).tap do |build|
- build.assign_attributes(self.class.deployment_attributes_for(build))
+ unless @pipeline.create_deployment_in_separate_transaction?
+ build.assign_attributes(self.class.deployment_attributes_for(build))
+ end
end
end
end
@@ -107,20 +107,7 @@ module Gitlab
environment = Seed::Environment.new(build).to_resource if environment.nil?
unless environment.persisted?
- if Feature.enabled?(:surface_environment_creation_failure, build.project, default_enabled: :yaml) &&
- Feature.disabled?(:surface_environment_creation_failure_override, build.project)
- return { status: :failed, failure_reason: :environment_creation_failure }
- end
-
- # If there is a validation error on environment creation, such as
- # the name contains invalid character, the build falls back to a
- # non-environment job.
- Gitlab::ErrorTracking.track_exception(
- EnvironmentCreationFailure.new,
- project_id: build.project_id,
- reason: environment.errors.full_messages.to_sentence)
-
- return { environment: nil }
+ return { status: :failed, failure_reason: :environment_creation_failure }
end
build.persisted_environment = environment
@@ -215,12 +202,14 @@ module Gitlab
end
def runner_tags
- { tag_list: evaluate_runner_tags }.compact
+ strong_memoize(:runner_tags) do
+ { tag_list: evaluate_runner_tags }.compact
+ end
end
def evaluate_runner_tags
- @seed_attributes[:tag_list]&.map do |tag|
- ExpandVariables.expand_existing(tag, evaluate_context.variables)
+ @seed_attributes.delete(:tag_list)&.map do |tag|
+ ExpandVariables.expand_existing(tag, -> { evaluate_context.variables_hash })
end
end
diff --git a/lib/gitlab/ci/reports/security/report.rb b/lib/gitlab/ci/reports/security/report.rb
index 417319cb5be..3e4a44a2e70 100644
--- a/lib/gitlab/ci/reports/security/report.rb
+++ b/lib/gitlab/ci/reports/security/report.rb
@@ -51,7 +51,7 @@ module Gitlab
def replace_with!(other)
instance_variables.each do |ivar|
- instance_variable_set(ivar, other.public_send(ivar.to_s[1..-1])) # rubocop:disable GitlabSecurity/PublicSend
+ instance_variable_set(ivar, other.public_send(ivar.to_s[1..])) # rubocop:disable GitlabSecurity/PublicSend
end
end
diff --git a/lib/gitlab/ci/status/bridge/common.rb b/lib/gitlab/ci/status/bridge/common.rb
index d66d4b20bba..eaa87157716 100644
--- a/lib/gitlab/ci/status/bridge/common.rb
+++ b/lib/gitlab/ci/status/bridge/common.rb
@@ -16,7 +16,11 @@ module Gitlab
def details_path
return unless can?(user, :read_pipeline, downstream_pipeline)
- project_pipeline_path(downstream_project, downstream_pipeline)
+ if Feature.enabled?(:ci_retry_downstream_pipeline, subject.project, default_enabled: :yaml)
+ project_job_path(subject.project, subject)
+ else
+ project_pipeline_path(downstream_project, downstream_pipeline)
+ end
end
def has_action?
diff --git a/lib/gitlab/ci/status/build/failed.rb b/lib/gitlab/ci/status/build/failed.rb
index b0f12ff7517..5dd28157b1f 100644
--- a/lib/gitlab/ci/status/build/failed.rb
+++ b/lib/gitlab/ci/status/build/failed.rb
@@ -34,7 +34,8 @@ module Gitlab
no_matching_runner: 'no matching runner available',
trace_size_exceeded: 'log size limit exceeded',
builds_disabled: 'project builds are disabled',
- environment_creation_failure: 'environment creation failure'
+ environment_creation_failure: 'environment creation failure',
+ deployment_rejected: 'deployment rejected'
}.freeze
private_constant :REASONS
diff --git a/lib/gitlab/ci/tags/bulk_insert.rb b/lib/gitlab/ci/tags/bulk_insert.rb
new file mode 100644
index 00000000000..a299df7e2d9
--- /dev/null
+++ b/lib/gitlab/ci/tags/bulk_insert.rb
@@ -0,0 +1,90 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Tags
+ class BulkInsert
+ TAGGINGS_BATCH_SIZE = 1000
+ TAGS_BATCH_SIZE = 500
+
+ def initialize(statuses, tag_list_by_status)
+ @statuses = statuses
+ @tag_list_by_status = tag_list_by_status
+ end
+
+ def insert!
+ return false if tag_list_by_status.empty?
+
+ persist_build_tags!
+ end
+
+ private
+
+ attr_reader :statuses, :tag_list_by_status
+
+ def persist_build_tags!
+ all_tags = tag_list_by_status.values.flatten.uniq.reject(&:blank?)
+ tag_records_by_name = create_tags(all_tags).index_by(&:name)
+ taggings = build_taggings_attributes(tag_records_by_name)
+
+ return false if taggings.empty?
+
+ taggings.each_slice(TAGGINGS_BATCH_SIZE) do |taggings_slice|
+ ActsAsTaggableOn::Tagging.insert_all!(taggings)
+ end
+
+ true
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def create_tags(tags)
+ existing_tag_records = ActsAsTaggableOn::Tag.where(name: tags).to_a
+ missing_tags = detect_missing_tags(tags, existing_tag_records)
+ return existing_tag_records if missing_tags.empty?
+
+ missing_tags
+ .map { |tag| { name: tag } }
+ .each_slice(TAGS_BATCH_SIZE) do |tags_attributes|
+ ActsAsTaggableOn::Tag.insert_all!(tags_attributes)
+ end
+
+ ActsAsTaggableOn::Tag.where(name: tags).to_a
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ def build_taggings_attributes(tag_records_by_name)
+ taggings = statuses.flat_map do |status|
+ tag_list = tag_list_by_status[status.name]
+ next unless tag_list
+
+ tags = tag_records_by_name.values_at(*tag_list)
+ taggings_for(tags, status)
+ end
+
+ taggings.compact!
+ taggings
+ end
+
+ def taggings_for(tags, status)
+ tags.map do |tag|
+ {
+ tag_id: tag.id,
+ taggable_type: CommitStatus.name,
+ taggable_id: status.id,
+ created_at: Time.current,
+ context: 'tags'
+ }
+ end
+ end
+
+ def detect_missing_tags(tags, tag_records)
+ if tags.size != tag_records.size
+ tags - tag_records.map(&:name)
+ else
+ []
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
index 89fd59d98f4..fddcc1492a8 100644
--- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
@@ -53,6 +53,9 @@ variables:
# KUBE_INGRESS_BASE_DOMAIN is the application deployment domain and should be set as a variable at the group or project level.
# KUBE_INGRESS_BASE_DOMAIN: domain.example.com
+ # Allows Container-Scanning to correctly correlate image names when using Jobs/Build.gitlab-ci.yml
+ CS_DEFAULT_BRANCH_IMAGE: $CI_REGISTRY_IMAGE/$CI_DEFAULT_BRANCH:$CI_COMMIT_SHA
+
POSTGRES_USER: user
POSTGRES_PASSWORD: testing-password
POSTGRES_ENABLED: "true"
diff --git a/lib/gitlab/ci/templates/Jobs/SAST-IaC.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/SAST-IaC.latest.gitlab-ci.yml
index b763705857e..fa7f6ffa2b7 100644
--- a/lib/gitlab/ci/templates/Jobs/SAST-IaC.latest.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/SAST-IaC.latest.gitlab-ci.yml
@@ -24,7 +24,7 @@ kics-iac-sast:
image:
name: "$SAST_ANALYZER_IMAGE"
variables:
- SAST_ANALYZER_IMAGE_TAG: 0
+ SAST_ANALYZER_IMAGE_TAG: 1
SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/kics:$SAST_ANALYZER_IMAGE_TAG"
rules:
- if: $SAST_DISABLED
diff --git a/lib/gitlab/ci/templates/Pages/HTML.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/HTML.gitlab-ci.yml
index 17ed1d2e87f..d32444833fb 100644
--- a/lib/gitlab/ci/templates/Pages/HTML.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/HTML.gitlab-ci.yml
@@ -9,6 +9,7 @@ pages:
script:
- mkdir .public
- cp -r * .public
+ - rm -rf public
- mv .public public
artifacts:
paths:
diff --git a/lib/gitlab/ci/templates/Python.gitlab-ci.yml b/lib/gitlab/ci/templates/Python.gitlab-ci.yml
index aec41c137a4..4917abf6ae9 100644
--- a/lib/gitlab/ci/templates/Python.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Python.gitlab-ci.yml
@@ -23,7 +23,7 @@ cache:
- venv/
before_script:
- - python -V # Print out python version for debugging
+ - python --version # For debugging
- pip install virtualenv
- virtualenv venv
- source venv/bin/activate
diff --git a/lib/gitlab/ci/templates/Scala.gitlab-ci.yml b/lib/gitlab/ci/templates/Scala.gitlab-ci.yml
index ff8f9601189..de54d64dc42 100644
--- a/lib/gitlab/ci/templates/Scala.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Scala.gitlab-ci.yml
@@ -13,8 +13,10 @@ before_script:
- apt-get update -yqq
- apt-get install apt-transport-https -yqq
# Add keyserver for SBT
- - echo "deb http://dl.bintray.com/sbt/debian /" | tee -a /etc/apt/sources.list.d/sbt.list
- - apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 2EE0EA64E40A89B84B2DF73499E82A75642AC823
+ - echo "deb https://repo.scala-sbt.org/scalasbt/debian /" | tee -a /etc/apt/sources.list.d/sbt.list
+ - mkdir -p /root/.gnupg
+ - gpg --recv-keys --no-default-keyring --keyring gnupg-ring:/etc/apt/trusted.gpg.d/scalasbt-release.gpg --keyserver hkp://keyserver.ubuntu.com:80 2EE0EA64E40A89B84B2DF73499E82A75642AC823
+ - chmod 644 /etc/apt/trusted.gpg.d/scalasbt-release.gpg
# Install SBT
- apt-get update -yqq
- apt-get install sbt -yqq
diff --git a/lib/gitlab/ci/templates/Security/API-Fuzzing.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/API-Fuzzing.latest.gitlab-ci.yml
index 544774d3b06..01041f4f056 100644
--- a/lib/gitlab/ci/templates/Security/API-Fuzzing.latest.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/API-Fuzzing.latest.gitlab-ci.yml
@@ -11,11 +11,11 @@
variables:
FUZZAPI_VERSION: "1"
SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/gitlab-org/security-products/analyzers"
- FUZZAPI_IMAGE: ${SECURE_ANALYZERS_PREFIX}/api-fuzzing:${FUZZAPI_VERSION}
+ FUZZAPI_IMAGE: api-fuzzing
apifuzzer_fuzz:
stage: fuzz
- image: $FUZZAPI_IMAGE
+ image: $SECURE_ANALYZERS_PREFIX/$FUZZAPI_IMAGE:$FUZZAPI_VERSION
allow_failure: true
rules:
- if: $API_FUZZING_DISABLED
diff --git a/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml
index 89e6743b0e4..65a2b20d5c0 100644
--- a/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml
@@ -38,7 +38,8 @@ container_scanning:
artifacts:
reports:
container_scanning: gl-container-scanning-report.json
- paths: [gl-container-scanning-report.json]
+ dependency_scanning: gl-dependency-scanning-report.json
+ paths: [gl-container-scanning-report.json, gl-dependency-scanning-report.json]
dependencies: []
script:
- gtcs scan
diff --git a/lib/gitlab/ci/templates/Security/DAST-API.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/DAST-API.latest.gitlab-ci.yml
new file mode 100644
index 00000000000..57f1993921d
--- /dev/null
+++ b/lib/gitlab/ci/templates/Security/DAST-API.latest.gitlab-ci.yml
@@ -0,0 +1,52 @@
+# To contribute improvements to CI/CD templates, please follow the Development guide at:
+# https://docs.gitlab.com/ee/development/cicd/templates.html
+# This specific template is located at:
+# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Security/Dast-API.gitlab-ci.yml
+
+# To use this template, add the following to your .gitlab-ci.yml file:
+#
+# include:
+# template: DAST-API.latest.gitlab-ci.yml
+#
+# You also need to add a `dast` stage to your `stages:` configuration. A sample configuration for DAST API:
+#
+# stages:
+# - build
+# - test
+# - deploy
+# - dast
+
+# Read more about this feature here: https://docs.gitlab.com/ee/user/application_security/dast_api/index.html
+
+# Configure DAST API scanning with CI/CD variables (https://docs.gitlab.com/ee/ci/variables/index.html).
+# List of available variables: https://docs.gitlab.com/ee/user/application_security/dast_api/index.html#available-cicd-variables
+
+variables:
+ # Setting this variable affects all Security templates
+ # (SAST, Dependency Scanning, ...)
+ SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/gitlab-org/security-products/analyzers"
+ #
+ DAST_API_VERSION: "1"
+ DAST_API_IMAGE: api-fuzzing
+
+dast_api:
+ stage: dast
+ image: $SECURE_ANALYZERS_PREFIX/$DAST_API_IMAGE:$DAST_API_VERSION
+ allow_failure: true
+ rules:
+ - if: $DAST_API_DISABLED
+ when: never
+ - if: $DAST_API_DISABLED_FOR_DEFAULT_BRANCH &&
+ $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME
+ when: never
+ - if: $CI_COMMIT_BRANCH
+ script:
+ - /peach/analyzer-dast-api
+ artifacts:
+ when: always
+ paths:
+ - gl-assets
+ - gl-dast-api-report.json
+ - gl-*.log
+ reports:
+ dast: gl-dast-api-report.json
diff --git a/lib/gitlab/ci/templates/Verify/Accessibility.gitlab-ci.yml b/lib/gitlab/ci/templates/Verify/Accessibility.gitlab-ci.yml
index 4f63ff93d4d..8f4a836441d 100644
--- a/lib/gitlab/ci/templates/Verify/Accessibility.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Verify/Accessibility.gitlab-ci.yml
@@ -13,7 +13,7 @@ stages:
a11y:
stage: accessibility
- image: registry.gitlab.com/gitlab-org/ci-cd/accessibility:6.0.1
+ image: registry.gitlab.com/gitlab-org/ci-cd/accessibility:6.1.1
script: /gitlab-accessibility.sh $a11y_urls
allow_failure: true
artifacts:
diff --git a/lib/gitlab/ci/templates/dotNET-Core.gitlab-ci.yml b/lib/gitlab/ci/templates/dotNET-Core.gitlab-ci.yml
index edd0fb0ba07..09fce67db2d 100644
--- a/lib/gitlab/ci/templates/dotNET-Core.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/dotNET-Core.gitlab-ci.yml
@@ -1,4 +1,4 @@
-# To contribute improvements to CI/CD templates, please follow the Development guide at:
+# To contribute improvements to CI/CD templates, please follow the Development guide at:
# https://docs.gitlab.com/ee/development/cicd/templates.html
# This specific template is located at:
# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/dotNET-Core.yml
@@ -14,10 +14,10 @@
# The 'latest' tag targets the latest available version of .NET Core SDK image.
# If preferred, you can explicitly specify version of .NET Core (e.g. using '2.2-sdk' tag).
#
-# See other available tags for .NET Core: https://hub.docker.com/r/microsoft/dotnet
+# See other available tags for .NET Core: https://hub.docker.com/_/microsoft-dotnet
# Learn more about Docker tags: https://docs.docker.com/glossary/?term=tag
# and the Docker itself: https://opensource.com/resources/what-docker
-image: microsoft/dotnet:latest
+image: mcr.microsoft.com/dotnet/sdk:latest
# ### Define variables
#
diff --git a/lib/gitlab/ci/variables/builder.rb b/lib/gitlab/ci/variables/builder.rb
index f4c5a06af97..3e2c2c7fc1a 100644
--- a/lib/gitlab/ci/variables/builder.rb
+++ b/lib/gitlab/ci/variables/builder.rb
@@ -12,7 +12,7 @@ module Gitlab
def scoped_variables(job, environment:, dependencies:)
Gitlab::Ci::Variables::Collection.new.tap do |variables|
- variables.concat(predefined_variables(job)) if pipeline.predefined_vars_in_builder_enabled?
+ variables.concat(predefined_variables(job))
end
end
diff --git a/lib/gitlab/ci/yaml_processor.rb b/lib/gitlab/ci/yaml_processor.rb
index 1aa3dbc5e47..296b0cfded2 100644
--- a/lib/gitlab/ci/yaml_processor.rb
+++ b/lib/gitlab/ci/yaml_processor.rb
@@ -29,10 +29,8 @@ module Gitlab
run_logical_validations!
Result.new(ci_config: @ci_config, warnings: @ci_config&.warnings)
-
rescue Gitlab::Ci::Config::ConfigError => e
Result.new(ci_config: @ci_config, errors: [e.message], warnings: @ci_config&.warnings)
-
rescue ValidationError => e
Result.new(ci_config: @ci_config, errors: [e.message], warnings: @ci_config&.warnings)
end
diff --git a/lib/gitlab/ci/yaml_processor/result.rb b/lib/gitlab/ci/yaml_processor/result.rb
index 6215ba40ebe..f14279dca2d 100644
--- a/lib/gitlab/ci/yaml_processor/result.rb
+++ b/lib/gitlab/ci/yaml_processor/result.rb
@@ -92,6 +92,7 @@ module Gitlab
script: job[:script],
after_script: job[:after_script],
environment: job[:environment],
+ resource_group_key: job[:resource_group],
retry: job[:retry],
parallel: job[:parallel],
instance: job[:instance],
diff --git a/lib/gitlab/config/entry/undefined.rb b/lib/gitlab/config/entry/undefined.rb
index 5f708abc80c..55393890693 100644
--- a/lib/gitlab/config/entry/undefined.rb
+++ b/lib/gitlab/config/entry/undefined.rb
@@ -31,6 +31,10 @@ module Gitlab
false
end
+ def type
+ nil
+ end
+
def inspect
"#<#{self.class.name}>"
end
diff --git a/lib/gitlab/content_security_policy/config_loader.rb b/lib/gitlab/content_security_policy/config_loader.rb
index bdae59e7e3c..87bc2ace204 100644
--- a/lib/gitlab/content_security_policy/config_loader.rb
+++ b/lib/gitlab/content_security_policy/config_loader.rb
@@ -36,6 +36,7 @@ module Gitlab
if Rails.env.development?
allow_webpack_dev_server(directives)
allow_letter_opener(directives)
+ allow_snowplow_micro(directives) if Gitlab::Tracking.snowplow_micro_enabled?
allow_customersdot(directives) if ENV['CUSTOMER_PORTAL_URL'].present?
end
@@ -138,13 +139,15 @@ module Gitlab
append_to_directive(directives, 'frame_src', Gitlab::Utils.append_path(Gitlab.config.gitlab.url, '/rails/letter_opener/'))
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)
+ 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)
- # We need the version without trailing / for the sidekiq page itself
- # and we also need the version with trailing / for "deeper" pages
- # like /admin/sidekiq/busy
- ['/admin/sidekiq', '/admin/sidekiq/', '/-/speedscope/index.html'].map do |path|
+ ['/admin/', '/assets/', '/-/speedscope/index.html'].map do |path|
append_to_directive(directives, 'frame_src', Gitlab::Utils.append_path(Gitlab.config.gitlab.url, path))
end
end
diff --git a/lib/gitlab/content_security_policy/directives.rb b/lib/gitlab/content_security_policy/directives.rb
index 30f3c16247d..3b958f8c92e 100644
--- a/lib/gitlab/content_security_policy/directives.rb
+++ b/lib/gitlab/content_security_policy/directives.rb
@@ -8,7 +8,7 @@ module Gitlab
module ContentSecurityPolicy
module Directives
def self.frame_src
- "https://www.google.com/recaptcha/ https://www.recaptcha.net/ https://content.googleapis.com https://content-compute.googleapis.com https://content-cloudbilling.googleapis.com https://content-cloudresourcemanager.googleapis.com"
+ "https://www.google.com/recaptcha/ https://www.recaptcha.net/ https://content.googleapis.com https://content-compute.googleapis.com https://content-cloudbilling.googleapis.com https://content-cloudresourcemanager.googleapis.com https://www.googletagmanager.com/ns.html"
end
def self.script_src
diff --git a/lib/gitlab/contributions_calendar.rb b/lib/gitlab/contributions_calendar.rb
index deaaab953aa..f48ba27888c 100644
--- a/lib/gitlab/contributions_calendar.rb
+++ b/lib/gitlab/contributions_calendar.rb
@@ -10,7 +10,7 @@ module Gitlab
def initialize(contributor, current_user = nil)
@contributor = contributor
- @contributor_time_instance = local_time_instance(contributor.timezone)
+ @contributor_time_instance = local_timezone_instance(contributor.timezone).now
@current_user = current_user
@projects = if @contributor.include_private_contributions?
ContributedProjectsFinder.new(@contributor).execute(@contributor)
@@ -21,27 +21,33 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord
def activity_dates
+ return {} if @projects.empty?
return @activity_dates if @activity_dates.present?
+ start_time = @contributor_time_instance.years_ago(1).beginning_of_day
+ end_time = @contributor_time_instance.end_of_day
+
+ date_interval = "INTERVAL '#{@contributor_time_instance.utc_offset} seconds'"
+
# Can't use Event.contributions here because we need to check 3 different
# project_features for the (currently) 3 different contribution types
- date_from = @contributor_time_instance.now.years_ago(1)
- repo_events = event_counts(date_from, :repository)
- .having(action: :pushed)
- issue_events = event_counts(date_from, :issues)
- .having(action: [:created, :closed], target_type: "Issue")
- mr_events = event_counts(date_from, :merge_requests)
- .having(action: [:merged, :created, :closed], target_type: "MergeRequest")
- note_events = event_counts(date_from, :merge_requests)
- .having(action: :commented)
+ repo_events = events_created_between(start_time, end_time, :repository)
+ .where(action: :pushed)
+ issue_events = events_created_between(start_time, end_time, :issues)
+ .where(action: [:created, :closed], target_type: "Issue")
+ mr_events = events_created_between(start_time, end_time, :merge_requests)
+ .where(action: [:merged, :created, :closed], target_type: "MergeRequest")
+ note_events = events_created_between(start_time, end_time, :merge_requests)
+ .where(action: :commented)
events = Event
- .select(:project_id, :target_type, :action, :date, :total_amount)
- .from_union([repo_events, issue_events, mr_events, note_events])
+ .select("date(created_at + #{date_interval}) AS date", 'COUNT(*) AS num_events')
+ .from_union([repo_events, issue_events, mr_events, note_events], remove_duplicates: false)
+ .group(:date)
.map(&:attributes)
@activity_dates = events.each_with_object(Hash.new {|h, k| h[k] = 0 }) do |event, activities|
- activities[event["date"]] += event["total_amount"]
+ activities[event["date"]] += event["num_events"]
end
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -50,7 +56,7 @@ module Gitlab
def events_by_date(date)
return Event.none unless can_read_cross_project?
- date_in_time_zone = date.in_time_zone(@contributor_time_instance)
+ date_in_time_zone = date.in_time_zone(@contributor_time_instance.time_zone)
Event.contributions.where(author_id: contributor.id)
.where(created_at: date_in_time_zone.beginning_of_day..date_in_time_zone.end_of_day)
@@ -60,11 +66,11 @@ module Gitlab
# rubocop: enable CodeReuse/ActiveRecord
def starting_year
- @contributor_time_instance.now.years_ago(1).year
+ @contributor_time_instance.years_ago(1).year
end
def starting_month
- @contributor_time_instance.today.month
+ @contributor_time_instance.month
end
private
@@ -74,29 +80,31 @@ module Gitlab
end
# rubocop: disable CodeReuse/ActiveRecord
- def event_counts(date_from, feature)
- t = Event.arel_table
-
+ def events_created_between(start_time, end_time, feature)
# re-running the contributed projects query in each union is expensive, so
# use IN(project_ids...) instead. It's the intersection of two users so
# the list will be (relatively) short
@contributed_project_ids ||= projects.distinct.pluck(:id)
- authed_projects = Project.where(id: @contributed_project_ids)
- .with_feature_available_for_user(feature, current_user)
- .reorder(nil)
- .select(:id)
-
- conditions = t[:created_at].gteq(date_from.beginning_of_day)
- .and(t[:created_at].lteq(@contributor_time_instance.today.end_of_day))
- .and(t[:author_id].eq(contributor.id))
- date_interval = "INTERVAL '#{@contributor_time_instance.now.utc_offset} seconds'"
+ # no need to check feature access of current user, if the contributor opted-in
+ # to show all private events anyway - otherwise they would get filtered out again
+ authed_projects = if @contributor.include_private_contributions?
+ @contributed_project_ids
+ else
+ ProjectFeature
+ .with_feature_available_for_user(feature, current_user)
+ .where(project_id: @contributed_project_ids)
+ .reorder(nil)
+ .select(:project_id)
+ end
Event.reorder(nil)
- .select(t[:project_id], t[:target_type], t[:action], "date(created_at + #{date_interval}) AS date", 'count(id) as total_amount')
- .group(t[:project_id], t[:target_type], t[:action], "date(created_at + #{date_interval})")
- .where(conditions)
- .where("events.project_id in (#{authed_projects.to_sql})") # rubocop:disable GitlabSecurity/SqlInjection
+ .select(:created_at)
+ .where(
+ author_id: contributor.id,
+ created_at: start_time..end_time,
+ events: { project_id: authed_projects }
+ )
end
# rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/lib/gitlab/daemon.rb b/lib/gitlab/daemon.rb
index ddb9d907640..058fe1c8139 100644
--- a/lib/gitlab/daemon.rb
+++ b/lib/gitlab/daemon.rb
@@ -2,16 +2,16 @@
module Gitlab
class Daemon
- def self.initialize_instance(*args)
+ def self.initialize_instance(...)
raise "#{name} singleton instance already initialized" if @instance
- @instance = new(*args)
+ @instance = new(...)
Kernel.at_exit(&@instance.method(:stop))
@instance
end
- def self.instance(*args)
- @instance ||= initialize_instance(*args)
+ def self.instance(...)
+ @instance ||= initialize_instance(...)
end
attr_reader :thread
@@ -20,7 +20,8 @@ module Gitlab
!thread.nil?
end
- def initialize
+ def initialize(**options)
+ @synchronous = options[:synchronous]
@mutex = Mutex.new
end
@@ -43,6 +44,10 @@ module Gitlab
Thread.current.name = thread_name
run_thread
end
+
+ @thread.join if @synchronous
+
+ @thread
end
end
end
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index 9c74e5d2ca8..f9c346a272f 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -63,6 +63,19 @@ module Gitlab
}.compact.with_indifferent_access.freeze
end
+ # This returns a list of base models with connection associated for a given gitlab_schema
+ def self.schemas_to_base_models
+ @schemas_to_base_models ||= {
+ gitlab_main: [self.database_base_models.fetch(:main)],
+ gitlab_ci: [self.database_base_models[:ci] || self.database_base_models.fetch(:main)], # use CI or fallback to main
+ gitlab_shared: self.database_base_models.values # all models
+ }.with_indifferent_access.freeze
+ end
+
+ def self.all_database_names
+ DATABASE_NAMES
+ end
+
# We configure the database connection pool size automatically based on the
# configured concurrency. We also add some headroom, to make sure we don't
# run out of connections when more threads besides the 'user-facing' ones
diff --git a/lib/gitlab/database/async_indexes/index_creator.rb b/lib/gitlab/database/async_indexes/index_creator.rb
index 994a1deba57..2fb4cc8f675 100644
--- a/lib/gitlab/database/async_indexes/index_creator.rb
+++ b/lib/gitlab/database/async_indexes/index_creator.rb
@@ -47,6 +47,10 @@ module Gitlab
TIMEOUT_PER_ACTION
end
+ def lease_key
+ [super, async_index.connection_db_config.name].join('/')
+ end
+
def set_statement_timeout
connection.execute("SET statement_timeout TO '%ds'" % STATEMENT_TIMEOUT)
yield
diff --git a/lib/gitlab/database/background_migration/batched_job.rb b/lib/gitlab/database/background_migration/batched_job.rb
index 32765cb6a56..503172dd750 100644
--- a/lib/gitlab/database/background_migration/batched_job.rb
+++ b/lib/gitlab/database/background_migration/batched_job.rb
@@ -21,6 +21,7 @@ module Gitlab
from_union([failed_jobs, self.stuck])
}
+ scope :except_succeeded, -> { where(status: self.statuses.except(:succeeded).values) }
enum status: {
pending: 0,
diff --git a/lib/gitlab/database/background_migration/batched_migration.rb b/lib/gitlab/database/background_migration/batched_migration.rb
index d9fc2ea48f6..2844cbe4a74 100644
--- a/lib/gitlab/database/background_migration/batched_migration.rb
+++ b/lib/gitlab/database/background_migration/batched_migration.rb
@@ -18,6 +18,8 @@ module Gitlab
scope: [:job_class_name, :table_name, :column_name]
}
+ validate :validate_batched_jobs_status, if: -> { status_changed? && finished? }
+
scope :queue_order, -> { order(id: :asc) }
scope :queued, -> { where(status: [:active, :paused]) }
scope :for_configuration, ->(job_class_name, table_name, column_name, job_arguments) do
@@ -92,11 +94,11 @@ module Gitlab
end
def job_class_name=(class_name)
- write_attribute(:job_class_name, class_name.demodulize)
+ write_attribute(:job_class_name, class_name.delete_prefix("::"))
end
def batch_class_name=(class_name)
- write_attribute(:batch_class_name, class_name.demodulize)
+ write_attribute(:batch_class_name, class_name.delete_prefix("::"))
end
def migrated_tuple_count
@@ -133,6 +135,12 @@ module Gitlab
def optimize!
BatchOptimizer.new(self).optimize!
end
+
+ private
+
+ def validate_batched_jobs_status
+ errors.add(:batched_jobs, 'jobs need to be succeeded') if batched_jobs.except_succeeded.exists?
+ end
end
end
end
diff --git a/lib/gitlab/database/count/reltuples_count_strategy.rb b/lib/gitlab/database/count/reltuples_count_strategy.rb
index 870cf25984b..68a0c15480a 100644
--- a/lib/gitlab/database/count/reltuples_count_strategy.rb
+++ b/lib/gitlab/database/count/reltuples_count_strategy.rb
@@ -32,12 +32,12 @@ module Gitlab
# Models using single-type inheritance (STI) don't work with
# reltuple count estimates. We just have to ignore them and
# use another strategy to compute them.
- def non_sti_models
+ def non_sti_models(models)
models.reject { |model| sti_model?(model) }
end
- def non_sti_table_names
- non_sti_models.map(&:table_name)
+ def non_sti_table_names(models)
+ non_sti_models(models).map(&:table_name)
end
def sti_model?(model)
@@ -45,21 +45,34 @@ module Gitlab
model.base_class != model
end
- def table_names
- models.map(&:table_name)
+ def table_to_model_mapping
+ @table_to_model_mapping ||= models.each_with_object({}) { |model, h| h[model.table_name] = model }
+ end
+
+ def table_to_model(table_name)
+ table_to_model_mapping[table_name]
end
def size_estimates(check_statistics: true)
- table_to_model = models.each_with_object({}) { |model, h| h[model.table_name] = model }
-
- # Querying tuple stats only works on the primary. Due to load balancing, the
- # easiest way to do this is to start a transaction.
- ActiveRecord::Base.transaction do # rubocop: disable Database/MultipleDatabases
- get_statistics(non_sti_table_names, check_statistics: check_statistics).each_with_object({}) do |row, data|
- model = table_to_model[row.table_name]
- data[model] = row.estimate
+ results = {}
+
+ models.group_by { |model| model.connection_db_config.name }.map do |db_name, models_for_db|
+ base_model = Gitlab::Database.database_base_models[db_name]
+ tables = non_sti_table_names(models_for_db)
+
+ # Querying tuple stats only works on the primary. Due to load balancing, the
+ # easiest way to do this is to start a transaction.
+ base_model.transaction do
+ Gitlab::Database::SharedModel.using_connection(base_model.connection) do
+ get_statistics(tables, check_statistics: check_statistics).each do |row|
+ model = table_to_model(row.table_name)
+ results[model] = row.estimate
+ end
+ end
end
end
+
+ results
end
# Generates the PostgreSQL query to return the tuples for tables
diff --git a/lib/gitlab/database/count/tablesample_count_strategy.rb b/lib/gitlab/database/count/tablesample_count_strategy.rb
index 489bc0aacea..92c8de9aeac 100644
--- a/lib/gitlab/database/count/tablesample_count_strategy.rb
+++ b/lib/gitlab/database/count/tablesample_count_strategy.rb
@@ -61,7 +61,7 @@ module Gitlab
#{where_clause(model)}
SQL
- rows = ActiveRecord::Base.connection.select_all(query) # rubocop: disable Database/MultipleDatabases
+ rows = model.connection.select_all(query)
Integer(rows.first['count'])
end
diff --git a/lib/gitlab/database/gitlab_loose_foreign_keys.yml b/lib/gitlab/database/gitlab_loose_foreign_keys.yml
new file mode 100644
index 00000000000..0343c054f23
--- /dev/null
+++ b/lib/gitlab/database/gitlab_loose_foreign_keys.yml
@@ -0,0 +1,69 @@
+ci_pipeline_chat_data:
+ - table: chat_names
+ column: chat_name_id
+ on_delete: async_delete
+dast_scanner_profiles_builds:
+ - table: ci_builds
+ column: ci_build_id
+ on_delete: async_delete
+dast_scanner_profiles_builds:
+ - table: ci_builds
+ column: ci_build_id
+ on_delete: async_delete
+dast_profiles_pipelines:
+ - table: ci_pipelines
+ column: ci_pipeline_id
+ on_delete: async_delete
+clusters_applications_runners:
+ - table: ci_runners
+ column: runner_id
+ on_delete: async_nullify
+ci_namespace_mirrors:
+ - table: namespaces
+ column: namespace_id
+ on_delete: async_delete
+ci_builds:
+ - table: users
+ column: user_id
+ on_delete: async_nullify
+ci_pipelines:
+ - table: merge_requests
+ column: merge_request_id
+ on_delete: async_delete
+ - table: external_pull_requests
+ column: external_pull_request_id
+ on_delete: async_nullify
+ - table: users
+ column: user_id
+ on_delete: async_nullify
+ci_project_mirrors:
+ - table: projects
+ column: project_id
+ on_delete: async_delete
+ - table: namespaces
+ column: namespace_id
+ on_delete: async_delete
+packages_build_infos:
+ - table: ci_pipelines
+ column: pipeline_id
+ on_delete: async_nullify
+packages_package_file_build_infos:
+ - table: ci_pipelines
+ column: pipeline_id
+ on_delete: async_nullify
+pages_deployments:
+ - table: ci_builds
+ column: ci_build_id
+ on_delete: async_nullify
+terraform_state_versions:
+ - table: ci_builds
+ column: ci_build_id
+ on_delete: async_nullify
+merge_request_metrics:
+ - table: ci_pipelines
+ column: pipeline_id
+ on_delete: async_delete
+project_pages_metadata:
+ - table: ci_job_artifacts
+ column: artifacts_archive_id
+ on_delete: async_nullify
diff --git a/lib/gitlab/database/gitlab_schemas.yml b/lib/gitlab/database/gitlab_schemas.yml
index 66157e998a0..24c2d634780 100644
--- a/lib/gitlab/database/gitlab_schemas.yml
+++ b/lib/gitlab/database/gitlab_schemas.yml
@@ -1,4 +1,5 @@
abuse_reports: :gitlab_main
+agent_activity_events: :gitlab_main
agent_group_authorizations: :gitlab_main
agent_project_authorizations: :gitlab_main
alert_management_alert_assignees: :gitlab_main
@@ -85,6 +86,7 @@ ci_job_token_project_scope_links: :gitlab_ci
ci_job_variables: :gitlab_ci
ci_minutes_additional_packs: :gitlab_ci
ci_namespace_monthly_usages: :gitlab_ci
+ci_namespace_mirrors: :gitlab_ci
ci_pending_builds: :gitlab_ci
ci_pipeline_artifacts: :gitlab_ci
ci_pipeline_chat_data: :gitlab_ci
@@ -96,6 +98,7 @@ ci_pipelines: :gitlab_ci
ci_pipeline_variables: :gitlab_ci
ci_platform_metrics: :gitlab_ci
ci_project_monthly_usages: :gitlab_ci
+ci_project_mirrors: :gitlab_ci
ci_refs: :gitlab_ci
ci_resource_groups: :gitlab_ci
ci_resources: :gitlab_ci
@@ -161,6 +164,7 @@ dependency_proxy_group_settings: :gitlab_main
dependency_proxy_image_ttl_group_policies: :gitlab_main
dependency_proxy_manifests: :gitlab_main
deploy_keys_projects: :gitlab_main
+deployment_approvals: :gitlab_main
deployment_clusters: :gitlab_main
deployment_merge_requests: :gitlab_main
deployments: :gitlab_main
@@ -249,6 +253,7 @@ incident_management_oncall_schedules: :gitlab_main
incident_management_oncall_shifts: :gitlab_main
incident_management_pending_alert_escalations: :gitlab_main
incident_management_pending_issue_escalations: :gitlab_main
+incident_management_timeline_events: :gitlab_main
index_statuses: :gitlab_main
in_product_marketing_emails: :gitlab_main
insights: :gitlab_main
@@ -260,6 +265,7 @@ issuable_severities: :gitlab_main
issuable_slas: :gitlab_main
issue_assignees: :gitlab_main
issue_customer_relations_contacts: :gitlab_main
+issue_emails: :gitlab_main
issue_email_participants: :gitlab_main
issue_links: :gitlab_main
issue_metrics: :gitlab_main
@@ -281,6 +287,7 @@ ldap_group_links: :gitlab_main
lfs_file_locks: :gitlab_main
lfs_objects: :gitlab_main
lfs_objects_projects: :gitlab_main
+lfs_object_states: :gitlab_main
licenses: :gitlab_main
lists: :gitlab_main
list_user_preferences: :gitlab_main
@@ -290,6 +297,7 @@ members: :gitlab_main
merge_request_assignees: :gitlab_main
merge_request_blocks: :gitlab_main
merge_request_cleanup_schedules: :gitlab_main
+merge_requests_compliance_violations: :gitlab_main
merge_request_context_commit_diff_files: :gitlab_main
merge_request_context_commits: :gitlab_main
merge_request_diff_commits: :gitlab_main
@@ -314,6 +322,7 @@ namespace_package_settings: :gitlab_main
namespace_root_storage_statistics: :gitlab_main
namespace_settings: :gitlab_main
namespaces: :gitlab_main
+namespaces_sync_events: :gitlab_main
namespace_statistics: :gitlab_main
note_diff_files: :gitlab_main
notes: :gitlab_main
@@ -363,6 +372,7 @@ packages_pypi_metadata: :gitlab_main
packages_rubygems_metadata: :gitlab_main
packages_tags: :gitlab_main
pages_deployments: :gitlab_main
+pages_deployment_states: :gitlab_main
pages_domain_acme_orders: :gitlab_main
pages_domains: :gitlab_main
partitioned_foreign_keys: :gitlab_main
@@ -408,6 +418,7 @@ project_repository_storage_moves: :gitlab_main
project_security_settings: :gitlab_main
project_settings: :gitlab_main
projects: :gitlab_main
+projects_sync_events: :gitlab_main
project_statistics: :gitlab_main
project_topics: :gitlab_main
project_tracing_settings: :gitlab_main
@@ -485,6 +496,7 @@ trending_projects: :gitlab_main
u2f_registrations: :gitlab_main
upcoming_reconciliations: :gitlab_main
uploads: :gitlab_main
+upload_states: :gitlab_main
user_agent_details: :gitlab_main
user_callouts: :gitlab_main
user_canonical_emails: :gitlab_main
@@ -526,6 +538,7 @@ vulnerability_issue_links: :gitlab_main
vulnerability_occurrence_identifiers: :gitlab_main
vulnerability_occurrence_pipelines: :gitlab_main
vulnerability_occurrences: :gitlab_main
+vulnerability_reads: :gitlab_main
vulnerability_remediations: :gitlab_main
vulnerability_scanners: :gitlab_main
vulnerability_statistics: :gitlab_main
diff --git a/lib/gitlab/database/load_balancing.rb b/lib/gitlab/database/load_balancing.rb
index 52eb0764ae3..e16db5af8ce 100644
--- a/lib/gitlab/database/load_balancing.rb
+++ b/lib/gitlab/database/load_balancing.rb
@@ -30,6 +30,10 @@ module Gitlab
end
end
+ def self.primary_only?
+ each_load_balancer.all?(&:primary_only?)
+ end
+
def self.release_hosts
each_load_balancer(&:release_host)
end
diff --git a/lib/gitlab/database/load_balancing/configuration.rb b/lib/gitlab/database/load_balancing/configuration.rb
index da313361073..e769cb5c35c 100644
--- a/lib/gitlab/database/load_balancing/configuration.rb
+++ b/lib/gitlab/database/load_balancing/configuration.rb
@@ -107,7 +107,11 @@ module Gitlab
hosts.any? || service_discovery_enabled?
end
+ # This is disabled for Rake tasks to ensure e.g. database migrations
+ # always produce consistent results.
def service_discovery_enabled?
+ return false if Gitlab::Runtime.rake?
+
service_discovery[:record].present?
end
diff --git a/lib/gitlab/database/load_balancing/sidekiq_server_middleware.rb b/lib/gitlab/database/load_balancing/sidekiq_server_middleware.rb
index b9acc36b4cc..5d91292b8de 100644
--- a/lib/gitlab/database/load_balancing/sidekiq_server_middleware.rb
+++ b/lib/gitlab/database/load_balancing/sidekiq_server_middleware.rb
@@ -6,6 +6,8 @@ module Gitlab
class SidekiqServerMiddleware
JobReplicaNotUpToDate = Class.new(StandardError)
+ MINIMUM_DELAY_INTERVAL_SECONDS = 0.8
+
def call(worker, job, _queue)
worker_class = worker.class
strategy = select_load_balancing_strategy(worker_class, job)
@@ -42,11 +44,15 @@ module Gitlab
wal_locations = get_wal_locations(job)
- return :primary_no_wal unless wal_locations
+ return :primary_no_wal if wal_locations.blank?
+
+ # Happy case: we can read from a replica.
+ return replica_strategy(worker_class, job) if databases_in_sync?(wal_locations)
+
+ sleep_if_needed(job)
if databases_in_sync?(wal_locations)
- # Happy case: we can read from a replica.
- retried_before?(worker_class, job) ? :replica_retried : :replica
+ replica_strategy(worker_class, job)
elsif can_retry?(worker_class, job)
# Optimistic case: The worker allows retries and we have retries left.
:retry
@@ -56,17 +62,14 @@ module Gitlab
end
end
- def get_wal_locations(job)
- job['dedup_wal_locations'] || job['wal_locations'] || legacy_wal_location(job)
- end
+ def sleep_if_needed(job)
+ remaining_delay = MINIMUM_DELAY_INTERVAL_SECONDS - (Time.current.to_f - job['created_at'].to_f)
- # Already scheduled jobs could still contain legacy database write location.
- # TODO: remove this in the next iteration
- # https://gitlab.com/gitlab-org/gitlab/-/issues/338213
- def legacy_wal_location(job)
- wal_location = job['database_write_location'] || job['database_replica_location']
+ sleep remaining_delay if remaining_delay > 0 && remaining_delay < MINIMUM_DELAY_INTERVAL_SECONDS
+ end
- { ::Gitlab::Database::MAIN_DATABASE_NAME.to_sym => wal_location } if wal_location
+ def get_wal_locations(job)
+ job['dedup_wal_locations'] || job['wal_locations']
end
def load_balancing_available?(worker_class)
@@ -79,6 +82,10 @@ module Gitlab
worker_class.get_data_consistency == :delayed && not_yet_retried?(job)
end
+ def replica_strategy(worker_class, job)
+ retried_before?(worker_class, job) ? :replica_retried : :replica
+ end
+
def retried_before?(worker_class, job)
worker_class.get_data_consistency == :delayed && !not_yet_retried?(job)
end
diff --git a/lib/gitlab/database/load_balancing/sticking.rb b/lib/gitlab/database/load_balancing/sticking.rb
index 834e9c6d3c6..8e5dc98e96e 100644
--- a/lib/gitlab/database/load_balancing/sticking.rb
+++ b/lib/gitlab/database/load_balancing/sticking.rb
@@ -123,21 +123,18 @@ module Gitlab
def unstick(namespace, id)
Gitlab::Redis::SharedState.with do |redis|
redis.del(redis_key_for(namespace, id))
- redis.del(old_redis_key_for(namespace, id))
end
end
def set_write_location_for(namespace, id, location)
Gitlab::Redis::SharedState.with do |redis|
redis.set(redis_key_for(namespace, id), location, ex: EXPIRATION)
- redis.set(old_redis_key_for(namespace, id), location, ex: EXPIRATION)
end
end
def last_write_location_for(namespace, id)
Gitlab::Redis::SharedState.with do |redis|
- redis.get(redis_key_for(namespace, id)) ||
- redis.get(old_redis_key_for(namespace, id))
+ redis.get(redis_key_for(namespace, id))
end
end
@@ -146,10 +143,6 @@ module Gitlab
"database-load-balancing/write-location/#{name}/#{namespace}/#{id}"
end
-
- def old_redis_key_for(namespace, id)
- "database-load-balancing/write-location/#{namespace}/#{id}"
- end
end
end
end
diff --git a/lib/gitlab/database/loose_foreign_keys.rb b/lib/gitlab/database/loose_foreign_keys.rb
new file mode 100644
index 00000000000..1ecfb5ce47f
--- /dev/null
+++ b/lib/gitlab/database/loose_foreign_keys.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ module LooseForeignKeys
+ def self.definitions_by_table
+ @definitions_by_table ||= definitions.group_by(&:to_table).with_indifferent_access.freeze
+ end
+
+ def self.definitions
+ @definitions ||= loose_foreign_keys_yaml.flat_map do |child_table_name, configs|
+ configs.map { |config| build_definition(child_table_name, config) }
+ end.freeze
+ end
+
+ def self.build_definition(child_table_name, config)
+ parent_table_name = config.fetch('table')
+
+ ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(
+ child_table_name,
+ parent_table_name,
+ {
+ column: config.fetch('column'),
+ on_delete: config.fetch('on_delete').to_sym,
+ gitlab_schema: GitlabSchema.table_schema(child_table_name)
+ }
+ )
+ end
+
+ def self.loose_foreign_keys_yaml
+ @loose_foreign_keys_yaml ||= YAML.load_file(Rails.root.join('lib/gitlab/database/gitlab_loose_foreign_keys.yml'))
+ end
+
+ private_class_method :build_definition
+ private_class_method :loose_foreign_keys_yaml
+ end
+ end
+end
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index 7dce4fa0ce2..4245dd80714 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -4,6 +4,7 @@ module Gitlab
module Database
module MigrationHelpers
include Migrations::BackgroundMigrationHelpers
+ include Migrations::BatchedBackgroundMigrationHelpers
include DynamicModelHelpers
include RenameTableHelpers
include AsyncIndexes::MigrationHelpers
diff --git a/lib/gitlab/database/migrations/background_migration_helpers.rb b/lib/gitlab/database/migrations/background_migration_helpers.rb
index bdaf0d35a83..8c33c41ce77 100644
--- a/lib/gitlab/database/migrations/background_migration_helpers.rb
+++ b/lib/gitlab/database/migrations/background_migration_helpers.rb
@@ -5,59 +5,7 @@ module Gitlab
module Migrations
module BackgroundMigrationHelpers
BATCH_SIZE = 1_000 # Number of rows to process per job
- SUB_BATCH_SIZE = 100 # Number of rows to process per sub-batch
JOB_BUFFER_SIZE = 1_000 # Number of jobs to bulk queue at a time
- BATCH_CLASS_NAME = 'PrimaryKeyBatchingStrategy' # Default batch class for batched migrations
- BATCH_MIN_VALUE = 1 # Default minimum value for batched migrations
- BATCH_MIN_DELAY = 2.minutes.freeze # Minimum delay between batched migrations
-
- # Bulk queues background migration jobs for an entire table, batched by ID range.
- # "Bulk" meaning many jobs will be pushed at a time for efficiency.
- # If you need a delay interval per job, then use `queue_background_migration_jobs_by_range_at_intervals`.
- #
- # model_class - The table being iterated over
- # job_class_name - The background migration job class as a string
- # batch_size - The maximum number of rows per job
- #
- # Example:
- #
- # class Route < ActiveRecord::Base
- # include EachBatch
- # self.table_name = 'routes'
- # end
- #
- # bulk_queue_background_migration_jobs_by_range(Route, 'ProcessRoutes')
- #
- # Where the model_class includes EachBatch, and the background migration exists:
- #
- # class Gitlab::BackgroundMigration::ProcessRoutes
- # def perform(start_id, end_id)
- # # do something
- # end
- # end
- def bulk_queue_background_migration_jobs_by_range(model_class, job_class_name, batch_size: BATCH_SIZE)
- raise "#{model_class} does not have an ID to use for batch ranges" unless model_class.column_names.include?('id')
-
- jobs = []
- table_name = model_class.quoted_table_name
-
- model_class.each_batch(of: batch_size) do |relation|
- start_id, end_id = relation.pluck("MIN(#{table_name}.id)", "MAX(#{table_name}.id)").first
-
- if jobs.length >= JOB_BUFFER_SIZE
- # Note: This code path generally only helps with many millions of rows
- # We push multiple jobs at a time to reduce the time spent in
- # Sidekiq/Redis operations. We're using this buffer based approach so we
- # don't need to run additional queries for every range.
- bulk_migrate_async(jobs)
- jobs.clear
- end
-
- jobs << [job_class_name, [start_id, end_id]]
- end
-
- bulk_migrate_async(jobs) unless jobs.empty?
- end
# Queues background migration jobs for an entire table in batches.
# The default batching column used is the standard primary key `id`.
@@ -137,6 +85,7 @@ module Gitlab
# Requeue pending jobs previously queued with #queue_background_migration_jobs_by_range_at_intervals
#
# This method is useful to schedule jobs that had previously failed.
+ # It can only be used if the previous background migration used job tracking like the queue_background_migration_jobs_by_range_at_intervals helper.
#
# job_class_name - The background migration job class as a string
# delay_interval - The duration between each job's scheduled time
@@ -170,100 +119,6 @@ module Gitlab
duration
end
- # Creates a batched background migration for the given table. A batched migration runs one job
- # at a time, computing the bounds of the next batch based on the current migration settings and the previous
- # batch bounds. Each job's execution status is tracked in the database as the migration runs. The given job
- # class must be present in the Gitlab::BackgroundMigration module, and the batch class (if specified) must be
- # present in the Gitlab::BackgroundMigration::BatchingStrategies module.
- #
- # If migration with same job_class_name, table_name, column_name, and job_aruments already exists, this helper
- # will log an warning and not create a new one.
- #
- # job_class_name - The background migration job class as a string
- # batch_table_name - The name of the table the migration will batch over
- # batch_column_name - The name of the column the migration will batch over
- # job_arguments - Extra arguments to pass to the job instance when the migration runs
- # job_interval - The pause interval between each job's execution, minimum of 2 minutes
- # batch_min_value - The value in the column the batching will begin at
- # batch_max_value - The value in the column the batching will end at, defaults to `SELECT MAX(batch_column)`
- # batch_class_name - The name of the class that will be called to find the range of each next batch
- # batch_size - The maximum number of rows per job
- # sub_batch_size - The maximum number of rows processed per "iteration" within the job
- #
- #
- # *Returns the created BatchedMigration record*
- #
- # Example:
- #
- # queue_batched_background_migration(
- # 'CopyColumnUsingBackgroundMigrationJob',
- # :events,
- # :id,
- # job_interval: 2.minutes,
- # other_job_arguments: ['column1', 'column2'])
- #
- # Where the the background migration exists:
- #
- # class Gitlab::BackgroundMigration::CopyColumnUsingBackgroundMigrationJob
- # def perform(start_id, end_id, batch_table, batch_column, sub_batch_size, *other_args)
- # # do something
- # end
- # end
- def queue_batched_background_migration( # rubocop:disable Metrics/ParameterLists
- job_class_name,
- batch_table_name,
- batch_column_name,
- *job_arguments,
- job_interval:,
- batch_min_value: BATCH_MIN_VALUE,
- batch_max_value: nil,
- batch_class_name: BATCH_CLASS_NAME,
- batch_size: BATCH_SIZE,
- sub_batch_size: SUB_BATCH_SIZE
- )
-
- if Gitlab::Database::BackgroundMigration::BatchedMigration.for_configuration(job_class_name, batch_table_name, batch_column_name, job_arguments).exists?
- Gitlab::AppLogger.warn "Batched background migration not enqueued because it already exists: " \
- "job_class_name: #{job_class_name}, table_name: #{batch_table_name}, column_name: #{batch_column_name}, " \
- "job_arguments: #{job_arguments.inspect}"
- return
- end
-
- job_interval = BATCH_MIN_DELAY if job_interval < BATCH_MIN_DELAY
-
- batch_max_value ||= connection.select_value(<<~SQL)
- SELECT MAX(#{connection.quote_column_name(batch_column_name)})
- FROM #{connection.quote_table_name(batch_table_name)}
- SQL
-
- migration_status = batch_max_value.nil? ? :finished : :active
- batch_max_value ||= batch_min_value
-
- migration = Gitlab::Database::BackgroundMigration::BatchedMigration.create!(
- job_class_name: job_class_name,
- table_name: batch_table_name,
- column_name: batch_column_name,
- job_arguments: job_arguments,
- interval: job_interval,
- min_value: batch_min_value,
- max_value: batch_max_value,
- batch_class_name: batch_class_name,
- batch_size: batch_size,
- sub_batch_size: sub_batch_size,
- status: migration_status)
-
- # This guard is necessary since #total_tuple_count was only introduced schema-wise,
- # after this migration helper had been used for the first time.
- return migration unless migration.respond_to?(:total_tuple_count)
-
- # We keep track of the estimated number of tuples to reason later
- # about the overall progress of a migration.
- migration.total_tuple_count = Gitlab::Database::PgClass.for_table(batch_table_name)&.cardinality_estimate
- migration.save!
-
- migration
- end
-
# Force a background migration to complete.
#
# WARNING: This method will block the caller and move the background migration from an
@@ -275,6 +130,7 @@ module Gitlab
# 4. Optionally remove job tracking information.
#
# This method does not garauntee that all jobs completed successfully.
+ # It can only be used if the previous background migration used the queue_background_migration_jobs_by_range_at_intervals helper.
def finalize_background_migration(class_name, delete_tracking_jobs: ['succeeded'])
# Empty the sidekiq queue.
Gitlab::BackgroundMigration.steal(class_name)
diff --git a/lib/gitlab/database/migrations/batched_background_migration_helpers.rb b/lib/gitlab/database/migrations/batched_background_migration_helpers.rb
new file mode 100644
index 00000000000..dcaf7fad05f
--- /dev/null
+++ b/lib/gitlab/database/migrations/batched_background_migration_helpers.rb
@@ -0,0 +1,118 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ module Migrations
+ # BatchedBackgroundMigrations are a new approach to scheduling and executing background migrations, which uses
+ # persistent state in the database to track each migration. This avoids having to batch over an entire table and
+ # schedule a large number of sidekiq jobs upfront. It also provides for more flexibility as the migration runs,
+ # as it can be paused and restarted, and have configuration values like the batch size updated dynamically as the
+ # migration runs.
+ #
+ # For now, these migrations are not considered ready for general use, for more information see the tracking epic:
+ # https://gitlab.com/groups/gitlab-org/-/epics/6751
+ module BatchedBackgroundMigrationHelpers
+ BATCH_SIZE = 1_000 # Number of rows to process per job
+ SUB_BATCH_SIZE = 100 # Number of rows to process per sub-batch
+ BATCH_CLASS_NAME = 'PrimaryKeyBatchingStrategy' # Default batch class for batched migrations
+ BATCH_MIN_VALUE = 1 # Default minimum value for batched migrations
+ BATCH_MIN_DELAY = 2.minutes.freeze # Minimum delay between batched migrations
+
+ # Creates a batched background migration for the given table. A batched migration runs one job
+ # at a time, computing the bounds of the next batch based on the current migration settings and the previous
+ # batch bounds. Each job's execution status is tracked in the database as the migration runs. The given job
+ # class must be present in the Gitlab::BackgroundMigration module, and the batch class (if specified) must be
+ # present in the Gitlab::BackgroundMigration::BatchingStrategies module.
+ #
+ # If migration with same job_class_name, table_name, column_name, and job_aruments already exists, this helper
+ # will log an warning and not create a new one.
+ #
+ # job_class_name - The background migration job class as a string
+ # batch_table_name - The name of the table the migration will batch over
+ # batch_column_name - The name of the column the migration will batch over
+ # job_arguments - Extra arguments to pass to the job instance when the migration runs
+ # job_interval - The pause interval between each job's execution, minimum of 2 minutes
+ # batch_min_value - The value in the column the batching will begin at
+ # batch_max_value - The value in the column the batching will end at, defaults to `SELECT MAX(batch_column)`
+ # batch_class_name - The name of the class that will be called to find the range of each next batch
+ # batch_size - The maximum number of rows per job
+ # sub_batch_size - The maximum number of rows processed per "iteration" within the job
+ #
+ # *Returns the created BatchedMigration record*
+ #
+ # Example:
+ #
+ # queue_batched_background_migration(
+ # 'CopyColumnUsingBackgroundMigrationJob',
+ # :events,
+ # :id,
+ # job_interval: 2.minutes,
+ # other_job_arguments: ['column1', 'column2'])
+ #
+ # Where the the background migration exists:
+ #
+ # class Gitlab::BackgroundMigration::CopyColumnUsingBackgroundMigrationJob
+ # def perform(start_id, end_id, batch_table, batch_column, sub_batch_size, *other_args)
+ # # do something
+ # end
+ # end
+ def queue_batched_background_migration( # rubocop:disable Metrics/ParameterLists
+ job_class_name,
+ batch_table_name,
+ batch_column_name,
+ *job_arguments,
+ job_interval:,
+ batch_min_value: BATCH_MIN_VALUE,
+ batch_max_value: nil,
+ batch_class_name: BATCH_CLASS_NAME,
+ batch_size: BATCH_SIZE,
+ sub_batch_size: SUB_BATCH_SIZE
+ )
+
+ if Gitlab::Database::BackgroundMigration::BatchedMigration.for_configuration(job_class_name, batch_table_name, batch_column_name, job_arguments).exists?
+ Gitlab::AppLogger.warn "Batched background migration not enqueued because it already exists: " \
+ "job_class_name: #{job_class_name}, table_name: #{batch_table_name}, column_name: #{batch_column_name}, " \
+ "job_arguments: #{job_arguments.inspect}"
+ return
+ end
+
+ job_interval = BATCH_MIN_DELAY if job_interval < BATCH_MIN_DELAY
+
+ batch_max_value ||= connection.select_value(<<~SQL)
+ SELECT MAX(#{connection.quote_column_name(batch_column_name)})
+ FROM #{connection.quote_table_name(batch_table_name)}
+ SQL
+
+ migration_status = batch_max_value.nil? ? :finished : :active
+ batch_max_value ||= batch_min_value
+
+ migration = Gitlab::Database::BackgroundMigration::BatchedMigration.create!(
+ job_class_name: job_class_name,
+ table_name: batch_table_name,
+ column_name: batch_column_name,
+ job_arguments: job_arguments,
+ interval: job_interval,
+ min_value: batch_min_value,
+ max_value: batch_max_value,
+ batch_class_name: batch_class_name,
+ batch_size: batch_size,
+ sub_batch_size: sub_batch_size,
+ status: migration_status)
+
+ # This guard is necessary since #total_tuple_count was only introduced schema-wise,
+ # after this migration helper had been used for the first time.
+ return migration unless migration.respond_to?(:total_tuple_count)
+
+ # We keep track of the estimated number of tuples to reason later
+ # about the overall progress of a migration.
+ migration.total_tuple_count = Gitlab::Database::SharedModel.using_connection(connection) do
+ Gitlab::Database::PgClass.for_table(batch_table_name)&.cardinality_estimate
+ end
+ migration.save!
+
+ migration
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database/migrations/instrumentation.rb b/lib/gitlab/database/migrations/instrumentation.rb
index 6e5ffb74411..1f7e81cae84 100644
--- a/lib/gitlab/database/migrations/instrumentation.rb
+++ b/lib/gitlab/database/migrations/instrumentation.rb
@@ -14,11 +14,11 @@ module Gitlab
@result_dir = result_dir
end
- def observe(version:, name:, &block)
+ def observe(version:, name:, connection:, &block)
observation = Observation.new(version, name)
observation.success = true
- observers = observer_classes.map { |c| c.new(observation, @result_dir) }
+ observers = observer_classes.map { |c| c.new(observation, @result_dir, connection) }
exception = nil
diff --git a/lib/gitlab/database/migrations/observers/migration_observer.rb b/lib/gitlab/database/migrations/observers/migration_observer.rb
index 106f8f1f829..0006af73f6c 100644
--- a/lib/gitlab/database/migrations/observers/migration_observer.rb
+++ b/lib/gitlab/database/migrations/observers/migration_observer.rb
@@ -7,8 +7,8 @@ module Gitlab
class MigrationObserver
attr_reader :connection, :observation, :output_dir
- def initialize(observation, output_dir)
- @connection = ActiveRecord::Base.connection
+ def initialize(observation, output_dir, connection)
+ @connection = connection
@observation = observation
@output_dir = output_dir
end
diff --git a/lib/gitlab/database/migrations/runner.rb b/lib/gitlab/database/migrations/runner.rb
index b267a64256b..f0bac594119 100644
--- a/lib/gitlab/database/migrations/runner.rb
+++ b/lib/gitlab/database/migrations/runner.rb
@@ -69,7 +69,7 @@ module Gitlab
instrumentation = Instrumentation.new(result_dir: result_dir)
sorted_migrations.each do |migration|
- instrumentation.observe(version: migration.version, name: migration.name) do
+ instrumentation.observe(version: migration.version, name: migration.name, connection: ActiveRecord::Migration.connection) do
ActiveRecord::Migrator.new(direction, migration_context.migrations, migration_context.schema_migration, migration.version).run
end
end
diff --git a/lib/gitlab/database/partitioning/detached_partition_dropper.rb b/lib/gitlab/database/partitioning/detached_partition_dropper.rb
index 593824384b5..5e32ecad4ca 100644
--- a/lib/gitlab/database/partitioning/detached_partition_dropper.rb
+++ b/lib/gitlab/database/partitioning/detached_partition_dropper.rb
@@ -4,8 +4,6 @@ module Gitlab
module Partitioning
class DetachedPartitionDropper
def perform
- return unless Feature.enabled?(:drop_detached_partitions, default_enabled: :yaml)
-
Gitlab::AppLogger.info(message: "Checking for previously detached partitions to drop")
Postgresql::DetachedPartition.ready_to_drop.find_each do |detached_partition|
diff --git a/lib/gitlab/database/partitioning/monthly_strategy.rb b/lib/gitlab/database/partitioning/monthly_strategy.rb
index c93e775d7ed..9c8cccb3dc6 100644
--- a/lib/gitlab/database/partitioning/monthly_strategy.rb
+++ b/lib/gitlab/database/partitioning/monthly_strategy.rb
@@ -36,6 +36,10 @@ module Gitlab
partitions
end
+ def after_adding_partitions
+ # No-op, required by the partition manager
+ end
+
private
def desired_partitions
diff --git a/lib/gitlab/database/partitioning/partition_manager.rb b/lib/gitlab/database/partitioning/partition_manager.rb
index 8742c0ff166..aa824dfbd2f 100644
--- a/lib/gitlab/database/partitioning/partition_manager.rb
+++ b/lib/gitlab/database/partitioning/partition_manager.rb
@@ -25,10 +25,8 @@ module Gitlab
partitions_to_create = missing_partitions
create(partitions_to_create) unless partitions_to_create.empty?
- if Feature.enabled?(:partition_pruning, default_enabled: :yaml)
- partitions_to_detach = extra_partitions
- detach(partitions_to_detach) unless partitions_to_detach.empty?
- end
+ partitions_to_detach = extra_partitions
+ detach(partitions_to_detach) unless partitions_to_detach.empty?
end
rescue StandardError => e
Gitlab::AppLogger.error(message: "Failed to create / detach partition(s)",
@@ -73,6 +71,8 @@ module Gitlab
partition_name: partition.partition_name,
table_name: partition.table)
end
+
+ model.partitioning_strategy.after_adding_partitions
end
end
end
diff --git a/lib/gitlab/database/partitioning/single_numeric_list_partition.rb b/lib/gitlab/database/partitioning/single_numeric_list_partition.rb
new file mode 100644
index 00000000000..23ac73a0e53
--- /dev/null
+++ b/lib/gitlab/database/partitioning/single_numeric_list_partition.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ module Partitioning
+ class SingleNumericListPartition
+ include Comparable
+
+ def self.from_sql(table, partition_name, definition)
+ # A list partition can support multiple values, but we only support a single number
+ matches = definition.match(/FOR VALUES IN \('(?<value>\d+)'\)/)
+
+ raise ArgumentError, 'Unknown partition definition' unless matches
+
+ value = Integer(matches[:value])
+
+ new(table, value, partition_name: partition_name)
+ end
+
+ attr_reader :table, :value
+
+ def initialize(table, value, partition_name: nil )
+ @table = table
+ @value = value
+ @partition_name = partition_name
+ end
+
+ def partition_name
+ @partition_name || "#{table}_#{value}"
+ end
+
+ def to_sql
+ <<~SQL
+ CREATE TABLE IF NOT EXISTS #{fully_qualified_partition}
+ PARTITION OF #{conn.quote_table_name(table)}
+ FOR VALUES IN (#{conn.quote(value)})
+ SQL
+ end
+
+ def to_detach_sql
+ <<~SQL
+ ALTER TABLE #{conn.quote_table_name(table)}
+ DETACH PARTITION #{fully_qualified_partition}
+ SQL
+ end
+
+ def ==(other)
+ table == other.table &&
+ partition_name == other.partition_name &&
+ value == other.value
+ end
+ alias_method :eql?, :==
+
+ def hash
+ [table, partition_name, value].hash
+ end
+
+ def <=>(other)
+ return if table != other.table
+
+ value <=> other.value
+ end
+
+ private
+
+ def fully_qualified_partition
+ "%s.%s" % [conn.quote_table_name(Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA), conn.quote_table_name(partition_name)]
+ end
+
+ def conn
+ @conn ||= Gitlab::Database::SharedModel.connection
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database/partitioning/sliding_list_strategy.rb b/lib/gitlab/database/partitioning/sliding_list_strategy.rb
new file mode 100644
index 00000000000..21b86b43ae7
--- /dev/null
+++ b/lib/gitlab/database/partitioning/sliding_list_strategy.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ module Partitioning
+ class SlidingListStrategy
+ attr_reader :model, :partitioning_key, :next_partition_if, :detach_partition_if
+
+ delegate :table_name, to: :model
+
+ def initialize(model, partitioning_key, next_partition_if:, detach_partition_if:)
+ @model = model
+ @partitioning_key = partitioning_key
+ @next_partition_if = next_partition_if
+ @detach_partition_if = detach_partition_if
+
+ ensure_partitioning_column_ignored!
+ end
+
+ def current_partitions
+ Gitlab::Database::PostgresPartition.for_parent_table(table_name).map do |partition|
+ SingleNumericListPartition.from_sql(table_name, partition.name, partition.condition)
+ end.sort
+ end
+
+ def missing_partitions
+ if no_partitions_exist?
+ [initial_partition]
+ elsif next_partition_if.call(active_partition.value)
+ [next_partition]
+ else
+ []
+ end
+ end
+
+ def initial_partition
+ SingleNumericListPartition.new(table_name, 1)
+ end
+
+ def next_partition
+ SingleNumericListPartition.new(table_name, active_partition.value + 1)
+ end
+
+ def extra_partitions
+ possibly_extra = current_partitions[0...-1] # Never consider the most recent partition
+
+ possibly_extra.take_while { |p| detach_partition_if.call(p.value) }
+ end
+
+ def after_adding_partitions
+ active_value = active_partition.value
+ model.connection.change_column_default(model.table_name, partitioning_key, active_value)
+ end
+
+ def active_partition
+ # The current partitions list is sorted, so the last partition has the highest value
+ # This is the only partition that receives inserts.
+ current_partitions.last
+ end
+
+ def no_partitions_exist?
+ current_partitions.empty?
+ end
+
+ private
+
+ def ensure_partitioning_column_ignored!
+ unless model.ignored_columns.include?(partitioning_key.to_s)
+ raise "Add #{partitioning_key} to #{model.name}.ignored_columns to use it with SlidingListStrategy"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database/pg_class.rb b/lib/gitlab/database/pg_class.rb
index 0ce9eebc14c..bd582d903c6 100644
--- a/lib/gitlab/database/pg_class.rb
+++ b/lib/gitlab/database/pg_class.rb
@@ -2,7 +2,7 @@
module Gitlab
module Database
- class PgClass < ActiveRecord::Base
+ class PgClass < SharedModel
self.table_name = 'pg_class'
def self.for_table(relname)
diff --git a/lib/gitlab/database/postgres_hll/buckets.rb b/lib/gitlab/database/postgres_hll/buckets.rb
index 429e823379f..76818bbf340 100644
--- a/lib/gitlab/database/postgres_hll/buckets.rb
+++ b/lib/gitlab/database/postgres_hll/buckets.rb
@@ -65,8 +65,7 @@ module Gitlab
).to_i
if num_zero_buckets > 0 && num_uniques < 2.5 * TOTAL_BUCKETS
- ((0.7213 / (1 + 1.079 / TOTAL_BUCKETS)) * (TOTAL_BUCKETS *
- Math.log2(TOTAL_BUCKETS.to_f / num_zero_buckets)))
+ TOTAL_BUCKETS * Math.log(TOTAL_BUCKETS.to_f / num_zero_buckets)
else
num_uniques
end
diff --git a/lib/gitlab/database/query_analyzer.rb b/lib/gitlab/database/query_analyzer.rb
index 0f285688876..2736f9d18dc 100644
--- a/lib/gitlab/database/query_analyzer.rb
+++ b/lib/gitlab/database/query_analyzer.rb
@@ -58,17 +58,15 @@ module Gitlab
return unless parsed
analyzers.each do |analyzer|
- next if analyzer.suppressed?
+ next if analyzer.suppressed? && !analyzer.requires_tracking?(parsed)
analyzer.analyze(parsed)
- rescue StandardError => e
+ rescue StandardError, QueryAnalyzers::Base::QueryAnalyzerError => e
# We catch all standard errors to prevent validation errors to introduce fatal errors in production
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e)
end
end
- private
-
# Enable query analyzers
def begin!
analyzers = all_analyzers.select do |analyzer|
@@ -77,7 +75,7 @@ module Gitlab
true
end
- rescue StandardError => e
+ rescue StandardError, QueryAnalyzers::Base::QueryAnalyzerError => e
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e)
false
@@ -90,13 +88,15 @@ module Gitlab
def end!
enabled_analyzers.select do |analyzer|
analyzer.end!
- rescue StandardError => e
+ rescue StandardError, QueryAnalyzers::Base::QueryAnalyzerError => e
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e)
end
Thread.current[:query_analyzer_enabled_analyzers] = nil
end
+ private
+
def enabled_analyzers
Thread.current[:query_analyzer_enabled_analyzers]
end
diff --git a/lib/gitlab/database/query_analyzers/base.rb b/lib/gitlab/database/query_analyzers/base.rb
index e8066f7a706..0802d3c8013 100644
--- a/lib/gitlab/database/query_analyzers/base.rb
+++ b/lib/gitlab/database/query_analyzers/base.rb
@@ -4,10 +4,17 @@ module Gitlab
module Database
module QueryAnalyzers
class Base
+ # `Exception` to ensure that is not easily rescued when running in test env
+ QueryAnalyzerError = Class.new(Exception) # rubocop:disable Lint/InheritException
+
def self.suppressed?
Thread.current[self.suppress_key]
end
+ def self.requires_tracking?(parsed)
+ false
+ end
+
def self.suppress=(value)
Thread.current[self.suppress_key] = value
end
diff --git a/lib/gitlab/database/query_analyzers/prevent_cross_database_modification.rb b/lib/gitlab/database/query_analyzers/prevent_cross_database_modification.rb
index 2233f3c4646..2e3db2a5c6e 100644
--- a/lib/gitlab/database/query_analyzers/prevent_cross_database_modification.rb
+++ b/lib/gitlab/database/query_analyzers/prevent_cross_database_modification.rb
@@ -4,7 +4,7 @@ module Gitlab
module Database
module QueryAnalyzers
class PreventCrossDatabaseModification < Database::QueryAnalyzers::Base
- CrossDatabaseModificationAcrossUnsupportedTablesError = Class.new(StandardError)
+ CrossDatabaseModificationAcrossUnsupportedTablesError = Class.new(QueryAnalyzerError)
# This method will allow cross database modifications within the block
# Example:
@@ -36,29 +36,36 @@ module Gitlab
Feature.enabled?(:detect_cross_database_modification, default_enabled: :yaml)
end
+ def self.requires_tracking?(parsed)
+ # The transaction boundaries always needs to be tracked regardless of suppress behavior
+ self.transaction_begin?(parsed) || self.transaction_end?(parsed)
+ end
+
# rubocop:disable Metrics/AbcSize
def self.analyze(parsed)
- return if in_factory_bot_create?
-
database = ::Gitlab::Database.db_config_name(parsed.connection)
sql = parsed.sql
# We ignore BEGIN in tests as this is the outer transaction for
# DatabaseCleaner
- if sql.start_with?('SAVEPOINT') || (!Rails.env.test? && sql.start_with?('BEGIN'))
+ if self.transaction_begin?(parsed)
context[:transaction_depth_by_db][database] += 1
return
- elsif sql.start_with?('RELEASE SAVEPOINT', 'ROLLBACK TO SAVEPOINT') || (!Rails.env.test? && sql.start_with?('ROLLBACK', 'COMMIT'))
+ elsif self.transaction_end?(parsed)
context[:transaction_depth_by_db][database] -= 1
- if context[:transaction_depth_by_db][database] <= 0
+ if context[:transaction_depth_by_db][database] == 0
context[:modified_tables_by_db][database].clear
+ elsif context[:transaction_depth_by_db][database] < 0
+ context[:transaction_depth_by_db][database] = 0
+ raise CrossDatabaseModificationAcrossUnsupportedTablesError, "Misaligned cross-DB transactions discovered at query #{sql}. This could be a bug in #{self.class} or a valid issue to investigate. Read more at https://docs.gitlab.com/ee/development/database/multiple_databases.html#removing-cross-database-transactions ."
end
return
end
- return if context[:transaction_depth_by_db].values.all?(&:zero?)
+ return unless self.in_transaction?
+ return if in_factory_bot_create?
# PgQuery might fail in some cases due to limited nesting:
# https://github.com/pganalyze/pg_query/issues/209
@@ -97,6 +104,42 @@ module Gitlab
end
# rubocop:enable Metrics/AbcSize
+ def self.transaction_begin?(parsed)
+ # We ignore BEGIN or START in tests
+ unless Rails.env.test?
+ return true if transaction_stmt?(parsed, :TRANS_STMT_BEGIN)
+ return true if transaction_stmt?(parsed, :TRANS_STMT_START)
+ end
+
+ # SAVEPOINT
+ return true if transaction_stmt?(parsed, :TRANS_STMT_SAVEPOINT)
+
+ false
+ end
+
+ def self.transaction_end?(parsed)
+ # We ignore ROLLBACK or COMMIT in tests
+ unless Rails.env.test?
+ return true if transaction_stmt?(parsed, :TRANS_STMT_COMMIT)
+ return true if transaction_stmt?(parsed, :TRANS_STMT_COMMIT_PREPARED)
+ return true if transaction_stmt?(parsed, :TRANS_STMT_ROLLBACK)
+ return true if transaction_stmt?(parsed, :TRANS_STMT_ROLLBACK_PREPARED)
+ end
+
+ # RELEASE (SAVEPOINT) or ROLLBACK TO (SAVEPOINT)
+ return true if transaction_stmt?(parsed, :TRANS_STMT_RELEASE)
+ return true if transaction_stmt?(parsed, :TRANS_STMT_ROLLBACK_TO)
+
+ false
+ end
+
+ # Known kinds: https://github.com/pganalyze/pg_query/blob/f6588703deb9d7a94b87b34b7c3bab240087fbc4/ext/pg_query/include/nodes/parsenodes.h#L3050
+ def self.transaction_stmt?(parsed, kind)
+ parsed.pg.tree.stmts.map(&:stmt).any? do |stmt|
+ stmt.node == :transaction_stmt && stmt.transaction_stmt.kind == kind
+ end
+ end
+
# We only raise in tests for now otherwise some features will be broken
# in development. For now we've mostly only added allowlist based on
# spec names. Until we have allowed all the violations inline we don't
@@ -105,13 +148,21 @@ module Gitlab
Rails.env.test?
end
+ def self.in_transaction?
+ context[:transaction_depth_by_db].values.any?(&:positive?)
+ end
+
# We ignore execution in the #create method from FactoryBot
# because it is not representative of real code we run in
# production. There are far too many false positives caused
# by instantiating objects in different `gitlab_schema` in a
# FactoryBot `create`.
def self.in_factory_bot_create?
- Rails.env.test? && caller_locations.any? { |l| l.path.end_with?('lib/factory_bot/evaluation.rb') && l.label == 'create' }
+ Rails.env.test? && caller_locations.any? do |l|
+ l.path.end_with?('lib/factory_bot/evaluation.rb') && l.label == 'create' ||
+ l.path.end_with?('lib/factory_bot/strategy/create.rb') ||
+ l.path.end_with?('shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb') && l.label == 'create_existing_record'
+ end
end
end
end
diff --git a/lib/gitlab/database/reindexing.rb b/lib/gitlab/database/reindexing.rb
index 7a22e324bdb..6ffe14249f0 100644
--- a/lib/gitlab/database/reindexing.rb
+++ b/lib/gitlab/database/reindexing.rb
@@ -15,6 +15,26 @@ module Gitlab
# on e.g. vacuum.
REMOVE_INDEX_RETRY_CONFIG = [[1.minute, 9.minutes]] * 30
+ def self.enabled?
+ Feature.enabled?(:database_reindexing, type: :ops, default_enabled: :yaml)
+ end
+
+ def self.invoke(database = nil)
+ Gitlab::Database::EachDatabase.each_database_connection do |connection, connection_name|
+ next if database && database.to_s != connection_name.to_s
+
+ Gitlab::Database::SharedModel.logger = Logger.new($stdout) if Gitlab::Utils.to_boolean(ENV['LOG_QUERIES_TO_CONSOLE'], default: false)
+
+ # Hack: Before we do actual reindexing work, create async indexes
+ Gitlab::Database::AsyncIndexes.create_pending_indexes! if Feature.enabled?(:database_async_index_creation, type: :ops)
+
+ automatic_reindexing
+ end
+ rescue StandardError => e
+ Gitlab::AppLogger.error(e)
+ raise
+ end
+
# Performs automatic reindexing for a limited number of indexes per call
# 1. Consume from the explicit reindexing queue
# 2. Apply bloat heuristic to find most bloated indexes and reindex those
diff --git a/lib/gitlab/database/reindexing/coordinator.rb b/lib/gitlab/database/reindexing/coordinator.rb
index 13298f67ca9..3e4a83aa2e7 100644
--- a/lib/gitlab/database/reindexing/coordinator.rb
+++ b/lib/gitlab/database/reindexing/coordinator.rb
@@ -53,6 +53,10 @@ module Gitlab
def lease_timeout
TIMEOUT_PER_ACTION
end
+
+ def lease_key
+ [super, index.connection_db_config.name].join('/')
+ end
end
end
end
diff --git a/lib/gitlab/database/schema_cache_with_renamed_table.rb b/lib/gitlab/database/schema_cache_with_renamed_table.rb
index 28123edd708..74900dc0d26 100644
--- a/lib/gitlab/database/schema_cache_with_renamed_table.rb
+++ b/lib/gitlab/database/schema_cache_with_renamed_table.rb
@@ -42,7 +42,7 @@ module Gitlab
def renamed_tables_cache
@renamed_tables ||= begin
Gitlab::Database::TABLES_TO_BE_RENAMED.select do |old_name, new_name|
- ActiveRecord::Base.connection.view_exists?(old_name)
+ connection.view_exists?(old_name)
end
end
end
diff --git a/lib/gitlab/database/schema_helpers.rb b/lib/gitlab/database/schema_helpers.rb
index 3d929c62933..9ddc5391689 100644
--- a/lib/gitlab/database/schema_helpers.rb
+++ b/lib/gitlab/database/schema_helpers.rb
@@ -25,6 +25,7 @@ module Gitlab
CREATE TRIGGER #{name}
#{fires} ON #{table_name}
FOR EACH ROW
+ #{yield if block_given?}
EXECUTE FUNCTION #{function_name}()
SQL
end
diff --git a/lib/gitlab/database/shared_model.rb b/lib/gitlab/database/shared_model.rb
index f31dbc01907..17d7886e8c8 100644
--- a/lib/gitlab/database/shared_model.rb
+++ b/lib/gitlab/database/shared_model.rb
@@ -39,6 +39,10 @@ module Gitlab
Thread.current[:overriding_connection] = connection
end
end
+
+ def connection_db_config
+ self.class.connection_db_config
+ end
end
end
end
diff --git a/lib/gitlab/database/type/json_pg_safe.rb b/lib/gitlab/database/type/json_pg_safe.rb
new file mode 100644
index 00000000000..bbc207bd0d9
--- /dev/null
+++ b/lib/gitlab/database/type/json_pg_safe.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ module Type
+ # Extends Rails' ActiveRecord::Type::Json data type to remove JSON
+ # encooded nullbytes `\u0000` to prevent PostgreSQL errors like
+ # `PG::UntranslatableCharacter: ERROR: unsupported Unicode escape
+ # sequence`.
+ #
+ # Example:
+ #
+ # class SomeModel < ApplicationRecord
+ # # some_model.a_field is of type `jsonb`
+ # attribute :a_field, Gitlab::Database::Type::JsonPgSafe.new
+ # end
+ class JsonPgSafe < ActiveRecord::Type::Json
+ def serialize(value)
+ super&.gsub('\u0000', '')
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/diff/custom_diff.rb b/lib/gitlab/diff/custom_diff.rb
new file mode 100644
index 00000000000..3928ece9281
--- /dev/null
+++ b/lib/gitlab/diff/custom_diff.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+module Gitlab
+ module Diff
+ module CustomDiff
+ class << self
+ def preprocess_before_diff(path, old_blob, new_blob)
+ return unless path.ends_with? '.ipynb'
+
+ transformed_diff(old_blob&.data, new_blob&.data)&.tap do
+ transformed_for_diff(new_blob, old_blob)
+ Gitlab::AppLogger.info({ message: 'IPYNB_DIFF_GENERATED' })
+ end
+ rescue IpynbDiff::InvalidNotebookError => e
+ Gitlab::ErrorTracking.log_exception(e)
+ nil
+ end
+
+ def transformed_diff(before, after)
+ transformed_diff = IpynbDiff.diff(before, after,
+ diff_opts: { context: 5, include_diff_info: true },
+ transform_options: { cell_decorator: :percent },
+ raise_if_invalid_notebook: true)
+ strip_diff_frontmatter(transformed_diff)
+ end
+
+ def transformed_blob_language(blob)
+ 'md' if transformed_for_diff?(blob)
+ end
+
+ def transformed_blob_data(blob)
+ if transformed_for_diff?(blob)
+ IpynbDiff.transform(blob.data,
+ raise_errors: true,
+ options: { include_metadata: false, cell_decorator: :percent })
+ end
+ end
+
+ def strip_diff_frontmatter(diff_content)
+ diff_content.scan(/.*\n/)[2..]&.join('') if diff_content.present?
+ end
+
+ def blobs_with_transformed_diffs
+ @blobs_with_transformed_diffs ||= {}
+ end
+
+ def transformed_for_diff?(blob)
+ blobs_with_transformed_diffs[blob]
+ end
+
+ def transformed_for_diff(*blobs)
+ blobs.each do |b|
+ blobs_with_transformed_diffs[b] = true if b
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb
index 83f242ff902..d9860d9fb86 100644
--- a/lib/gitlab/diff/file.rb
+++ b/lib/gitlab/diff/file.rb
@@ -44,7 +44,11 @@ module Gitlab
new_blob_lazy
old_blob_lazy
- preprocess_before_diff(diff) if Feature.enabled?(:jupyter_clean_diffs, repository.project, default_enabled: true)
+ diff.diff = Gitlab::Diff::CustomDiff.preprocess_before_diff(diff.new_path, old_blob_lazy, new_blob_lazy) || diff.diff if use_custom_diff?
+ end
+
+ def use_custom_diff?
+ strong_memoize(:_custom_diff_enabled) { Feature.enabled?(:jupyter_clean_diffs, repository.project, default_enabled: true) }
end
def position(position_marker, position_type: :text)
@@ -450,33 +454,6 @@ module Gitlab
find_renderable_viewer_class(classes)
end
- def preprocess_before_diff(diff)
- return unless diff.new_path.ends_with? '.ipynb'
-
- from = old_blob_lazy&.data
- to = new_blob_lazy&.data
-
- transformed_diff = IpynbDiff.diff(from, to,
- diff_opts: { context: 5, include_diff_info: true },
- transform_options: { cell_decorator: :percent },
- raise_if_invalid_notebook: true)
- new_diff = strip_diff_frontmatter(transformed_diff)
-
- if new_diff
- diff.diff = new_diff
- new_blob_lazy.transformed_for_diff = true if new_blob_lazy
- old_blob_lazy.transformed_for_diff = true if old_blob_lazy
- end
-
- Gitlab::AppLogger.info({ message: new_diff ? 'IPYNB_DIFF_GENERATED' : 'IPYNB_DIFF_NIL' })
- rescue IpynbDiff::InvalidNotebookError => e
- Gitlab::ErrorTracking.log_exception(e)
- end
-
- def strip_diff_frontmatter(diff_content)
- diff_content.scan(/.*\n/)[2..-1]&.join('') if diff_content.present?
- end
-
def alternate_viewer_class
return unless viewer.instance_of?(DiffViewer::Renamed)
diff --git a/lib/gitlab/diff/file_collection/merge_request_diff_base.rb b/lib/gitlab/diff/file_collection/merge_request_diff_base.rb
index 692186fc323..b459e3f6619 100644
--- a/lib/gitlab/diff/file_collection/merge_request_diff_base.rb
+++ b/lib/gitlab/diff/file_collection/merge_request_diff_base.rb
@@ -13,7 +13,7 @@ module Gitlab
super(merge_request_diff,
project: merge_request_diff.project,
- diff_options: merged_diff_options(diff_options),
+ diff_options: diff_options,
diff_refs: merge_request_diff.diff_refs,
fallback_diff_refs: merge_request_diff.fallback_diff_refs)
end
@@ -68,13 +68,6 @@ module Gitlab
diff_stats_cache.read || super
end
end
-
- def merged_diff_options(diff_options)
- project = @merge_request_diff.project
- max_diff_options = ::Commit.max_diff_options(project: project).merge(project: project)
-
- diff_options.present? ? diff_options.merge(max_diff_options) : max_diff_options
- end
end
end
end
diff --git a/lib/gitlab/diff/highlight.rb b/lib/gitlab/diff/highlight.rb
index aedcfe3cb40..47f3324752d 100644
--- a/lib/gitlab/diff/highlight.rb
+++ b/lib/gitlab/diff/highlight.rb
@@ -153,13 +153,10 @@ module Gitlab
blob.load_all_data!
- return blob.present.highlight_transformed.lines if Feature.enabled?(:jupyter_clean_diffs, @project, default_enabled: true)
-
blob.present.highlight.lines
end
def blobs_too_large?
- return false unless Feature.enabled?(:limited_diff_highlighting, project, default_enabled: :yaml)
return true if Gitlab::Highlight.too_large?(diff_file.old_blob&.size)
Gitlab::Highlight.too_large?(diff_file.new_blob&.size)
diff --git a/lib/gitlab/diff/inline_diff.rb b/lib/gitlab/diff/inline_diff.rb
index f70618195d0..802da50cfc6 100644
--- a/lib/gitlab/diff/inline_diff.rb
+++ b/lib/gitlab/diff/inline_diff.rb
@@ -6,8 +6,8 @@ module Gitlab
attr_accessor :old_line, :new_line, :offset
def initialize(old_line, new_line, offset: 0)
- @old_line = old_line[offset..-1]
- @new_line = new_line[offset..-1]
+ @old_line = old_line[offset..]
+ @new_line = new_line[offset..]
@offset = offset
end
diff --git a/lib/gitlab/email/handler/create_issue_handler.rb b/lib/gitlab/email/handler/create_issue_handler.rb
index 4b490ae0d26..7b31dd9926b 100644
--- a/lib/gitlab/email/handler/create_issue_handler.rb
+++ b/lib/gitlab/email/handler/create_issue_handler.rb
@@ -60,7 +60,7 @@ module Gitlab
current_user: author,
params: {
title: mail.subject,
- description: message_including_reply
+ description: message_including_reply_or_only_quotes
},
spam_params: nil
).execute
diff --git a/lib/gitlab/email/handler/reply_processing.rb b/lib/gitlab/email/handler/reply_processing.rb
index a717509e24d..43cbc49b9d2 100644
--- a/lib/gitlab/email/handler/reply_processing.rb
+++ b/lib/gitlab/email/handler/reply_processing.rb
@@ -35,6 +35,10 @@ module Gitlab
@message_with_reply ||= process_message(trim_reply: false)
end
+ def message_including_reply_or_only_quotes
+ @message_including_reply_or_only_quotes ||= process_message(trim_reply: false, allow_only_quotes: true)
+ end
+
def message_with_appended_reply
@message_with_appended_reply ||= process_message(append_reply: true)
end
diff --git a/lib/gitlab/email/handler/service_desk_handler.rb b/lib/gitlab/email/handler/service_desk_handler.rb
index 8d73aa842be..71b1d4ed8f9 100644
--- a/lib/gitlab/email/handler/service_desk_handler.rb
+++ b/lib/gitlab/email/handler/service_desk_handler.rb
@@ -32,11 +32,11 @@ module Gitlab
def execute
raise ProjectNotFound if project.nil?
- create_issue!
+ create_issue_or_note
if from_address
add_email_participant
- send_thank_you_email
+ send_thank_you_email unless reply_email?
end
end
@@ -82,6 +82,14 @@ module Gitlab
project.present? && slug == project.full_path_slug
end
+ def create_issue_or_note
+ if reply_email?
+ create_note_from_reply_email
+ else
+ create_issue!
+ end
+ end
+
def create_issue!
@issue = ::Issues::CreateService.new(
project: project,
@@ -97,18 +105,42 @@ module Gitlab
raise InvalidIssueError unless @issue.persisted?
+ begin
+ ::Issue::Email.create!(issue: @issue, email_message_id: mail.message_id)
+ rescue StandardError => e
+ Gitlab::ErrorTracking.log_exception(e)
+ end
+
if service_desk_setting&.issue_template_missing?
- create_template_not_found_note(@issue)
+ create_template_not_found_note
+ end
+ end
+
+ def issue_from_reply_to
+ strong_memoize(:issue_from_reply_to) do
+ next unless mail.in_reply_to
+
+ Issue::Email.find_by_email_message_id(mail.in_reply_to)&.issue
end
end
+ def reply_email?
+ issue_from_reply_to.present?
+ end
+
+ def create_note_from_reply_email
+ @issue = issue_from_reply_to
+
+ create_note(message_including_reply)
+ end
+
def send_thank_you_email
Notify.service_desk_thank_you_email(@issue.id).deliver_later
Gitlab::Metrics::BackgroundTransaction.current&.add_event(:service_desk_thank_you_email)
end
def message_including_template
- description = process_message(trim_reply: false, allow_only_quotes: true)
+ description = message_including_reply_or_only_quotes
template_content = service_desk_setting&.issue_template_content
if template_content.present?
@@ -124,7 +156,7 @@ module Gitlab
end
end
- def create_template_not_found_note(issue)
+ def create_template_not_found_note
issue_template_key = service_desk_setting&.issue_template_key
warning_note = <<-MD.strip_heredoc
@@ -132,15 +164,15 @@ module Gitlab
Please check service desk settings and update the file to be used.
MD
- note_params = {
- noteable: issue,
- note: warning_note
- }
+ create_note(warning_note)
+ end
+ def create_note(note)
::Notes::CreateService.new(
project,
User.support_bot,
- note_params
+ noteable: @issue,
+ note: note
).execute
end
@@ -157,6 +189,8 @@ module Gitlab
end
def add_email_participant
+ return if reply_email? && !Feature.enabled?(:issue_email_participants, @issue.project)
+
@issue.issue_email_participants.create(email: from_address)
end
end
diff --git a/lib/gitlab/email/hook/smime_signature_interceptor.rb b/lib/gitlab/email/hook/smime_signature_interceptor.rb
index 0b092b3e41e..ed1bf3e12bf 100644
--- a/lib/gitlab/email/hook/smime_signature_interceptor.rb
+++ b/lib/gitlab/email/hook/smime_signature_interceptor.rb
@@ -45,7 +45,6 @@ module Gitlab
end
def overwrite_headers(message, signed_email)
- message.content_disposition = signed_email.content_disposition
message.content_transfer_encoding = signed_email.content_transfer_encoding
message.content_type = signed_email.content_type
end
diff --git a/lib/gitlab/email/message/in_product_marketing/admin_verify.rb b/lib/gitlab/email/message/in_product_marketing/admin_verify.rb
index 19d9cf99cdb..888f84cde23 100644
--- a/lib/gitlab/email/message/in_product_marketing/admin_verify.rb
+++ b/lib/gitlab/email/message/in_product_marketing/admin_verify.rb
@@ -38,7 +38,7 @@ module Gitlab
end
def invite_members?
- invite_members_for_task_experiment_enabled?
+ user.can?(:admin_group_member, group)
end
end
end
diff --git a/lib/gitlab/email/message/in_product_marketing/base.rb b/lib/gitlab/email/message/in_product_marketing/base.rb
index 7cd54390b9f..9b50d86de58 100644
--- a/lib/gitlab/email/message/in_product_marketing/base.rb
+++ b/lib/gitlab/email/message/in_product_marketing/base.rb
@@ -88,29 +88,6 @@ module Gitlab
end
end
- def address
- s_('InProductMarketing|%{strong_start}GitLab Inc.%{strong_end} 268 Bush Street, #350, San Francisco, CA 94104, USA').html_safe % strong_options
- end
-
- def footer_links
- links = [
- [s_('InProductMarketing|Blog'), 'https://about.gitlab.com/blog'],
- [s_('InProductMarketing|Twitter'), 'https://twitter.com/gitlab'],
- [s_('InProductMarketing|Facebook'), 'https://www.facebook.com/gitlab'],
- [s_('InProductMarketing|YouTube'), 'https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg']
- ]
- case format
- when :html
- links.map do |text, link|
- ActionController::Base.helpers.link_to(text, link)
- end
- else
- '| ' + links.map do |text, link|
- [text, link].join(' ')
- end.join("\n| ")
- end
- end
-
def logo_path
["mailers/in_product_marketing", "#{track}-#{series}.png"].join('/')
end
@@ -166,16 +143,6 @@ module Gitlab
link(s_('InProductMarketing|update your preferences'), preference_link)
end
- def invite_members_for_task_experiment_enabled?
- return unless user.can?(:admin_group_member, group)
-
- experiment(:invite_members_for_task, namespace: group) do |e|
- e.candidate { true }
- e.record!
- e.run
- end
- end
-
def validate_series!
raise ArgumentError, "Only #{total_series} series available for this track." unless @series.between?(0, total_series - 1)
end
diff --git a/lib/gitlab/email/message/in_product_marketing/create.rb b/lib/gitlab/email/message/in_product_marketing/create.rb
index 2c396775374..6b01c83b8e7 100644
--- a/lib/gitlab/email/message/in_product_marketing/create.rb
+++ b/lib/gitlab/email/message/in_product_marketing/create.rb
@@ -62,7 +62,7 @@ module Gitlab
end
def invite_members?
- invite_members_for_task_experiment_enabled?
+ user.can?(:admin_group_member, group)
end
private
diff --git a/lib/gitlab/email/message/in_product_marketing/helper.rb b/lib/gitlab/email/message/in_product_marketing/helper.rb
index bffa90ed4ec..329cace9e9d 100644
--- a/lib/gitlab/email/message/in_product_marketing/helper.rb
+++ b/lib/gitlab/email/message/in_product_marketing/helper.rb
@@ -8,6 +8,29 @@ module Gitlab
include ActionView::Context
include ActionView::Helpers::TagHelper
+ def footer_links
+ links = [
+ [s_('InProductMarketing|Blog'), 'https://about.gitlab.com/blog'],
+ [s_('InProductMarketing|Twitter'), 'https://twitter.com/gitlab'],
+ [s_('InProductMarketing|Facebook'), 'https://www.facebook.com/gitlab'],
+ [s_('InProductMarketing|YouTube'), 'https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg']
+ ]
+ case format
+ when :html
+ links.map do |text, link|
+ ActionController::Base.helpers.link_to(text, link)
+ end
+ else
+ '| ' + links.map do |text, link|
+ [text, link].join(' ')
+ end.join("\n| ")
+ end
+ end
+
+ def address
+ s_('InProductMarketing|%{strong_start}GitLab Inc.%{strong_end} 268 Bush Street, #350, San Francisco, CA 94104, USA').html_safe % strong_options
+ end
+
private
def list(array)
diff --git a/lib/gitlab/email/message/in_product_marketing/verify.rb b/lib/gitlab/email/message/in_product_marketing/verify.rb
index daf0c969f2b..d2a78b53e1f 100644
--- a/lib/gitlab/email/message/in_product_marketing/verify.rb
+++ b/lib/gitlab/email/message/in_product_marketing/verify.rb
@@ -66,7 +66,7 @@ module Gitlab
end
def invite_members?
- invite_members_for_task_experiment_enabled?
+ user.can?(:admin_group_member, group)
end
private
diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb
index 526f1188065..5b2bbfbe66b 100644
--- a/lib/gitlab/email/receiver.rb
+++ b/lib/gitlab/email/receiver.rb
@@ -73,7 +73,7 @@ module Gitlab
def key_from_to_header
mail.to.find do |address|
- key = Gitlab::IncomingEmail.key_from_address(address)
+ key = email_class.key_from_address(address)
break key if key
end
end
@@ -100,7 +100,7 @@ module Gitlab
def find_key_from_references
ensure_references_array(mail.references).find do |mail_id|
- key = Gitlab::IncomingEmail.key_from_fallback_message_id(mail_id)
+ key = email_class.key_from_fallback_message_id(mail_id)
break key if key
end
end
@@ -119,21 +119,21 @@ module Gitlab
def find_key_from_delivered_to_header
delivered_to.find do |header|
- key = Gitlab::IncomingEmail.key_from_address(header.value)
+ key = email_class.key_from_address(header.value)
break key if key
end
end
def find_key_from_envelope_to_header
envelope_to.find do |header|
- key = Gitlab::IncomingEmail.key_from_address(header.value)
+ key = email_class.key_from_address(header.value)
break key if key
end
end
def find_key_from_x_envelope_to_header
x_envelope_to.find do |header|
- key = Gitlab::IncomingEmail.key_from_address(header.value)
+ key = email_class.key_from_address(header.value)
break key if key
end
end
@@ -158,6 +158,10 @@ module Gitlab
autoreply && autoreply == 'yes'
end
+
+ def email_class
+ Gitlab::IncomingEmail
+ end
end
end
end
diff --git a/lib/gitlab/email/service_desk_receiver.rb b/lib/gitlab/email/service_desk_receiver.rb
index 133c4ee4b45..6c6eb3b0a65 100644
--- a/lib/gitlab/email/service_desk_receiver.rb
+++ b/lib/gitlab/email/service_desk_receiver.rb
@@ -6,22 +6,13 @@ module Gitlab
private
def find_handler
- return unless service_desk_key
+ return unless mail_key
- Gitlab::Email::Handler::ServiceDeskHandler.new(mail, nil, service_desk_key: service_desk_key)
+ Gitlab::Email::Handler::ServiceDeskHandler.new(mail, nil, service_desk_key: mail_key)
end
- def service_desk_key
- strong_memoize(:service_desk_key) do
- find_service_desk_key
- end
- end
-
- def find_service_desk_key
- mail.to.find do |address|
- key = ::Gitlab::ServiceDeskEmail.key_from_address(address)
- break key if key
- end
+ def email_class
+ ::Gitlab::ServiceDeskEmail
end
end
end
diff --git a/lib/gitlab/emoji.rb b/lib/gitlab/emoji.rb
index 519b1d94bf5..3c5d223b106 100644
--- a/lib/gitlab/emoji.rb
+++ b/lib/gitlab/emoji.rb
@@ -6,7 +6,7 @@ module Gitlab
# When updating emoji assets increase the version below
# and update the version number in `app/assets/javascripts/emoji/index.js`
- EMOJI_VERSION = 1
+ EMOJI_VERSION = 2
# Return a Pathname to emoji's current versioned folder
#
diff --git a/lib/gitlab/empty_search_results.rb b/lib/gitlab/empty_search_results.rb
new file mode 100644
index 00000000000..71a78cb297b
--- /dev/null
+++ b/lib/gitlab/empty_search_results.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Gitlab
+ # This class has the same interface as SearchResults except
+ # it is empty and does not do any work.
+ #
+ # We use this when responding to abusive search requests.
+ class EmptySearchResults
+ def initialize(*)
+ end
+
+ def objects(*)
+ Kaminari.paginate_array([])
+ end
+
+ def formatted_count(*)
+ '0'
+ end
+
+ def highlight_map(*)
+ {}
+ end
+
+ def aggregations(*)
+ []
+ end
+ end
+end
diff --git a/lib/gitlab/error_tracking.rb b/lib/gitlab/error_tracking.rb
index 38ac5d9af74..6a637306225 100644
--- a/lib/gitlab/error_tracking.rb
+++ b/lib/gitlab/error_tracking.rb
@@ -140,12 +140,9 @@ module Gitlab
end
def inject_context_for_exception(event, ex)
- case ex
- when ActiveRecord::StatementInvalid
- event.extra[:sql] = PgQuery.normalize(ex.sql.to_s)
- else
- inject_context_for_exception(event, ex.cause) if ex.cause.present?
- end
+ sql = Gitlab::ExceptionLogFormatter.find_sql(ex)
+
+ event.extra[:sql] = sql if sql
end
end
end
diff --git a/lib/gitlab/etag_caching/router/graphql.rb b/lib/gitlab/etag_caching/router/graphql.rb
index 2b8639b9411..1f56670ee7f 100644
--- a/lib/gitlab/etag_caching/router/graphql.rb
+++ b/lib/gitlab/etag_caching/router/graphql.rb
@@ -17,6 +17,11 @@ module Gitlab
%r(\Apipelines/sha/\w{7,40}\z),
'ci_editor',
'pipeline_authoring'
+ ],
+ [
+ %r(\Aon_demand_scan/counts/),
+ 'on_demand_scans',
+ 'dynamic_application_security_testing'
]
].map(&method(:build_route)).freeze
diff --git a/lib/gitlab/etag_caching/store.rb b/lib/gitlab/etag_caching/store.rb
index d0d790a7c72..44c6984c09b 100644
--- a/lib/gitlab/etag_caching/store.rb
+++ b/lib/gitlab/etag_caching/store.rb
@@ -12,14 +12,18 @@ module Gitlab
Gitlab::Redis::SharedState.with { |redis| redis.get(redis_shared_state_key(key)) }
end
- def touch(key, only_if_missing: false)
- etag = generate_etag
+ def touch(*keys, only_if_missing: false)
+ etags = keys.map { generate_etag }
Gitlab::Redis::SharedState.with do |redis|
- redis.set(redis_shared_state_key(key), etag, ex: EXPIRY_TIME, nx: only_if_missing)
+ redis.pipelined do
+ keys.each_with_index do |key, i|
+ redis.set(redis_shared_state_key(key), etags[i], ex: EXPIRY_TIME, nx: only_if_missing)
+ end
+ end
end
- etag
+ keys.size > 1 ? etags : etags.first
end
private
diff --git a/lib/gitlab/exception_log_formatter.rb b/lib/gitlab/exception_log_formatter.rb
index 9898651c9e3..315574fed31 100644
--- a/lib/gitlab/exception_log_formatter.rb
+++ b/lib/gitlab/exception_log_formatter.rb
@@ -2,18 +2,41 @@
module Gitlab
module ExceptionLogFormatter
- def self.format!(exception, payload)
- return unless exception
+ class << self
+ def format!(exception, payload)
+ return unless exception
- # Elasticsearch/Fluentd don't handle nested structures well.
- # Use periods to flatten the fields.
- payload.merge!(
- 'exception.class' => exception.class.name,
- 'exception.message' => exception.message
- )
+ # Elasticsearch/Fluentd don't handle nested structures well.
+ # Use periods to flatten the fields.
+ payload.merge!(
+ 'exception.class' => exception.class.name,
+ 'exception.message' => exception.message
+ )
- if exception.backtrace
- payload['exception.backtrace'] = Rails.backtrace_cleaner.clean(exception.backtrace)
+ if exception.backtrace
+ payload['exception.backtrace'] = Rails.backtrace_cleaner.clean(exception.backtrace)
+ end
+
+ if sql = find_sql(exception)
+ payload['exception.sql'] = sql
+ end
+ end
+
+ def find_sql(exception)
+ if exception.is_a?(ActiveRecord::StatementInvalid)
+ # StatementInvalid may be caused by a statement timeout or a bad query
+ normalize_query(exception.sql.to_s)
+ elsif exception.cause.present?
+ find_sql(exception.cause)
+ end
+ end
+
+ private
+
+ def normalize_query(sql)
+ PgQuery.normalize(sql)
+ rescue PgQuery::ParseError
+ sql
end
end
end
diff --git a/lib/gitlab/experimentation.rb b/lib/gitlab/experimentation.rb
index c2009628c56..4cc653bec43 100644
--- a/lib/gitlab/experimentation.rb
+++ b/lib/gitlab/experimentation.rb
@@ -37,9 +37,6 @@ module Gitlab
remove_known_trial_form_fields_noneditable: {
tracking_category: 'Growth::Conversion::Experiment::RemoveKnownTrialFormFieldsNoneditable',
rollout_strategy: :user
- },
- invite_members_new_dropdown: {
- tracking_category: 'Growth::Expansion::Experiment::InviteMembersNewDropdown'
}
}.freeze
diff --git a/lib/gitlab/experimentation/controller_concern.rb b/lib/gitlab/experimentation/controller_concern.rb
index 7cc29cde45c..303d952381f 100644
--- a/lib/gitlab/experimentation/controller_concern.rb
+++ b/lib/gitlab/experimentation/controller_concern.rb
@@ -16,7 +16,7 @@ module Gitlab
included do
before_action :set_experimentation_subject_id_cookie, unless: :dnt_enabled?
- helper_method :experiment_enabled?, :experiment_tracking_category_and_group, :record_experiment_group, :tracking_label
+ helper_method :experiment_enabled?, :experiment_tracking_category_and_group, :record_experiment_group
end
def set_experimentation_subject_id_cookie
diff --git a/lib/gitlab/gfm/reference_rewriter.rb b/lib/gitlab/gfm/reference_rewriter.rb
index 4d82acd9d87..5d0a638f97a 100644
--- a/lib/gitlab/gfm/reference_rewriter.rb
+++ b/lib/gitlab/gfm/reference_rewriter.rb
@@ -57,7 +57,7 @@ module Gitlab
def unfold_reference(reference, match, target_parent)
before = @text[0...match.begin(0)]
- after = @text[match.end(0)..-1]
+ after = @text[match.end(0)..]
referable = find_referable(reference)
return reference unless referable
diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb
index b0d194f309a..f72217dedde 100644
--- a/lib/gitlab/git/blob.rb
+++ b/lib/gitlab/git/blob.rb
@@ -24,7 +24,7 @@ module Gitlab
LFS_POINTER_MIN_SIZE = 120.bytes
LFS_POINTER_MAX_SIZE = 200.bytes
- attr_accessor :size, :mode, :id, :commit_id, :loaded_size, :binary, :transformed_for_diff
+ attr_accessor :size, :mode, :id, :commit_id, :loaded_size, :binary
attr_writer :name, :path, :data
def self.gitlab_blob_truncated_true
@@ -127,7 +127,6 @@ module Gitlab
# Retain the actual size before it is encoded
@loaded_size = @data.bytesize if @data
@loaded_all_data = @loaded_size == size
- @transformed_for_diff = false
record_metric_blob_size
record_metric_truncated(truncated?)
diff --git a/lib/gitlab/git/diff_collection.rb b/lib/gitlab/git/diff_collection.rb
index 631624c068c..24b67424f28 100644
--- a/lib/gitlab/git/diff_collection.rb
+++ b/lib/gitlab/git/diff_collection.rb
@@ -11,13 +11,13 @@ module Gitlab
delegate :max_files, :max_lines, :max_bytes, :safe_max_files, :safe_max_lines, :safe_max_bytes, to: :limits
- def self.default_limits(project: nil)
- { max_files: ::Commit.diff_safe_max_files(project: project), max_lines: ::Commit.diff_safe_max_lines(project: project) }
+ def self.default_limits
+ { max_files: ::Commit.diff_safe_max_files, max_lines: ::Commit.diff_safe_max_lines }
end
def self.limits(options = {})
limits = {}
- defaults = default_limits(project: options[:project])
+ defaults = default_limits
limits[:max_files] = options.fetch(:max_files, defaults[:max_files])
limits[:max_lines] = options.fetch(:max_lines, defaults[:max_lines])
limits[:max_bytes] = limits[:max_files] * 5.kilobytes # Average 5 KB per file
diff --git a/lib/gitlab/git/diff_stats_collection.rb b/lib/gitlab/git/diff_stats_collection.rb
index e30ec836a49..6a689f55478 100644
--- a/lib/gitlab/git/diff_stats_collection.rb
+++ b/lib/gitlab/git/diff_stats_collection.rb
@@ -22,8 +22,8 @@ module Gitlab
@collection.map(&:path)
end
- def real_size(project: nil)
- max_files = ::Commit.max_diff_options(project: project)[:max_files]
+ def real_size
+ max_files = ::Commit.diff_max_files
if paths.size > max_files
"#{max_files}+"
else
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 5afdcc0bd4c..240a701aba9 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -160,6 +160,8 @@ module Gitlab
wrapped_gitaly_errors do
gitaly_repository_client.remove
end
+ rescue NoRepository
+ nil
end
def replicate(source_repository)
diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb
index 7c688044e9c..c2b4182f609 100644
--- a/lib/gitlab/gitaly_client/commit_service.rb
+++ b/lib/gitlab/gitaly_client/commit_service.rb
@@ -204,19 +204,6 @@ module Gitlab
Gitlab::Git::Commit.new(@repository, gitaly_commit)
end
- def between(from, to)
- return list_commits(["^" + from, to], reverse: true) if Feature.enabled?(:between_commits_via_list_commits)
-
- request = Gitaly::CommitsBetweenRequest.new(
- repository: @gitaly_repo,
- from: from,
- to: to
- )
-
- response = GitalyClient.call(@repository.storage, :commit_service, :commits_between, request, timeout: GitalyClient.medium_timeout)
- consume_commits_response(response)
- end
-
def diff_stats(left_commit_sha, right_commit_sha)
request = Gitaly::DiffStatsRequest.new(
repository: @gitaly_repo,
diff --git a/lib/gitlab/github_import/client.rb b/lib/gitlab/github_import/client.rb
index efa816c5eb0..d2495b32800 100644
--- a/lib/gitlab/github_import/client.rb
+++ b/lib/gitlab/github_import/client.rb
@@ -21,6 +21,7 @@ module Gitlab
SEARCH_MAX_REQUESTS_PER_MINUTE = 30
DEFAULT_PER_PAGE = 100
LOWER_PER_PAGE = 50
+ CLIENT_CONNECTION_ERROR = ::Faraday::ConnectionFailed # used/set in sawyer agent which octokit uses
# A single page of data and the corresponding page number.
Page = Struct.new(:objects, :number)
@@ -148,14 +149,14 @@ module Gitlab
# whether we are running in parallel mode or not. For more information see
# `#rate_or_wait_for_rate_limit`.
def with_rate_limit
- return yield unless rate_limiting_enabled?
+ return with_retry { yield } unless rate_limiting_enabled?
request_count_counter.increment
raise_or_wait_for_rate_limit unless requests_remaining?
begin
- yield
+ with_retry { yield }
rescue ::Octokit::TooManyRequests
raise_or_wait_for_rate_limit
@@ -166,7 +167,7 @@ module Gitlab
end
def search_repos_by_name(name, options = {})
- octokit.search_repositories(search_query(str: name, type: :name), options)
+ with_retry { octokit.search_repositories(search_query(str: name, type: :name), options) }
end
def search_query(str:, type:, include_collaborations: true, include_orgs: true)
@@ -270,6 +271,25 @@ module Gitlab
.map { |org| "org:#{org.login}" }
.join(' ')
end
+
+ def with_retry
+ Retriable.retriable(on: CLIENT_CONNECTION_ERROR, on_retry: on_retry) do
+ yield
+ end
+ end
+
+ def on_retry
+ proc do |exception, try, elapsed_time, next_interval|
+ Gitlab::Import::Logger.info(
+ message: "GitHub connection retry triggered",
+ 'error.class': exception.class,
+ 'error.message': exception.message,
+ try_count: try,
+ elapsed_time_s: elapsed_time,
+ wait_to_retry_s: next_interval
+ )
+ end
+ end
end
end
end
diff --git a/lib/gitlab/github_import/importer/diff_note_importer.rb b/lib/gitlab/github_import/importer/diff_note_importer.rb
index 0aa0896aa57..8a8d23401c1 100644
--- a/lib/gitlab/github_import/importer/diff_note_importer.rb
+++ b/lib/gitlab/github_import/importer/diff_note_importer.rb
@@ -31,6 +31,10 @@ module Gitlab
else
import_with_legacy_diff_note
end
+ rescue ::DiffNote::NoteDiffFileCreationError => e
+ Logger.warn(message: e.message, 'error.class': e.class.name)
+
+ import_with_legacy_diff_note
rescue ActiveRecord::InvalidForeignKey => e
# It's possible the project and the issue have been deleted since
# scheduling this job. In this case we'll just skip creating the note
diff --git a/lib/gitlab/github_import/importer/note_importer.rb b/lib/gitlab/github_import/importer/note_importer.rb
index 2cc3a82dd9b..673f56b5753 100644
--- a/lib/gitlab/github_import/importer/note_importer.rb
+++ b/lib/gitlab/github_import/importer/note_importer.rb
@@ -29,6 +29,7 @@ module Gitlab
project_id: project.id,
author_id: author_id,
note: note_body,
+ discussion_id: note.discussion_id,
system: false,
created_at: note.created_at,
updated_at: note.updated_at
diff --git a/lib/gitlab/github_import/parallel_importer.rb b/lib/gitlab/github_import/parallel_importer.rb
index f72e595e8e9..a71590c02f8 100644
--- a/lib/gitlab/github_import/parallel_importer.rb
+++ b/lib/gitlab/github_import/parallel_importer.rb
@@ -35,7 +35,9 @@ module Gitlab
def execute
Gitlab::Import::SetAsyncJid.set_jid(project.import_state)
+ # We need to track this job's status for use by Gitlab::GithubImport::RefreshImportJidWorker.
Stage::ImportRepositoryWorker
+ .with_status
.perform_async(project.id)
true
diff --git a/lib/gitlab/github_import/representation/diff_note.rb b/lib/gitlab/github_import/representation/diff_note.rb
index fecff0644c2..04f53accfeb 100644
--- a/lib/gitlab/github_import/representation/diff_note.rb
+++ b/lib/gitlab/github_import/representation/diff_note.rb
@@ -4,6 +4,7 @@ module Gitlab
module GithubImport
module Representation
class DiffNote
+ include Gitlab::Utils::StrongMemoize
include ToHash
include ExposeAttribute
@@ -127,15 +128,17 @@ module Gitlab
end
def discussion_id
- if in_reply_to_id.present?
- current_discussion_id
- else
- Discussion.discussion_id(
- Struct
- .new(:noteable_id, :noteable_type)
- .new(merge_request.id, NOTEABLE_TYPE)
- ).tap do |discussion_id|
- cache_discussion_id(discussion_id)
+ strong_memoize(:discussion_id) do
+ if in_reply_to_id.present?
+ current_discussion_id
+ else
+ Discussion.discussion_id(
+ Struct
+ .new(:noteable_id, :noteable_type)
+ .new(merge_request.id, NOTEABLE_TYPE)
+ ).tap do |discussion_id|
+ cache_discussion_id(discussion_id)
+ end
end
end
end
diff --git a/lib/gitlab/github_import/representation/note.rb b/lib/gitlab/github_import/representation/note.rb
index bcdb1a5459b..bbf20b7e9e6 100644
--- a/lib/gitlab/github_import/representation/note.rb
+++ b/lib/gitlab/github_import/representation/note.rb
@@ -63,6 +63,14 @@ module Gitlab
@attributes = attributes
end
+ def discussion_id
+ Discussion.discussion_id(
+ Struct
+ .new(:noteable_id, :noteable_type)
+ .new(noteable_id, noteable_type)
+ )
+ end
+
alias_method :issuable_type, :noteable_type
def github_identifiers
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index 9ad902efb3a..bb3ba1129fc 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -56,7 +56,6 @@ module Gitlab
push_frontend_feature_flag(:security_auto_fix, default_enabled: false)
push_frontend_feature_flag(:improved_emoji_picker, default_enabled: :yaml)
push_frontend_feature_flag(:new_header_search, default_enabled: :yaml)
- push_frontend_feature_flag(:configure_iac_scanning_via_mr, current_user, default_enabled: :yaml)
push_frontend_feature_flag(:bootstrap_confirmation_modals, default_enabled: :yaml)
end
diff --git a/lib/gitlab/gpg.rb b/lib/gitlab/gpg.rb
index 3d9b06855ff..f55afd90ac0 100644
--- a/lib/gitlab/gpg.rb
+++ b/lib/gitlab/gpg.rb
@@ -48,7 +48,7 @@ module Gitlab
raw_keys.each_with_object({}) do |raw_key, grouped_subkeys|
primary_subkey_id = raw_key.primary_subkey.keyid
- grouped_subkeys[primary_subkey_id] = raw_key.subkeys[1..-1].map do |s|
+ grouped_subkeys[primary_subkey_id] = raw_key.subkeys[1..].map do |s|
{ keyid: s.keyid, fingerprint: s.fingerprint }
end
end
diff --git a/lib/gitlab/gpg/commit.rb b/lib/gitlab/gpg/commit.rb
index 9a6317e2b76..59882e8d4f8 100644
--- a/lib/gitlab/gpg/commit.rb
+++ b/lib/gitlab/gpg/commit.rb
@@ -25,7 +25,7 @@ module Gitlab
def lazy_signature
BatchLoader.for(@commit.sha).batch do |shas, loader|
- GpgSignature.by_commit_sha(shas).each do |signature|
+ CommitSignatures::GpgSignature.by_commit_sha(shas).each do |signature|
loader.call(signature.commit_sha, signature)
end
end
@@ -62,9 +62,9 @@ module Gitlab
def create_cached_signature!
using_keychain do |gpg_key|
attributes = attributes(gpg_key)
- break GpgSignature.new(attributes) if Gitlab::Database.read_only?
+ break CommitSignatures::GpgSignature.new(attributes) if Gitlab::Database.read_only?
- GpgSignature.safe_create!(attributes)
+ CommitSignatures::GpgSignature.safe_create!(attributes)
end
end
diff --git a/lib/gitlab/gpg/invalid_gpg_signature_updater.rb b/lib/gitlab/gpg/invalid_gpg_signature_updater.rb
index d892d27a917..0eb105143ea 100644
--- a/lib/gitlab/gpg/invalid_gpg_signature_updater.rb
+++ b/lib/gitlab/gpg/invalid_gpg_signature_updater.rb
@@ -9,9 +9,9 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord
def run
- GpgSignature
+ CommitSignatures::GpgSignature
.select(:id, :commit_sha, :project_id)
- .where('gpg_key_id IS NULL OR verification_status <> ?', GpgSignature.verification_statuses[:verified])
+ .where('gpg_key_id IS NULL OR verification_status <> ?', CommitSignatures::GpgSignature.verification_statuses[:verified])
.where(gpg_key_primary_keyid: @gpg_key.keyids)
.find_each { |sig| sig.gpg_commit&.update_signature!(sig) }
end
diff --git a/lib/gitlab/graphql/tracers/logger_tracer.rb b/lib/gitlab/graphql/tracers/logger_tracer.rb
index c7ba56824db..3302b2bae3f 100644
--- a/lib/gitlab/graphql/tracers/logger_tracer.rb
+++ b/lib/gitlab/graphql/tracers/logger_tracer.rb
@@ -11,19 +11,20 @@ module Gitlab
end
def trace(key, data)
- result = yield
-
+ yield
+ rescue StandardError => e
+ data[:exception] = e
+ raise e
+ ensure
case key
when "execute_query"
log_execute_query(**data)
end
-
- result
end
private
- def log_execute_query(query: nil, duration_s: 0)
+ def log_execute_query(query: nil, duration_s: 0, exception: nil)
# execute_query should always have :query, but we're just being defensive
return unless query
@@ -39,6 +40,8 @@ module Gitlab
query_string: query.query_string
}
+ Gitlab::ExceptionLogFormatter.format!(exception, info)
+
info.merge!(::Gitlab::ApplicationContext.current)
info.merge!(analysis_info) if analysis_info
diff --git a/lib/gitlab/graphql/tracers/timer_tracer.rb b/lib/gitlab/graphql/tracers/timer_tracer.rb
index 326620a22bc..8e058621110 100644
--- a/lib/gitlab/graphql/tracers/timer_tracer.rb
+++ b/lib/gitlab/graphql/tracers/timer_tracer.rb
@@ -17,13 +17,9 @@ module Gitlab
def trace(key, data)
start_time = Gitlab::Metrics::System.monotonic_time
- result = yield
-
- duration_s = Gitlab::Metrics::System.monotonic_time - start_time
-
- data[:duration_s] = duration_s
-
- result
+ yield
+ ensure
+ data[:duration_s] = Gitlab::Metrics::System.monotonic_time - start_time
end
end
end
diff --git a/lib/gitlab/hook_data/merge_request_builder.rb b/lib/gitlab/hook_data/merge_request_builder.rb
index db807a3c557..0e787a77a25 100644
--- a/lib/gitlab/hook_data/merge_request_builder.rb
+++ b/lib/gitlab/hook_data/merge_request_builder.rb
@@ -7,6 +7,7 @@ module Gitlab
%i[
assignee_id
author_id
+ blocking_discussions_resolved
created_at
description
head_pipeline_id
@@ -57,7 +58,8 @@ module Gitlab
human_time_estimate: merge_request.human_time_estimate,
assignee_ids: merge_request.assignee_ids,
assignee_id: merge_request.assignee_ids.first, # This key is deprecated
- state: merge_request.state # This key is deprecated
+ state: merge_request.state, # This key is deprecated
+ blocking_discussions_resolved: merge_request.mergeable_discussions_state?
}
merge_request.attributes.with_indifferent_access.slice(*self.class.safe_hook_attributes)
diff --git a/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb
index 251bc34d462..12203cab8c8 100644
--- a/lib/gitlab/i18n.rb
+++ b/lib/gitlab/i18n.rb
@@ -43,27 +43,27 @@ module Gitlab
TRANSLATION_LEVELS = {
'bg' => 0,
'cs_CZ' => 0,
- 'da_DK' => 52,
+ 'da_DK' => 51,
'de' => 15,
'en' => 100,
'eo' => 0,
- 'es' => 40,
+ 'es' => 39,
'fil_PH' => 0,
- 'fr' => 11,
+ 'fr' => 12,
'gl_ES' => 0,
'id_ID' => 0,
'it' => 2,
- 'ja' => 36,
+ 'ja' => 35,
'ko' => 11,
- 'nb_NO' => 34,
+ 'nb_NO' => 33,
'nl_NL' => 0,
'pl_PL' => 5,
'pt_BR' => 49,
- 'ro_RO' => 24,
- 'ru' => 26,
+ 'ro_RO' => 23,
+ 'ru' => 25,
'tr_TR' => 15,
- 'uk' => 39,
- 'zh_CN' => 97,
+ 'uk' => 45,
+ 'zh_CN' => 95,
'zh_HK' => 2,
'zh_TW' => 3
}.freeze
diff --git a/lib/gitlab/import/import_failure_service.rb b/lib/gitlab/import/import_failure_service.rb
index 142c00f7a6b..bebd64b29a9 100644
--- a/lib/gitlab/import/import_failure_service.rb
+++ b/lib/gitlab/import/import_failure_service.rb
@@ -15,11 +15,21 @@ module Gitlab
exception: exception,
import_state: import_state,
project_id: project_id,
- error_source: error_source
- ).execute(fail_import: fail_import, metrics: metrics)
+ error_source: error_source,
+ fail_import: fail_import,
+ metrics: metrics
+ ).execute
end
- def initialize(exception:, import_state: nil, project_id: nil, error_source: nil)
+ def initialize(
+ exception:,
+ import_state: nil,
+ project_id: nil,
+ error_source: nil,
+ fail_import: false,
+ metrics: false
+ )
+
if import_state.blank? && project_id.blank?
raise ArgumentError, 'import_state OR project_id must be provided'
end
@@ -34,9 +44,11 @@ module Gitlab
@exception = exception
@error_source = error_source
+ @fail_import = fail_import
+ @metrics = metrics
end
- def execute(fail_import:, metrics:)
+ def execute
track_exception
persist_failure
@@ -46,7 +58,7 @@ module Gitlab
private
- attr_reader :exception, :import_state, :project, :error_source
+ attr_reader :exception, :import_state, :project, :error_source, :fail_import, :metrics
def track_exception
attributes = {
@@ -65,12 +77,15 @@ module Gitlab
Gitlab::ErrorTracking.track_exception(exception, attributes)
end
+ # Failures with `retry_count: 0` are considered "hard_failures" and those
+ # are exposed on the REST API projects/:id/import
def persist_failure
project.import_failures.create(
source: error_source,
exception_class: exception.class.to_s,
exception_message: exception.message.truncate(255),
- correlation_id_value: Labkit::Correlation::CorrelationId.current_or_new_id
+ correlation_id_value: Labkit::Correlation::CorrelationId.current_or_new_id,
+ retry_count: fail_import ? 0 : nil
)
end
diff --git a/lib/gitlab/import/set_async_jid.rb b/lib/gitlab/import/set_async_jid.rb
index 527d84477fe..054fcdb433f 100644
--- a/lib/gitlab/import/set_async_jid.rb
+++ b/lib/gitlab/import/set_async_jid.rb
@@ -13,7 +13,7 @@ module Gitlab
def self.set_jid(import_state)
jid = generate_jid(import_state)
- Gitlab::SidekiqStatus.set(jid, Gitlab::Import::StuckImportJob::IMPORT_JOBS_EXPIRATION)
+ Gitlab::SidekiqStatus.set(jid, Gitlab::Import::StuckImportJob::IMPORT_JOBS_EXPIRATION, value: 2)
import_state.update_column(:jid, jid)
end
diff --git a/lib/gitlab/import_export/group/relation_tree_restorer.rb b/lib/gitlab/import_export/group/relation_tree_restorer.rb
index f3c392b8c20..cbc8ee9e18b 100644
--- a/lib/gitlab/import_export/group/relation_tree_restorer.rb
+++ b/lib/gitlab/import_export/group/relation_tree_restorer.rb
@@ -106,12 +106,7 @@ module Gitlab
def update_params!
params = @importable_attributes.except(*relations.keys.map(&:to_s))
params = params.merge(present_override_params)
-
- # Cleaning all imported and overridden params
- params = Gitlab::ImportExport::AttributeCleaner.clean(
- relation_hash: params,
- relation_class: importable_class,
- excluded_keys: excluded_keys_for_relation(importable_class_sym))
+ params = filter_attributes(params)
@importable.assign_attributes(params)
@@ -122,6 +117,25 @@ module Gitlab
end
end
+ def filter_attributes(params)
+ if use_attributes_permitter? && attributes_permitter.permitted_attributes_defined?(importable_class_sym)
+ attributes_permitter.permit(importable_class_sym, params)
+ else
+ Gitlab::ImportExport::AttributeCleaner.clean(
+ relation_hash: params,
+ relation_class: importable_class,
+ excluded_keys: excluded_keys_for_relation(importable_class_sym))
+ end
+ end
+
+ def attributes_permitter
+ @attributes_permitter ||= Gitlab::ImportExport::AttributesPermitter.new
+ end
+
+ def use_attributes_permitter?
+ Feature.enabled?(:permitted_attributes_for_import_export, default_enabled: :yaml)
+ end
+
def present_override_params
# we filter out the empty strings from the overrides
# keeping the default values configured
diff --git a/lib/gitlab/import_export/json/streaming_serializer.rb b/lib/gitlab/import_export/json/streaming_serializer.rb
index 9ab8fa68d0e..fb8d6e7d89b 100644
--- a/lib/gitlab/import_export/json/streaming_serializer.rb
+++ b/lib/gitlab/import_export/json/streaming_serializer.rb
@@ -40,6 +40,13 @@ module Gitlab
end
end
+ def serialize_root(exportable_path = @exportable_path)
+ attributes = exportable.as_json(
+ relations_schema.merge(include: nil, preloads: nil))
+
+ json_writer.write_attributes(exportable_path, attributes)
+ end
+
def serialize_relation(definition)
raise ArgumentError, 'definition needs to be Hash' unless definition.is_a?(Hash)
raise ArgumentError, 'definition needs to have exactly one Hash element' unless definition.one?
@@ -60,12 +67,6 @@ module Gitlab
attr_reader :json_writer, :relations_schema, :exportable
- def serialize_root
- attributes = exportable.as_json(
- relations_schema.merge(include: nil, preloads: nil))
- json_writer.write_attributes(@exportable_path, attributes)
- end
-
def serialize_many_relations(key, records, options)
enumerator = Enumerator.new do |items|
key_preloads = preloads&.dig(key)
diff --git a/lib/gitlab/import_export/project/import_export.yml b/lib/gitlab/import_export/project/import_export.yml
index d815dd284ba..ef146359da9 100644
--- a/lib/gitlab/import_export/project/import_export.yml
+++ b/lib/gitlab/import_export/project/import_export.yml
@@ -126,7 +126,6 @@ included_attributes:
- :project_id
project_badges:
- :created_at
- - :group_id
- :image_url
- :link_url
- :name
@@ -414,7 +413,6 @@ included_attributes:
- :b_mode
- :too_large
- :binary
- - :diff
metrics:
- :created_at
- :updated_at
@@ -572,7 +570,6 @@ included_attributes:
- :updated_at
actions:
- :event
- - :image_v432x230
design: &design_definition
- :iid
- :project_id
@@ -632,6 +629,74 @@ included_attributes:
- :expires_at
- :ldap
- :override
+ project:
+ - :approvals_before_merge
+ - :archived
+ - :auto_cancel_pending_pipelines
+ - :autoclose_referenced_issues
+ - :build_allow_git_fetch
+ - :build_coverage_regex
+ - :build_timeout
+ - :ci_config_path
+ - :delete_error
+ - :description
+ - :disable_overriding_approvers_per_merge_request
+ - :external_authorization_classification_label
+ - :has_external_issue_tracker
+ - :has_external_wiki
+ - :issues_template
+ - :jobs_cache_index
+ - :last_repository_check_failed
+ - :merge_requests_author_approval
+ - :merge_requests_disable_committers_approval
+ - :merge_requests_ff_only_enabled
+ - :merge_requests_rebase_enabled
+ - :merge_requests_template
+ - :only_allow_merge_if_all_discussions_are_resolved
+ - :only_allow_merge_if_pipeline_succeeds
+ - :pages_https_only
+ - :pending_delete
+ - :printing_merge_request_link_enabled
+ - :public_builds
+ - :remove_source_branch_after_merge
+ - :request_access_enabled
+ - :require_password_to_approve
+ - :reset_approvals_on_push
+ - :resolve_outdated_diff_discussions
+ - :service_desk_enabled
+ - :shared_runners_enabled
+ - :suggestion_commit_message
+ - :visibility_level
+ - :hooks
+ - :issues_access_level
+ - :forking_access_level
+ - :merge_requests_access_level
+ - :wiki_access_level
+ - :snippets_access_level
+ - :builds_access_level
+ - :repository_access_level
+ - :pages_access_level
+ - :metrics_dashboard_access_level
+ - :analytics_access_level
+ - :operations_access_level
+ - :security_and_compliance_access_level
+ - :container_registry_access_level
+ - :allow_merge_on_skipped_pipeline
+ - :auto_devops_deploy_strategy
+ - :auto_devops_enabled
+ - :container_registry_enabled
+ - :issues_enabled
+ - :jobs_enabled
+ - :merge_method
+ - :merge_requests_enabled
+ - :snippets_enabled
+ - :squash_option
+ - :topics
+ - :visibility
+ - :wiki_enabled
+ - :build_git_strategy
+ - :build_enabled
+ - :security_and_compliance_enabled
# Do not include the following attributes for the models specified.
excluded_attributes:
@@ -747,6 +812,7 @@ excluded_attributes:
- :service_desk_reply_to
- :upvotes_count
- :work_item_type_id
+ - :email_message_id
merge_request: &merge_request_excluded_definition
- :milestone_id
- :sprint_id
@@ -889,8 +955,6 @@ excluded_attributes:
system_note_metadata:
- :description_version_id
- :note_id
- pipeline_schedules:
- - :active
methods:
notes:
- :type
@@ -1021,3 +1085,6 @@ ee:
- :auto_fix_dast
- :auto_fix_dependency_scanning
- :auto_fix_sast
+ project:
+ - :requirements_enabled
+ - :requirements_access_level
diff --git a/lib/gitlab/import_export/project/relation_factory.rb b/lib/gitlab/import_export/project/relation_factory.rb
index d84db92fe69..c391f86b47b 100644
--- a/lib/gitlab/import_export/project/relation_factory.rb
+++ b/lib/gitlab/import_export/project/relation_factory.rb
@@ -131,7 +131,9 @@ module Gitlab
end
def setup_diff
- @relation_hash['diff'] = @relation_hash.delete('utf8_diff')
+ diff = @relation_hash.delete('utf8_diff')
+
+ parsed_relation_hash['diff'] = diff
end
def setup_pipeline
diff --git a/lib/gitlab/lograge/custom_options.rb b/lib/gitlab/lograge/custom_options.rb
index e6c9ba0773c..84ead5119d5 100644
--- a/lib/gitlab/lograge/custom_options.rb
+++ b/lib/gitlab/lograge/custom_options.rb
@@ -32,6 +32,10 @@ module Gitlab
::Gitlab::ExceptionLogFormatter.format!(exception, payload)
+ if Feature.enabled?(:feature_flag_state_logs, type: :ops)
+ payload[:feature_flag_states] = Feature.logged_states.map { |key, state| "#{key}:#{state ? 1 : 0}" }
+ end
+
payload
end
end
diff --git a/lib/gitlab/merge_requests/merge_commit_message.rb b/lib/gitlab/merge_requests/commit_message_generator.rb
index 2a6a7859b33..0e9ec6f5cb3 100644
--- a/lib/gitlab/merge_requests/merge_commit_message.rb
+++ b/lib/gitlab/merge_requests/commit_message_generator.rb
@@ -1,31 +1,21 @@
# frozen_string_literal: true
module Gitlab
module MergeRequests
- class MergeCommitMessage
+ class CommitMessageGenerator
def initialize(merge_request:)
@merge_request = merge_request
end
- def message
+ def merge_message
return unless @merge_request.target_project.merge_commit_template.present?
- message = @merge_request.target_project.merge_commit_template
- message = message.delete("\r")
+ replace_placeholders(@merge_request.target_project.merge_commit_template)
+ end
- # Remove placeholders that correspond to empty values and are the last word in the line
- # along with all whitespace characters preceding them.
- # This allows us to recreate previous default merge commit message behaviour - we skipped new line character
- # before empty description and before closed issues when none were present.
- PLACEHOLDERS.each do |key, value|
- unless value.call(merge_request).present?
- message = message.gsub(BLANK_PLACEHOLDERS_REGEXES[key], '')
- end
- end
+ def squash_message
+ return unless @merge_request.target_project.squash_commit_template.present?
- Gitlab::StringPlaceholderReplacer
- .replace_string_placeholders(message, PLACEHOLDERS_REGEX) do |key|
- PLACEHOLDERS[key].call(merge_request)
- end
+ replace_placeholders(@merge_request.target_project.squash_commit_template)
end
private
@@ -45,7 +35,9 @@ module Gitlab
"Closes #{closes_issues_references.to_sentence}"
end,
'description' => ->(merge_request) { merge_request.description.presence || '' },
- 'reference' => ->(merge_request) { merge_request.to_reference(full: true) }
+ 'reference' => ->(merge_request) { merge_request.to_reference(full: true) },
+ 'first_commit' => -> (merge_request) { merge_request.first_commit&.safe_message&.strip.presence || '' },
+ 'first_multiline_commit' => -> (merge_request) { merge_request.first_multiline_commit&.safe_message&.strip.presence || merge_request.title }
}.freeze
PLACEHOLDERS_REGEX = Regexp.union(PLACEHOLDERS.keys.map do |key|
@@ -55,6 +47,26 @@ module Gitlab
BLANK_PLACEHOLDERS_REGEXES = (PLACEHOLDERS.map do |key, value|
[key, Regexp.new("[\n\r]+%{#{Regexp.escape(key)}}$")]
end).to_h.freeze
+
+ def replace_placeholders(message)
+ # convert CRLF to LF
+ message = message.delete("\r")
+
+ # Remove placeholders that correspond to empty values and are the last word in the line
+ # along with all whitespace characters preceding them.
+ # This allows us to recreate previous default merge commit message behaviour - we skipped new line character
+ # before empty description and before closed issues when none were present.
+ PLACEHOLDERS.each do |key, value|
+ unless value.call(merge_request).present?
+ message = message.gsub(BLANK_PLACEHOLDERS_REGEXES[key], '')
+ end
+ end
+
+ Gitlab::StringPlaceholderReplacer
+ .replace_string_placeholders(message, PLACEHOLDERS_REGEX) do |key|
+ PLACEHOLDERS[key].call(merge_request)
+ end
+ end
end
end
end
diff --git a/lib/gitlab/merge_requests/mergeability/redis_interface.rb b/lib/gitlab/merge_requests/mergeability/redis_interface.rb
index 081ccfca360..b0e739f91ff 100644
--- a/lib/gitlab/merge_requests/mergeability/redis_interface.rb
+++ b/lib/gitlab/merge_requests/mergeability/redis_interface.rb
@@ -7,13 +7,13 @@ module Gitlab
VERSION = 1
def save_check(merge_check:, result_hash:)
- Gitlab::Redis::SharedState.with do |redis|
+ Gitlab::Redis::Cache.with do |redis|
redis.set(merge_check.cache_key + ":#{VERSION}", result_hash.to_json, ex: EXPIRATION)
end
end
def retrieve_check(merge_check:)
- Gitlab::Redis::SharedState.with do |redis|
+ Gitlab::Redis::Cache.with do |redis|
Gitlab::Json.parse(redis.get(merge_check.cache_key + ":#{VERSION}"))
end
end
diff --git a/lib/gitlab/metrics/exporter/base_exporter.rb b/lib/gitlab/metrics/exporter/base_exporter.rb
index ff8b8bf2237..47c862c0232 100644
--- a/lib/gitlab/metrics/exporter/base_exporter.rb
+++ b/lib/gitlab/metrics/exporter/base_exporter.rb
@@ -11,12 +11,14 @@ module Gitlab
attr_accessor :readiness_checks
- def enabled?
- settings.enabled
+ def initialize(settings, **options)
+ super(**options)
+
+ @settings = settings
end
- def settings
- raise NotImplementedError
+ def enabled?
+ settings.enabled
end
def log_filename
@@ -25,6 +27,8 @@ module Gitlab
private
+ attr_reader :settings
+
def start_working
logger = WEBrick::Log.new(log_filename)
logger.time_format = "[%Y-%m-%dT%H:%M:%S.%L%z]"
diff --git a/lib/gitlab/metrics/exporter/sidekiq_exporter.rb b/lib/gitlab/metrics/exporter/sidekiq_exporter.rb
index 4d38d9e67bf..eea71fda6a0 100644
--- a/lib/gitlab/metrics/exporter/sidekiq_exporter.rb
+++ b/lib/gitlab/metrics/exporter/sidekiq_exporter.rb
@@ -4,10 +4,6 @@ module Gitlab
module Metrics
module Exporter
class SidekiqExporter < BaseExporter
- def settings
- Settings.monitoring.sidekiq_exporter
- end
-
def log_filename
if settings['log_enabled']
File.join(Rails.root, 'log', 'sidekiq_exporter.log')
@@ -15,29 +11,6 @@ module Gitlab
File::NULL
end
end
-
- private
-
- # Sidekiq Exporter does not work properly in sidekiq-cluster
- # mode. It tries to start the service on the same port for
- # each of the cluster workers, this results in failure
- # due to duplicate binding.
- #
- # For now we ignore this error, as metrics are still "kind of"
- # valid as they are rendered from shared directory.
- #
- # Issue: https://gitlab.com/gitlab-org/gitlab/issues/5714
- def start_working
- super
- rescue Errno::EADDRINUSE => e
- Sidekiq.logger.error(
- class: self.class.to_s,
- message: 'Cannot start sidekiq_exporter',
- 'exception.message' => e.message
- )
-
- false
- end
end
end
end
diff --git a/lib/gitlab/metrics/exporter/web_exporter.rb b/lib/gitlab/metrics/exporter/web_exporter.rb
index c5fa1e545d7..d41484aaaa7 100644
--- a/lib/gitlab/metrics/exporter/web_exporter.rb
+++ b/lib/gitlab/metrics/exporter/web_exporter.rb
@@ -27,7 +27,7 @@ module Gitlab
# This exporter is always run on master process
def initialize
- super
+ super(Settings.monitoring.web_exporter)
# DEPRECATED:
# these `readiness_checks` are deprecated
@@ -39,10 +39,6 @@ module Gitlab
]
end
- def settings
- Gitlab.config.monitoring.web_exporter
- end
-
def log_filename
File.join(Rails.root, 'log', 'web_exporter.log')
end
diff --git a/lib/gitlab/metrics/samplers/database_sampler.rb b/lib/gitlab/metrics/samplers/database_sampler.rb
index 5d7f434b660..965d85e20e5 100644
--- a/lib/gitlab/metrics/samplers/database_sampler.rb
+++ b/lib/gitlab/metrics/samplers/database_sampler.rb
@@ -38,16 +38,42 @@ module Gitlab
end
def host_stats
- return [] unless ActiveRecord::Base.connected?
+ connection_class_stats + replica_host_stats
+ end
+
+ def connection_class_stats
+ Gitlab::Database.database_base_models.each_value.with_object([]) do |base_model, stats|
+ next unless base_model.connected?
+
+ stats << { labels: labels_for_class(base_model), stats: base_model.connection_pool.stat }
+ end
+ end
+
+ def replica_host_stats
+ Gitlab::Database::LoadBalancing.each_load_balancer.with_object([]) do |load_balancer, stats|
+ next if load_balancer.primary_only?
- [{ labels: labels_for_class(ActiveRecord::Base), stats: ActiveRecord::Base.connection_pool.stat }]
+ load_balancer.host_list.hosts.each do |host|
+ stats << { labels: labels_for_replica_host(load_balancer, host), stats: host.connection.pool.stat }
+ end
+ end
end
def labels_for_class(klass)
{
host: klass.connection_db_config.host,
port: klass.connection_db_config.configuration_hash[:port],
- class: klass.to_s
+ class: klass.to_s,
+ db_config_name: klass.connection_db_config.name
+ }
+ end
+
+ def labels_for_replica_host(load_balancer, host)
+ {
+ host: host.host,
+ port: host.port,
+ class: load_balancer.configuration.primary_connection_specification_name,
+ db_config_name: Gitlab::Database.db_config_name(host.connection)
}
end
end
diff --git a/lib/gitlab/metrics/subscribers/active_record.rb b/lib/gitlab/metrics/subscribers/active_record.rb
index df0582149a9..715dd86d93c 100644
--- a/lib/gitlab/metrics/subscribers/active_record.rb
+++ b/lib/gitlab/metrics/subscribers/active_record.rb
@@ -5,6 +5,8 @@ module Gitlab
module Subscribers
# Class for tracking the total query duration of a transaction.
class ActiveRecord < ActiveSupport::Subscriber
+ extend Gitlab::Utils::StrongMemoize
+
attach_to :active_record
IGNORABLE_SQL = %w{BEGIN COMMIT}.freeze
@@ -107,7 +109,7 @@ module Gitlab
# Per database metrics
db_config_name = db_config_name(event.payload)
- duration_key = compose_metric_key(:duration_s, db_role, db_config_name)
+ duration_key = compose_metric_key(:duration_s, nil, db_config_name)
::Gitlab::SafeRequestStore[duration_key] = (::Gitlab::SafeRequestStore[duration_key].presence || 0) + duration
end
@@ -144,7 +146,7 @@ module Gitlab
# when we are also logging the db_role. Otherwise it will be hard to
# tell if the log key is referring to a db_role OR a db_config_name.
if db_role.present? && db_config_name.present?
- log_key = compose_metric_key(counter, db_role, db_config_name)
+ log_key = compose_metric_key(counter, nil, db_config_name)
Gitlab::SafeRequestStore[log_key] = Gitlab::SafeRequestStore[log_key].to_i + 1
end
end
@@ -172,26 +174,34 @@ module Gitlab
end
def self.load_balancing_metric_counter_keys
- load_balancing_metric_keys(DB_LOAD_BALANCING_COUNTERS)
+ strong_memoize(:load_balancing_metric_counter_keys) do
+ load_balancing_metric_keys(DB_LOAD_BALANCING_COUNTERS)
+ end
end
def self.load_balancing_metric_duration_keys
- load_balancing_metric_keys(DB_LOAD_BALANCING_DURATIONS)
+ strong_memoize(:load_balancing_metric_duration_keys) do
+ load_balancing_metric_keys(DB_LOAD_BALANCING_DURATIONS)
+ end
end
def self.load_balancing_metric_keys(metrics)
- [].tap do |counters|
+ counters = []
+
+ metrics.each do |metric|
DB_LOAD_BALANCING_ROLES.each do |role|
- metrics.each do |metric|
- counters << compose_metric_key(metric, role)
- next unless ENV['GITLAB_MULTIPLE_DATABASE_METRICS']
+ counters << compose_metric_key(metric, role)
+ end
- ::Gitlab::Database.db_config_names.each do |config_name|
- counters << compose_metric_key(metric, role, config_name)
- end
+ if ENV['GITLAB_MULTIPLE_DATABASE_METRICS']
+ ::Gitlab::Database.db_config_names.each do |config_name|
+ counters << compose_metric_key(metric, nil, config_name) # main
+ counters << compose_metric_key(metric, nil, config_name + ::Gitlab::Database::LoadBalancing::LoadBalancer::REPLICA_SUFFIX) # main_replica
end
end
end
+
+ counters
end
def compose_metric_key(metric, db_role = nil, db_config_name = nil)
diff --git a/lib/gitlab/pagination/keyset/iterator.rb b/lib/gitlab/pagination/keyset/iterator.rb
index bcd17fd0d34..33587f9d6f4 100644
--- a/lib/gitlab/pagination/keyset/iterator.rb
+++ b/lib/gitlab/pagination/keyset/iterator.rb
@@ -9,7 +9,7 @@ module Gitlab
raise(UnsupportedScopeOrder) unless success
@cursor = cursor
- @order = Gitlab::Pagination::Keyset::Order.extract_keyset_order_object(scope)
+ @order = Gitlab::Pagination::Keyset::Order.extract_keyset_order_object(@scope)
@use_union_optimization = in_operator_optimization_options ? false : use_union_optimization
@in_operator_optimization_options = in_operator_optimization_options
end
diff --git a/lib/gitlab/pagination/keyset/order.rb b/lib/gitlab/pagination/keyset/order.rb
index 80726fc8efd..1a00692bdbe 100644
--- a/lib/gitlab/pagination/keyset/order.rb
+++ b/lib/gitlab/pagination/keyset/order.rb
@@ -155,7 +155,7 @@ module Gitlab
def apply_cursor_conditions(scope, values = {}, options = { use_union_optimization: false, in_operator_optimization_options: nil })
values ||= {}
transformed_values = values.with_indifferent_access
- scope = apply_custom_projections(scope.dup)
+ scope = apply_custom_projections(scope)
where_values = build_where_values(transformed_values)
diff --git a/lib/gitlab/pagination/offset_pagination.rb b/lib/gitlab/pagination/offset_pagination.rb
index 7b5013f137b..4f8a6ffb2cc 100644
--- a/lib/gitlab/pagination/offset_pagination.rb
+++ b/lib/gitlab/pagination/offset_pagination.rb
@@ -29,7 +29,7 @@ module Gitlab
return pagination_data unless Feature.enabled?(:api_kaminari_count_with_limit, type: :ops)
limited_total_count = pagination_data.total_count_with_limit
- if limited_total_count > max_limit
+ if limited_total_count > Kaminari::ActiveRecordRelationMethods::MAX_COUNT_LIMIT
# The call to `total_count_with_limit` memoizes `@arel` because of a call to `references_eager_loaded_tables?`
# We need to call `reset` because `without_count` relies on `@arel` being unmemoized
pagination_data.reset.without_count
@@ -38,14 +38,6 @@ module Gitlab
end
end
- def max_limit
- if Feature.enabled?(:lower_relation_max_count_limit, type: :ops)
- Kaminari::ActiveRecordRelationMethods::MAX_COUNT_NEW_LOWER_LIMIT
- else
- Kaminari::ActiveRecordRelationMethods::MAX_COUNT_LIMIT
- end
- end
-
def needs_pagination?(relation)
return true unless relation.respond_to?(:current_page)
return true if params[:page].present? && relation.current_page != params[:page].to_i
diff --git a/lib/gitlab/patch/legacy_database_config.rb b/lib/gitlab/patch/legacy_database_config.rb
index a7d4fdf7490..6040f737c75 100644
--- a/lib/gitlab/patch/legacy_database_config.rb
+++ b/lib/gitlab/patch/legacy_database_config.rb
@@ -35,6 +35,40 @@ module Gitlab
attr_reader :uses_legacy_database_config
end
+ def load_database_yaml
+ return super unless Gitlab.ee?
+
+ super.deep_merge(load_geo_database_yaml)
+ end
+
+ # This method is taken from Rails to load a database YAML file without
+ # evaluating ERB. This allows us to create the rake tasks for the Geo
+ # tracking database without filling in the configuration values or
+ # loading the environment. To be removed when we start configure Geo
+ # tracking database in database.yml instead of custom database_geo.yml
+ #
+ # https://github.com/rails/rails/blob/v6.1.4/railties/lib/rails/application/configuration.rb#L255
+ def load_geo_database_yaml
+ path = Rails.root.join("config/database_geo.yml")
+ return {} unless File.exist?(path)
+
+ require "rails/application/dummy_erb_compiler"
+
+ yaml = DummyERB.new(Pathname.new(path).read).result
+ config = YAML.load(yaml) || {} # rubocop:disable Security/YAMLLoad
+
+ config.to_h do |env, configs|
+ # This check is taken from Rails where the transformation
+ # of a flat database.yml is done into `primary:`
+ # https://github.com/rails/rails/blob/v6.1.4/activerecord/lib/active_record/database_configurations.rb#L169
+ if configs.is_a?(Hash) && !configs.all? { |_, v| v.is_a?(Hash) }
+ configs = { "geo" => configs }
+ end
+
+ [env, configs]
+ end
+ end
+
def database_configuration
@uses_legacy_database_config = false # rubocop:disable Gitlab/ModuleWithInstanceVariables
@@ -48,6 +82,16 @@ module Gitlab
@uses_legacy_database_config = true # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
+ if Gitlab.ee? && File.exist?(Rails.root.join("config/database_geo.yml"))
+ migrations_paths = ["ee/db/geo/migrate"]
+ migrations_paths << "ee/db/geo/post_migrate" unless ENV['SKIP_POST_DEPLOYMENT_MIGRATIONS']
+
+ configs["geo"] =
+ Rails.application.config_for(:database_geo)
+ .merge(migrations_paths: migrations_paths, schema_migrations_path: "ee/db/geo/schema_migrations")
+ .stringify_keys
+ end
+
[env, configs]
end
end
diff --git a/lib/gitlab/patch/sidekiq_client.rb b/lib/gitlab/patch/sidekiq_client.rb
deleted file mode 100644
index 2de13560cce..00000000000
--- a/lib/gitlab/patch/sidekiq_client.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Patch
- module SidekiqClient
- private
-
- # This is a copy of https://github.com/mperham/sidekiq/blob/v6.2.2/lib/sidekiq/client.rb#L187-L194
- # but using `conn.pipelined` instead of `conn.multi`. The multi call isn't needed here because in
- # the case of scheduled jobs, only one Redis call is made. For other jobs, we don't really need
- # the commands to be atomic.
- def raw_push(payloads)
- @redis_pool.with do |conn| # rubocop:disable Gitlab/ModuleWithInstanceVariables
- conn.pipelined do
- atomic_push(conn, payloads)
- end
- end
- true
- end
- end
- end
-end
diff --git a/lib/gitlab/patch/sidekiq_cron_poller.rb b/lib/gitlab/patch/sidekiq_poller.rb
index 56ca24c68f5..d4264cec1ab 100644
--- a/lib/gitlab/patch/sidekiq_cron_poller.rb
+++ b/lib/gitlab/patch/sidekiq_poller.rb
@@ -2,7 +2,7 @@
module Gitlab
module Patch
- module SidekiqCronPoller
+ module SidekiqPoller
def enqueue
Rails.application.reloader.wrap do
::Gitlab::WithRequestStore.with_request_store do
diff --git a/lib/gitlab/process_management.rb b/lib/gitlab/process_management.rb
new file mode 100644
index 00000000000..25a198e4a6a
--- /dev/null
+++ b/lib/gitlab/process_management.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module ProcessManagement
+ # Traps the given signals and yields the block whenever these signals are
+ # received.
+ #
+ # The block is passed the name of the signal.
+ #
+ # Example:
+ #
+ # trap_signals(%i(HUP TERM)) do |signal|
+ # ...
+ # end
+ def self.trap_signals(signals)
+ signals.each do |signal|
+ trap(signal) do
+ yield signal
+ end
+ end
+ end
+
+ # Traps the given signals with the given command.
+ #
+ # Example:
+ #
+ # modify_signals(%i(HUP TERM), 'DEFAULT')
+ def self.modify_signals(signals, command)
+ signals.each { |signal| trap(signal, command) }
+ end
+
+ def self.signal(pid, signal)
+ Process.kill(signal, pid)
+ true
+ rescue Errno::ESRCH
+ false
+ end
+
+ def self.signal_processes(pids, signal)
+ pids.each { |pid| signal(pid, signal) }
+ end
+
+ # Waits for the given process to complete using a separate thread.
+ def self.wait_async(pid)
+ Thread.new do
+ Process.wait(pid) rescue Errno::ECHILD
+ end
+ end
+
+ # Returns true if all the processes are alive.
+ def self.all_alive?(pids)
+ pids.each do |pid|
+ return false unless process_alive?(pid)
+ end
+
+ true
+ end
+
+ def self.any_alive?(pids)
+ pids_alive(pids).any?
+ end
+
+ def self.pids_alive(pids)
+ pids.select { |pid| process_alive?(pid) }
+ end
+
+ def self.process_alive?(pid)
+ return false if pid.nil?
+
+ # Signal 0 tests whether the process exists and we have access to send signals
+ # but is otherwise a noop (doesn't actually send a signal to the process)
+ signal(pid, 0)
+ end
+
+ def self.process_died?(pid)
+ !process_alive?(pid)
+ end
+
+ def self.write_pid(path)
+ File.open(path, 'w') do |handle|
+ handle.write(Process.pid.to_s)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/quick_actions/issue_actions.rb b/lib/gitlab/quick_actions/issue_actions.rb
index a55ead519e2..5fd422db722 100644
--- a/lib/gitlab/quick_actions/issue_actions.rb
+++ b/lib/gitlab/quick_actions/issue_actions.rb
@@ -206,7 +206,7 @@ module Gitlab
end
desc _('Add Zoom meeting')
- explanation _('Adds a Zoom meeting')
+ explanation _('Adds a Zoom meeting.')
params '<Zoom URL>'
types Issue
condition do
@@ -223,7 +223,7 @@ module Gitlab
end
desc _('Remove Zoom meeting')
- explanation _('Remove Zoom meeting')
+ explanation _('Remove Zoom meeting.')
execution_message _('Zoom meeting removed')
types Issue
condition do
@@ -236,7 +236,7 @@ module Gitlab
end
desc _('Add email participant(s)')
- explanation _('Adds email participant(s)')
+ explanation _('Adds email participant(s).')
params 'email1@example.com email2@example.com (up to 6 emails)'
types Issue
condition do
@@ -285,6 +285,34 @@ module Gitlab
end
end
+ desc _('Add customer relation contacts')
+ explanation _('Add customer relation contact(s).')
+ params 'contact@example.com person@example.org'
+ types Issue
+ condition do
+ current_user.can?(:set_issue_crm_contacts, quick_action_target)
+ end
+ execution_message do
+ _('One or more contacts were successfully added.')
+ end
+ command :add_contacts do |contact_emails|
+ @updates[:add_contacts] = contact_emails.split(' ')
+ end
+
+ desc _('Remove customer relation contacts')
+ explanation _('Remove customer relation contact(s).')
+ params 'contact@example.com person@example.org'
+ types Issue
+ condition do
+ current_user.can?(:set_issue_crm_contacts, quick_action_target)
+ end
+ execution_message do
+ _('One or more contacts were successfully removed.')
+ end
+ command :remove_contacts do |contact_emails|
+ @updates[:remove_contacts] = contact_emails.split(' ')
+ end
+
private
def zoom_link_service
diff --git a/lib/gitlab/rack_attack/request.rb b/lib/gitlab/rack_attack/request.rb
index dbc77c9f9d7..94ae29af3d0 100644
--- a/lib/gitlab/rack_attack/request.rb
+++ b/lib/gitlab/rack_attack/request.rb
@@ -139,14 +139,12 @@ module Gitlab
def throttle_unauthenticated_files_api?
files_api_path? &&
- Feature.enabled?(:files_api_throttling, default_enabled: :yaml) &&
Gitlab::Throttle.settings.throttle_unauthenticated_files_api_enabled &&
unauthenticated?
end
def throttle_authenticated_files_api?
files_api_path? &&
- Feature.enabled?(:files_api_throttling, default_enabled: :yaml) &&
Gitlab::Throttle.settings.throttle_authenticated_files_api_enabled
end
diff --git a/lib/gitlab/rate_limit_helpers.rb b/lib/gitlab/rate_limit_helpers.rb
deleted file mode 100644
index 653410a40a5..00000000000
--- a/lib/gitlab/rate_limit_helpers.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module RateLimitHelpers
- ARCHIVE_RATE_LIMIT_REACHED_MESSAGE = 'This archive has been requested too many times. Try again later.'
- ARCHIVE_RATE_ANONYMOUS_THRESHOLD = 100 # Allow 100 requests/min for anonymous users
- ARCHIVE_RATE_THROTTLE_KEY = :project_repositories_archive
-
- def archive_rate_limit_reached?(user, project)
- return false unless Feature.enabled?(:archive_rate_limit)
-
- key = ARCHIVE_RATE_THROTTLE_KEY
-
- if rate_limiter.throttled?(key, scope: [project, user], threshold: archive_rate_threshold_by_user(user))
- rate_limiter.log_request(request, "#{key}_request_limit".to_sym, user)
-
- return true
- end
-
- false
- end
-
- def archive_rate_threshold_by_user(user)
- if user
- nil # Use the defaults
- else
- ARCHIVE_RATE_ANONYMOUS_THRESHOLD
- end
- end
-
- def rate_limiter
- ::Gitlab::ApplicationRateLimiter
- end
- end
-end
diff --git a/lib/gitlab/redis/multi_store.rb b/lib/gitlab/redis/multi_store.rb
index f930a0040bc..500b62bf0e8 100644
--- a/lib/gitlab/redis/multi_store.rb
+++ b/lib/gitlab/redis/multi_store.rb
@@ -21,6 +21,8 @@ module Gitlab
FAILED_TO_READ_ERROR_MESSAGE = 'Failed to read from the redis primary_store.'
FAILED_TO_WRITE_ERROR_MESSAGE = 'Failed to write to the redis primary_store.'
+ SKIP_LOG_METHOD_MISSING_FOR_COMMANDS = %i(info).freeze
+
READ_COMMANDS = %i(
get
mget
@@ -39,41 +41,42 @@ module Gitlab
flushdb
).freeze
- def initialize(primary_store, secondary_store, instance_name = nil)
+ def initialize(primary_store, secondary_store, instance_name)
@primary_store = primary_store
@secondary_store = secondary_store
@instance_name = instance_name
validate_stores!
end
-
+ # rubocop:disable GitlabSecurity/PublicSend
READ_COMMANDS.each do |name|
define_method(name) do |*args, &block|
- if multi_store_enabled?
+ if use_primary_and_secondary_stores?
read_command(name, *args, &block)
else
- secondary_store.send(name, *args, &block) # rubocop:disable GitlabSecurity/PublicSend
+ default_store.send(name, *args, &block)
end
end
end
WRITE_COMMANDS.each do |name|
define_method(name) do |*args, &block|
- if multi_store_enabled?
+ if use_primary_and_secondary_stores?
write_command(name, *args, &block)
else
- secondary_store.send(name, *args, &block) # rubocop:disable GitlabSecurity/PublicSend
+ default_store.send(name, *args, &block)
end
end
end
def method_missing(...)
- return @instance.send(...) if @instance # rubocop:disable GitlabSecurity/PublicSend
+ return @instance.send(...) if @instance
log_method_missing(...)
- secondary_store.send(...) # rubocop:disable GitlabSecurity/PublicSend
+ default_store.send(...)
end
+ # rubocop:enable GitlabSecurity/PublicSend
def respond_to_missing?(command_name, include_private = false)
true
@@ -83,23 +86,37 @@ module Gitlab
# https://github.com/redis-store/redis-rack/blob/a833086ba494083b6a384a1a4e58b36573a9165d/lib/redis/rack/connection.rb#L15
# Done similarly in https://github.com/lsegal/yard/blob/main/lib/yard/templates/template.rb#L122
def is_a?(klass)
- return true if klass == secondary_store.class
+ return true if klass == default_store.class
super(klass)
end
alias_method :kind_of?, :is_a?
def to_s
- if multi_store_enabled?
- primary_store.to_s
- else
- secondary_store.to_s
- end
+ use_primary_and_secondary_stores? ? primary_store.to_s : default_store.to_s
+ end
+
+ def use_primary_and_secondary_stores?
+ feature_flags_available? &&
+ Feature.enabled?("use_primary_and_secondary_stores_for_#{instance_name.underscore}", default_enabled: :yaml) &&
+ !same_redis_store?
+ end
+
+ def use_primary_store_as_default?
+ feature_flags_available? &&
+ Feature.enabled?("use_primary_store_as_default_for_#{instance_name.underscore}", default_enabled: :yaml) &&
+ !same_redis_store?
end
private
+ def default_store
+ use_primary_store_as_default? ? primary_store : secondary_store
+ end
+
def log_method_missing(command_name, *_args)
+ return if SKIP_LOG_METHOD_MISSING_FOR_COMMANDS.include?(command_name)
+
log_error(MethodMissingError.new, command_name)
increment_method_missing_count(command_name)
end
@@ -155,10 +172,6 @@ module Gitlab
send_command(secondary_store, command_name, *args, &block)
end
- def multi_store_enabled?
- Feature.enabled?(:use_multi_store, default_enabled: :yaml) && !same_redis_store?
- end
-
def same_redis_store?
strong_memoize(:same_redis_store) do
# <Redis client v4.4.0 for redis:///path_to/redis/redis.socket/5>"
@@ -194,12 +207,13 @@ module Gitlab
def increment_method_missing_count(command_name)
@method_missing_counter ||= Gitlab::Metrics.counter(:gitlab_redis_multi_store_method_missing_total, 'Client side Redis MultiStore method missing')
- @method_missing_counter.increment(command: command_name, innamece_name: instance_name)
+ @method_missing_counter.increment(command: command_name, instance_name: instance_name)
end
def validate_stores!
raise ArgumentError, 'primary_store is required' unless primary_store
raise ArgumentError, 'secondary_store is required' unless secondary_store
+ raise ArgumentError, 'instance_name is required' unless instance_name
raise ArgumentError, 'invalid primary_store' unless primary_store.is_a?(::Redis)
raise ArgumentError, 'invalid secondary_store' unless secondary_store.is_a?(::Redis)
end
diff --git a/lib/gitlab/redis/sessions.rb b/lib/gitlab/redis/sessions.rb
index 3bf1eb6211d..c547828d907 100644
--- a/lib/gitlab/redis/sessions.rb
+++ b/lib/gitlab/redis/sessions.rb
@@ -3,9 +3,45 @@
module Gitlab
module Redis
class Sessions < ::Gitlab::Redis::Wrapper
- # The data we store on Sessions used to be stored on SharedState.
- def self.config_fallback
- SharedState
+ SESSION_NAMESPACE = 'session:gitlab'
+ USER_SESSIONS_NAMESPACE = 'session:user:gitlab'
+ USER_SESSIONS_LOOKUP_NAMESPACE = 'session:lookup:user:gitlab'
+ IP_SESSIONS_LOOKUP_NAMESPACE = 'session:lookup:ip:gitlab2'
+ OTP_SESSIONS_NAMESPACE = 'session:otp'
+
+ class << self
+ # The data we store on Sessions used to be stored on SharedState.
+ def config_fallback
+ SharedState
+ end
+
+ private
+
+ def redis
+ # Don't use multistore if redis.sessions configuration is not provided
+ return super if config_fallback?
+
+ primary_store = ::Redis.new(params)
+ secondary_store = ::Redis.new(config_fallback.params)
+
+ MultiStore.new(primary_store, secondary_store, store_name)
+ end
+ end
+
+ def store(extras = {})
+ # Don't use multistore if redis.sessions configuration is not provided
+ return super if self.class.config_fallback?
+
+ primary_store = create_redis_store(redis_store_options, extras)
+ secondary_store = create_redis_store(self.class.config_fallback.params, extras)
+
+ MultiStore.new(primary_store, secondary_store, self.class.store_name)
+ end
+
+ private
+
+ def create_redis_store(options, extras)
+ ::Redis::Store.new(options.merge(extras))
end
end
end
diff --git a/lib/gitlab/redis/sessions_store_helper.rb b/lib/gitlab/redis/sessions_store_helper.rb
new file mode 100644
index 00000000000..c80442847f1
--- /dev/null
+++ b/lib/gitlab/redis/sessions_store_helper.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Redis
+ module SessionsStoreHelper
+ extend ActiveSupport::Concern
+
+ module StoreMethods
+ def redis_store_class
+ use_redis_session_store? ? Gitlab::Redis::Sessions : Gitlab::Redis::SharedState
+ end
+
+ private
+
+ def use_redis_session_store?
+ Gitlab::Utils.to_boolean(ENV['GITLAB_USE_REDIS_SESSIONS_STORE'], default: true)
+ end
+ end
+
+ include StoreMethods
+
+ included do
+ extend StoreMethods
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/redis/shared_state.rb b/lib/gitlab/redis/shared_state.rb
index 1250eabb041..fb3a143121b 100644
--- a/lib/gitlab/redis/shared_state.rb
+++ b/lib/gitlab/redis/shared_state.rb
@@ -3,10 +3,6 @@
module Gitlab
module Redis
class SharedState < ::Gitlab::Redis::Wrapper
- SESSION_NAMESPACE = 'session:gitlab'
- USER_SESSIONS_NAMESPACE = 'session:user:gitlab'
- USER_SESSIONS_LOOKUP_NAMESPACE = 'session:lookup:user:gitlab'
- IP_SESSIONS_LOOKUP_NAMESPACE = 'session:lookup:ip:gitlab2'
end
end
end
diff --git a/lib/gitlab/redis/wrapper.rb b/lib/gitlab/redis/wrapper.rb
index 985c8dc619c..75dbccb965d 100644
--- a/lib/gitlab/redis/wrapper.rb
+++ b/lib/gitlab/redis/wrapper.rb
@@ -28,7 +28,7 @@ module Gitlab
end
def pool
- @pool ||= ConnectionPool.new(size: pool_size) { ::Redis.new(params) }
+ @pool ||= ConnectionPool.new(size: pool_size) { redis }
end
def pool_size
@@ -67,6 +67,10 @@ module Gitlab
File.expand_path('../../..', __dir__)
end
+ def config_fallback?
+ config_file_name == config_fallback&.config_file_name
+ end
+
def config_file_name
[
# Instance specific config sources:
@@ -100,6 +104,12 @@ module Gitlab
"::Gitlab::Instrumentation::Redis::#{store_name}".constantize
end
+
+ private
+
+ def redis
+ ::Redis.new(params)
+ end
end
def initialize(rails_env = nil)
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index 904fc744c6b..8e139ae0709 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -16,8 +16,13 @@ module Gitlab
@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
- @conan_recipe_component_regex ||= %r{\A[a-zA-Z0-9_][a-zA-Z0-9_\+\.-]{1,49}\z}.freeze
+ # 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
@@ -211,6 +216,12 @@ module Gitlab
def generic_package_file_name_regex
generic_package_name_regex
end
+
+ private
+
+ def conan_name_regex
+ @conan_name_regex ||= %r{[a-zA-Z0-9_][a-zA-Z0-9_\+\.-]{1,49}}.freeze
+ end
end
extend self
@@ -413,7 +424,11 @@ module Gitlab
end
def issue
- @issue ||= /(?<issue>\d+\b)/
+ @issue ||= /(?<issue>\d+)(?<format>\+)?(?=\W|\z)/
+ end
+
+ def merge_request
+ @merge_request ||= /(?<merge_request>\d+)(?<format>\+)?/
end
def base64_regex
@@ -430,3 +445,5 @@ module Gitlab
end
end
end
+
+Gitlab::Regex.prepend_mod
diff --git a/lib/gitlab/relative_positioning/item_context.rb b/lib/gitlab/relative_positioning/item_context.rb
index 1e738aef9b0..98e52e8e767 100644
--- a/lib/gitlab/relative_positioning/item_context.rb
+++ b/lib/gitlab/relative_positioning/item_context.rb
@@ -66,19 +66,11 @@ module Gitlab
end
def lhs_neighbour
- scoped_items
- .where('relative_position < ?', relative_position)
- .reorder(relative_position: :desc)
- .first
- .then { |x| neighbour(x) }
+ neighbour(object.next_object_by_relative_position(ignoring: ignoring, order: :desc))
end
def rhs_neighbour
- scoped_items
- .where('relative_position > ?', relative_position)
- .reorder(relative_position: :asc)
- .first
- .then { |x| neighbour(x) }
+ neighbour(object.next_object_by_relative_position(ignoring: ignoring, order: :asc))
end
def neighbour(item)
@@ -87,12 +79,6 @@ module Gitlab
self.class.new(item, range, ignoring: ignoring)
end
- def scoped_items
- r = model_class.relative_positioning_query_base(object)
- r = object.exclude_self(r, excluded: ignoring) if ignoring.present?
- r
- end
-
def calculate_relative_position(calculation)
# When calculating across projects, this is much more efficient than
# MAX(relative_position) without the GROUP BY, due to index usage:
@@ -186,6 +172,10 @@ module Gitlab
Gap.new(gap.first, gap.second || default_end)
end
+ def scoped_items
+ object.relative_positioning_scoped_items(ignoring: ignoring)
+ end
+
def relative_position
object.relative_position
end
diff --git a/lib/gitlab/repository_archive_rate_limiter.rb b/lib/gitlab/repository_archive_rate_limiter.rb
new file mode 100644
index 00000000000..31a3dc34bf6
--- /dev/null
+++ b/lib/gitlab/repository_archive_rate_limiter.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module RepositoryArchiveRateLimiter
+ def check_archive_rate_limit!(current_user, project, &block)
+ return unless Feature.enabled?(:archive_rate_limit)
+
+ threshold = current_user ? nil : 100
+
+ check_rate_limit!(:project_repositories_archive, scope: [project, current_user], threshold: threshold, &block)
+ end
+ end
+end
diff --git a/lib/gitlab/saas.rb b/lib/gitlab/saas.rb
index 1e00bd4cbfc..577e33fd700 100644
--- a/lib/gitlab/saas.rb
+++ b/lib/gitlab/saas.rb
@@ -13,6 +13,10 @@ module Gitlab
'https://staging.gitlab.com'
end
+ def self.canary_toggle_com_url
+ 'https://next.gitlab.com'
+ end
+
def self.subdomain_regex
%r{\Ahttps://[a-z0-9]+\.gitlab\.com\z}.freeze
end
diff --git a/lib/gitlab/search/abuse_detection.rb b/lib/gitlab/search/abuse_detection.rb
new file mode 100644
index 00000000000..7b5377bce88
--- /dev/null
+++ b/lib/gitlab/search/abuse_detection.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Search
+ class AbuseDetection
+ include ActiveModel::Validations
+ include AbuseValidators
+
+ ABUSIVE_TERM_SIZE = 100
+ ALLOWED_CHARS_REGEX = %r{\A[[:alnum:]_\-\/\.!]+\z}.freeze
+ MINIMUM_SEARCH_CHARS = 2
+
+ ALLOWED_SCOPES = %w(
+ blobs
+ code
+ commits
+ epics
+ issues
+ merge_requests
+ milestones
+ notes
+ projects
+ snippet_titles
+ users
+ wiki_blobs
+ ).freeze
+
+ READABLE_PARAMS = %i(
+ group_id
+ project_id
+ project_ref
+ query_string
+ repository_ref
+ scope
+ ).freeze
+
+ STOP_WORDS = %w(
+ a an and are as at be but by for if in into is it no not of on or such that the their then there these they this to was will with
+ ).freeze
+
+ validates :project_id, :group_id,
+ numericality: { only_integer: true, message: "abusive ID detected" }, allow_blank: true
+
+ validates :scope, inclusion: { in: ALLOWED_SCOPES, message: 'abusive scope detected' }, allow_blank: true
+
+ validates :repository_ref, :project_ref,
+ format: { with: ALLOWED_CHARS_REGEX, message: "abusive characters detected" }, allow_blank: true
+
+ validates :query_string,
+ exclusion: { in: STOP_WORDS, message: 'stopword only abusive search detected' }, allow_blank: true
+
+ validates :query_string,
+ length: { minimum: MINIMUM_SEARCH_CHARS, message: 'abusive tiny search detected' }, unless: :skip_tiny_search_validation?, allow_blank: true
+
+ validates :query_string,
+ no_abusive_term_length: { maximum: ABUSIVE_TERM_SIZE, maximum_for_url: ABUSIVE_TERM_SIZE * 2 }
+
+ validates :query_string, :repository_ref, :project_ref, no_abusive_coercion_from_string: true
+
+ attr_reader(*READABLE_PARAMS)
+
+ def initialize(params)
+ READABLE_PARAMS.each { |p| instance_variable_set("@#{p}", params[p]) }
+ end
+
+ private
+
+ def skip_tiny_search_validation?
+ wildcard_search? || stop_word_search?
+ end
+
+ def wildcard_search?
+ query_string == '*'
+ end
+
+ def stop_word_search?
+ STOP_WORDS.include? query_string
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/search/abuse_validators/no_abusive_coercion_from_string_validator.rb b/lib/gitlab/search/abuse_validators/no_abusive_coercion_from_string_validator.rb
new file mode 100644
index 00000000000..06464980afd
--- /dev/null
+++ b/lib/gitlab/search/abuse_validators/no_abusive_coercion_from_string_validator.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Search
+ module AbuseValidators
+ class NoAbusiveCoercionFromStringValidator < ActiveModel::EachValidator
+ def validate_each(instance, attribute, value)
+ if value.present? && !value.is_a?(String)
+ instance.errors.add attribute, "abusive coercion from string detected"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/search/abuse_validators/no_abusive_term_length_validator.rb b/lib/gitlab/search/abuse_validators/no_abusive_term_length_validator.rb
new file mode 100644
index 00000000000..8a94520d8fd
--- /dev/null
+++ b/lib/gitlab/search/abuse_validators/no_abusive_term_length_validator.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Search
+ module AbuseValidators
+ class NoAbusiveTermLengthValidator < ActiveModel::EachValidator
+ def validate_each(instance, attribute, value)
+ return unless value.is_a?(String)
+
+ if value.split.any? { |term| term_too_long?(term) }
+ instance.errors.add attribute, 'abusive term length detected'
+ end
+ end
+
+ private
+
+ def term_too_long?(term)
+ char_limit = url_detected?(term) ? maximum_for_url : maximum
+ term.length >= char_limit
+ end
+
+ def url_detected?(uri_str)
+ URI::DEFAULT_PARSER.regexp[:ABS_URI].match? uri_str
+ end
+
+ def maximum_for_url
+ options.fetch(:maximum_for_url, maximum)
+ end
+
+ def maximum
+ options.fetch(:maximum)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/search/params.rb b/lib/gitlab/search/params.rb
new file mode 100644
index 00000000000..e6a1305a82a
--- /dev/null
+++ b/lib/gitlab/search/params.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Search
+ class Params
+ include ActiveModel::Validations
+
+ SEARCH_CHAR_LIMIT = 4096
+ SEARCH_TERM_LIMIT = 64
+
+ # Generic validation
+ validates :query_string, length: { maximum: SEARCH_CHAR_LIMIT }
+ validate :not_too_many_terms
+
+ attr_reader :raw_params, :query_string, :abuse_detection
+ alias_method :search, :query_string
+ alias_method :term, :query_string
+
+ def initialize(params, detect_abuse: true)
+ @raw_params = params.is_a?(Hash) ? params.with_indifferent_access : params.dup
+ @query_string = strip_surrounding_whitespace(@raw_params[:search] || @raw_params[:term])
+ @detect_abuse = detect_abuse
+ @abuse_detection = AbuseDetection.new(self) if @detect_abuse
+
+ validate
+ end
+
+ def [](key)
+ if respond_to? key
+ # We have this logic here to support reading custom attributes
+ # like @query_string
+ #
+ # This takes precedence over values in @raw_params
+ public_send(key) # rubocop:disable GitlabSecurity/PublicSend
+ else
+ raw_params[key]
+ end
+ end
+
+ def abusive?
+ detect_abuse? && abuse_detection.errors.any?
+ end
+
+ def valid_query_length?
+ return true unless errors.has_key? :query_string
+
+ errors[:query_string].none? { |msg| msg.include? SEARCH_CHAR_LIMIT.to_s }
+ end
+
+ def valid_terms_count?
+ return true unless errors.has_key? :query_string
+
+ errors[:query_string].none? { |msg| msg.include? SEARCH_TERM_LIMIT.to_s }
+ end
+
+ def validate
+ if detect_abuse?
+ abuse_detection.validate
+ end
+
+ super
+ end
+
+ def valid?
+ if detect_abuse?
+ abuse_detection.valid? && super
+ else
+ super
+ end
+ end
+
+ private
+
+ def detect_abuse?
+ @detect_abuse
+ end
+
+ def not_too_many_terms
+ if query_string.split.count { |word| word.length >= 3 } > SEARCH_TERM_LIMIT
+ errors.add :query_string, "has too many search terms (maximum is #{SEARCH_TERM_LIMIT})"
+ end
+ end
+
+ def strip_surrounding_whitespace(obj)
+ obj.to_s.strip
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/search/query.rb b/lib/gitlab/search/query.rb
index c0420126ada..97ee7c7817d 100644
--- a/lib/gitlab/search/query.rb
+++ b/lib/gitlab/search/query.rb
@@ -44,7 +44,7 @@ module Gitlab
next unless match
- input = match.split(':')[1..-1].join
+ input = match.split(':')[1..].join
next if input.empty?
filter[:negated] = match.start_with?("-")
diff --git a/lib/gitlab/security/scan_configuration.rb b/lib/gitlab/security/scan_configuration.rb
new file mode 100644
index 00000000000..eaccbb3be7e
--- /dev/null
+++ b/lib/gitlab/security/scan_configuration.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Security
+ class ScanConfiguration
+ include ::Gitlab::Utils::StrongMemoize
+ include Gitlab::Routing.url_helpers
+
+ attr_reader :type
+
+ def initialize(project:, type:, configured: false)
+ @project = project
+ @type = type
+ @configured = configured
+ end
+
+ def available?
+ # SAST and Secret Detection are always available, but this isn't
+ # reflected by our license model yet.
+ # TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/333113
+ %i[sast secret_detection].include?(type)
+ end
+
+ def configured?
+ configured
+ end
+
+ def configuration_path
+ configurable_scans[type]
+ end
+
+ private
+
+ attr_reader :project, :configured
+
+ def configurable_scans
+ strong_memoize(:configurable_scans) do
+ {
+ sast: project_security_configuration_sast_path(project)
+ }
+ end
+ end
+ end
+ end
+end
+
+Gitlab::Security::ScanConfiguration.prepend_mod_with('Gitlab::Security::ScanConfiguration')
diff --git a/lib/gitlab/sherlock/line_profiler.rb b/lib/gitlab/sherlock/line_profiler.rb
index 52d88f074b7..aa25eb5a571 100644
--- a/lib/gitlab/sherlock/line_profiler.rb
+++ b/lib/gitlab/sherlock/line_profiler.rb
@@ -70,7 +70,7 @@ module Gitlab
next if total_duration <= MINIMUM_DURATION
- stats[1..-1].each_with_index do |data, index|
+ stats[1..].each_with_index do |data, index|
next unless source_lines[index]
duration = microsec_to_millisec(data[0])
diff --git a/lib/gitlab/sidekiq_config.rb b/lib/gitlab/sidekiq_config.rb
index 07ddac209f8..3eef41a2ca2 100644
--- a/lib/gitlab/sidekiq_config.rb
+++ b/lib/gitlab/sidekiq_config.rb
@@ -8,6 +8,7 @@ module Gitlab
EE_QUEUE_CONFIG_PATH = 'ee/app/workers/all_queues.yml'
JH_QUEUE_CONFIG_PATH = 'jh/app/workers/all_queues.yml'
SIDEKIQ_QUEUES_PATH = 'config/sidekiq_queues.yml'
+ JH_SIDEKIQ_QUEUES_PATH = 'jh/config/sidekiq_queues.yml'
QUEUE_CONFIG_PATHS = [
FOSS_QUEUE_CONFIG_PATH,
@@ -100,18 +101,24 @@ module Gitlab
def queues_for_sidekiq_queues_yml
namespaces_with_equal_weights =
workers
+ .reject { |worker| worker.jh? }
.group_by(&:queue_namespace)
.map(&:last)
.select { |workers| workers.map(&:get_weight).uniq.count == 1 }
.map(&:first)
namespaces = namespaces_with_equal_weights.map(&:queue_namespace).to_set
- remaining_queues = workers.reject { |worker| namespaces.include?(worker.queue_namespace) }
+ remaining_queues = workers.reject { |worker| worker.jh? }.reject { |worker| namespaces.include?(worker.queue_namespace) }
(namespaces_with_equal_weights.map(&:namespace_and_weight) +
remaining_queues.map(&:queue_and_weight)).sort
end
+ # Override in JH repo
+ def jh_queues_for_sidekiq_queues_yml
+ []
+ end
+
# YAML.load_file is OK here as we control the file contents
def sidekiq_queues_yml_outdated?
config_queues = YAML.load_file(SIDEKIQ_QUEUES_PATH)[:queues]
@@ -154,3 +161,5 @@ module Gitlab
end
end
end
+
+Gitlab::SidekiqConfig.prepend_mod
diff --git a/lib/gitlab/sidekiq_enq.rb b/lib/gitlab/sidekiq_enq.rb
deleted file mode 100644
index de0c00fe561..00000000000
--- a/lib/gitlab/sidekiq_enq.rb
+++ /dev/null
@@ -1,111 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- class SidekiqEnq
- LUA_ZPOPBYSCORE = <<~EOS
- local key, now = KEYS[1], ARGV[1]
- local jobs = redis.call("zrangebyscore", key, "-inf", now, "limit", 0, 1)
- if jobs[1] then
- redis.call("zrem", key, jobs[1])
- return jobs[1]
- end
- EOS
-
- LUA_ZPOPBYSCORE_SHA = Digest::SHA1.hexdigest(LUA_ZPOPBYSCORE)
-
- def enqueue_jobs(now = Time.now.to_f.to_s, sorted_sets = Sidekiq::Scheduled::SETS)
- Rails.application.reloader.wrap do
- ::Gitlab::WithRequestStore.with_request_store do
- if Feature.enabled?(:atomic_sidekiq_scheduler, default_enabled: :yaml)
- atomic_find_jobs_and_enqueue(now, sorted_sets)
- else
- find_jobs_and_enqueue(now, sorted_sets)
- end
-
- ensure
- ::Gitlab::Database::LoadBalancing.release_hosts
- end
- end
- end
-
- private
-
- # This is a copy of https://github.com/mperham/sidekiq/blob/32c55e31659a1e6bd42f98334cca5eef2863de8d/lib/sidekiq/scheduled.rb#L11-L34
- #
- # It effectively reverts
- # https://github.com/mperham/sidekiq/commit/9b75467b33759888753191413eddbc15c37a219e
- # because we observe that the extra ZREMs caused by this change can lead to high
- # CPU usage on Redis at peak times:
- # https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1179
- #
- def find_jobs_and_enqueue(now, sorted_sets)
- # A job's "score" in Redis is the time at which it should be processed.
- # Just check Redis for the set of jobs with a timestamp before now.
- Sidekiq.redis do |conn|
- sorted_sets.each do |sorted_set|
- start_time = ::Gitlab::Metrics::System.monotonic_time
- jobs = redundant_jobs = 0
-
- Sidekiq.logger.info(message: 'Enqueuing scheduled jobs', status: 'start', sorted_set: sorted_set)
-
- # Get the next item in the queue if it's score (time to execute) is <= now.
- # We need to go through the list one at a time to reduce the risk of something
- # going wrong between the time jobs are popped from the scheduled queue and when
- # they are pushed onto a work queue and losing the jobs.
- while job = conn.zrangebyscore(sorted_set, "-inf", now, limit: [0, 1]).first
- # Pop item off the queue and add it to the work queue. If the job can't be popped from
- # the queue, it's because another process already popped it so we can move on to the
- # next one.
- if conn.zrem(sorted_set, job)
- jobs += 1
- Sidekiq::Client.push(Sidekiq.load_json(job))
- else
- redundant_jobs += 1
- end
- end
-
- end_time = ::Gitlab::Metrics::System.monotonic_time
- Sidekiq.logger.info(message: 'Enqueuing scheduled jobs',
- status: 'done',
- sorted_set: sorted_set,
- jobs_count: jobs,
- redundant_jobs_count: redundant_jobs,
- duration_s: end_time - start_time)
- end
- end
- end
-
- def atomic_find_jobs_and_enqueue(now, sorted_sets)
- Sidekiq.redis do |conn|
- sorted_sets.each do |sorted_set|
- start_time = ::Gitlab::Metrics::System.monotonic_time
- jobs = 0
-
- Sidekiq.logger.info(message: 'Atomically enqueuing scheduled jobs', status: 'start', sorted_set: sorted_set)
-
- while job = redis_eval_lua(conn, LUA_ZPOPBYSCORE, LUA_ZPOPBYSCORE_SHA, keys: [sorted_set], argv: [now])
- jobs += 1
- Sidekiq::Client.push(Sidekiq.load_json(job))
- end
-
- end_time = ::Gitlab::Metrics::System.monotonic_time
- Sidekiq.logger.info(message: 'Atomically enqueuing scheduled jobs',
- status: 'done',
- sorted_set: sorted_set,
- jobs_count: jobs,
- duration_s: end_time - start_time)
- end
- end
- end
-
- def redis_eval_lua(conn, script, sha, keys: nil, argv: nil)
- conn.evalsha(sha, keys: keys, argv: argv)
- rescue ::Redis::CommandError => e
- if e.message.start_with?('NOSCRIPT')
- conn.eval(script, keys: keys, argv: argv)
- else
- raise
- end
- end
- end
-end
diff --git a/lib/gitlab/sidekiq_status.rb b/lib/gitlab/sidekiq_status.rb
index fbf2718d718..120d18f63f2 100644
--- a/lib/gitlab/sidekiq_status.rb
+++ b/lib/gitlab/sidekiq_status.rb
@@ -29,13 +29,16 @@ module Gitlab
# for most jobs.
DEFAULT_EXPIRATION = 30.minutes.to_i
+ DEFAULT_VALUE = 1
+ DEFAULT_VALUE_MESSAGE = 'Keys using the default value for SidekiqStatus detected'
+
# Starts tracking of the given job.
#
# jid - The Sidekiq job ID
# expire - The expiration time of the Redis key.
- def self.set(jid, expire = DEFAULT_EXPIRATION)
+ def self.set(jid, expire = DEFAULT_EXPIRATION, value: DEFAULT_VALUE)
Sidekiq.redis do |redis|
- redis.set(key_for(jid), 1, ex: expire)
+ redis.set(key_for(jid), value, ex: expire)
end
end
@@ -88,13 +91,20 @@ module Gitlab
# true = job is still running or enqueued
# false = job completed
def self.job_status(job_ids)
+ return [] if job_ids.empty?
+
keys = job_ids.map { |jid| key_for(jid) }
+ results = Sidekiq.redis { |redis| redis.mget(*keys) }
- Sidekiq.redis do |redis|
- redis.pipelined do
- keys.each { |key| redis.exists(key) }
- end
+ if Feature.enabled?(:log_implicit_sidekiq_status_calls, default_enabled: :yaml)
+ to_log = keys.zip(results).select do |_key, result|
+ result == DEFAULT_VALUE.to_s
+ end.map(&:first)
+
+ Sidekiq.logger.info(message: DEFAULT_VALUE_MESSAGE, keys: to_log) if to_log.any?
end
+
+ results.map { |result| !result.nil? }
end
# Returns the JIDs that are completed
diff --git a/lib/gitlab/sidekiq_status/client_middleware.rb b/lib/gitlab/sidekiq_status/client_middleware.rb
index bfd5038557d..cee7270f2fb 100644
--- a/lib/gitlab/sidekiq_status/client_middleware.rb
+++ b/lib/gitlab/sidekiq_status/client_middleware.rb
@@ -5,8 +5,9 @@ module Gitlab
class ClientMiddleware
def call(_, job, _, _)
status_expiration = job['status_expiration'] || Gitlab::SidekiqStatus::DEFAULT_EXPIRATION
+ value = job['status_expiration'] ? 2 : Gitlab::SidekiqStatus::DEFAULT_VALUE
- Gitlab::SidekiqStatus.set(job['jid'], status_expiration)
+ Gitlab::SidekiqStatus.set(job['jid'], status_expiration, value: value)
yield
end
end
diff --git a/lib/gitlab/spamcheck/client.rb b/lib/gitlab/spamcheck/client.rb
index 925ca44dfc9..40b01552244 100644
--- a/lib/gitlab/spamcheck/client.rb
+++ b/lib/gitlab/spamcheck/client.rb
@@ -21,14 +21,16 @@ module Gitlab
update: ::Spamcheck::Action::UPDATE
}.freeze
+ URL_SCHEME_REGEX = %r{^grpc://|^tls://}.freeze
+
def initialize
@endpoint_url = Gitlab::CurrentSettings.current_application_settings.spam_check_endpoint_url
- # remove the `grpc://` as it's only useful to ensure we're expecting to
- # connect with Spamcheck
- @endpoint_url = @endpoint_url.gsub(%r(^grpc:\/\/), '')
+ @creds = client_creds(@endpoint_url)
- @creds = stub_creds
+ # remove the `grpc://` or 'tls://' as it's only useful to ensure we're expecting to
+ # connect with Spamcheck
+ @endpoint_url = @endpoint_url.sub(URL_SCHEME_REGEX, '')
end
def issue_spam?(spam_issue:, user:, context: {})
@@ -96,11 +98,11 @@ module Gitlab
nanos: ar_timestamp.to_time.nsec)
end
- def stub_creds
- if Rails.env.development? || Rails.env.test?
- :this_channel_is_insecure
+ def client_creds(url)
+ if URI(url).scheme == 'tls'
+ GRPC::Core::ChannelCredentials.new(::Gitlab::X509::Certificate.ca_certs_bundle)
else
- GRPC::Core::ChannelCredentials.new ::Gitlab::X509::Certificate.ca_certs_bundle
+ :this_channel_is_insecure
end
end
diff --git a/lib/gitlab/string_range_marker.rb b/lib/gitlab/string_range_marker.rb
index 5ddc88edf50..292a9d07e6a 100644
--- a/lib/gitlab/string_range_marker.rb
+++ b/lib/gitlab/string_range_marker.rb
@@ -99,7 +99,7 @@ module Gitlab
start = prev = positions[0]
range = MarkerRange.new(start, prev, mode: mode)
- positions[1..-1].each do |pos|
+ positions[1..].each do |pos|
if pos == prev + 1
range = MarkerRange.new(start, pos, mode: mode)
prev = pos
diff --git a/lib/gitlab/subscription_portal.rb b/lib/gitlab/subscription_portal.rb
index 4f6d25097e2..d987247fdc4 100644
--- a/lib/gitlab/subscription_portal.rb
+++ b/lib/gitlab/subscription_portal.rb
@@ -18,6 +18,10 @@ module Gitlab
"#{self.subscriptions_url}/payment_forms/cc_validation"
end
+ def self.registration_validation_form_url
+ "#{self.subscriptions_url}/payment_forms/cc_registration_validation"
+ end
+
def self.subscriptions_comparison_url
'https://about.gitlab.com/pricing/gitlab-com/feature-comparison'
end
@@ -62,6 +66,10 @@ module Gitlab
"#{self.subscriptions_url}/gitlab/namespaces/#{group_id}/renew"
end
+ def self.edit_account_url
+ "#{self.subscriptions_url}/customers/edit"
+ end
+
def self.subscription_portal_admin_email
ENV.fetch('SUBSCRIPTION_PORTAL_ADMIN_EMAIL', 'gl_com_api@gitlab.com')
end
@@ -69,9 +77,15 @@ module Gitlab
def self.subscription_portal_admin_token
ENV.fetch('SUBSCRIPTION_PORTAL_ADMIN_TOKEN', 'customer_admin_token')
end
+
+ def self.renewal_service_email
+ 'renewals-support@gitlab.com'
+ end
end
end
Gitlab::SubscriptionPortal.prepend_mod
Gitlab::SubscriptionPortal::SUBSCRIPTIONS_URL = Gitlab::SubscriptionPortal.subscriptions_url.freeze
Gitlab::SubscriptionPortal::PAYMENT_FORM_URL = Gitlab::SubscriptionPortal.payment_form_url.freeze
+Gitlab::SubscriptionPortal::RENEWAL_SERVICE_EMAIL = Gitlab::SubscriptionPortal.renewal_service_email.freeze
+Gitlab::SubscriptionPortal::REGISTRATION_VALIDATION_FORM_URL = Gitlab::SubscriptionPortal.registration_validation_form_url.freeze
diff --git a/lib/gitlab/task_helpers.rb b/lib/gitlab/task_helpers.rb
index 227962fc0f7..6a98fa12903 100644
--- a/lib/gitlab/task_helpers.rb
+++ b/lib/gitlab/task_helpers.rb
@@ -190,6 +190,9 @@ module Gitlab
end
def checkout_version(version, target_dir)
+ # Explicitly setting the git protocol version to v2 allows older Git binaries
+ # to do have a shallow clone obtain objects by object ID.
+ run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} config protocol.version 2])
run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} fetch --quiet origin #{version}])
run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} checkout -f --quiet FETCH_HEAD --])
end
diff --git a/lib/gitlab/tracking.rb b/lib/gitlab/tracking.rb
index ec032cf2d3c..a58b4beb0df 100644
--- a/lib/gitlab/tracking.rb
+++ b/lib/gitlab/tracking.rb
@@ -2,11 +2,9 @@
module Gitlab
module Tracking
- SNOWPLOW_NAMESPACE = 'gl'
-
class << self
def enabled?
- snowplow_micro_enabled? || Gitlab::CurrentSettings.snowplow_enabled?
+ snowplow.enabled?
end
def event(category, action, label: nil, property: nil, value: nil, context: [], project: nil, user: nil, namespace: nil, **extra) # rubocop:disable Metrics/ParameterLists
@@ -25,6 +23,10 @@ module Gitlab
snowplow.hostname
end
+ def snowplow_micro_enabled?
+ Rails.env.development? && Gitlab::Utils.to_boolean(ENV['SNOWPLOW_MICRO_ENABLE'])
+ end
+
private
def snowplow
@@ -34,10 +36,6 @@ module Gitlab
Gitlab::Tracking::Destinations::Snowplow.new
end
end
-
- def snowplow_micro_enabled?
- Rails.env.development? && Gitlab::Utils.to_boolean(ENV['SNOWPLOW_MICRO_ENABLE'])
- end
end
end
end
diff --git a/lib/gitlab/tracking/destinations/snowplow.rb b/lib/gitlab/tracking/destinations/snowplow.rb
index 5596e9acd30..ddcd4693738 100644
--- a/lib/gitlab/tracking/destinations/snowplow.rb
+++ b/lib/gitlab/tracking/destinations/snowplow.rb
@@ -8,6 +8,8 @@ module Gitlab
class Snowplow < Base
extend ::Gitlab::Utils::Override
+ SNOWPLOW_NAMESPACE = 'gl'
+
override :event
def event(category, action, label: nil, property: nil, value: nil, context: nil)
return unless enabled?
@@ -19,7 +21,7 @@ module Gitlab
def options(group)
additional_features = Feature.enabled?(:additional_snowplow_tracking, group, type: :ops)
{
- namespace: Gitlab::Tracking::SNOWPLOW_NAMESPACE,
+ namespace: SNOWPLOW_NAMESPACE,
hostname: hostname,
cookie_domain: cookie_domain,
app_id: app_id,
@@ -28,16 +30,16 @@ module Gitlab
}.transform_keys! { |key| key.to_s.camelize(:lower).to_sym }
end
+ def enabled?
+ Gitlab::CurrentSettings.snowplow_enabled?
+ end
+
def hostname
Gitlab::CurrentSettings.snowplow_collector_hostname
end
private
- def enabled?
- Gitlab::Tracking.enabled?
- end
-
def app_id
Gitlab::CurrentSettings.snowplow_app_id
end
@@ -54,7 +56,7 @@ module Gitlab
@tracker ||= SnowplowTracker::Tracker.new(
emitter,
SnowplowTracker::Subject.new,
- Gitlab::Tracking::SNOWPLOW_NAMESPACE,
+ SNOWPLOW_NAMESPACE,
app_id
)
end
diff --git a/lib/gitlab/tracking/destinations/snowplow_micro.rb b/lib/gitlab/tracking/destinations/snowplow_micro.rb
index b818d349a6d..3553efba1e1 100644
--- a/lib/gitlab/tracking/destinations/snowplow_micro.rb
+++ b/lib/gitlab/tracking/destinations/snowplow_micro.rb
@@ -15,7 +15,12 @@ module Gitlab
protocol: uri.scheme,
port: uri.port,
force_secure_tracker: false
- )
+ ).transform_keys! { |key| key.to_s.camelize(:lower).to_sym }
+ end
+
+ override :enabled?
+ def enabled?
+ true
end
override :hostname
@@ -23,8 +28,6 @@ module Gitlab
"#{uri.host}:#{uri.port}"
end
- private
-
def uri
strong_memoize(:snowplow_uri) do
uri = URI(ENV['SNOWPLOW_MICRO_URI'] || DEFAULT_URI)
@@ -33,6 +36,8 @@ module Gitlab
end
end
+ private
+
override :cookie_domain
def cookie_domain
'.gitlab.com'
diff --git a/lib/gitlab/usage/metrics/instrumentations/snowplow_configured_to_gitlab_collector_metric.rb b/lib/gitlab/usage/metrics/instrumentations/snowplow_configured_to_gitlab_collector_metric.rb
new file mode 100644
index 00000000000..3a92525303b
--- /dev/null
+++ b/lib/gitlab/usage/metrics/instrumentations/snowplow_configured_to_gitlab_collector_metric.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Usage
+ module Metrics
+ module Instrumentations
+ class SnowplowConfiguredToGitlabCollectorMetric < GenericMetric
+ GITLAB_SNOWPLOW_COLLECTOR_HOSTNAME = 'snowplow.trx.gitlab.net'
+
+ def value
+ Gitlab::CurrentSettings.snowplow_collector_hostname == GITLAB_SNOWPLOW_COLLECTOR_HOSTNAME
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/usage/metrics/instrumentations/snowplow_enabled_metric.rb b/lib/gitlab/usage/metrics/instrumentations/snowplow_enabled_metric.rb
new file mode 100644
index 00000000000..5d504c70e73
--- /dev/null
+++ b/lib/gitlab/usage/metrics/instrumentations/snowplow_enabled_metric.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Usage
+ module Metrics
+ module Instrumentations
+ class SnowplowEnabledMetric < GenericMetric
+ def value
+ Gitlab::CurrentSettings.snowplow_enabled?
+ 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
index 6dcbe5f5fe5..d1a7bb65cc3 100644
--- a/lib/gitlab/usage/metrics/names_suggestions/generator.rb
+++ b/lib/gitlab/usage/metrics/names_suggestions/generator.rb
@@ -18,10 +18,6 @@ module Gitlab
private
- def instrumentation_metrics
- ::Gitlab::UsageDataMetrics.suggested_names
- end
-
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
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index 20e526aeefa..917c273d3f6 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -10,6 +10,12 @@
# alt_usage_data { Gitlab::VERSION }
# redis_usage_data(Gitlab::UsageDataCounters::WikiPageCounter)
# redis_usage_data { ::Gitlab::UsageCounters::PodLogs.usage_totals[:total] }
+
+# NOTE:
+# Implementing metrics direct in `usage_data.rb` is deprecated,
+# please add new instrumentation class and use add_metric method.
+# For more information, see https://docs.gitlab.com/ee/development/service_ping/metrics_instrumentation.html
+
module Gitlab
class UsageData
DEPRECATED_VALUE = -1000
@@ -45,10 +51,7 @@ module Gitlab
clear_memoized
with_finished_at(:recording_ce_finished_at) do
- usage_data = usage_data_metrics
- usage_data = usage_data.with_indifferent_access.deep_merge(instrumentation_metrics.with_indifferent_access) if Feature.enabled?(:usage_data_instrumentation)
-
- usage_data
+ usage_data_metrics
end
end
@@ -225,7 +228,9 @@ module Gitlab
operating_system: alt_usage_data(fallback: nil) { operating_system },
gitaly_apdex: alt_usage_data { gitaly_apdex },
collected_data_categories: add_metric('CollectedDataCategoriesMetric', time_frame: 'none'),
- service_ping_features_enabled: add_metric('ServicePingFeaturesMetric', time_frame: 'none')
+ service_ping_features_enabled: add_metric('ServicePingFeaturesMetric', time_frame: 'none'),
+ snowplow_enabled: add_metric('SnowplowEnabledMetric', time_frame: 'none'),
+ snowplow_configured_to_gitlab_collector: add_metric('SnowplowConfiguredToGitlabCollectorMetric', time_frame: 'none')
}
}
end
@@ -401,7 +406,8 @@ module Gitlab
results[:projects_jira_cloud_active] = jira_integration_data_hash[:projects_jira_cloud_active]
results
- rescue ActiveRecord::StatementInvalid
+ rescue ActiveRecord::StatementInvalid => error
+ Gitlab::ErrorTracking.track_and_raise_for_dev_exception(error)
{ projects_jira_server_active: FALLBACK, projects_jira_cloud_active: FALLBACK }
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -663,8 +669,6 @@ module Gitlab
end
def redis_hll_counters
- return {} unless Feature.enabled?(:redis_hll_tracking, type: :ops, default_enabled: :yaml)
-
{ redis_hll_counters: ::Gitlab::UsageDataCounters::HLLRedisCounter.unique_events_data }
end
@@ -726,7 +730,7 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord
# rubocop: disable UsageData/LargeTable
start = ::Event.where(time_period).select(:id).order(created_at: :asc).first&.id
- finish = ::Event.where(time_period).select(:id).order(created_at: :asc).first&.id
+ finish = ::Event.where(time_period).select(:id).order(created_at: :desc).first&.id
estimate_batch_distinct_count(::Event.where(time_period), :author_id, start: start, finish: finish)
# rubocop: enable UsageData/LargeTable
# rubocop: enable CodeReuse/ActiveRecord
@@ -753,10 +757,6 @@ module Gitlab
.deep_merge(aggregated_metrics_data)
end
- def instrumentation_metrics
- Gitlab::UsageDataMetrics.uncached_data # rubocop:disable UsageData/LargeTable
- end
-
def metric_time_period(time_period)
time_period.present? ? '28d' : 'none'
end
diff --git a/lib/gitlab/usage_data_counters/known_events/analytics.yml b/lib/gitlab/usage_data_counters/known_events/analytics.yml
index 261bdeb9bfa..5a1e7f03278 100644
--- a/lib/gitlab/usage_data_counters/known_events/analytics.yml
+++ b/lib/gitlab/usage_data_counters/known_events/analytics.yml
@@ -66,3 +66,15 @@
category: analytics
redis_slot: analytics
aggregation: weekly
+- name: p_analytics_ci_cd_pipelines
+ category: analytics
+ redis_slot: analytics
+ aggregation: weekly
+- name: p_analytics_ci_cd_deployment_frequency
+ category: analytics
+ redis_slot: analytics
+ aggregation: weekly
+- name: p_analytics_ci_cd_lead_time
+ category: analytics
+ redis_slot: analytics
+ 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
index 40922433635..d90960b344c 100644
--- a/lib/gitlab/usage_data_counters/known_events/ci_templates.yml
+++ b/lib/gitlab/usage_data_counters/known_events/ci_templates.yml
@@ -119,6 +119,10 @@
category: ci_templates
redis_slot: ci_templates
aggregation: weekly
+- name: p_ci_templates_security_dast_api_latest
+ category: ci_templates
+ redis_slot: ci_templates
+ aggregation: weekly
- name: p_ci_templates_security_container_scanning
category: ci_templates
redis_slot: ci_templates
@@ -551,6 +555,10 @@
category: ci_templates
redis_slot: ci_templates
aggregation: weekly
+- name: p_ci_templates_implicit_security_dast_api_latest
+ category: ci_templates
+ redis_slot: ci_templates
+ aggregation: weekly
- name: p_ci_templates_implicit_security_container_scanning
category: ci_templates
redis_slot: ci_templates
diff --git a/lib/gitlab/usage_data_counters/known_events/common.yml b/lib/gitlab/usage_data_counters/known_events/common.yml
index feebc7f395a..bb98a0b262a 100644
--- a/lib/gitlab/usage_data_counters/known_events/common.yml
+++ b/lib/gitlab/usage_data_counters/known_events/common.yml
@@ -355,6 +355,19 @@
category: testing
aggregation: weekly
feature_flag: users_expanding_widgets_usage_data
+- name: users_expanding_testing_license_compliance_report
+ redis_slot: testing
+ category: testing
+ aggregation: weekly
+ feature_flag: users_expanding_widgets_usage_data
+- name: users_visiting_testing_license_compliance_full_report
+ redis_slot: testing
+ category: testing
+ aggregation: weekly
+- name: users_visiting_testing_manage_license_compliance
+ redis_slot: testing
+ category: testing
+ aggregation: weekly
# Container Security - Network Policies
- name: clusters_using_network_policies_ui
redis_slot: network_policies
diff --git a/lib/gitlab/usage_data_counters/known_events/importer_events.yml b/lib/gitlab/usage_data_counters/known_events/importer_events.yml
index 79bbac229bc..c84d756a013 100644
--- a/lib/gitlab/usage_data_counters/known_events/importer_events.yml
+++ b/lib/gitlab/usage_data_counters/known_events/importer_events.yml
@@ -4,14 +4,11 @@
category: importer
redis_slot: import
aggregation: weekly
- feature_flag: track_importer_activity
- name: github_import_project_success
category: importer
redis_slot: import
aggregation: weekly
- feature_flag: track_importer_activity
- name: github_import_project_failure
category: importer
redis_slot: import
aggregation: weekly
- feature_flag: track_importer_activity
diff --git a/lib/gitlab/usage_data_counters/known_events/quickactions.yml b/lib/gitlab/usage_data_counters/known_events/quickactions.yml
index dff2c4f8d03..d831ac02dd1 100644
--- a/lib/gitlab/usage_data_counters/known_events/quickactions.yml
+++ b/lib/gitlab/usage_data_counters/known_events/quickactions.yml
@@ -279,3 +279,11 @@
category: quickactions
redis_slot: quickactions
aggregation: weekly
+- name: i_quickactions_add_contacts
+ category: quickactions
+ redis_slot: quickactions
+ aggregation: weekly
+- name: i_quickactions_remove_contacts
+ category: quickactions
+ redis_slot: quickactions
+ aggregation: weekly
diff --git a/lib/gitlab/usage_data_non_sql_metrics.rb b/lib/gitlab/usage_data_non_sql_metrics.rb
index be5a571fb82..1661a1b6987 100644
--- a/lib/gitlab/usage_data_non_sql_metrics.rb
+++ b/lib/gitlab/usage_data_non_sql_metrics.rb
@@ -5,13 +5,6 @@ module Gitlab
SQL_METRIC_DEFAULT = -3
class << self
- def uncached_data
- # instrumentation_metrics is already included with feature flag enabled
- return super if Feature.enabled?(:usage_data_instrumentation)
-
- super.with_indifferent_access.deep_merge(instrumentation_metrics.with_indifferent_access)
- end
-
def add_metric(metric, time_frame: 'none', options: {})
metric_class = "Gitlab::Usage::Metrics::Instrumentations::#{metric}".constantize
@@ -50,12 +43,6 @@ module Gitlab
projects_jira_cloud_active: 0
}
end
-
- private
-
- def instrumentation_metrics
- ::Gitlab::Usage::Metric.all.map(&:with_instrumentation).reduce({}, :deep_merge)
- end
end
end
end
diff --git a/lib/gitlab/usage_data_queries.rb b/lib/gitlab/usage_data_queries.rb
index f543b29e43f..c6490ba7374 100644
--- a/lib/gitlab/usage_data_queries.rb
+++ b/lib/gitlab/usage_data_queries.rb
@@ -5,13 +5,6 @@ module Gitlab
# See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41091
class UsageDataQueries < UsageData
class << self
- def uncached_data
- # instrumentation_metrics is already included with feature flag enabled
- return super if Feature.enabled?(:usage_data_instrumentation)
-
- super.with_indifferent_access.deep_merge(instrumentation_metrics.with_indifferent_access)
- end
-
def add_metric(metric, time_frame: 'none', options: {})
metric_class = "Gitlab::Usage::Metrics::Instrumentations::#{metric}".constantize
@@ -71,12 +64,6 @@ module Gitlab
def epics_deepest_relationship_level
{ epics_deepest_relationship_level: 0 }
end
-
- private
-
- def instrumentation_metrics
- ::Gitlab::Usage::Metric.all.map(&:with_instrumentation).reduce({}, :deep_merge)
- end
end
end
end
diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb
index cb34ed69a9c..96cff024371 100644
--- a/lib/gitlab/utils.rb
+++ b/lib/gitlab/utils.rb
@@ -120,18 +120,14 @@ module Gitlab
Random.rand(Float::MAX.to_i).to_s(36)
end
- # See: http://stackoverflow.com/questions/2108727/which-in-ruby-checking-if-program-exists-in-path-from-ruby
- # Cross-platform way of finding an executable in the $PATH.
+ # Behaves like `which` on Linux machines: given PATH, try to resolve the given
+ # executable name to an absolute path, or return nil.
#
# which('ruby') #=> /usr/bin/ruby
- def which(cmd, env = ENV)
- exts = env['PATHEXT'] ? env['PATHEXT'].split(';') : ['']
-
- env['PATH'].split(File::PATH_SEPARATOR).each do |path|
- exts.each do |ext|
- exe = File.join(path, "#{cmd}#{ext}")
- return exe if File.executable?(exe) && !File.directory?(exe)
- end
+ def which(filename)
+ ENV['PATH']&.split(File::PATH_SEPARATOR)&.each do |path|
+ full_path = File.join(path, filename)
+ return full_path if File.executable?(full_path)
end
nil
diff --git a/lib/gitlab/utils/nokogiri.rb b/lib/gitlab/utils/nokogiri.rb
index 4b37bb7e5ea..5113553c584 100644
--- a/lib/gitlab/utils/nokogiri.rb
+++ b/lib/gitlab/utils/nokogiri.rb
@@ -16,7 +16,7 @@ module Gitlab
# we remove the leading `//` and add `descendant-or-self::`
# in order to ensure we're searching from this node and all
# descendants.
- xpath.map { |t| "descendant-or-self::#{t[2..-1]}" }.join('|')
+ xpath.map { |t| "descendant-or-self::#{t[2..]}" }.join('|')
end
end
end
diff --git a/lib/gitlab/utils/strong_memoize.rb b/lib/gitlab/utils/strong_memoize.rb
index 483bfe12c68..255fa0169bf 100644
--- a/lib/gitlab/utils/strong_memoize.rb
+++ b/lib/gitlab/utils/strong_memoize.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-require_dependency 'gitlab/utils'
-
module Gitlab
module Utils
module StrongMemoize
diff --git a/lib/gitlab/utils/usage_data.rb b/lib/gitlab/utils/usage_data.rb
index 77f04929661..e347168f419 100644
--- a/lib/gitlab/utils/usage_data.rb
+++ b/lib/gitlab/utils/usage_data.rb
@@ -43,13 +43,8 @@ module Gitlab
HISTOGRAM_FALLBACK = { '-1' => -1 }.freeze
DISTRIBUTED_HLL_FALLBACK = -2
MAX_BUCKET_SIZE = 100
- INSTRUMENTATION_CLASS_FALLBACK = -100
def add_metric(metric, time_frame: 'none', options: {})
- # Results of this method should be overwritten by instrumentation class values
- # -100 indicates the metric was not properly merged.
- return INSTRUMENTATION_CLASS_FALLBACK if Feature.enabled?(:usage_data_instrumentation)
-
metric_class = "Gitlab::Usage::Metrics::Instrumentations::#{metric}".constantize
metric_class.new(time_frame: time_frame, options: options).value
@@ -61,7 +56,8 @@ module Gitlab
else
relation.count
end
- rescue ActiveRecord::StatementInvalid
+ rescue ActiveRecord::StatementInvalid => error
+ Gitlab::ErrorTracking.track_and_raise_for_dev_exception(error)
FALLBACK
end
@@ -71,7 +67,8 @@ module Gitlab
else
relation.distinct_count_by(column)
end
- rescue ActiveRecord::StatementInvalid
+ rescue ActiveRecord::StatementInvalid => error
+ Gitlab::ErrorTracking.track_and_raise_for_dev_exception(error)
FALLBACK
end
@@ -83,7 +80,8 @@ module Gitlab
yield buckets if block_given?
buckets.estimated_distinct_count
- rescue ActiveRecord::StatementInvalid
+ rescue ActiveRecord::StatementInvalid => error
+ Gitlab::ErrorTracking.track_and_raise_for_dev_exception(error)
FALLBACK
# catch all rescue should be removed as a part of feature flag rollout issue
# https://gitlab.com/gitlab-org/gitlab/-/issues/285485
@@ -94,7 +92,8 @@ module Gitlab
def sum(relation, column, batch_size: nil, start: nil, finish: nil)
Gitlab::Database::BatchCount.batch_sum(relation, column, batch_size: batch_size, start: start, finish: finish)
- rescue ActiveRecord::StatementInvalid
+ rescue ActiveRecord::StatementInvalid => error
+ Gitlab::ErrorTracking.track_and_raise_for_dev_exception(error)
FALLBACK
end
@@ -160,7 +159,8 @@ module Gitlab
query: query.to_sql,
message: e.message
)
-
+ # Raises error for dev env
+ Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e)
HISTOGRAM_FALLBACK
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/lib/gitlab/x509/commit.rb b/lib/gitlab/x509/commit.rb
index 91951a3e505..c7f4b7cbdf5 100644
--- a/lib/gitlab/x509/commit.rb
+++ b/lib/gitlab/x509/commit.rb
@@ -25,7 +25,7 @@ module Gitlab
def lazy_signature
BatchLoader.for(@commit.sha).batch do |shas, loader|
- X509CommitSignature.by_commit_sha(shas).each do |signature|
+ CommitSignatures::X509CommitSignature.by_commit_sha(shas).each do |signature|
loader.call(signature.commit_sha, signature)
end
end
@@ -49,9 +49,9 @@ module Gitlab
def create_cached_signature!
return if attributes.nil?
- return X509CommitSignature.new(attributes) if Gitlab::Database.read_only?
+ return CommitSignatures::X509CommitSignature.new(attributes) if Gitlab::Database.read_only?
- X509CommitSignature.safe_create!(attributes)
+ CommitSignatures::X509CommitSignature.safe_create!(attributes)
end
end
end
diff --git a/lib/google_api/cloud_platform/client.rb b/lib/google_api/cloud_platform/client.rb
index c917debd3d9..9bd2309d2b7 100644
--- a/lib/google_api/cloud_platform/client.rb
+++ b/lib/google_api/cloud_platform/client.rb
@@ -1,10 +1,12 @@
# frozen_string_literal: true
+require 'securerandom'
require 'google/apis/compute_v1'
require 'google/apis/container_v1'
require 'google/apis/container_v1beta1'
require 'google/apis/cloudbilling_v1'
require 'google/apis/cloudresourcemanager_v1'
+require 'google/apis/iam_v1'
module GoogleApi
module CloudPlatform
@@ -83,6 +85,51 @@ module GoogleApi
m[1] if m
end
+ def list_projects
+ result = []
+
+ service = Google::Apis::CloudresourcemanagerV1::CloudResourceManagerService.new
+ service.authorization = access_token
+
+ response = service.fetch_all(items: :projects) do |token|
+ service.list_projects
+ end
+
+ # Google API results are paged by default, so we need to iterate through
+ response.each do |project|
+ result.append(project)
+ end
+
+ result
+ end
+
+ def create_service_account(gcp_project_id, display_name, description)
+ name = "projects/#{gcp_project_id}"
+
+ # initialize google iam service
+ service = Google::Apis::IamV1::IamService.new
+ service.authorization = access_token
+
+ # generate account id
+ random_account_id = "gitlab-" + SecureRandom.hex(11)
+
+ body_params = { account_id: random_account_id,
+ service_account: { display_name: display_name,
+ description: description } }
+
+ request_body = Google::Apis::IamV1::CreateServiceAccountRequest.new(**body_params)
+ service.create_service_account(name, request_body)
+ end
+
+ def create_service_account_key(gcp_project_id, service_account_id)
+ service = Google::Apis::IamV1::IamService.new
+ service.authorization = access_token
+
+ name = "projects/#{gcp_project_id}/serviceAccounts/#{service_account_id}"
+ request_body = Google::Apis::IamV1::CreateServiceAccountKeyRequest.new
+ service.create_service_account_key(name, request_body)
+ end
+
private
def make_cluster_options(cluster_name, cluster_size, machine_type, legacy_abac, enable_addons)
diff --git a/lib/safe_zip/extract.rb b/lib/safe_zip/extract.rb
index ac33f0b3c2c..74df7895afe 100644
--- a/lib/safe_zip/extract.rb
+++ b/lib/safe_zip/extract.rb
@@ -25,7 +25,7 @@ module SafeZip
private
def extract_with_ruby_zip(params)
- ::Zip::File.open(archive_path) do |zip_archive|
+ ::Zip::File.open(archive_path) do |zip_archive| # rubocop:disable Performance/Rubyzip
# Extract all files in the following order:
# 1. Directories first,
# 2. Files next,
diff --git a/lib/sidebars/concerns/container_with_html_options.rb b/lib/sidebars/concerns/container_with_html_options.rb
index 79dddd309b5..796b7cbe275 100644
--- a/lib/sidebars/concerns/container_with_html_options.rb
+++ b/lib/sidebars/concerns/container_with_html_options.rb
@@ -3,6 +3,8 @@
module Sidebars
module Concerns
module ContainerWithHtmlOptions
+ include LinkWithHtmlOptions
+
# The attributes returned from this method
# will be applied to helper methods like
# `link_to` or the div containing the container.
diff --git a/lib/sidebars/concerns/link_with_html_options.rb b/lib/sidebars/concerns/link_with_html_options.rb
new file mode 100644
index 00000000000..4ca748e0a71
--- /dev/null
+++ b/lib/sidebars/concerns/link_with_html_options.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Sidebars
+ module Concerns
+ module LinkWithHtmlOptions
+ # add on specific items as the pertain to `link_to` objects specifically
+ def link_html_options
+ container_html_options.tap do |html_options|
+ html_options[:class] = [*html_options[:class], 'gl-link'].join(' ')
+ end
+ end
+ end
+ end
+end
diff --git a/lib/sidebars/concerns/positionable_list.rb b/lib/sidebars/concerns/positionable_list.rb
index 0bbe1d918e5..459af466690 100644
--- a/lib/sidebars/concerns/positionable_list.rb
+++ b/lib/sidebars/concerns/positionable_list.rb
@@ -44,6 +44,14 @@ module Sidebars
list[index] = new_element
end
+ def remove_element(list, element_to_remove)
+ index = index_of(list, element_to_remove)
+
+ return unless index
+
+ list.slice!(index)
+ end
+
private
# Classes including this method will have to define
diff --git a/lib/sidebars/groups/menus/customer_relations_menu.rb b/lib/sidebars/groups/menus/customer_relations_menu.rb
index fdbbd662ad6..002197965d1 100644
--- a/lib/sidebars/groups/menus/customer_relations_menu.rb
+++ b/lib/sidebars/groups/menus/customer_relations_menu.rb
@@ -32,7 +32,7 @@ module Sidebars
def contacts_menu_item
::Sidebars::MenuItem.new(
title: _('Contacts'),
- link: contacts_group_crm_index_path(context.group),
+ link: group_crm_contacts_path(context.group),
active_routes: { path: 'groups/crm#contacts' },
item_id: :crm_contacts
)
@@ -41,7 +41,7 @@ module Sidebars
def organizations_menu_item
::Sidebars::MenuItem.new(
title: _('Organizations'),
- link: organizations_group_crm_index_path(context.group),
+ link: group_crm_organizations_path(context.group),
active_routes: { path: 'groups/crm#organizations' },
item_id: :crm_organizations
)
diff --git a/lib/sidebars/groups/menus/packages_registries_menu.rb b/lib/sidebars/groups/menus/packages_registries_menu.rb
index 46fcec9f7b8..60d91c8fd10 100644
--- a/lib/sidebars/groups/menus/packages_registries_menu.rb
+++ b/lib/sidebars/groups/menus/packages_registries_menu.rb
@@ -26,9 +26,7 @@ module Sidebars
private
def packages_registry_menu_item
- unless context.group.packages_feature_enabled?
- return ::Sidebars::NilMenuItem.new(item_id: :packages_registry)
- end
+ return nil_menu_item(:packages_registry) unless context.group.packages_feature_enabled?
::Sidebars::MenuItem.new(
title: _('Package Registry'),
@@ -40,7 +38,7 @@ module Sidebars
def container_registry_menu_item
if !::Gitlab.config.registry.enabled || !can?(context.current_user, :read_container_image, context.group)
- return ::Sidebars::NilMenuItem.new(item_id: :container_registry)
+ return nil_menu_item(:container_registry)
end
::Sidebars::MenuItem.new(
@@ -52,9 +50,11 @@ module Sidebars
end
def dependency_proxy_menu_item
- unless can?(context.current_user, :read_dependency_proxy, context.group)
- return ::Sidebars::NilMenuItem.new(item_id: :dependency_proxy)
- end
+ setting_does_not_exist_or_is_enabled = !context.group.dependency_proxy_setting ||
+ context.group.dependency_proxy_setting.enabled
+
+ return nil_menu_item(:dependency_proxy) unless can?(context.current_user, :read_dependency_proxy, context.group)
+ return nil_menu_item(:dependency_proxy) unless setting_does_not_exist_or_is_enabled
::Sidebars::MenuItem.new(
title: _('Dependency Proxy'),
@@ -63,6 +63,10 @@ module Sidebars
item_id: :dependency_proxy
)
end
+
+ def nil_menu_item(item_id)
+ ::Sidebars::NilMenuItem.new(item_id: item_id)
+ end
end
end
end
diff --git a/lib/sidebars/menu_item.rb b/lib/sidebars/menu_item.rb
index 1375f9fffca..7ed3e47ae34 100644
--- a/lib/sidebars/menu_item.rb
+++ b/lib/sidebars/menu_item.rb
@@ -2,6 +2,8 @@
module Sidebars
class MenuItem
+ include ::Sidebars::Concerns::LinkWithHtmlOptions
+
attr_reader :title, :link, :active_routes, :item_id, :container_html_options, :sprite_icon, :sprite_icon_html_options, :hint_html_options
def initialize(title:, link:, active_routes:, item_id: nil, container_html_options: {}, sprite_icon: nil, sprite_icon_html_options: {}, hint_html_options: {})
diff --git a/lib/sidebars/panel.rb b/lib/sidebars/panel.rb
index e8c02a2d707..2a172cfffe3 100644
--- a/lib/sidebars/panel.rb
+++ b/lib/sidebars/panel.rb
@@ -37,6 +37,10 @@ module Sidebars
replace_element(@menus, menu_to_replace, new_menu)
end
+ def remove_menu(menu_to_remove)
+ remove_element(@menus, menu_to_remove)
+ end
+
def set_scope_menu(scope_menu)
@scope_menu = scope_menu
end
diff --git a/lib/sidebars/projects/menus/analytics_menu.rb b/lib/sidebars/projects/menus/analytics_menu.rb
index 2a89dc66219..b9bcc3267d6 100644
--- a/lib/sidebars/projects/menus/analytics_menu.rb
+++ b/lib/sidebars/projects/menus/analytics_menu.rb
@@ -10,9 +10,9 @@ module Sidebars
def configure_menu_items
return false unless can?(context.current_user, :read_analytics, context.project)
+ add_item(cycle_analytics_menu_item)
add_item(ci_cd_analytics_menu_item)
add_item(repository_analytics_menu_item)
- add_item(cycle_analytics_menu_item)
true
end
diff --git a/lib/sidebars/projects/menus/infrastructure_menu.rb b/lib/sidebars/projects/menus/infrastructure_menu.rb
index ccc4787601a..1018bdd545b 100644
--- a/lib/sidebars/projects/menus/infrastructure_menu.rb
+++ b/lib/sidebars/projects/menus/infrastructure_menu.rb
@@ -57,9 +57,9 @@ module Sidebars
data: { trigger: 'manual',
container: 'body',
placement: 'right',
- highlight: UserCalloutsHelper::GKE_CLUSTER_INTEGRATION,
- highlight_priority: UserCallout.feature_names[:GKE_CLUSTER_INTEGRATION],
- dismiss_endpoint: user_callouts_path,
+ highlight: Users::CalloutsHelper::GKE_CLUSTER_INTEGRATION,
+ highlight_priority: Users::Callout.feature_names[:GKE_CLUSTER_INTEGRATION],
+ dismiss_endpoint: callouts_path,
auto_devops_help_path: help_page_path('topics/autodevops/index.md') } }
end
@@ -90,7 +90,7 @@ module Sidebars
end
def google_cloud_menu_item
- feature_is_enabled = Feature.enabled?(:incubation_5mp_google_cloud)
+ feature_is_enabled = Feature.enabled?(:incubation_5mp_google_cloud, context.project)
user_has_permissions = can?(context.current_user, :admin_project_google_cloud, context.project)
unless feature_is_enabled && user_has_permissions
@@ -100,7 +100,7 @@ module Sidebars
::Sidebars::MenuItem.new(
title: _('Google Cloud'),
link: project_google_cloud_index_path(context.project),
- active_routes: { controller: :google_cloud },
+ active_routes: { controller: [:google_cloud, :service_accounts] },
item_id: :google_cloud
)
end
diff --git a/lib/sidebars/projects/menus/shimo_menu.rb b/lib/sidebars/projects/menus/shimo_menu.rb
new file mode 100644
index 00000000000..c93c4f6a0a4
--- /dev/null
+++ b/lib/sidebars/projects/menus/shimo_menu.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module Sidebars
+ module Projects
+ module Menus
+ class ShimoMenu < ::Sidebars::Menu
+ override :link
+ def link
+ project_integrations_shimo_path(context.project)
+ end
+
+ override :title
+ def title
+ s_('Shimo|Shimo')
+ end
+
+ override :image_path
+ def image_path
+ 'logos/shimo.svg'
+ end
+
+ override :image_html_options
+ def image_html_options
+ {
+ size: 16
+ }
+ end
+
+ override :render?
+ def render?
+ context.project.has_shimo?
+ end
+
+ override :active_routes
+ def active_routes
+ { controller: :shimo }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/sidebars/projects/panel.rb b/lib/sidebars/projects/panel.rb
index 8fbd71c1543..6bb4fb52e2a 100644
--- a/lib/sidebars/projects/panel.rb
+++ b/lib/sidebars/projects/panel.rb
@@ -32,8 +32,7 @@ module Sidebars
add_menu(Sidebars::Projects::Menus::InfrastructureMenu.new(context))
add_menu(Sidebars::Projects::Menus::PackagesRegistriesMenu.new(context))
add_menu(Sidebars::Projects::Menus::AnalyticsMenu.new(context))
- add_menu(confluence_or_wiki_menu)
- add_menu(Sidebars::Projects::Menus::ExternalWikiMenu.new(context))
+ add_wiki_menus
add_menu(Sidebars::Projects::Menus::SnippetsMenu.new(context))
add_menu(Sidebars::Projects::Menus::SettingsMenu.new(context))
add_invite_members_menu
@@ -46,10 +45,16 @@ module Sidebars
end
end
- def confluence_or_wiki_menu
- confluence_menu = ::Sidebars::Projects::Menus::ConfluenceMenu.new(context)
+ def add_wiki_menus
+ add_menu((third_party_wiki_menu || Sidebars::Projects::Menus::WikiMenu).new(context))
+ add_menu(Sidebars::Projects::Menus::ExternalWikiMenu.new(context))
+ end
+
+ def third_party_wiki_menu
+ wiki_menu_list = [::Sidebars::Projects::Menus::ConfluenceMenu]
+ wiki_menu_list << ::Sidebars::Projects::Menus::ShimoMenu if Feature.enabled?(:shimo_integration, context.project)
- confluence_menu.render? ? confluence_menu : Sidebars::Projects::Menus::WikiMenu.new(context)
+ wiki_menu_list.find { |wiki_menu| wiki_menu.new(context).render? }
end
end
end
diff --git a/lib/tasks/gettext.rake b/lib/tasks/gettext.rake
index e03c78d5a40..17f9414ad52 100644
--- a/lib/tasks/gettext.rake
+++ b/lib/tasks/gettext.rake
@@ -58,6 +58,7 @@ namespace :gettext do
task lint: :environment do
require 'simple_po_parser'
require 'gitlab/utils'
+ require 'parallel'
FastGettext.silence_errors
files = Dir.glob(Rails.root.join('locale/*/gitlab.po'))
@@ -70,7 +71,9 @@ namespace :gettext do
linters.unshift(Gitlab::I18n::PoLinter.new(po_path: pot_file_path))
- failed_linters = linters.select { |linter| linter.errors.any? }
+ failed_linters = Parallel
+ .map(linters, progress: 'Linting po files') { |linter| linter if linter.errors.any? }
+ .compact
if failed_linters.empty?
puts 'All PO files are valid.'
@@ -129,14 +132,6 @@ namespace :gettext do
)
end
- # Disallow HTML from translatable strings
- # See: https://docs.gitlab.com/ee/development/i18n/externalization.html#html
- def html_todolist
- return @html_todolist if defined?(@html_todolist)
-
- @html_todolist = YAML.safe_load(File.read(Rails.root.join('lib/gitlab/i18n/html_todo.yml')))
- end
-
def report_errors_for_file(file, errors_for_file)
puts "Errors in `#{file}`:"
diff --git a/lib/tasks/gitlab/background_migrations.rake b/lib/tasks/gitlab/background_migrations.rake
index c978a2807ca..c7f3d003f9f 100644
--- a/lib/tasks/gitlab/background_migrations.rake
+++ b/lib/tasks/gitlab/background_migrations.rake
@@ -2,6 +2,7 @@
namespace :gitlab do
namespace :background_migrations do
+ desc 'Synchronously finish executing a batched background migration'
task :finalize, [:job_class_name, :table_name, :column_name, :job_arguments] => :environment do |_, args|
[:job_class_name, :table_name, :column_name, :job_arguments].each do |argument|
unless args[argument]
@@ -19,5 +20,23 @@ namespace :gitlab do
puts "Done.".color(:green)
end
+
+ desc 'Display the status of batched background migrations'
+ task status: :environment do
+ statuses = Gitlab::Database::BackgroundMigration::BatchedMigration.statuses
+ max_status_length = statuses.keys.map(&:length).max
+ format_string = "%-#{max_status_length}s | %s\n"
+
+ Gitlab::Database::BackgroundMigration::BatchedMigration.find_each(batch_size: 100) do |migration|
+ identification_fields = [
+ migration.job_class_name,
+ migration.table_name,
+ migration.column_name,
+ migration.job_arguments.to_json
+ ].join(',')
+
+ printf(format_string, migration.status, identification_fields)
+ end
+ end
end
end
diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake
index 0cd4ab354c9..8f033a41e3d 100644
--- a/lib/tasks/gitlab/cleanup.rake
+++ b/lib/tasks/gitlab/cleanup.rake
@@ -100,13 +100,15 @@ namespace :gitlab do
namespace :sessions do
desc "GitLab | Cleanup | Sessions | Clean ActiveSession lookup keys"
task active_sessions_lookup_keys: :gitlab_environment do
- session_key_pattern = "#{Gitlab::Redis::SharedState::USER_SESSIONS_LOOKUP_NAMESPACE}:*"
+ use_redis_session_store = Gitlab::Utils.to_boolean(ENV['GITLAB_USE_REDIS_SESSIONS_STORE'], default: true)
+ redis_store_class = use_redis_session_store ? Gitlab::Redis::Sessions : Gitlab::Redis::SharedState
+ session_key_pattern = "#{Gitlab::Redis::Sessions::USER_SESSIONS_LOOKUP_NAMESPACE}:*"
last_save_check = Time.at(0)
wait_time = 10.seconds
cursor = 0
total_users_scanned = 0
- Gitlab::Redis::SharedState.with do |redis|
+ redis_store_class.with do |redis|
begin
cursor, keys = redis.scan(cursor, match: session_key_pattern)
total_users_scanned += keys.count
@@ -119,27 +121,16 @@ namespace :gitlab do
last_save_check = Time.now
end
+ user = Struct.new(:id)
+
keys.each do |key|
user_id = key.split(':').last
- lookup_key_count = redis.scard(key)
-
- session_ids = ActiveSession.session_ids_for_user(user_id)
- entries = ActiveSession.raw_active_session_entries(redis, session_ids, user_id)
- session_ids_and_entries = session_ids.zip(entries)
-
- inactive_session_ids = session_ids_and_entries.map do |session_id, session|
- session_id if session.nil?
- end.compact
-
- redis.pipelined do |conn|
- inactive_session_ids.each do |session_id|
- conn.srem(key, session_id)
- end
- end
+ removed = []
+ active = ActiveSession.cleaned_up_lookup_entries(redis, user.new(user_id), removed)
- if inactive_session_ids
- puts "deleted #{inactive_session_ids.count} out of #{lookup_key_count} lookup keys for User ##{user_id}"
+ if removed.any?
+ puts "deleted #{removed.count} out of #{active.count + removed.count} lookup keys for User ##{user_id}"
end
end
end while cursor.to_i != 0
diff --git a/lib/tasks/gitlab/db.rake b/lib/tasks/gitlab/db.rake
index e83c4cbdb39..9e733fc3a0f 100644
--- a/lib/tasks/gitlab/db.rake
+++ b/lib/tasks/gitlab/db.rake
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+databases = ActiveRecord::Tasks::DatabaseTasks.setup_initial_database_yaml
+
namespace :gitlab do
namespace :db do
desc 'GitLab | DB | Manually insert schema migration version'
@@ -49,7 +51,7 @@ namespace :gitlab do
# Drop all extra schema objects GitLab owns
Gitlab::Database::EXTRA_SCHEMAS.each do |schema|
- connection.execute("DROP SCHEMA IF EXISTS #{connection.quote_table_name(schema)}")
+ connection.execute("DROP SCHEMA IF EXISTS #{connection.quote_table_name(schema)} CASCADE")
end
end
@@ -83,7 +85,7 @@ namespace :gitlab do
desc 'GitLab | DB | Sets up EE specific database functionality'
if Gitlab.ee?
- task setup_ee: %w[geo:db:drop geo:db:create geo:db:schema:load geo:db:migrate]
+ task setup_ee: %w[db:drop:geo db:create:geo db:schema:load:geo db:migrate:geo]
else
task :setup_ee
end
@@ -116,6 +118,19 @@ namespace :gitlab do
Rake::Task['gitlab:db:clean_structure_sql'].invoke
end
+ ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name|
+ # Inform Rake that custom tasks should be run every time rake db:structure:dump is run
+ #
+ # Rails 6.1 deprecates db:structure:dump in favor of db:schema:dump
+ Rake::Task["db:structure:dump:#{name}"].enhance do
+ Rake::Task['gitlab:db:clean_structure_sql'].invoke
+ end
+
+ Rake::Task["db:schema:dump:#{name}"].enhance do
+ Rake::Task['gitlab:db:clean_structure_sql'].invoke
+ end
+ end
+
desc 'Create missing dynamic database partitions'
task create_dynamic_partitions: :environment do
Gitlab::Database::Partitioning.sync_partitions
@@ -160,24 +175,30 @@ namespace :gitlab do
Rake::Task['gitlab:db:create_dynamic_partitions'].invoke
end
- desc 'execute reindexing without downtime to eliminate bloat'
+ desc "Reindex database without downtime to eliminate bloat"
task reindex: :environment do
- unless Feature.enabled?(:database_reindexing, type: :ops, default_enabled: :yaml)
+ unless Gitlab::Database::Reindexing.enabled?
puts "This feature (database_reindexing) is currently disabled.".color(:yellow)
exit
end
- Gitlab::Database::EachDatabase.each_database_connection do |connection, connection_name|
- Gitlab::Database::SharedModel.logger = Logger.new($stdout) if Gitlab::Utils.to_boolean(ENV['LOG_QUERIES_TO_CONSOLE'], default: false)
+ Gitlab::Database::Reindexing.invoke
+ end
+
+ namespace :reindex do
+ databases = ActiveRecord::Tasks::DatabaseTasks.setup_initial_database_yaml
- # Hack: Before we do actual reindexing work, create async indexes
- Gitlab::Database::AsyncIndexes.create_pending_indexes! if Feature.enabled?(:database_async_index_creation, type: :ops)
+ ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |database_name|
+ desc "Reindex #{database_name} database without downtime to eliminate bloat"
+ task database_name => :environment do
+ unless Gitlab::Database::Reindexing.enabled?
+ puts "This feature (database_reindexing) is currently disabled.".color(:yellow)
+ exit
+ end
- Gitlab::Database::Reindexing.automatic_reindexing
+ Gitlab::Database::Reindexing.invoke(database_name)
+ end
end
- rescue StandardError => e
- Gitlab::AppLogger.error(e)
- raise
end
desc 'Enqueue an index for reindexing'
@@ -243,7 +264,9 @@ namespace :gitlab do
# Only for development environments,
# we execute pending data migrations inline for convenience.
Rake::Task['db:migrate'].enhance do
- Rake::Task['gitlab:db:execute_batched_migrations'].invoke if Rails.env.development?
+ if Rails.env.development? && Gitlab::Database::BackgroundMigration::BatchedMigration.table_exists?
+ Rake::Task['gitlab:db:execute_batched_migrations'].invoke
+ end
end
end
end
diff --git a/lib/tasks/gitlab/gitaly.rake b/lib/tasks/gitlab/gitaly.rake
index eabbb8652f1..b01a7902bf2 100644
--- a/lib/tasks/gitlab/gitaly.rake
+++ b/lib/tasks/gitlab/gitaly.rake
@@ -37,8 +37,8 @@ namespace :gitlab do
raise
end
- desc 'GitLab | Gitaly | Install or upgrade gitaly'
- task :install, [:dir, :storage_path, :repo] => :gitlab_environment do |t, args|
+ desc 'GitLab | Gitaly | Clone and checkout gitaly'
+ task :clone, [:dir, :storage_path, :repo] => :gitlab_environment do |t, args|
warn_user_is_not_gitlab
unless args.dir.present? && args.storage_path.present?
@@ -51,6 +51,11 @@ Usage: rake "gitlab:gitaly:install[/installation/dir,/storage/path]")
version = Gitlab::GitalyClient.expected_server_version
checkout_or_clone_version(version: version, repo: args.repo, target_dir: args.dir, clone_opts: %w[--depth 1])
+ end
+
+ desc 'GitLab | Gitaly | Install or upgrade gitaly'
+ task :install, [:dir, :storage_path, :repo] => [:gitlab_environment, 'gitlab:gitaly:clone'] do |t, args|
+ warn_user_is_not_gitlab
storage_paths = { 'default' => args.storage_path }
Gitlab::SetupHelper::Gitaly.create_configuration(args.dir, storage_paths)
diff --git a/lib/tasks/gitlab/seed/group_seed.rake b/lib/tasks/gitlab/seed/group_seed.rake
index bc705c94422..a9a350fb6c3 100644
--- a/lib/tasks/gitlab/seed/group_seed.rake
+++ b/lib/tasks/gitlab/seed/group_seed.rake
@@ -184,7 +184,7 @@ class GroupSeeder
group = Group.find(group_id)
@resource_count.times do |i|
- _, project_path = PROJECT_URL.split('/')[-2..-1]
+ _, project_path = PROJECT_URL.split('/')[-2..]
project_path.gsub!('.git', '')
diff --git a/lib/tasks/gitlab/sidekiq.rake b/lib/tasks/gitlab/sidekiq.rake
index 2e383065b64..10492e183c5 100644
--- a/lib/tasks/gitlab/sidekiq.rake
+++ b/lib/tasks/gitlab/sidekiq.rake
@@ -100,6 +100,10 @@ namespace :gitlab do
queues_and_weights = Gitlab::SidekiqConfig.queues_for_sidekiq_queues_yml
write_yaml(Gitlab::SidekiqConfig::SIDEKIQ_QUEUES_PATH, banner, queues: queues_and_weights)
+
+ if Gitlab.jh?
+ write_yaml(Gitlab::SidekiqConfig::JH_SIDEKIQ_QUEUES_PATH, banner, queues: Gitlab::SidekiqConfig.jh_queues_for_sidekiq_queues_yml)
+ end
end
desc 'GitLab | Sidekiq | Validate that sidekiq_queues.yml matches worker definitions'
@@ -113,6 +117,7 @@ namespace :gitlab do
Then commit and push the changes from:
- #{Gitlab::SidekiqConfig::SIDEKIQ_QUEUES_PATH}
+ #{"- " + Gitlab::SidekiqConfig::JH_SIDEKIQ_QUEUES_PATH if Gitlab.jh?}
MSG
end
diff --git a/lib/tasks/gitlab/update_templates.rake b/lib/tasks/gitlab/update_templates.rake
index e3a4e7f50b8..247897bed0b 100644
--- a/lib/tasks/gitlab/update_templates.rake
+++ b/lib/tasks/gitlab/update_templates.rake
@@ -32,7 +32,7 @@ namespace :gitlab do
tmp_namespace_path = "tmp-project-import-#{Time.now.to_i}"
puts "Creating temporary namespace #{tmp_namespace_path}"
- tmp_namespace = Namespace.create!(owner: admin, name: tmp_namespace_path, path: tmp_namespace_path)
+ tmp_namespace = Namespace.create!(owner: admin, name: tmp_namespace_path, path: tmp_namespace_path, type: Namespaces::UserNamespace.sti_name)
templates = if template_names.empty?
Gitlab::ProjectTemplate.all
diff --git a/lib/tasks/gitlab/x509/update.rake b/lib/tasks/gitlab/x509/update.rake
index d3c63fa8514..7b7d15479bf 100644
--- a/lib/tasks/gitlab/x509/update.rake
+++ b/lib/tasks/gitlab/x509/update.rake
@@ -12,14 +12,14 @@ namespace :gitlab do
def update_certificates
logger = Logger.new($stdout)
- unless X509CommitSignature.exists?
+ unless CommitSignatures::X509CommitSignature.exists?
logger.info("Unable to find any x509 commit signatures. Exiting.")
return
end
logger.info("Start to update x509 commit signatures")
- X509CommitSignature.find_each do |sig|
+ CommitSignatures::X509CommitSignature.find_each do |sig|
sig.x509_commit&.update_signature!(sig)
end
diff --git a/lib/version_check.rb b/lib/version_check.rb
index a8b7c7371ca..e5a4c244c7a 100644
--- a/lib/version_check.rb
+++ b/lib/version_check.rb
@@ -2,22 +2,60 @@
require "base64"
-# This class is used to build image URL to
-# check if it is a new version for update
class VersionCheck
+ include ReactiveCaching
+
+ self.reactive_cache_work_type = :external_dependency
+ self.reactive_cache_worker_finder = ->(_id, *args) { from_cache }
+
def self.data
{ version: Gitlab::VERSION }
end
- def self.url
+ def self.headers
+ { "REFERER": Gitlab.config.gitlab.url }
+ end
+
+ # This is temporary and will be removed when the new UI is hooked up
+ # to the version_check.json endpoint.
+ def self.image_url
encoded_data = Base64.urlsafe_encode64(data.to_json)
"#{host}/check.svg?gitlab_info=#{encoded_data}"
end
+ def self.url
+ encoded_data = Base64.urlsafe_encode64(data.to_json)
+
+ "#{host}/check.json?gitlab_info=#{encoded_data}"
+ end
+
def self.host
'https://version.gitlab.com'
end
+
+ def self.from_cache(*)
+ new
+ end
+
+ def id
+ Gitlab::VERSION
+ end
+
+ def calculate_reactive_cache(*)
+ response = Gitlab::HTTP.try_get(self.class.url, headers: self.class.headers)
+
+ case response&.code
+ when 200
+ response.body
+ end
+ end
+
+ def response
+ with_reactive_cache do |data|
+ Gitlab::Json.parse(data) if data
+ end
+ end
end
VersionCheck.prepend_mod