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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-12-20 17:22:11 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-12-20 17:22:11 +0300
commit0c872e02b2c822e3397515ec324051ff540f0cd5 (patch)
treece2fb6ce7030e4dad0f4118d21ab6453e5938cdd /lib/gitlab
parentf7e05a6853b12f02911494c4b3fe53d9540d74fc (diff)
Add latest changes from gitlab-org/gitlab@15-7-stable-eev15.7.0-rc42
Diffstat (limited to 'lib/gitlab')
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_events.rb4
-rw-r--r--lib/gitlab/application_context.rb10
-rw-r--r--lib/gitlab/application_rate_limiter.rb5
-rw-r--r--lib/gitlab/audit/auditor.rb44
-rw-r--r--lib/gitlab/audit/type/definition.rb21
-rw-r--r--lib/gitlab/audit/type/shared.rb2
-rw-r--r--lib/gitlab/auth.rb6
-rw-r--r--lib/gitlab/auth/current_user_mode.rb12
-rw-r--r--lib/gitlab/auth/ldap/access.rb2
-rw-r--r--lib/gitlab/auth/ldap/adapter.rb2
-rw-r--r--lib/gitlab/auth/ldap/config.rb3
-rw-r--r--lib/gitlab/auth/ldap/dn.rb4
-rw-r--r--lib/gitlab/background_migration/backfill_environment_tiers.rb40
-rw-r--r--lib/gitlab/background_migration/backfill_note_discussion_id.rb4
-rw-r--r--lib/gitlab/background_migration/backfill_project_statistics_storage_size_without_uploads_size.rb14
-rw-r--r--lib/gitlab/background_migration/batched_migration_job.rb85
-rw-r--r--lib/gitlab/background_migration/delete_orphans_approval_merge_request_rules.rb18
-rw-r--r--lib/gitlab/background_migration/delete_orphans_approval_project_rules.rb16
-rw-r--r--lib/gitlab/background_migration/disable_legacy_open_source_license_for_projects_less_than_five_mb.rb26
-rw-r--r--lib/gitlab/background_migration/fix_approval_project_rules_without_protected_branches.rb15
-rw-r--r--lib/gitlab/background_migration/fix_security_scan_statuses.rb14
-rw-r--r--lib/gitlab/background_migration/migrate_vulnerabilities_feedback_to_vulnerabilities_state_transition.rb13
-rw-r--r--lib/gitlab/background_migration/prune_stale_project_export_jobs.rb17
-rw-r--r--lib/gitlab/background_migration/reset_status_on_container_repositories.rb139
-rw-r--r--lib/gitlab/background_migration/update_jira_tracker_data_deployment_type_based_on_url.rb12
-rw-r--r--lib/gitlab/bitbucket_import/importer.rb2
-rw-r--r--lib/gitlab/bullet.rb2
-rw-r--r--lib/gitlab/changes_list.rb12
-rw-r--r--lib/gitlab/ci/ansi2html.rb4
-rw-r--r--lib/gitlab/ci/build/cache.rb4
-rw-r--r--lib/gitlab/ci/build/context/build.rb20
-rw-r--r--lib/gitlab/ci/build/hook.rb24
-rw-r--r--lib/gitlab/ci/config.rb26
-rw-r--r--lib/gitlab/ci/config/entry/artifacts.rb9
-rw-r--r--lib/gitlab/ci/config/entry/cache.rb16
-rw-r--r--lib/gitlab/ci/config/entry/default.rb26
-rw-r--r--lib/gitlab/ci/config/entry/hooks.rb25
-rw-r--r--lib/gitlab/ci/config/entry/id_token.rb28
-rw-r--r--lib/gitlab/ci/config/entry/job.rb21
-rw-r--r--lib/gitlab/ci/config/entry/reports.rb5
-rw-r--r--lib/gitlab/ci/config/entry/root.rb20
-rw-r--r--lib/gitlab/ci/config/entry/trigger.rb2
-rw-r--r--lib/gitlab/ci/config/entry/variable.rb85
-rw-r--r--lib/gitlab/ci/config/entry/variables.rb2
-rw-r--r--lib/gitlab/ci/config/external/file/base.rb6
-rw-r--r--lib/gitlab/ci/config/external/file/remote.rb2
-rw-r--r--lib/gitlab/ci/config/external/mapper.rb40
-rw-r--r--lib/gitlab/ci/config/external/mapper/base.rb36
-rw-r--r--lib/gitlab/ci/config/external/mapper/filter.rb22
-rw-r--r--lib/gitlab/ci/config/external/mapper/location_expander.rb42
-rw-r--r--lib/gitlab/ci/config/external/mapper/matcher.rb49
-rw-r--r--lib/gitlab/ci/config/external/mapper/normalizer.rb46
-rw-r--r--lib/gitlab/ci/config/external/mapper/variables_expander.rb49
-rw-r--r--lib/gitlab/ci/config/external/mapper/verifier.rb37
-rw-r--r--lib/gitlab/ci/config/external/processor.rb4
-rw-r--r--lib/gitlab/ci/environment_matcher.rb39
-rw-r--r--lib/gitlab/ci/lint.rb10
-rw-r--r--lib/gitlab/ci/parsers/security/common.rb8
-rw-r--r--lib/gitlab/ci/pipeline/chain/build/associations.rb3
-rw-r--r--lib/gitlab/ci/pipeline/chain/cancel_pending_pipelines.rb16
-rw-r--r--lib/gitlab/ci/pipeline/chain/command.rb12
-rw-r--r--lib/gitlab/ci/pipeline/chain/config/process.rb2
-rw-r--r--lib/gitlab/ci/pipeline/chain/create.rb2
-rw-r--r--lib/gitlab/ci/pipeline/chain/ensure_environments.rb2
-rw-r--r--lib/gitlab/ci/pipeline/chain/seed.rb8
-rw-r--r--lib/gitlab/ci/pipeline/logger.rb92
-rw-r--r--lib/gitlab/ci/pipeline/metrics.rb3
-rw-r--r--lib/gitlab/ci/pipeline/seed/build.rb129
-rw-r--r--lib/gitlab/ci/pipeline/seed/build/cache.rb4
-rw-r--r--lib/gitlab/ci/pipeline/seed/pipeline.rb5
-rw-r--r--lib/gitlab/ci/pipeline/seed/stage.rb55
-rw-r--r--lib/gitlab/ci/reports/security/finding.rb6
-rw-r--r--lib/gitlab/ci/reports/security/finding_key.rb2
-rw-r--r--lib/gitlab/ci/reports/security/identifier.rb4
-rw-r--r--lib/gitlab/ci/reports/security/reports.rb23
-rw-r--r--lib/gitlab/ci/reports/test_suite.rb4
-rw-r--r--lib/gitlab/ci/runner_instructions.rb13
-rw-r--r--lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml10
-rw-r--r--lib/gitlab/ci/templates/Gradle.gitlab-ci.yml7
-rw-r--r--lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.latest.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml3
-rw-r--r--lib/gitlab/ci/templates/Jobs/Container-Scanning.gitlab-ci.yml54
-rw-r--r--lib/gitlab/ci/templates/Jobs/Container-Scanning.latest.gitlab-ci.yml68
-rw-r--r--lib/gitlab/ci/templates/Jobs/Load-Performance-Testing.gitlab-ci.yml4
-rw-r--r--lib/gitlab/ci/templates/Jobs/SAST.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Jobs/SAST.latest.gitlab-ci.yml4
-rw-r--r--lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml57
-rw-r--r--lib/gitlab/ci/templates/Security/Container-Scanning.latest.gitlab-ci.yml71
-rw-r--r--lib/gitlab/ci/templates/Security/Coverage-Fuzzing.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Security/Coverage-Fuzzing.latest.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml3
-rw-r--r--lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml6
-rw-r--r--lib/gitlab/ci/templates/Security/Secure-Binaries.gitlab-ci.yml6
-rw-r--r--lib/gitlab/ci/templates/Verify/Browser-Performance.gitlab-ci.yml4
-rw-r--r--lib/gitlab/ci/templates/Verify/Browser-Performance.latest.gitlab-ci.yml4
-rw-r--r--lib/gitlab/ci/templates/Verify/Load-Performance-Testing.gitlab-ci.yml6
-rw-r--r--lib/gitlab/ci/variables/builder.rb5
-rw-r--r--lib/gitlab/ci/yaml_processor/result.rb2
-rw-r--r--lib/gitlab/cluster/rack_timeout_observer.rb7
-rw-r--r--lib/gitlab/color.rb12
-rw-r--r--lib/gitlab/config/entry/attributable.rb12
-rw-r--r--lib/gitlab/conflict/file.rb12
-rw-r--r--lib/gitlab/content_security_policy/config_loader.rb16
-rw-r--r--lib/gitlab/contributions_calendar.rb2
-rw-r--r--lib/gitlab/counters/buffered_counter.rb113
-rw-r--r--lib/gitlab/counters/legacy_counter.rb34
-rw-r--r--lib/gitlab/data_builder/deployment.rb2
-rw-r--r--lib/gitlab/database.rb3
-rw-r--r--lib/gitlab/database/bulk_update.rb2
-rw-r--r--lib/gitlab/database/count/exact_count_strategy.rb4
-rw-r--r--lib/gitlab/database/gitlab_schema.rb42
-rw-r--r--lib/gitlab/database/gitlab_schemas.yml606
-rw-r--r--lib/gitlab/database/load_balancing/connection_proxy.rb2
-rw-r--r--lib/gitlab/database/load_balancing/service_discovery.rb7
-rw-r--r--lib/gitlab/database/load_balancing/sidekiq_client_middleware.rb2
-rw-r--r--lib/gitlab/database/load_balancing/sidekiq_server_middleware.rb8
-rw-r--r--lib/gitlab/database/lock_writes_manager.rb22
-rw-r--r--lib/gitlab/database/migration.rb6
-rw-r--r--lib/gitlab/database/migration_helpers.rb44
-rw-r--r--lib/gitlab/database/migration_helpers/automatic_lock_writes_on_tables.rb75
-rw-r--r--lib/gitlab/database/migrations/batched_migration_last_id.rb50
-rw-r--r--lib/gitlab/database/migrations/runner.rb28
-rw-r--r--lib/gitlab/database/migrations/sidekiq_helpers.rb112
-rw-r--r--lib/gitlab/database/migrations/test_batched_background_runner.rb9
-rw-r--r--lib/gitlab/database/obsolete_ignored_columns.rb4
-rw-r--r--lib/gitlab/database/partitioning/single_numeric_list_partition.rb2
-rw-r--r--lib/gitlab/database/postgres_hll/buckets.rb2
-rw-r--r--lib/gitlab/database/query_analyzers/prevent_cross_database_modification.rb4
-rw-r--r--lib/gitlab/database/query_analyzers/query_recorder.rb16
-rw-r--r--lib/gitlab/database/schema_cache_with_renamed_table.rb6
-rw-r--r--lib/gitlab/database/schema_cleaner.rb18
-rw-r--r--lib/gitlab/database/tables_sorted_by_foreign_keys.rb27
-rw-r--r--lib/gitlab/database/tables_truncate.rb42
-rw-r--r--lib/gitlab/database/type/indifferent_jsonb.rb28
-rw-r--r--lib/gitlab/database_importers/work_items/hierarchy_restrictions_importer.rb37
-rw-r--r--lib/gitlab/diff/file_collection/compare.rb8
-rw-r--r--lib/gitlab/diff/file_collection/merge_request_diff_batch.rb35
-rw-r--r--lib/gitlab/diff/file_collection/paginated_diffs.rb48
-rw-r--r--lib/gitlab/diff/file_collection/paginated_merge_request_diff.rb35
-rw-r--r--lib/gitlab/diff/parser.rb2
-rw-r--r--lib/gitlab/email/receiver.rb2
-rw-r--r--lib/gitlab/error_tracking/error_repository/open_api_strategy.rb2
-rw-r--r--lib/gitlab/favicon.rb4
-rw-r--r--lib/gitlab/gfm/uploads_rewriter.rb2
-rw-r--r--lib/gitlab/git.rb1
-rw-r--r--lib/gitlab/git/base_error.rb46
-rw-r--r--lib/gitlab/git/cross_repo.rb (renamed from lib/gitlab/git/cross_repo_comparer.rb)13
-rw-r--r--lib/gitlab/git/repository.rb41
-rw-r--r--lib/gitlab/git_access.rb10
-rw-r--r--lib/gitlab/gitaly_client/commit_service.rb2
-rw-r--r--lib/gitlab/gitaly_client/namespace_service.rb7
-rw-r--r--lib/gitlab/gitaly_client/operation_service.rb20
-rw-r--r--lib/gitlab/gitaly_client/ref_service.rb26
-rw-r--r--lib/gitlab/gitaly_client/with_feature_flag_actors.rb11
-rw-r--r--lib/gitlab/github_gists_import/importer/gist_importer.rb84
-rw-r--r--lib/gitlab/github_gists_import/importer/gists_importer.rb95
-rw-r--r--lib/gitlab/github_gists_import/representation/gist.rb71
-rw-r--r--lib/gitlab/github_gists_import/status.rb43
-rw-r--r--lib/gitlab/github_import/bulk_importing.rb48
-rw-r--r--lib/gitlab/github_import/client.rb14
-rw-r--r--lib/gitlab/github_import/clients/proxy.rb59
-rw-r--r--lib/gitlab/github_import/clients/search_repos.rb66
-rw-r--r--lib/gitlab/github_import/importer/diff_note_importer.rb10
-rw-r--r--lib/gitlab/github_import/importer/issue_importer.rb3
-rw-r--r--lib/gitlab/github_import/importer/label_links_importer.rb8
-rw-r--r--lib/gitlab/github_import/importer/labels_importer.rb11
-rw-r--r--lib/gitlab/github_import/importer/lfs_objects_importer.rb4
-rw-r--r--lib/gitlab/github_import/importer/milestones_importer.rb11
-rw-r--r--lib/gitlab/github_import/importer/note_importer.rb3
-rw-r--r--lib/gitlab/github_import/importer/pull_request_importer.rb5
-rw-r--r--lib/gitlab/github_import/importer/pull_request_merged_by_importer.rb54
-rw-r--r--lib/gitlab/github_import/importer/pull_request_review_importer.rb10
-rw-r--r--lib/gitlab/github_import/importer/releases_importer.rb11
-rw-r--r--lib/gitlab/github_import/markdown/attachment.rb4
-rw-r--r--lib/gitlab/github_import/page_counter.rb6
-rw-r--r--lib/gitlab/github_import/representation/diff_note.rb44
-rw-r--r--lib/gitlab/github_import/representation/diff_notes/discussion_id.rb57
-rw-r--r--lib/gitlab/gl_repository/repo_type.rb8
-rw-r--r--lib/gitlab/gon_helper.rb2
-rw-r--r--lib/gitlab/graphql/expose_permissions.rb8
-rw-r--r--lib/gitlab/graphql/extensions/forward_only_externally_paginated_array_extension.rb19
-rw-r--r--lib/gitlab/graphql/limit/field_call_count.rb13
-rw-r--r--lib/gitlab/graphql/pagination/keyset/connection.rb21
-rw-r--r--lib/gitlab/group_search_results.rb16
-rw-r--r--lib/gitlab/http.rb2
-rw-r--r--lib/gitlab/http_connection_adapter.rb3
-rw-r--r--lib/gitlab/i18n.rb20
-rw-r--r--lib/gitlab/import_export/base/relation_factory.rb32
-rw-r--r--lib/gitlab/import_export/decompressed_archive_size_validator.rb2
-rw-r--r--lib/gitlab/import_export/group/import_export.yml20
-rw-r--r--lib/gitlab/import_export/json/legacy_reader.rb2
-rw-r--r--lib/gitlab/import_export/lfs_restorer.rb2
-rw-r--r--lib/gitlab/import_export/members_mapper.rb16
-rw-r--r--lib/gitlab/import_export/project/import_export.yml17
-rw-r--r--lib/gitlab/import_export/project/tree_saver.rb16
-rw-r--r--lib/gitlab/import_export/remote_stream_upload.rb2
-rw-r--r--lib/gitlab/import_export/repo_restorer.rb5
-rw-r--r--lib/gitlab/import_sources.rb1
-rw-r--r--lib/gitlab/incident_management/pager_duty/incident_issue_description.rb9
-rw-r--r--lib/gitlab/instrumentation/redis.rb3
-rw-r--r--lib/gitlab/instrumentation/redis_base.rb39
-rw-r--r--lib/gitlab/instrumentation/redis_cluster_validator.rb27
-rw-r--r--lib/gitlab/instrumentation/redis_interceptor.rb17
-rw-r--r--lib/gitlab/instrumentation/redis_payload.rb2
-rw-r--r--lib/gitlab/instrumentation_helper.rb9
-rw-r--r--lib/gitlab/issuable_metadata.rb4
-rw-r--r--lib/gitlab/jira/http_client.rb6
-rw-r--r--lib/gitlab/jira_import/issues_importer.rb2
-rw-r--r--lib/gitlab/jwt_authenticatable.rb2
-rw-r--r--lib/gitlab/jwt_token.rb2
-rw-r--r--lib/gitlab/kubernetes/helm/v2/install_command.rb14
-rw-r--r--lib/gitlab/kubernetes/helm/v2/patch_command.rb8
-rw-r--r--lib/gitlab/kubernetes/helm/v3/install_command.rb14
-rw-r--r--lib/gitlab/kubernetes/helm/v3/patch_command.rb8
-rw-r--r--lib/gitlab/kubernetes/kube_client.rb2
-rw-r--r--lib/gitlab/memory/jemalloc.rb33
-rw-r--r--lib/gitlab/memory/reporter.rb130
-rw-r--r--lib/gitlab/memory/reports/heap_dump.rb35
-rw-r--r--lib/gitlab/memory/reports/jemalloc_stats.rb59
-rw-r--r--lib/gitlab/memory/reports_daemon.rb63
-rw-r--r--lib/gitlab/memory/watchdog.rb89
-rw-r--r--lib/gitlab/memory/watchdog/configuration.rb20
-rw-r--r--lib/gitlab/memory/watchdog/configurator.rb64
-rw-r--r--lib/gitlab/memory/watchdog/event_reporter.rb68
-rw-r--r--lib/gitlab/memory/watchdog/monitor/heap_fragmentation.rb5
-rw-r--r--lib/gitlab/memory/watchdog/monitor/rss_memory_limit.rb27
-rw-r--r--lib/gitlab/memory/watchdog/monitor_state.rb19
-rw-r--r--lib/gitlab/memory/watchdog/sidekiq_event_reporter.rb53
-rw-r--r--lib/gitlab/merge_requests/message_generator.rb (renamed from lib/gitlab/merge_requests/commit_message_generator.rb)73
-rw-r--r--lib/gitlab/metrics.rb4
-rw-r--r--lib/gitlab/metrics/dashboard/importers/prometheus_metrics.rb8
-rw-r--r--lib/gitlab/metrics/dashboard/validator.rb2
-rw-r--r--lib/gitlab/metrics/global_search_slis.rb5
-rw-r--r--lib/gitlab/metrics/rails_slis.rb11
-rw-r--r--lib/gitlab/metrics/requests_rack_middleware.rb25
-rw-r--r--lib/gitlab/metrics/subscribers/ldap.rb103
-rw-r--r--lib/gitlab/metrics/subscribers/rails_cache.rb11
-rw-r--r--lib/gitlab/middleware/compressed_json.rb27
-rw-r--r--lib/gitlab/middleware/go.rb4
-rw-r--r--lib/gitlab/other_markup.rb22
-rw-r--r--lib/gitlab/pages/cache_control.rb66
-rw-r--r--lib/gitlab/pagination/cursor_based_keyset.rb2
-rw-r--r--lib/gitlab/pagination/offset_pagination.rb17
-rw-r--r--lib/gitlab/patch/prependable.rb2
-rw-r--r--lib/gitlab/phabricator_import/project_creator.rb10
-rw-r--r--lib/gitlab/process_management.rb9
-rw-r--r--lib/gitlab/process_supervisor.rb2
-rw-r--r--lib/gitlab/profiler.rb6
-rw-r--r--lib/gitlab/project_search_results.rb22
-rw-r--r--lib/gitlab/project_template.rb4
-rw-r--r--lib/gitlab/prometheus_client.rb2
-rw-r--r--lib/gitlab/quick_actions/issuable_actions.rb2
-rw-r--r--lib/gitlab/quick_actions/issue_actions.rb4
-rw-r--r--lib/gitlab/quick_actions/issue_and_merge_request_actions.rb6
-rw-r--r--lib/gitlab/rack_attack.rb2
-rw-r--r--lib/gitlab/rack_attack/request.rb76
-rw-r--r--lib/gitlab/redis/multi_store.rb2
-rw-r--r--lib/gitlab/redis/wrapper.rb15
-rw-r--r--lib/gitlab/reference_extractor.rb5
-rw-r--r--lib/gitlab/repository_size_error_message.rb2
-rw-r--r--lib/gitlab/safe_request_store.rb2
-rw-r--r--lib/gitlab/shell.rb4
-rw-r--r--lib/gitlab/sidekiq_daemon/memory_killer.rb28
-rw-r--r--lib/gitlab/sidekiq_daemon/monitor.rb21
-rw-r--r--lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job.rb2
-rw-r--r--lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies.rb6
-rw-r--r--lib/gitlab/sidekiq_status.rb2
-rw-r--r--lib/gitlab/slash_commands/application_help.rb11
-rw-r--r--lib/gitlab/slash_commands/command.rb6
-rw-r--r--lib/gitlab/slash_commands/deploy.rb2
-rw-r--r--lib/gitlab/sql/pattern.rb10
-rw-r--r--lib/gitlab/ssh/signature.rb20
-rw-r--r--lib/gitlab/task_helpers.rb12
-rw-r--r--lib/gitlab/template/base_template.rb4
-rw-r--r--lib/gitlab/timeless.rb4
-rw-r--r--lib/gitlab/tracking/destinations/snowplow.rb28
-rw-r--r--lib/gitlab/tracking/incident_management.rb2
-rw-r--r--lib/gitlab/tracking/service_ping_context.rb48
-rw-r--r--lib/gitlab/url_blocker.rb6
-rw-r--r--lib/gitlab/usage/metrics/aggregates.rb1
-rw-r--r--lib/gitlab/usage/metrics/aggregates/aggregate.rb2
-rw-r--r--lib/gitlab/usage/metrics/aggregates/sources/calculations/intersection.rb6
-rw-r--r--lib/gitlab/usage/metrics/instrumentations/count_merge_request_authors_metric.rb15
-rw-r--r--lib/gitlab/usage/metrics/instrumentations/database_metric.rb2
-rw-r--r--lib/gitlab/usage/service_ping/payload_keys_processor.rb4
-rw-r--r--lib/gitlab/usage/time_frame.rb4
-rw-r--r--lib/gitlab/usage_data.rb32
-rw-r--r--lib/gitlab/usage_data_counters/editor_unique_counter.rb11
-rw-r--r--lib/gitlab/usage_data_counters/hll_redis_counter.rb60
-rw-r--r--lib/gitlab/usage_data_counters/issue_activity_unique_counter.rb11
-rw-r--r--lib/gitlab/usage_data_counters/known_events/ci_templates.yml12
-rw-r--r--lib/gitlab/usage_data_counters/known_events/code_review_events.yml30
-rw-r--r--lib/gitlab/usage_data_counters/known_events/common.yml1
-rw-r--r--lib/gitlab/usage_data_counters/merge_request_activity_unique_counter.rb33
-rw-r--r--lib/gitlab/usage_data_counters/merge_request_widget_extension_counter.rb11
-rw-r--r--lib/gitlab/utils/delegator_override/validator.rb2
-rw-r--r--lib/gitlab/utils/override.rb4
-rw-r--r--lib/gitlab/utils/sanitize_node_link.rb2
-rw-r--r--lib/gitlab/utils/strong_memoize.rb23
-rw-r--r--lib/gitlab/visibility_level.rb2
-rw-r--r--lib/gitlab/work_items/work_item_hierarchy.rb48
-rw-r--r--lib/gitlab/workhorse.rb10
-rw-r--r--lib/gitlab/x509/signature.rb17
304 files changed, 4278 insertions, 2308 deletions
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events.rb b/lib/gitlab/analytics/cycle_analytics/stage_events.rb
index b7a11bc0418..9ac433944a8 100644
--- a/lib/gitlab/analytics/cycle_analytics/stage_events.rb
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events.rb
@@ -80,6 +80,10 @@ module Gitlab
def self.internal_events
INTERNAL_EVENTS
end
+
+ def self.selectable_events
+ (events - internal_events).sort_by(&:name)
+ end
end
end
end
diff --git a/lib/gitlab/application_context.rb b/lib/gitlab/application_context.rb
index b6ad25e700b..06ce1dbdc77 100644
--- a/lib/gitlab/application_context.rb
+++ b/lib/gitlab/application_context.rb
@@ -164,7 +164,11 @@ module Gitlab
end
def include_client?
- set_values.include?(:user) || set_values.include?(:runner) || set_values.include?(:remote_ip)
+ # Don't overwrite an existing more specific client id with an `ip/` one.
+ original_client_id = self.class.current_context_attribute(:client_id).to_s
+ return false if original_client_id.starts_with?('user/') || original_client_id.starts_with?('runner/')
+
+ include_user? || set_values.include?(:runner) || set_values.include?(:remote_ip)
end
def include_user?
@@ -178,8 +182,8 @@ module Gitlab
def client
if runner
"runner/#{runner.id}"
- elsif user
- "user/#{user.id}"
+ elsif user_id
+ "user/#{user_id}"
else
"ip/#{remote_ip}"
end
diff --git a/lib/gitlab/application_rate_limiter.rb b/lib/gitlab/application_rate_limiter.rb
index 507f94d87a5..5b1bf99e297 100644
--- a/lib/gitlab/application_rate_limiter.rb
+++ b/lib/gitlab/application_rate_limiter.rb
@@ -51,8 +51,11 @@ module Gitlab
project_testing_integration: { threshold: 5, interval: 1.minute },
email_verification: { threshold: 10, interval: 10.minutes },
email_verification_code_send: { threshold: 10, interval: 1.hour },
+ phone_verification_send_code: { threshold: 10, interval: 1.hour },
+ phone_verification_verify_code: { threshold: 10, interval: 10.minutes },
namespace_exists: { threshold: 20, interval: 1.minute },
- fetch_google_ip_list: { threshold: 10, interval: 1.minute }
+ fetch_google_ip_list: { threshold: 10, interval: 1.minute },
+ jobs_index: { threshold: 600, interval: 1.minute }
}.freeze
end
diff --git a/lib/gitlab/audit/auditor.rb b/lib/gitlab/audit/auditor.rb
index 4a6e4e2e06e..fddc1f830aa 100644
--- a/lib/gitlab/audit/auditor.rb
+++ b/lib/gitlab/audit/auditor.rb
@@ -59,7 +59,8 @@ module Gitlab
@context = context
@name = @context.fetch(:name, 'audit_operation')
- @stream_only = @context.fetch(:stream_only, false)
+ @is_audit_event_yaml_defined = Gitlab::Audit::Type::Definition.defined?(@name)
+ @stream_only = stream_only?
@author = @context.fetch(:author)
@scope = @context.fetch(:scope)
@target = @context.fetch(:target)
@@ -70,6 +71,14 @@ module Gitlab
@target_details = @context[:target_details]
@authentication_event = @context.fetch(:authentication_event, false)
@authentication_provider = @context[:authentication_provider]
+
+ # TODO: Remove this code once we close https://gitlab.com/gitlab-org/gitlab/-/issues/367870
+ return unless @is_audit_event_yaml_defined
+
+ # rubocop:disable Gitlab/RailsLogger
+ Rails.logger.warn('WARNING: Logging audit events without an event type definition will be deprecated soon.')
+ Rails.logger.warn('See https://docs.gitlab.com/ee/development/audit_event_guide/#event-type-definitions')
+ # rubocop:enable Gitlab/RailsLogger
end
def single_audit
@@ -84,14 +93,23 @@ module Gitlab
end
def record(events)
- log_events(events) unless @stream_only
- send_to_stream(events)
+ @stream_only ? send_to_stream(events) : log_events_and_stream(events)
end
- def log_events(events)
+ def log_events_and_stream(events)
log_authentication_event
- log_to_database(events)
+ saved_events = log_to_database(events)
+
+ # we only want to override events with saved_events when it successfully saves into database.
+ # we are doing so to ensure events in memory reflects events saved in database and have id column.
+ events = saved_events if saved_events.present?
+
+ log_to_file_and_stream(events)
+ end
+
+ def log_to_file_and_stream(events)
log_to_file(events)
+ send_to_stream(events)
end
def audit_enabled?
@@ -102,6 +120,14 @@ module Gitlab
@authentication_event
end
+ def stream_only?
+ if @is_audit_event_yaml_defined
+ Gitlab::Audit::Type::Definition.stream_only?(@name)
+ else
+ @context.fetch(:stream_only, false)
+ end
+ end
+
def log_authentication_event
return unless Gitlab::Database.read_write? && authentication_event?
@@ -145,7 +171,13 @@ module Gitlab
end
def log_to_database(events)
- AuditEvent.bulk_insert!(events)
+ if events.one?
+ events.first.save!
+ events
+ else
+ event_ids = AuditEvent.bulk_insert!(events, returns: :ids)
+ AuditEvent.id_in(event_ids)
+ end
rescue ActiveRecord::RecordInvalid => e
::Gitlab::ErrorTracking.track_exception(e, audit_operation: @name)
end
diff --git a/lib/gitlab/audit/type/definition.rb b/lib/gitlab/audit/type/definition.rb
index f64f66f4ca4..81c88a3a0ae 100644
--- a/lib/gitlab/audit/type/definition.rb
+++ b/lib/gitlab/audit/type/definition.rb
@@ -59,19 +59,36 @@ module Gitlab
end
class << self
+ include ::Gitlab::Utils::StrongMemoize
+
def paths
@paths ||= [Rails.root.join('config', 'audit_events', 'types', '*.yml')]
end
def definitions
- # We lazily load all definitions
- @definitions ||= load_all!
+ load_all!
end
+ strong_memoize_attr :definitions
def get(key)
definitions[key.to_sym]
end
+ def event_names
+ definitions.keys.map(&:to_s)
+ end
+
+ def defined?(key)
+ get(key).present?
+ end
+
+ def stream_only?(key)
+ event_definition = get(key)
+ return false unless event_definition
+
+ event_definition.streamed && !event_definition.saved_to_database
+ end
+
private
def load_all!
diff --git a/lib/gitlab/audit/type/shared.rb b/lib/gitlab/audit/type/shared.rb
index 999b7de13e2..1e3e26d3735 100644
--- a/lib/gitlab/audit/type/shared.rb
+++ b/lib/gitlab/audit/type/shared.rb
@@ -14,7 +14,7 @@ module Gitlab
description
introduced_by_issue
introduced_by_mr
- group
+ feature_category
milestone
saved_to_database
streamed
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index c567df8e133..7e8f9c76dea 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -135,15 +135,13 @@ module Gitlab
# it is important to reset the ban counter once the client has proven
# they are not a 'bad guy'.
rate_limiter.reset!
- else
+ elsif rate_limiter.register_fail!
# Register a login failure so that Rack::Attack can block the next
# request from this IP if needed.
# This returns true when the failures are over the threshold and the IP
# is banned.
- if rate_limiter.register_fail!
- Gitlab::AppLogger.info "IP #{rate_limiter.ip} failed to login " \
+ Gitlab::AppLogger.info "IP #{rate_limiter.ip} failed to login " \
"as #{login} but has been temporarily banned from Git auth"
- end
end
end
diff --git a/lib/gitlab/auth/current_user_mode.rb b/lib/gitlab/auth/current_user_mode.rb
index fc391543f4d..9bd4711c4bb 100644
--- a/lib/gitlab/auth/current_user_mode.rb
+++ b/lib/gitlab/auth/current_user_mode.rb
@@ -106,8 +106,8 @@ module Gitlab
end
def enable_admin_mode!(password: nil, skip_password_validation: false)
- return unless user&.admin?
- return unless skip_password_validation || user&.valid_password?(password)
+ return false unless user&.admin?
+ return false unless skip_password_validation || user&.valid_password?(password)
raise NotRequestedError unless admin_mode_requested?
@@ -115,6 +115,10 @@ module Gitlab
current_session_data[ADMIN_MODE_REQUESTED_TIME_KEY] = nil
current_session_data[ADMIN_MODE_START_TIME_KEY] = Time.now
+
+ audit_user_enable_admin_mode
+
+ true
end
def disable_admin_mode!
@@ -175,6 +179,10 @@ module Gitlab
def privileged_runtime?
Gitlab::Runtime.rake? || Gitlab::Runtime.rails_runner? || Gitlab::Runtime.console?
end
+
+ def audit_user_enable_admin_mode; end
end
end
end
+
+Gitlab::Auth::CurrentUserMode.prepend_mod_with('Gitlab::Auth::CurrentUserMode')
diff --git a/lib/gitlab/auth/ldap/access.rb b/lib/gitlab/auth/ldap/access.rb
index 62a817d7c4d..ea098ff8057 100644
--- a/lib/gitlab/auth/ldap/access.rb
+++ b/lib/gitlab/auth/ldap/access.rb
@@ -12,7 +12,7 @@ module Gitlab
def self.open(user, &block)
Gitlab::Auth::Ldap::Adapter.open(user.ldap_identity.provider) do |adapter|
- block.call(self.new(user, adapter))
+ yield(self.new(user, adapter))
end
end
diff --git a/lib/gitlab/auth/ldap/adapter.rb b/lib/gitlab/auth/ldap/adapter.rb
index 47eca74aa5b..9aedc131e92 100644
--- a/lib/gitlab/auth/ldap/adapter.rb
+++ b/lib/gitlab/auth/ldap/adapter.rb
@@ -11,7 +11,7 @@ module Gitlab
def self.open(provider, &block)
Net::LDAP.open(config(provider).adapter_options) do |ldap|
- block.call(self.new(provider, ldap))
+ yield(self.new(provider, ldap))
end
end
diff --git a/lib/gitlab/auth/ldap/config.rb b/lib/gitlab/auth/ldap/config.rb
index 9dafd59561a..6c99b505797 100644
--- a/lib/gitlab/auth/ldap/config.rb
+++ b/lib/gitlab/auth/ldap/config.rb
@@ -82,7 +82,8 @@ module Gitlab
def adapter_options
opts = base_options.merge(
- encryption: encryption_options
+ encryption: encryption_options,
+ instrumentation_service: ActiveSupport::Notifications
)
opts.merge!(auth_options) if has_auth?
diff --git a/lib/gitlab/auth/ldap/dn.rb b/lib/gitlab/auth/ldap/dn.rb
index a188aa168c1..84bf455c98a 100644
--- a/lib/gitlab/auth/ldap/dn.rb
+++ b/lib/gitlab/auth/ldap/dn.rb
@@ -51,7 +51,7 @@ module Gitlab
##
# Parse a DN into key value pairs using ASN from
- # http://tools.ietf.org/html/rfc2253 section 3.
+ # https://www.rfc-editor.org/rfc/rfc2253 section 3.
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
@@ -231,7 +231,7 @@ module Gitlab
self.class.new(*to_a).to_s.downcase
end
- # https://tools.ietf.org/html/rfc4514 section 2.4 lists these exceptions
+ # https://www.rfc-editor.org/rfc/rfc4514 section 2.4 lists these exceptions
# for DN values. All of the following must be escaped in any normal string
# using a single backslash ('\') as escape. The space character is left
# out here because in a "normalized" string, spaces should only be escaped
diff --git a/lib/gitlab/background_migration/backfill_environment_tiers.rb b/lib/gitlab/background_migration/backfill_environment_tiers.rb
new file mode 100644
index 00000000000..6f381577274
--- /dev/null
+++ b/lib/gitlab/background_migration/backfill_environment_tiers.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # This class backfills the `environments.tier` column by using `guess_tier` logic.
+ # Environments created after 13.10 already have a value, however, environments created before 13.10 don't.
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/300741 for more information.
+ class BackfillEnvironmentTiers < BatchedMigrationJob
+ operation_name :backfill_environment_tiers
+
+ # Equivalent to `Environment#guess_tier` pattern matching.
+ PRODUCTION_TIER = 0
+ STAGING_TIER = 1
+ TESTING_TIER = 2
+ DEVELOPMENT_TIER = 3
+ OTHER_TIER = 4
+
+ TIER_REGEXP_PAIR = [
+ { tier: DEVELOPMENT_TIER, regexp: '(dev|review|trunk)' },
+ { tier: TESTING_TIER, regexp: '(test|tst|int|ac(ce|)pt|qa|qc|control|quality)' },
+ { tier: STAGING_TIER, regexp: '(st(a|)g|mod(e|)l|pre|demo|non)' },
+ { tier: PRODUCTION_TIER, regexp: '(pr(o|)d|live)' }
+ ].freeze
+
+ def perform
+ TIER_REGEXP_PAIR.each do |pair|
+ each_sub_batch(
+ batching_scope: ->(relation) { relation.where(tier: nil).where("name ~* '#{pair[:regexp]}'") } # rubocop:disable GitlabSecurity/SqlInjection
+ ) do |sub_batch|
+ sub_batch.update_all(tier: pair[:tier])
+ end
+ end
+
+ each_sub_batch(batching_scope: ->(relation) { relation.where(tier: nil) }) do |sub_batch|
+ sub_batch.update_all(tier: OTHER_TIER)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/backfill_note_discussion_id.rb b/lib/gitlab/background_migration/backfill_note_discussion_id.rb
index da2c31ebd11..ce2698b3cb8 100644
--- a/lib/gitlab/background_migration/backfill_note_discussion_id.rb
+++ b/lib/gitlab/background_migration/backfill_note_discussion_id.rb
@@ -33,8 +33,8 @@ module Gitlab
private
def update_discussion_ids(notes)
- mapping = notes.each_with_object({}) do |note, hash|
- hash[note] = { discussion_id: note.generate_discussion_id }
+ mapping = notes.index_with do |note|
+ { discussion_id: note.generate_discussion_id }
end
Gitlab::Database::BulkUpdate.execute(%i(discussion_id), mapping)
diff --git a/lib/gitlab/background_migration/backfill_project_statistics_storage_size_without_uploads_size.rb b/lib/gitlab/background_migration/backfill_project_statistics_storage_size_without_uploads_size.rb
new file mode 100644
index 00000000000..1a3dd88ea31
--- /dev/null
+++ b/lib/gitlab/background_migration/backfill_project_statistics_storage_size_without_uploads_size.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # Back-fill storage_size for project_statistics
+ class BackfillProjectStatisticsStorageSizeWithoutUploadsSize < Gitlab::BackgroundMigration::BatchedMigrationJob
+ def perform
+ # no-op
+ end
+ end
+ end
+end
+
+Gitlab::BackgroundMigration::BackfillProjectStatisticsStorageSizeWithoutUploadsSize.prepend_mod_with('Gitlab::BackgroundMigration::BackfillProjectStatisticsStorageSizeWithoutUploadsSize') # rubocop:disable Layout/LineLength
diff --git a/lib/gitlab/background_migration/batched_migration_job.rb b/lib/gitlab/background_migration/batched_migration_job.rb
index 64401bc0674..973ab20f547 100644
--- a/lib/gitlab/background_migration/batched_migration_job.rb
+++ b/lib/gitlab/background_migration/batched_migration_job.rb
@@ -9,55 +9,68 @@ module Gitlab
# see https://docs.gitlab.com/ee/development/database/batched_background_migrations.html#job-arguments.
class BatchedMigrationJob
include Gitlab::Database::DynamicModelHelpers
+ include Gitlab::ClassAttributes
- def initialize(
- start_id:, end_id:, batch_table:, batch_column:, sub_batch_size:, pause_ms:, job_arguments: [], connection:
- )
+ DEFAULT_FEATURE_CATEGORY = :database
- @start_id = start_id
- @end_id = end_id
- @batch_table = batch_table
- @batch_column = batch_column
- @sub_batch_size = sub_batch_size
- @pause_ms = pause_ms
- @job_arguments = job_arguments
- @connection = connection
- end
+ class << self
+ def generic_instance(batch_table:, batch_column:, job_arguments: [], connection:)
+ new(
+ batch_table: batch_table, batch_column: batch_column,
+ job_arguments: job_arguments, connection: connection,
+ start_id: 0, end_id: 0, sub_batch_size: 0, pause_ms: 0
+ )
+ end
- def self.generic_instance(batch_table:, batch_column:, job_arguments: [], connection:)
- new(
- batch_table: batch_table, batch_column: batch_column,
- job_arguments: job_arguments, connection: connection,
- start_id: 0, end_id: 0, sub_batch_size: 0, pause_ms: 0
- )
- end
+ def job_arguments_count
+ 0
+ end
- def self.job_arguments_count
- 0
- end
+ def operation_name(operation)
+ define_method('operation_name') do
+ operation
+ end
+ end
- def self.operation_name(operation)
- define_method('operation_name') do
- operation
+ def job_arguments(*args)
+ args.each.with_index do |arg, index|
+ define_method(arg) do
+ @job_arguments[index]
+ end
+ end
+
+ define_singleton_method(:job_arguments_count) do
+ args.count
+ end
end
- end
- def self.job_arguments(*args)
- args.each.with_index do |arg, index|
- define_method(arg) do
- @job_arguments[index]
+ def scope_to(scope)
+ define_method(:filter_batch) do |relation|
+ instance_exec(relation, &scope)
end
end
- define_singleton_method(:job_arguments_count) do
- args.count
+ def feature_category(feature_category_name = nil)
+ if feature_category_name.present?
+ set_class_attribute(:feature_category, feature_category_name)
+ else
+ get_class_attribute(:feature_category) || DEFAULT_FEATURE_CATEGORY
+ end
end
end
- def self.scope_to(scope)
- define_method(:filter_batch) do |relation|
- instance_exec(relation, &scope)
- end
+ def initialize(
+ start_id:, end_id:, batch_table:, batch_column:, sub_batch_size:, pause_ms:, job_arguments: [], connection:
+ )
+
+ @start_id = start_id
+ @end_id = end_id
+ @batch_table = batch_table
+ @batch_column = batch_column
+ @sub_batch_size = sub_batch_size
+ @pause_ms = pause_ms
+ @job_arguments = job_arguments
+ @connection = connection
end
def filter_batch(relation)
diff --git a/lib/gitlab/background_migration/delete_orphans_approval_merge_request_rules.rb b/lib/gitlab/background_migration/delete_orphans_approval_merge_request_rules.rb
new file mode 100644
index 00000000000..4b7b7d42c77
--- /dev/null
+++ b/lib/gitlab/background_migration/delete_orphans_approval_merge_request_rules.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # Deletes orphans records whenever report_type equals to scan_finding (i.e., 4)
+ class DeleteOrphansApprovalMergeRequestRules < BatchedMigrationJob
+ scope_to ->(relation) { relation.where(report_type: 4) }
+
+ operation_name :delete_all
+
+ def perform
+ each_sub_batch do |sub_batch|
+ sub_batch.where(security_orchestration_policy_configuration_id: nil).delete_all
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/delete_orphans_approval_project_rules.rb b/lib/gitlab/background_migration/delete_orphans_approval_project_rules.rb
new file mode 100644
index 00000000000..33aa1a8d29d
--- /dev/null
+++ b/lib/gitlab/background_migration/delete_orphans_approval_project_rules.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # Deletes orphans records whenever report_type equals to scan_finding (i.e., 4)
+ class DeleteOrphansApprovalProjectRules < BatchedMigrationJob
+ operation_name :delete_all
+
+ def perform
+ each_sub_batch do |sub_batch|
+ sub_batch.where(report_type: 4, security_orchestration_policy_configuration_id: nil).delete_all
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/disable_legacy_open_source_license_for_projects_less_than_five_mb.rb b/lib/gitlab/background_migration/disable_legacy_open_source_license_for_projects_less_than_five_mb.rb
new file mode 100644
index 00000000000..dcef4f086e2
--- /dev/null
+++ b/lib/gitlab/background_migration/disable_legacy_open_source_license_for_projects_less_than_five_mb.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # Set `project_settings.legacy_open_source_license_available` to false for projects less than 5 MB
+ class DisableLegacyOpenSourceLicenseForProjectsLessThanFiveMb < ::Gitlab::BackgroundMigration::BatchedMigrationJob
+ scope_to ->(relation) do
+ relation
+ .where(legacy_open_source_license_available: true)
+ end
+
+ operation_name :disable_legacy_open_source_license_for_projects_less_than_five_mb
+
+ def perform
+ each_sub_batch do |sub_batch|
+ updates = { legacy_open_source_license_available: false, updated_at: Time.current }
+
+ sub_batch
+ .joins('INNER JOIN project_statistics ON project_statistics.project_id = project_settings.project_id')
+ .where('project_statistics.repository_size < ?', 5.megabyte)
+ .update_all(updates)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/fix_approval_project_rules_without_protected_branches.rb b/lib/gitlab/background_migration/fix_approval_project_rules_without_protected_branches.rb
new file mode 100644
index 00000000000..4b283bae79d
--- /dev/null
+++ b/lib/gitlab/background_migration/fix_approval_project_rules_without_protected_branches.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # This class doesn't update approval project rules
+ # as this feature exists only in EE
+ class FixApprovalProjectRulesWithoutProtectedBranches < BatchedMigrationJob
+ def perform; end
+ end
+ end
+end
+
+# rubocop:disable Layout/LineLength
+Gitlab::BackgroundMigration::FixApprovalProjectRulesWithoutProtectedBranches.prepend_mod_with('Gitlab::BackgroundMigration::FixApprovalProjectRulesWithoutProtectedBranches')
+# rubocop:enable Layout/LineLength
diff --git a/lib/gitlab/background_migration/fix_security_scan_statuses.rb b/lib/gitlab/background_migration/fix_security_scan_statuses.rb
new file mode 100644
index 00000000000..b60e739f870
--- /dev/null
+++ b/lib/gitlab/background_migration/fix_security_scan_statuses.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # Fixes the `status` attribute of `security_scans` records
+ class FixSecurityScanStatuses < BatchedMigrationJob
+ def perform
+ # no-op. The logic is defined in EE module.
+ end
+ end
+ end
+end
+
+::Gitlab::BackgroundMigration::FixSecurityScanStatuses.prepend_mod
diff --git a/lib/gitlab/background_migration/migrate_vulnerabilities_feedback_to_vulnerabilities_state_transition.rb b/lib/gitlab/background_migration/migrate_vulnerabilities_feedback_to_vulnerabilities_state_transition.rb
new file mode 100644
index 00000000000..81b29b5a6cd
--- /dev/null
+++ b/lib/gitlab/background_migration/migrate_vulnerabilities_feedback_to_vulnerabilities_state_transition.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+# rubocop:disable Style/Documentation
+module Gitlab
+ module BackgroundMigration
+ class MigrateVulnerabilitiesFeedbackToVulnerabilitiesStateTransition < BatchedMigrationJob
+ def perform; end
+ end
+ end
+end
+
+Gitlab::BackgroundMigration::MigrateVulnerabilitiesFeedbackToVulnerabilitiesStateTransition.prepend_mod
+# rubocop:enable Style/Documentation
diff --git a/lib/gitlab/background_migration/prune_stale_project_export_jobs.rb b/lib/gitlab/background_migration/prune_stale_project_export_jobs.rb
new file mode 100644
index 00000000000..a91cda2c427
--- /dev/null
+++ b/lib/gitlab/background_migration/prune_stale_project_export_jobs.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # Background migration for deleting stale project import jobs
+ class PruneStaleProjectExportJobs < BatchedMigrationJob
+ EXPIRES_IN = 7.days
+
+ scope_to ->(relation) { relation.where("updated_at < ?", EXPIRES_IN.ago) }
+ operation_name :delete_all
+
+ def perform
+ each_sub_batch(&:delete_all)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/reset_status_on_container_repositories.rb b/lib/gitlab/background_migration/reset_status_on_container_repositories.rb
new file mode 100644
index 00000000000..09cd3b1895f
--- /dev/null
+++ b/lib/gitlab/background_migration/reset_status_on_container_repositories.rb
@@ -0,0 +1,139 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # A job that:
+ # * pickup container repositories with delete_scheduled status.
+ # * check if there are tags linked to it.
+ # * if there are tags, reset the status to nil.
+ class ResetStatusOnContainerRepositories < BatchedMigrationJob
+ DELETE_SCHEDULED_STATUS = 0
+ DUMMY_TAGS = %w[tag].freeze
+ MIGRATOR = 'ResetStatusOnContainerRepositories'
+
+ scope_to ->(relation) { relation.where(status: DELETE_SCHEDULED_STATUS) }
+ operation_name :reset_status_on_container_repositories
+
+ def perform
+ each_sub_batch do |sub_batch|
+ reset_status_if_tags(sub_batch)
+ end
+ end
+
+ private
+
+ def reset_status_if_tags(container_repositories)
+ container_repositories_with_tags = container_repositories.select { |cr| cr.becomes(ContainerRepository).tags? } # rubocop:disable Cop/AvoidBecomes
+
+ ContainerRepository.where(id: container_repositories_with_tags.map(&:id))
+ .update_all(status: nil)
+ end
+
+ # rubocop:disable Style/Documentation
+ module Routable
+ extend ActiveSupport::Concern
+
+ included do
+ has_one :route,
+ as: :source,
+ class_name: '::Gitlab::BackgroundMigration::ResetStatusOnContainerRepositories::Route'
+ end
+
+ def full_path
+ route&.path || build_full_path
+ end
+
+ def build_full_path
+ if parent && path
+ "#{parent.full_path}/#{path}"
+ else
+ path
+ end
+ end
+ end
+
+ class Route < ::ApplicationRecord
+ self.table_name = 'routes'
+ end
+
+ class Namespace < ::ApplicationRecord
+ include ::Gitlab::BackgroundMigration::ResetStatusOnContainerRepositories::Routable
+ include ::Namespaces::Traversal::Recursive
+ include ::Namespaces::Traversal::Linear
+ include ::Gitlab::Utils::StrongMemoize
+
+ self.table_name = 'namespaces'
+ self.inheritance_column = :_type_disabled
+
+ belongs_to :parent,
+ class_name: '::Gitlab::BackgroundMigration::ResetStatusOnContainerRepositories::Namespace'
+
+ def self.polymorphic_name
+ 'Namespace'
+ end
+ end
+
+ class Project < ::ApplicationRecord
+ include ::Gitlab::BackgroundMigration::ResetStatusOnContainerRepositories::Routable
+
+ self.table_name = 'projects'
+
+ belongs_to :namespace,
+ class_name: '::Gitlab::BackgroundMigration::ResetStatusOnContainerRepositories::Namespace'
+
+ alias_method :parent, :namespace
+ alias_attribute :parent_id, :namespace_id
+
+ delegate :root_ancestor, to: :namespace, allow_nil: true
+ end
+
+ class ContainerRepository < ::ApplicationRecord
+ self.table_name = 'container_repositories'
+
+ belongs_to :project,
+ class_name: '::Gitlab::BackgroundMigration::ResetStatusOnContainerRepositories::Project'
+
+ def tags?
+ result = ContainerRegistry.tags_for(path).any?
+ ::Gitlab::BackgroundMigration::Logger.info(
+ migrator: MIGRATOR,
+ has_tags: result,
+ container_repository_id: id,
+ container_repository_path: path
+ )
+ result
+ end
+
+ def path
+ @path ||= [project.full_path, name].select(&:present?).join('/').downcase
+ end
+ end
+
+ class ContainerRegistry
+ class << self
+ def tags_for(path)
+ response = ContainerRegistryClient.repository_tags(path, page_size: 1)
+ return DUMMY_TAGS unless response
+
+ response['tags'] || []
+ rescue StandardError
+ DUMMY_TAGS
+ end
+ end
+ end
+
+ class ContainerRegistryClient
+ def self.repository_tags(path, page_size:)
+ registry_config = ::Gitlab.config.registry
+
+ return { 'tags' => DUMMY_TAGS } unless registry_config.enabled && registry_config.api_url.present?
+
+ pull_token = ::Auth::ContainerRegistryAuthenticationService.pull_access_token(path)
+ client = ::ContainerRegistry::Client.new(registry_config.api_url, token: pull_token)
+ client.repository_tags(path, page_size: page_size)
+ end
+ end
+ # rubocop:enable Style/Documentation
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/update_jira_tracker_data_deployment_type_based_on_url.rb b/lib/gitlab/background_migration/update_jira_tracker_data_deployment_type_based_on_url.rb
index e9a38916999..8aab7d13b45 100644
--- a/lib/gitlab/background_migration/update_jira_tracker_data_deployment_type_based_on_url.rb
+++ b/lib/gitlab/background_migration/update_jira_tracker_data_deployment_type_based_on_url.rb
@@ -37,16 +37,16 @@ module Gitlab
end
end
- cloud_mappings = cloud.each_with_object({}) do |tracker_data, hash|
- hash[tracker_data] = { deployment_type: 2 }
+ cloud_mappings = cloud.index_with do
+ { deployment_type: 2 }
end
- server_mappings = server.each_with_object({}) do |tracker_data, hash|
- hash[tracker_data] = { deployment_type: 1 }
+ server_mappings = server.index_with do
+ { deployment_type: 1 }
end
- unknown_mappings = unknown.each_with_object({}) do |tracker_data, hash|
- hash[tracker_data] = { deployment_type: 0 }
+ unknown_mappings = unknown.index_with do
+ { deployment_type: 0 }
end
mappings = cloud_mappings.merge(server_mappings, unknown_mappings)
diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb
index 7de6be45349..49b8ab760f3 100644
--- a/lib/gitlab/bitbucket_import/importer.rb
+++ b/lib/gitlab/bitbucket_import/importer.rb
@@ -98,7 +98,7 @@ module Gitlab
create_labels
- issue_type_id = WorkItems::Type.default_issue_type.id
+ issue_type_id = ::WorkItems::Type.default_issue_type.id
client.issues(repo).each do |issue|
import_issue(issue, issue_type_id)
diff --git a/lib/gitlab/bullet.rb b/lib/gitlab/bullet.rb
index f5f8a316855..9759a82be0c 100644
--- a/lib/gitlab/bullet.rb
+++ b/lib/gitlab/bullet.rb
@@ -10,7 +10,7 @@ module Gitlab
alias_method :extra_logging_enabled?, :enabled?
def configure_bullet?
- defined?(::Bullet) && (enabled? || Rails.env.development?)
+ defined?(::Bullet) && (enabled? || Gitlab.config.bullet.enabled)
end
end
end
diff --git a/lib/gitlab/changes_list.rb b/lib/gitlab/changes_list.rb
index fb75a78a978..999d2ee4356 100644
--- a/lib/gitlab/changes_list.rb
+++ b/lib/gitlab/changes_list.rb
@@ -15,14 +15,12 @@ module Gitlab
end
def changes
- @changes ||= begin
- @raw_changes.map do |change|
- next if change.blank?
+ @changes ||= @raw_changes.map do |change|
+ next if change.blank?
- oldrev, newrev, ref = change.strip.split(' ')
- { oldrev: oldrev, newrev: newrev, ref: ref }
- end.compact
- end
+ oldrev, newrev, ref = change.strip.split(' ')
+ { oldrev: oldrev, newrev: newrev, ref: ref }
+ end.compact
end
end
end
diff --git a/lib/gitlab/ci/ansi2html.rb b/lib/gitlab/ci/ansi2html.rb
index 19819ff7275..42a8b561d34 100644
--- a/lib/gitlab/ci/ansi2html.rb
+++ b/lib/gitlab/ci/ansi2html.rb
@@ -448,8 +448,8 @@ module Gitlab
end
def state
- state = STATE_PARAMS.each_with_object({}) do |param, h|
- h[param] = send(param) # rubocop:disable GitlabSecurity/PublicSend
+ state = STATE_PARAMS.index_with do |param|
+ send(param) # rubocop:disable GitlabSecurity/PublicSend
end
Base64.urlsafe_encode64(state.to_json)
end
diff --git a/lib/gitlab/ci/build/cache.rb b/lib/gitlab/ci/build/cache.rb
index 375e6b4a96f..1cddc9fcc98 100644
--- a/lib/gitlab/ci/build/cache.rb
+++ b/lib/gitlab/ci/build/cache.rb
@@ -8,9 +8,9 @@ module Gitlab
def initialize(cache, pipeline)
cache = Array.wrap(cache)
- @cache = cache.map do |cache|
+ @cache = cache.map.with_index do |cache, index|
Gitlab::Ci::Pipeline::Seed::Build::Cache
- .new(pipeline, cache)
+ .new(pipeline, cache, index)
end
end
diff --git a/lib/gitlab/ci/build/context/build.rb b/lib/gitlab/ci/build/context/build.rb
index a1a8e9288c7..1025e1cc2d7 100644
--- a/lib/gitlab/ci/build/context/build.rb
+++ b/lib/gitlab/ci/build/context/build.rb
@@ -9,25 +9,29 @@ module Gitlab
attr_reader :attributes
- def initialize(pipeline, attributes = {})
+ def initialize(pipeline, attributes = {}, build = nil)
super(pipeline)
+ @build = build
@attributes = attributes
end
def variables
- strong_memoize(:variables) do
- # This is a temporary piece of technical debt to allow us access
- # to the CI variables to evaluate rules before we persist a Build
- # with the result. We should refactor away the extra Build.new,
- # but be able to get CI Variables directly from the Seed::Build.
- stub_build.scoped_variables
- end
+ build.scoped_variables
end
+ strong_memoize_attr :variables
private
+ def build
+ @build || stub_build
+ end
+
def stub_build
+ # This is a temporary piece of technical debt to allow us access
+ # to the CI variables to evaluate rules before we persist a Build
+ # with the result. We should refactor away the extra Build.new,
+ # but be able to get CI Variables directly from the Seed::Build.
::Ci::Build.new(build_attributes)
end
diff --git a/lib/gitlab/ci/build/hook.rb b/lib/gitlab/ci/build/hook.rb
new file mode 100644
index 00000000000..b731228678c
--- /dev/null
+++ b/lib/gitlab/ci/build/hook.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Build
+ class Hook
+ attr_reader :name, :script
+
+ class << self
+ def from_hooks(job)
+ job.options[:hooks].to_a.map do |name, script|
+ new(name.to_s, script)
+ end
+ end
+ end
+
+ def initialize(name, script)
+ @name = name
+ @script = script
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb
index ee537f4efe5..142f0b8dfd8 100644
--- a/lib/gitlab/ci/config.rb
+++ b/lib/gitlab/ci/config.rb
@@ -10,7 +10,7 @@ module Gitlab
ConfigError = Class.new(StandardError)
TIMEOUT_SECONDS = 30.seconds
- TIMEOUT_MESSAGE = 'Resolving config took longer than expected'
+ TIMEOUT_MESSAGE = 'Request timed out when fetching configuration files.'
RESCUE_ERRORS = [
Gitlab::Config::Loader::FormatError,
@@ -26,7 +26,7 @@ module Gitlab
@source_ref_path = pipeline&.source_ref_path
@project = project
- @context = self.logger.instrument(:config_build_context) do
+ @context = self.logger.instrument(:config_build_context, once: true) do
pipeline ||= ::Ci::Pipeline.new(project: project, sha: sha, user: user, source: source)
build_context(project: project, pipeline: pipeline, sha: sha, user: user, parent_pipeline: parent_pipeline)
end
@@ -35,12 +35,16 @@ module Gitlab
@source = source
- @config = self.logger.instrument(:config_expand) do
+ @config = self.logger.instrument(:config_expand, once: true) do
expand_config(config)
end
- @root = self.logger.instrument(:config_compose) do
- Entry::Root.new(@config, project: project, user: user).tap(&:compose!)
+ @root = self.logger.instrument(:config_root, once: true) do
+ Entry::Root.new(@config, project: project, user: user, logger: self.logger)
+ end
+
+ self.logger.instrument(:config_root_compose, once: true) do
+ @root.compose!
end
rescue *rescue_errors => e
raise Config::ConfigError, e.message
@@ -123,23 +127,23 @@ module Gitlab
end
def build_config(config)
- initial_config = logger.instrument(:config_yaml_load) do
+ initial_config = logger.instrument(:config_yaml_load, once: true) do
Config::Yaml.load!(config)
end
- initial_config = logger.instrument(:config_external_process) do
+ initial_config = logger.instrument(:config_external_process, once: true) do
Config::External::Processor.new(initial_config, @context).perform
end
- initial_config = logger.instrument(:config_yaml_extend) do
+ initial_config = logger.instrument(:config_yaml_extend, once: true) do
Config::Extendable.new(initial_config).to_hash
end
- initial_config = logger.instrument(:config_tags_resolve) do
+ initial_config = logger.instrument(:config_tags_resolve, once: true) do
Config::Yaml::Tags::Resolver.new(initial_config).to_hash
end
- logger.instrument(:config_stages_inject) do
+ logger.instrument(:config_stages_inject, once: true) do
Config::EdgeStagesInjector.new(initial_config).to_hash
end
end
@@ -163,7 +167,7 @@ module Gitlab
end
def build_variables(pipeline:)
- logger.instrument(:config_build_variables) do
+ logger.instrument(:config_build_variables, once: true) do
pipeline
.variables_builder
.config_variables
diff --git a/lib/gitlab/ci/config/entry/artifacts.rb b/lib/gitlab/ci/config/entry/artifacts.rb
index 3b0cbc6b69e..27206d7e3a8 100644
--- a/lib/gitlab/ci/config/entry/artifacts.rb
+++ b/lib/gitlab/ci/config/entry/artifacts.rb
@@ -12,6 +12,7 @@ module Gitlab
include ::Gitlab::Config::Entry::Validatable
include ::Gitlab::Config::Entry::Attributable
+ ALLOWED_WHEN = %w[on_success on_failure always].freeze
ALLOWED_KEYS = %i[name untracked paths reports when expire_in expose_as exclude public].freeze
EXPOSE_AS_REGEX = /\A\w[-\w ]*\z/.freeze
EXPOSE_AS_ERROR_MESSAGE = "can contain only letters, digits, '-', '_' and spaces"
@@ -38,10 +39,10 @@ module Gitlab
validates :expose_as, format: { with: EXPOSE_AS_REGEX, message: EXPOSE_AS_ERROR_MESSAGE }, if: :expose_as_present?
validates :exclude, array_of_strings: true
validates :reports, type: Hash
- validates :when,
- inclusion: { in: %w[on_success on_failure always],
- message: 'should be on_success, on_failure ' \
- 'or always' }
+ validates :when, type: String, inclusion: {
+ in: ALLOWED_WHEN,
+ message: "should be one of: #{ALLOWED_WHEN.join(', ')}"
+ }
validates :expire_in, duration: { parser: ::Gitlab::Ci::Build::DurationParser }
end
end
diff --git a/lib/gitlab/ci/config/entry/cache.rb b/lib/gitlab/ci/config/entry/cache.rb
index ab79add688b..a5481071fc5 100644
--- a/lib/gitlab/ci/config/entry/cache.rb
+++ b/lib/gitlab/ci/config/entry/cache.rb
@@ -17,16 +17,16 @@ module Gitlab
validations do
validates :config, type: Hash, allowed_keys: ALLOWED_KEYS
- validates :policy,
- inclusion: { in: ALLOWED_POLICY, message: 'should be pull-push, push, or pull' },
- allow_blank: true
+ validates :policy, type: String, allow_blank: true, inclusion: {
+ in: ALLOWED_POLICY,
+ message: "should be one of: #{ALLOWED_POLICY.join(', ')}"
+ }
with_options allow_nil: true do
- validates :when,
- inclusion: {
- in: ALLOWED_WHEN,
- message: 'should be on_success, on_failure or always'
- }
+ validates :when, type: String, inclusion: {
+ in: ALLOWED_WHEN,
+ message: "should be one of: #{ALLOWED_WHEN.join(', ')}"
+ }
end
end
diff --git a/lib/gitlab/ci/config/entry/default.rb b/lib/gitlab/ci/config/entry/default.rb
index 12d68b755b3..e996b6b1312 100644
--- a/lib/gitlab/ci/config/entry/default.rb
+++ b/lib/gitlab/ci/config/entry/default.rb
@@ -13,9 +13,8 @@ module Gitlab
include ::Gitlab::Config::Entry::Configurable
include ::Gitlab::Config::Entry::Inheritable
- ALLOWED_KEYS = %i[before_script image services
- after_script cache interruptible
- timeout retry tags artifacts].freeze
+ ALLOWED_KEYS = %i[before_script after_script hooks cache image services
+ interruptible timeout retry tags artifacts].freeze
validations do
validates :config, allowed_keys: ALLOWED_KEYS
@@ -25,22 +24,27 @@ module Gitlab
description: 'Script that will be executed before each job.',
inherit: true
- entry :image, Entry::Image,
- description: 'Docker image that will be used to execute jobs.',
- inherit: true
-
- entry :services, Entry::Services,
- description: 'Docker images that will be linked to the container.',
- inherit: true
-
entry :after_script, Entry::Commands,
description: 'Script that will be executed after each job.',
inherit: true
+ entry :hooks, Entry::Hooks,
+ description: 'Commands that will be executed on Runner before/after some events ' \
+ 'such as `clone` and `build-script`.',
+ inherit: false
+
entry :cache, Entry::Caches,
description: 'Configure caching between build jobs.',
inherit: true
+ entry :image, Entry::Image,
+ description: 'Docker image that will be used to execute jobs.',
+ inherit: true
+
+ entry :services, Entry::Services,
+ description: 'Docker images that will be linked to the container.',
+ inherit: true
+
entry :interruptible, ::Gitlab::Config::Entry::Boolean,
description: 'Set jobs interruptible default value.',
inherit: false
diff --git a/lib/gitlab/ci/config/entry/hooks.rb b/lib/gitlab/ci/config/entry/hooks.rb
new file mode 100644
index 00000000000..28bc2e4e7ce
--- /dev/null
+++ b/lib/gitlab/ci/config/entry/hooks.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module Entry
+ class Hooks < ::Gitlab::Config::Entry::Node
+ # `Configurable` alreadys adds `Validatable`
+ include ::Gitlab::Config::Entry::Configurable
+
+ # NOTE: If a new hook is added, inheriting should be changed because a `job:hooks` overrides all
+ # `default:hooks` now. We should implement merging; each hook must be overridden individually.
+ ALLOWED_HOOKS = %i[pre_get_sources_script].freeze
+
+ validations do
+ validates :config, type: Hash, allowed_keys: ALLOWED_HOOKS
+ end
+
+ entry :pre_get_sources_script, Entry::Commands,
+ description: 'Commands that will be executed on Runner before cloning/fetching the Git repository.'
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/entry/id_token.rb b/lib/gitlab/ci/config/entry/id_token.rb
new file mode 100644
index 00000000000..12e0975d1b1
--- /dev/null
+++ b/lib/gitlab/ci/config/entry/id_token.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module Entry
+ ##
+ # Entry that represents a JWT definition.
+ #
+ class IdToken < ::Gitlab::Config::Entry::Node
+ include ::Gitlab::Config::Entry::Attributable
+ include ::Gitlab::Config::Entry::Validatable
+
+ attributes %i[aud]
+
+ validations do
+ validates :config, required_keys: %i[aud], allowed_keys: %i[aud]
+ validates :aud, array_of_strings_or_string: true
+ end
+
+ def value
+ { aud: aud }
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb
index 8e7f6ba4326..7c49b59a7f0 100644
--- a/lib/gitlab/ci/config/entry/job.rb
+++ b/lib/gitlab/ci/config/entry/job.rb
@@ -12,9 +12,9 @@ module Gitlab
ALLOWED_WHEN = %w[on_success on_failure always manual delayed].freeze
ALLOWED_KEYS = %i[tags script image services start_in artifacts
- cache dependencies before_script after_script
+ cache dependencies before_script after_script hooks
environment coverage retry parallel interruptible timeout
- release].freeze
+ release id_tokens].freeze
validations do
validates :config, allowed_keys: Gitlab::Ci::Config::Entry::Job.allowed_keys + PROCESSABLE_ALLOWED_KEYS
@@ -59,6 +59,10 @@ module Gitlab
description: 'Commands that will be executed when finishing job.',
inherit: true
+ entry :hooks, Entry::Hooks,
+ description: 'Commands that will be executed on Runner before/after some events; clone, build-script.',
+ inherit: true
+
entry :cache, Entry::Caches,
description: 'Cache definition for this job.',
inherit: true
@@ -116,6 +120,11 @@ module Gitlab
description: 'Indicates whether this job is allowed to fail or not.',
inherit: false
+ entry :id_tokens, ::Gitlab::Config::Entry::ComposableHash,
+ description: 'Configured JWTs for this job',
+ inherit: false,
+ metadata: { composable_class: ::Gitlab::Ci::Config::Entry::IdToken }
+
attributes :script, :tags, :when, :dependencies,
:needs, :retry, :parallel, :start_in,
:interruptible, :timeout,
@@ -155,10 +164,12 @@ module Gitlab
artifacts: artifacts_value,
release: release_value,
after_script: after_script_value,
+ hooks: hooks_pre_get_sources_script_enabled? ? hooks_value : nil,
ignore: ignored?,
allow_failure_criteria: allow_failure_criteria,
needs: needs_defined? ? needs_value : nil,
- scheduling_type: needs_defined? ? :dag : :stage
+ scheduling_type: needs_defined? ? :dag : :stage,
+ id_tokens: id_tokens_value
).compact
end
@@ -183,6 +194,10 @@ module Gitlab
allow_failure_value
end
+
+ def hooks_pre_get_sources_script_enabled?
+ YamlProcessor::FeatureFlags.enabled?(:ci_hooks_pre_get_sources_script)
+ end
end
end
end
diff --git a/lib/gitlab/ci/config/entry/reports.rb b/lib/gitlab/ci/config/entry/reports.rb
index f77876cc926..16844fa88db 100644
--- a/lib/gitlab/ci/config/entry/reports.rb
+++ b/lib/gitlab/ci/config/entry/reports.rb
@@ -16,8 +16,8 @@ module Gitlab
%i[junit codequality sast secret_detection dependency_scanning container_scanning
dast performance browser_performance load_performance license_scanning metrics lsif
dotenv terraform accessibility
- requirements coverage_fuzzing api_fuzzing cluster_image_scanning
- coverage_report cyclonedx].freeze
+ coverage_fuzzing api_fuzzing cluster_image_scanning
+ requirements requirements_v2 coverage_report cyclonedx].freeze
attributes ALLOWED_KEYS
@@ -48,6 +48,7 @@ module Gitlab
validates :terraform, array_of_strings_or_string: true
validates :accessibility, array_of_strings_or_string: true
validates :requirements, array_of_strings_or_string: true
+ validates :requirements_v2, array_of_strings_or_string: true
validates :cyclonedx, array_of_strings_or_string: true
end
end
diff --git a/lib/gitlab/ci/config/entry/root.rb b/lib/gitlab/ci/config/entry/root.rb
index a30e6a0d9c3..a3d57ab6ac6 100644
--- a/lib/gitlab/ci/config/entry/root.rb
+++ b/lib/gitlab/ci/config/entry/root.rb
@@ -50,7 +50,7 @@ module Gitlab
entry :variables, Entry::Variables,
description: 'Environment variables that will be used.',
- metadata: { allowed_value_data: %i[value description expand], allow_array_value: true },
+ metadata: { allowed_value_data: %i[value description expand options] },
reserved: true
entry :stages, Entry::Stages,
@@ -103,12 +103,16 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord
def compose_jobs!
- factory = ::Gitlab::Config::Entry::Factory.new(Entry::Jobs)
- .value(jobs_config)
- .with(key: :jobs, parent: self,
- description: 'Jobs definition for this pipeline')
+ factory = logger.instrument(:config_root_compose_jobs_factory, once: true) do
+ ::Gitlab::Config::Entry::Factory.new(Entry::Jobs)
+ .value(jobs_config)
+ .with(key: :jobs, parent: self,
+ description: 'Jobs definition for this pipeline')
+ end
- @entries[:jobs] = factory.create!
+ @entries[:jobs] = logger.instrument(:config_root_compose_jobs_create, once: true) do
+ factory.create!
+ end
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -123,6 +127,10 @@ module Gitlab
@config = @config.except(*@jobs_config.keys)
end
+
+ def logger
+ metadata[:logger]
+ end
end
end
end
diff --git a/lib/gitlab/ci/config/entry/trigger.rb b/lib/gitlab/ci/config/entry/trigger.rb
index 0f94b3f94fe..4c254a4fa07 100644
--- a/lib/gitlab/ci/config/entry/trigger.rb
+++ b/lib/gitlab/ci/config/entry/trigger.rb
@@ -41,7 +41,7 @@ module Gitlab
validations do
validates :config, presence: true
validates :config, allowed_keys: ALLOWED_KEYS
- validates :project, presence: true
+ validates :project, type: String, presence: true
validates :branch, type: String, allow_nil: true
validates :strategy, type: String, inclusion: { in: %w[depend], message: 'should be depend' }, allow_nil: true
end
diff --git a/lib/gitlab/ci/config/entry/variable.rb b/lib/gitlab/ci/config/entry/variable.rb
index 16091758916..decb568ffc9 100644
--- a/lib/gitlab/ci/config/entry/variable.rb
+++ b/lib/gitlab/ci/config/entry/variable.rb
@@ -10,7 +10,6 @@ module Gitlab
class Variable < ::Gitlab::Config::Entry::Simplifiable
strategy :SimpleVariable, if: -> (config) { SimpleVariable.applies_to?(config) }
strategy :ComplexVariable, if: -> (config) { ComplexVariable.applies_to?(config) }
- strategy :ComplexArrayVariable, if: -> (config) { ComplexArrayVariable.applies_to?(config) }
class SimpleVariable < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Validatable
@@ -41,20 +40,24 @@ module Gitlab
class ComplexVariable < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Validatable
+ include ::Gitlab::Config::Entry::Attributable
class << self
def applies_to?(config)
- config.is_a?(Hash) && !config[:value].is_a?(Array)
+ config.is_a?(Hash)
end
end
+ attributes :value, :description, :expand, :options, prefix: :config
+
validations do
validates :key, alphanumeric: true
- validates :config_value, alphanumeric: true, allow_nil: false, if: :config_value_defined?
- validates :config_description, alphanumeric: true, allow_nil: false, if: :config_description_defined?
- validates :config_expand, boolean: true,
- allow_nil: false,
- if: -> { ci_raw_variables_in_yaml_config_enabled? && config_expand_defined? }
+ validates :config_value, alphanumeric: true, allow_nil: true
+ validates :config_description, alphanumeric: true, allow_nil: true
+ validates :config_expand, boolean: true, allow_nil: true, if: -> {
+ ci_raw_variables_in_yaml_config_enabled?
+ }
+ validates :config_options, array_of_strings: true, allow_nil: true
validate do
allowed_value_data = Array(opt(:allowed_value_data))
@@ -66,91 +69,43 @@ module Gitlab
else
errors.add(:config, "must be a string")
end
+
+ if config_options.present? && config_options.exclude?(config_value)
+ errors.add(:config, 'value must be present in options')
+ end
end
end
def value
+ # Needed since the `Entry::Node` provides `value` (which is current hash)
config_value.to_s
end
def value_with_data
if ci_raw_variables_in_yaml_config_enabled?
{
- value: value,
- raw: (!config_expand if config_expand_defined?)
+ value: config_value.to_s,
+ raw: (!config_expand if has_config_expand?)
}.compact
else
{
- value: value
+ value: config_value.to_s
}.compact
end
end
def value_with_prefill_data
value_with_data.merge(
- description: config_description
+ description: config_description,
+ options: config_options
).compact
end
- def config_value
- @config[:value]
- end
-
- def config_description
- @config[:description]
- end
-
- def config_expand
- @config[:expand]
- end
-
- def config_value_defined?
- config.key?(:value)
- end
-
- def config_description_defined?
- config.key?(:description)
- end
-
- def config_expand_defined?
- config.key?(:expand)
- end
-
def ci_raw_variables_in_yaml_config_enabled?
YamlProcessor::FeatureFlags.enabled?(:ci_raw_variables_in_yaml_config)
end
end
- class ComplexArrayVariable < ComplexVariable
- include ::Gitlab::Config::Entry::Validatable
-
- class << self
- def applies_to?(config)
- config.is_a?(Hash) && config[:value].is_a?(Array)
- end
- end
-
- validations do
- validates :config_value, array_of_strings: true, allow_nil: false, if: :config_value_defined?
-
- validate do
- next if opt(:allow_array_value)
-
- errors.add(:config, 'value must be an alphanumeric string')
- end
- end
-
- def value
- config_value.first
- end
-
- def value_with_prefill_data
- super.merge(
- value_options: config_value
- ).compact
- end
- end
-
class UnknownStrategy < ::Gitlab::Config::Entry::Node
def errors
["variable definition must be either a string or a hash"]
diff --git a/lib/gitlab/ci/config/entry/variables.rb b/lib/gitlab/ci/config/entry/variables.rb
index ef4f74b9f56..e338bce3109 100644
--- a/lib/gitlab/ci/config/entry/variables.rb
+++ b/lib/gitlab/ci/config/entry/variables.rb
@@ -42,7 +42,7 @@ module Gitlab
end
def composable_metadata
- { allowed_value_data: opt(:allowed_value_data), allow_array_value: opt(:allow_array_value) }
+ { allowed_value_data: opt(:allowed_value_data) }
end
end
end
diff --git a/lib/gitlab/ci/config/external/file/base.rb b/lib/gitlab/ci/config/external/file/base.rb
index 57ff606c9ee..65caf4ac47d 100644
--- a/lib/gitlab/ci/config/external/file/base.rb
+++ b/lib/gitlab/ci/config/external/file/base.rb
@@ -47,7 +47,7 @@ module Gitlab
end
def validate!
- validate_execution_time!
+ context.check_execution_time! if ::Feature.disabled?(:ci_refactoring_external_mapper, context.project)
validate_location!
validate_context! if valid?
fetch_and_validate_content! if valid?
@@ -87,10 +87,6 @@ module Gitlab
nil
end
- def validate_execution_time!
- context.check_execution_time!
- end
-
def validate_location!
if invalid_location_type?
errors.push("Included file `#{masked_location}` needs to be a string")
diff --git a/lib/gitlab/ci/config/external/file/remote.rb b/lib/gitlab/ci/config/external/file/remote.rb
index b0c540685d4..ed37357dc53 100644
--- a/lib/gitlab/ci/config/external/file/remote.rb
+++ b/lib/gitlab/ci/config/external/file/remote.rb
@@ -53,7 +53,7 @@ module Gitlab
errors.push("Remote file `#{masked_location}` could not be fetched because of a timeout error!")
rescue Gitlab::HTTP::Error
errors.push("Remote file `#{masked_location}` could not be fetched because of HTTP error!")
- rescue Gitlab::HTTP::BlockedUrlError => e
+ rescue Errno::ECONNREFUSED, Gitlab::HTTP::BlockedUrlError => e
errors.push("Remote file could not be fetched because #{e}!")
end
diff --git a/lib/gitlab/ci/config/external/mapper.rb b/lib/gitlab/ci/config/external/mapper.rb
index fc03ac125fd..a41bc2b39f2 100644
--- a/lib/gitlab/ci/config/external/mapper.rb
+++ b/lib/gitlab/ci/config/external/mapper.rb
@@ -7,6 +7,7 @@ module Gitlab
class Mapper
include Gitlab::Utils::StrongMemoize
+ # Will be removed with FF ci_refactoring_external_mapper
FILE_CLASSES = [
External::File::Local,
External::File::Project,
@@ -15,6 +16,7 @@ module Gitlab
External::File::Artifact
].freeze
+ # Will be removed with FF ci_refactoring_external_mapper
FILE_SUBKEYS = FILE_CLASSES.map { |f| f.name.demodulize.downcase }.freeze
Error = Class.new(StandardError)
@@ -22,27 +24,43 @@ module Gitlab
TooManyIncludesError = Class.new(Error)
def initialize(values, context)
- @locations = Array.wrap(values.fetch(:include, []))
+ @locations = Array.wrap(values.fetch(:include, [])).compact
@context = context
end
def process
- return [] if locations.empty?
+ return [] if @locations.empty?
- logger.instrument(:config_mapper_process) do
- process_without_instrumentation
+ context.logger.instrument(:config_mapper_process) do
+ if ::Feature.enabled?(:ci_refactoring_external_mapper, context.project)
+ process_without_instrumentation
+ else
+ legacy_process_without_instrumentation
+ end
end
end
private
- attr_reader :locations, :context
+ attr_reader :context
delegate :expandset, :logger, to: :context
def process_without_instrumentation
- locations
- .compact
+ locations = Normalizer.new(context).process(@locations)
+ locations = Filter.new(context).process(locations)
+ locations = LocationExpander.new(context).process(locations)
+ locations = VariablesExpander.new(context).process(locations)
+
+ files = Matcher.new(context).process(locations)
+ Verifier.new(context).process(files)
+
+ files
+ end
+
+ # This and the following methods will be removed with FF ci_refactoring_external_mapper
+ def legacy_process_without_instrumentation
+ @locations
.map(&method(:normalize_location))
.filter_map(&method(:verify_rules))
.flat_map(&method(:expand_project_files))
@@ -52,14 +70,8 @@ module Gitlab
.each(&method(:verify!))
end
- 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_without_instrumentation(location)
+ def normalize_location(location)
if location.is_a?(String)
expanded_location = expand_variables(location)
normalize_location_string(expanded_location)
diff --git a/lib/gitlab/ci/config/external/mapper/base.rb b/lib/gitlab/ci/config/external/mapper/base.rb
new file mode 100644
index 00000000000..d2f56d0b8f6
--- /dev/null
+++ b/lib/gitlab/ci/config/external/mapper/base.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module External
+ class Mapper
+ # Base class for mapper classes
+ class Base
+ def initialize(context)
+ @context = context
+ end
+
+ def process(*args)
+ context.logger.instrument(mapper_instrumentation_key) do
+ process_without_instrumentation(*args)
+ end
+ end
+
+ private
+
+ attr_reader :context
+
+ def process_without_instrumentation
+ raise NotImplementedError
+ end
+
+ def mapper_instrumentation_key
+ "config_mapper_#{self.class.name.demodulize.downcase}".to_sym
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/external/mapper/filter.rb b/lib/gitlab/ci/config/external/mapper/filter.rb
new file mode 100644
index 00000000000..4d2b26c7d98
--- /dev/null
+++ b/lib/gitlab/ci/config/external/mapper/filter.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module External
+ class Mapper
+ # Filters locations according to rules
+ class Filter < Base
+ private
+
+ def process_without_instrumentation(locations)
+ locations.select do |location|
+ Rules.new(location[:rules]).evaluate(context).pass?
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/external/mapper/location_expander.rb b/lib/gitlab/ci/config/external/mapper/location_expander.rb
new file mode 100644
index 00000000000..a4ca058f0d9
--- /dev/null
+++ b/lib/gitlab/ci/config/external/mapper/location_expander.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module External
+ class Mapper
+ # Expands locations to include all files matching the pattern
+ class LocationExpander < Base
+ private
+
+ def process_without_instrumentation(locations)
+ locations.flat_map do |location|
+ if location[:project]
+ expand_project_files(location)
+ elsif location[:local]
+ expand_wildcard_paths(location)
+ else
+ location
+ end
+ end
+ end
+
+ def expand_project_files(location)
+ Array.wrap(location[:file]).map do |file|
+ location.merge(file: file)
+ end
+ end
+
+ def expand_wildcard_paths(location)
+ return location unless location[:local].include?('*')
+
+ context.project.repository.search_files_by_wildcard_path(location[:local], context.sha).map do |path|
+ { local: path }
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/external/mapper/matcher.rb b/lib/gitlab/ci/config/external/mapper/matcher.rb
new file mode 100644
index 00000000000..85e19ff1ced
--- /dev/null
+++ b/lib/gitlab/ci/config/external/mapper/matcher.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module External
+ class Mapper
+ # Matches the first file type that matches the given location
+ class Matcher < Base
+ FILE_CLASSES = [
+ External::File::Local,
+ External::File::Project,
+ External::File::Remote,
+ External::File::Template,
+ External::File::Artifact
+ ].freeze
+
+ FILE_SUBKEYS = FILE_CLASSES.map { |f| f.name.demodulize.downcase }.freeze
+
+ private
+
+ def process_without_instrumentation(locations)
+ locations.map do |location|
+ matching = FILE_CLASSES.map do |file_class|
+ file_class.new(location, context)
+ end.select(&:matching?)
+
+ if matching.one?
+ matching.first
+ elsif matching.empty?
+ raise Mapper::AmbigiousSpecificationError,
+ "`#{masked_location(location.to_json)}` does not have a valid subkey for include. " \
+ "Valid subkeys are: `#{FILE_SUBKEYS.join('`, `')}`"
+ else
+ raise Mapper::AmbigiousSpecificationError,
+ "Each include must use only one of: `#{FILE_SUBKEYS.join('`, `')}`"
+ end
+ end
+ end
+
+ def masked_location(location)
+ context.mask_variables_from(location)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/external/mapper/normalizer.rb b/lib/gitlab/ci/config/external/mapper/normalizer.rb
new file mode 100644
index 00000000000..8fc798e78a0
--- /dev/null
+++ b/lib/gitlab/ci/config/external/mapper/normalizer.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module External
+ class Mapper
+ # Converts locations to canonical form (local:/remote:) if String
+ class Normalizer < Base
+ def initialize(context)
+ super
+
+ @variables_expander = VariablesExpander.new(context)
+ end
+
+ private
+
+ attr_reader :variables_expander
+
+ def process_without_instrumentation(locations)
+ locations.map do |location|
+ if location.is_a?(String)
+ # We need to expand before normalizing because the information of
+ # whether if it's a remote or local path may be hidden inside the variable.
+ location = variables_expander.expand(location)
+
+ normalize_location_string(location)
+ else
+ location.deep_symbolize_keys
+ end
+ end
+ end
+
+ def normalize_location_string(location)
+ if ::Gitlab::UrlSanitizer.valid?(location)
+ { remote: location }
+ else
+ { local: location }
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/external/mapper/variables_expander.rb b/lib/gitlab/ci/config/external/mapper/variables_expander.rb
new file mode 100644
index 00000000000..fddf04984d8
--- /dev/null
+++ b/lib/gitlab/ci/config/external/mapper/variables_expander.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module External
+ class Mapper
+ # Handles variable expansion
+ class VariablesExpander < Base
+ def expand(data)
+ if data.is_a?(String)
+ expand_variable(data)
+ else
+ transform_and_expand_variable(data)
+ end
+ end
+
+ private
+
+ def process_without_instrumentation(locations)
+ locations.map { |location| expand(location) }
+ end
+
+ def transform_and_expand_variable(data)
+ data.transform_values do |values|
+ case values
+ when Array
+ values.map { |value| expand_variable(value.to_s) }
+ when String
+ expand_variable(values)
+ else
+ values
+ end
+ end
+ end
+
+ def expand_variable(data)
+ ExpandVariables.expand(data, -> { variables })
+ end
+
+ def variables
+ @variables ||= context.variables_hash
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/external/mapper/verifier.rb b/lib/gitlab/ci/config/external/mapper/verifier.rb
new file mode 100644
index 00000000000..6d6f227b940
--- /dev/null
+++ b/lib/gitlab/ci/config/external/mapper/verifier.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module External
+ class Mapper
+ # Fetches file contents and verifies them
+ class Verifier < Base
+ private
+
+ def process_without_instrumentation(files)
+ files.select do |file|
+ verify_max_includes!
+ verify_execution_time!
+
+ file.validate!
+
+ context.expandset.add(file)
+ end
+ end
+
+ def verify_max_includes!
+ return if context.expandset.count < context.max_includes
+
+ raise Mapper::TooManyIncludesError, "Maximum of #{context.max_includes} nested includes are allowed!"
+ end
+
+ def verify_execution_time!
+ context.check_execution_time!
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/external/processor.rb b/lib/gitlab/ci/config/external/processor.rb
index 6a4aee26d80..e15b51fbff4 100644
--- a/lib/gitlab/ci/config/external/processor.rb
+++ b/lib/gitlab/ci/config/external/processor.rb
@@ -32,9 +32,7 @@ module Gitlab
def validate_external_files!
@external_files.each do |file|
- logger.instrument(:config_external_verify) do
- raise IncludeError, file.error_message unless file.valid?
- end
+ raise IncludeError, file.error_message unless file.valid?
end
end
diff --git a/lib/gitlab/ci/environment_matcher.rb b/lib/gitlab/ci/environment_matcher.rb
new file mode 100644
index 00000000000..7d7a7742f68
--- /dev/null
+++ b/lib/gitlab/ci/environment_matcher.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class EnvironmentMatcher
+ def initialize(pattern)
+ @pattern = pattern
+ end
+
+ def match?(environment)
+ return false if pattern.blank?
+
+ exact_match?(environment) || wildcard_match?(environment)
+ end
+
+ private
+
+ attr_reader :pattern, :match_type
+
+ def exact_match?(environment)
+ pattern == environment
+ end
+
+ def wildcard_match?(environment)
+ return false unless wildcard?
+
+ wildcard_regex.match?(environment)
+ end
+
+ def wildcard?
+ pattern.include?('*')
+ end
+
+ def wildcard_regex
+ @wildcard_regex ||= Regexp.new(pattern.gsub('*', '.*'))
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/lint.rb b/lib/gitlab/ci/lint.rb
index 51743a1f273..e0112a1b1c2 100644
--- a/lib/gitlab/ci/lint.rb
+++ b/lib/gitlab/ci/lint.rb
@@ -73,7 +73,7 @@ module Gitlab
end
def yaml_processor_result(content, logger)
- logger.instrument(:yaml_process) do
+ logger.instrument(:yaml_process, once: true) do
Gitlab::Ci::YamlProcessor.new(content, project: @project,
user: @current_user,
sha: @sha,
@@ -119,7 +119,7 @@ module Gitlab
environment: job[:environment],
when: job[:when],
allow_failure: job[:allow_failure],
- needs: job.dig(:needs_attributes)
+ needs: job[:needs_attributes]
}
end
end
@@ -130,10 +130,10 @@ module Gitlab
def build_logger
Gitlab::Ci::Pipeline::Logger.new(project: @project) do |l|
l.log_when do |observations|
- values = observations['yaml_process_duration_s']
- next false if values.empty?
+ duration = observations['yaml_process_duration_s']
+ next false unless duration
- values.max >= LOG_MAX_DURATION_THRESHOLD
+ duration >= LOG_MAX_DURATION_THRESHOLD
end
end
end
diff --git a/lib/gitlab/ci/parsers/security/common.rb b/lib/gitlab/ci/parsers/security/common.rb
index 0ac012b9fd1..67817c9f832 100644
--- a/lib/gitlab/ci/parsers/security/common.rb
+++ b/lib/gitlab/ci/parsers/security/common.rb
@@ -65,8 +65,14 @@ module Gitlab
)
end
+ # New Oj parsers are not thread safe, therefore,
+ # we need to initialize them for each thread.
+ def introspect_parser
+ Thread.current[:introspect_parser] ||= Oj::Introspect.new(filter: "remediations")
+ end
+
def report_data
- @report_data ||= Gitlab::Json.parse!(json_data)
+ @report_data ||= introspect_parser.parse(json_data)
end
def report_version
diff --git a/lib/gitlab/ci/pipeline/chain/build/associations.rb b/lib/gitlab/ci/pipeline/chain/build/associations.rb
index b5d63691849..b484a88a381 100644
--- a/lib/gitlab/ci/pipeline/chain/build/associations.rb
+++ b/lib/gitlab/ci/pipeline/chain/build/associations.rb
@@ -31,7 +31,8 @@ module Gitlab
source_pipeline: @command.bridge.pipeline,
source_project: @command.bridge.project,
source_bridge: @command.bridge,
- project: @command.project
+ project: @command.project,
+ source_partition_id: @command.bridge.partition_id
)
end
diff --git a/lib/gitlab/ci/pipeline/chain/cancel_pending_pipelines.rb b/lib/gitlab/ci/pipeline/chain/cancel_pending_pipelines.rb
index 07a3aff1862..53c8a7ac122 100644
--- a/lib/gitlab/ci/pipeline/chain/cancel_pending_pipelines.rb
+++ b/lib/gitlab/ci/pipeline/chain/cancel_pending_pipelines.rb
@@ -11,11 +11,10 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord
def perform!
- ff_enabled = Feature.enabled?(:ci_skip_auto_cancelation_on_child_pipelines, project)
- return if ff_enabled && pipeline.parent_pipeline? # skip if child pipeline
+ return if pipeline.parent_pipeline? # skip if child pipeline
return unless project.auto_cancel_pending_pipelines?
- Gitlab::OptimisticLocking.retry_lock(auto_cancelable_pipelines(ff_enabled), name: 'cancel_pending_pipelines') do |cancelables|
+ Gitlab::OptimisticLocking.retry_lock(auto_cancelable_pipelines, name: 'cancel_pending_pipelines') do |cancelables|
cancelables.select(:id).each_batch(of: BATCH_SIZE) do |cancelables_batch|
auto_cancel_interruptible_pipelines(cancelables_batch.ids)
end
@@ -29,19 +28,14 @@ module Gitlab
private
- def auto_cancelable_pipelines(ff_enabled)
- relation = project.all_pipelines
+ def auto_cancelable_pipelines
+ project.all_pipelines
.created_after(1.week.ago)
.ci_and_parent_sources
.for_ref(pipeline.ref)
.where_not_sha(project.commit(pipeline.ref).try(:id))
.alive_or_scheduled
-
- if ff_enabled
- relation.id_not_in(pipeline.id)
- else
- relation.id_not_in(pipeline.same_family_pipeline_ids)
- end
+ .id_not_in(pipeline.id)
end
def auto_cancel_interruptible_pipelines(pipeline_ids)
diff --git a/lib/gitlab/ci/pipeline/chain/command.rb b/lib/gitlab/ci/pipeline/chain/command.rb
index 5ec04b4889e..31b130b5ab7 100644
--- a/lib/gitlab/ci/pipeline/chain/command.rb
+++ b/lib/gitlab/ci/pipeline/chain/command.rb
@@ -98,7 +98,7 @@ module Gitlab
def observe_step_duration(step_class, duration)
step = step_class.name.underscore.parameterize(separator: '_')
- logger.observe("pipeline_step_#{step}_duration_s", duration)
+ logger.observe("pipeline_step_#{step}_duration_s", duration, once: true)
if Feature.enabled?(:ci_pipeline_creation_step_duration_tracking, type: :ops)
metrics.pipeline_creation_step_duration_histogram
@@ -107,14 +107,14 @@ module Gitlab
end
def observe_creation_duration(duration)
- logger.observe(:pipeline_creation_duration_s, duration)
+ logger.observe(:pipeline_creation_duration_s, duration, once: true)
metrics.pipeline_creation_duration_histogram
- .observe({}, duration.seconds)
+ .observe({ gitlab: gitlab_org_project?.to_s }, duration.seconds)
end
def observe_pipeline_size(pipeline)
- logger.observe(:pipeline_size_count, pipeline.total_size)
+ logger.observe(:pipeline_size_count, pipeline.total_size, once: true)
metrics.pipeline_size_histogram
.observe({ source: pipeline.source.to_s, plan: project.actual_plan_name }, pipeline.total_size)
@@ -157,6 +157,10 @@ module Gitlab
def full_git_ref_name_unavailable?
ref == origin_ref
end
+
+ def gitlab_org_project?
+ project.full_path == 'gitlab-org/gitlab'
+ end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/chain/config/process.rb b/lib/gitlab/ci/pipeline/chain/config/process.rb
index 5548fca320f..ad6b2fd3411 100644
--- a/lib/gitlab/ci/pipeline/chain/config/process.rb
+++ b/lib/gitlab/ci/pipeline/chain/config/process.rb
@@ -11,7 +11,7 @@ module Gitlab
def perform!
raise ArgumentError, 'missing config content' unless @command.config_content
- result = logger.instrument(:pipeline_config_process) do
+ result = logger.instrument(:pipeline_config_process, once: true) do
processor = ::Gitlab::Ci::YamlProcessor.new(
@command.config_content, {
project: project,
diff --git a/lib/gitlab/ci/pipeline/chain/create.rb b/lib/gitlab/ci/pipeline/chain/create.rb
index 207b4b5ff8b..d4c4f94c7d3 100644
--- a/lib/gitlab/ci/pipeline/chain/create.rb
+++ b/lib/gitlab/ci/pipeline/chain/create.rb
@@ -9,7 +9,7 @@ module Gitlab
include Gitlab::Utils::StrongMemoize
def perform!
- logger.instrument_with_sql(:pipeline_save) do
+ logger.instrument_once_with_sql(:pipeline_save) do
BulkInsertableAssociations.with_bulk_insert do
::Ci::BulkInsertableTags.with_bulk_insert_tags do
pipeline.transaction do
diff --git a/lib/gitlab/ci/pipeline/chain/ensure_environments.rb b/lib/gitlab/ci/pipeline/chain/ensure_environments.rb
index 1b9dd158733..ebea6a538ef 100644
--- a/lib/gitlab/ci/pipeline/chain/ensure_environments.rb
+++ b/lib/gitlab/ci/pipeline/chain/ensure_environments.rb
@@ -16,7 +16,7 @@ module Gitlab
private
def ensure_environment(build)
- ::Environments::CreateForBuildService.new.execute(build, merge_request: @command.merge_request)
+ ::Environments::CreateForBuildService.new.execute(build)
end
end
end
diff --git a/lib/gitlab/ci/pipeline/chain/seed.rb b/lib/gitlab/ci/pipeline/chain/seed.rb
index feae123f216..ae98c55e425 100644
--- a/lib/gitlab/ci/pipeline/chain/seed.rb
+++ b/lib/gitlab/ci/pipeline/chain/seed.rb
@@ -13,7 +13,7 @@ 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.
- logger.instrument(:pipeline_allocate_seed_attributes) do
+ logger.instrument(:pipeline_allocate_seed_attributes, once: true) do
pipeline.ensure_project_iid!
pipeline.ensure_ci_ref!
end
@@ -25,7 +25,7 @@ module Gitlab
##
# Gather all runtime build/stage errors
#
- seed_errors = logger.instrument(:pipeline_seed_evaluation) do
+ seed_errors = logger.instrument(:pipeline_seed_evaluation, once: true) do
pipeline_seed.errors
end
@@ -44,7 +44,7 @@ module Gitlab
def pipeline_seed
strong_memoize(:pipeline_seed) do
- logger.instrument(:pipeline_seed_initialization) do
+ logger.instrument(:pipeline_seed_initialization, once: true) do
stages_attributes = @command.yaml_processor_result.stages_attributes
Gitlab::Ci::Pipeline::Seed::Pipeline.new(context, stages_attributes)
@@ -61,7 +61,7 @@ module Gitlab
end
def root_variables
- logger.instrument(:pipeline_seed_merge_variables) do
+ strong_memoize(:root_variables) do
::Gitlab::Ci::Variables::Helpers.merge_variables(
@command.yaml_processor_result.root_variables,
@command.workflow_rules_result.variables
diff --git a/lib/gitlab/ci/pipeline/logger.rb b/lib/gitlab/ci/pipeline/logger.rb
index 4b7cbae5004..f393406b549 100644
--- a/lib/gitlab/ci/pipeline/logger.rb
+++ b/lib/gitlab/ci/pipeline/logger.rb
@@ -23,7 +23,7 @@ module Gitlab
log_conditions.push(block)
end
- def instrument(operation)
+ def instrument(operation, once: false)
return yield unless enabled?
raise ArgumentError, 'block not given' unless block_given?
@@ -32,63 +32,72 @@ module Gitlab
result = yield
- observe("#{operation}_duration_s", current_monotonic_time - op_started_at)
+ observe("#{operation}_duration_s", current_monotonic_time - op_started_at, once: once)
result
end
- def instrument_with_sql(operation, &block)
+ def instrument_once_with_sql(operation, &block)
op_start_db_counters = current_db_counter_payload
- result = instrument(operation, &block)
+ result = instrument(operation, once: true, &block)
- observe_sql_counters(operation, op_start_db_counters, current_db_counter_payload)
+ observe_sql_counters(operation, op_start_db_counters, current_db_counter_payload, once: true)
result
end
- def observe(operation, value)
+ def observe(operation, value, once: false)
return unless enabled?
- observations[operation.to_s].push(value)
+ if once
+ observations[operation.to_s] = value
+ else
+ observations[operation.to_s] ||= []
+ observations[operation.to_s].push(value)
+ end
end
def commit(pipeline:, caller:)
return unless log?
- attributes = {
- class: self.class.name.to_s,
- pipeline_creation_caller: caller,
- project_id: project&.id, # project is not available when called from `/ci/lint`
- pipeline_persisted: pipeline.persisted?,
- pipeline_source: pipeline.source,
- pipeline_creation_service_duration_s: age
- }
-
- if pipeline.persisted?
- attributes[:pipeline_builds_tags_count] = pipeline.tags_count
- attributes[:pipeline_builds_distinct_tags_count] = pipeline.distinct_tags_count
- attributes[:pipeline_id] = pipeline.id
+ Gitlab::ApplicationContext.with_context(project: project) do
+ attributes = Gitlab::ApplicationContext.current.merge(
+ class: self.class.name.to_s,
+ pipeline_creation_caller: caller,
+ project_id: project&.id, # project is not available when called from `/ci/lint`
+ pipeline_persisted: pipeline.persisted?,
+ pipeline_source: pipeline.source,
+ pipeline_creation_service_duration_s: age
+ )
+
+ if pipeline.persisted?
+ attributes[:pipeline_builds_tags_count] = pipeline.tags_count
+ attributes[:pipeline_builds_distinct_tags_count] = pipeline.distinct_tags_count
+ attributes[:pipeline_id] = pipeline.id
+ end
+
+ attributes.compact!
+ attributes.stringify_keys!
+ attributes.merge!(observations_hash)
+
+ destination.info(attributes)
end
-
- attributes.compact!
- attributes.stringify_keys!
- attributes.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,
- 'sum' => values.sum,
- 'avg' => values.sum / values.size
- }
+ observations.transform_values do |observation|
+ next if observation.blank?
+
+ if observation.is_a?(Array)
+ {
+ 'count' => observation.size,
+ 'max' => observation.max,
+ 'sum' => observation.sum
+ }
+ else
+ observation
+ end
end.compact
end
@@ -110,21 +119,20 @@ module Gitlab
end
def enabled?
- strong_memoize(:enabled) do
- ::Feature.enabled?(:ci_pipeline_creation_logger, project, type: :ops)
- end
+ ::Feature.enabled?(:ci_pipeline_creation_logger, project, type: :ops)
end
+ strong_memoize_attr :enabled?, :enabled
def observations
- @observations ||= Hash.new { |hash, key| hash[key] = [] }
+ @observations ||= {}
end
- def observe_sql_counters(operation, start_db_counters, end_db_counters)
+ def observe_sql_counters(operation, start_db_counters, end_db_counters, once: false)
end_db_counters.each do |key, value|
result = value - start_db_counters.fetch(key, 0)
next if result == 0
- observe("#{operation}_#{key}", result)
+ observe("#{operation}_#{key}", result, once: once)
end
end
diff --git a/lib/gitlab/ci/pipeline/metrics.rb b/lib/gitlab/ci/pipeline/metrics.rb
index c3e0f043b44..04565beeecc 100644
--- a/lib/gitlab/ci/pipeline/metrics.rb
+++ b/lib/gitlab/ci/pipeline/metrics.rb
@@ -9,7 +9,8 @@ module Gitlab
def self.pipeline_creation_duration_histogram
name = :gitlab_ci_pipeline_creation_duration_seconds
comment = 'Pipeline creation duration'
- labels = {}
+ # @gitlab: boolean value - if project is gitlab-org/gitlab
+ labels = { gitlab: false }
buckets = [0.01, 0.05, 0.1, 0.5, 1.0, 2.0, 5.0, 20.0, 50.0, 240.0]
::Gitlab::Metrics.histogram(name, comment, labels, buckets)
diff --git a/lib/gitlab/ci/pipeline/seed/build.rb b/lib/gitlab/ci/pipeline/seed/build.rb
index 2e4267e986b..b0b79b994c1 100644
--- a/lib/gitlab/ci/pipeline/seed/build.rb
+++ b/lib/gitlab/ci/pipeline/seed/build.rb
@@ -9,12 +9,13 @@ module Gitlab
delegate :dig, to: :@seed_attributes
- def initialize(context, attributes, stages_for_needs_lookup = [])
+ def initialize(context, attributes, stages_for_needs_lookup, stage)
@context = context
@pipeline = context.pipeline
@seed_attributes = attributes
@stages_for_needs_lookup = stages_for_needs_lookup.compact
@needs_attributes = dig(:needs_attributes)
+ @stage = stage
@resource_group_key = attributes.delete(:resource_group_key)
@job_variables = @seed_attributes.delete(:job_variables)
@root_variables_inheritance = @seed_attributes.delete(:root_variables_inheritance) { true }
@@ -33,6 +34,8 @@ module Gitlab
.new(attributes.delete(:cache), @pipeline)
calculate_yaml_variables!
+
+ @processable = initialize_processable
end
def name
@@ -40,21 +43,20 @@ module Gitlab
end
def included?
- strong_memoize(:inclusion) do
- logger.instrument(:pipeline_seed_build_inclusion) do
- if @using_rules
- rules_result.pass?
- elsif @using_only || @using_except
- all_of_only? && none_of_except?
- else
- true
- end
+ logger.instrument(:pipeline_seed_build_inclusion) do
+ if @using_rules
+ rules_result.pass?
+ elsif @using_only || @using_except
+ all_of_only? && none_of_except?
+ else
+ true
end
end
end
+ strong_memoize_attr :included?, :inclusion
def errors
- strong_memoize(:errors) do
+ logger.instrument(:pipeline_seed_build_errors) do
# We check rules errors before checking "included?" because rules affects its inclusion status.
next rules_errors if rules_errors
next unless included?
@@ -62,14 +64,22 @@ module Gitlab
[needs_errors, variable_expansion_errors].compact.flatten
end
end
+ strong_memoize_attr :errors
+ # TODO: Method used only in specs. Replace with `to_resource.attributes` when
+ # the feature flag ci_reuse_build_in_seed_context is removed.
+ # Then remove this method.
def attributes
- @seed_attributes
- .deep_merge(pipeline_attributes)
- .deep_merge(rules_attributes)
- .deep_merge(allow_failure_criteria_attributes)
- .deep_merge(@cache.cache_attributes)
- .deep_merge(runner_tags)
+ if reuse_build_in_seed_context?
+ initial_attributes.deep_merge(evaluated_attributes)
+ else
+ @seed_attributes
+ .deep_merge(pipeline_attributes)
+ .deep_merge(rules_attributes)
+ .deep_merge(allow_failure_criteria_attributes)
+ .deep_merge(@cache.cache_attributes)
+ .deep_merge(runner_tags)
+ end
end
def bridge?
@@ -80,12 +90,30 @@ module Gitlab
end
def to_resource
- strong_memoize(:resource) do
- initialize_processable
+ logger.instrument(:pipeline_seed_build_to_resource) do
+ if reuse_build_in_seed_context?
+ # The `options` attribute need to be entirely reassigned because they may
+ # be overridden by evaluated_attributes.
+ # We also don't want to reassign all the `initial_attributes` since those
+ # can affect performance. We only want to assign what's changed.
+ assignable_attributes = initial_attributes.slice(:options)
+ .deep_merge(evaluated_attributes)
+ processable.assign_attributes(assignable_attributes)
+ processable
+ else
+ legacy_initialize_processable
+ end
end
end
+ strong_memoize_attr :to_resource
- def initialize_processable
+ private
+
+ attr_reader :processable
+
+ delegate :logger, to: :@context
+
+ def legacy_initialize_processable
if bridge?
::Ci::Bridge.new(attributes)
else
@@ -93,9 +121,28 @@ module Gitlab
end
end
- private
+ def initialize_processable
+ return unless reuse_build_in_seed_context?
- delegate :logger, to: :@context
+ if bridge?
+ ::Ci::Bridge.new(initial_attributes)
+ else
+ ::Ci::Build.new(initial_attributes)
+ end
+ end
+
+ def initial_attributes
+ @seed_attributes
+ .deep_merge(pipeline_attributes)
+ .deep_merge(ci_stage: @stage)
+ .deep_merge(@cache.cache_attributes)
+ end
+
+ def evaluated_attributes
+ rules_attributes
+ .deep_merge(allow_failure_criteria_attributes)
+ .deep_merge(runner_tags)
+ end
def all_of_only?
@only.all? { |spec| spec.satisfied_by?(@pipeline, evaluate_context) }
@@ -155,40 +202,39 @@ module Gitlab
end
def rules_attributes
- strong_memoize(:rules_attributes) do
- next {} unless @using_rules
+ return {} unless @using_rules
- rules_variables_result = ::Gitlab::Ci::Variables::Helpers.merge_variables(
- @seed_attributes[:yaml_variables], rules_result.variables
- )
+ rules_variables_result = ::Gitlab::Ci::Variables::Helpers.merge_variables(
+ @seed_attributes[:yaml_variables], rules_result.variables
+ )
- rules_result.build_attributes.merge(yaml_variables: rules_variables_result)
- end
+ rules_result.build_attributes.merge(yaml_variables: rules_variables_result)
end
+ strong_memoize_attr :rules_attributes
def rules_result
- strong_memoize(:rules_result) do
- @rules.evaluate(@pipeline, evaluate_context)
- end
+ @rules.evaluate(@pipeline, evaluate_context)
end
+ strong_memoize_attr :rules_result
def rules_errors
- strong_memoize(:rules_errors) do
- ["Failed to parse rule for #{name}: #{rules_result.errors.join(', ')}"] if rules_result.errors.present?
- end
+ ["Failed to parse rule for #{name}: #{rules_result.errors.join(', ')}"] if rules_result.errors.present?
end
+ strong_memoize_attr :rules_errors
def evaluate_context
- strong_memoize(:evaluate_context) do
+ if reuse_build_in_seed_context?
+ Gitlab::Ci::Build::Context::Build.new(@pipeline, @seed_attributes, processable)
+ else
Gitlab::Ci::Build::Context::Build.new(@pipeline, @seed_attributes)
end
end
+ strong_memoize_attr :evaluate_context
def runner_tags
- strong_memoize(:runner_tags) do
- { tag_list: evaluate_runner_tags }.compact
- end
+ { tag_list: evaluate_runner_tags }.compact
end
+ strong_memoize_attr :runner_tags
def evaluate_runner_tags
@seed_attributes.delete(:tag_list)&.map do |tag|
@@ -211,6 +257,11 @@ module Gitlab
from: @context.root_variables, to: @job_variables, inheritance: @root_variables_inheritance
)
end
+
+ def reuse_build_in_seed_context?
+ Feature.enabled?(:ci_reuse_build_in_seed_context, @pipeline.project)
+ end
+ strong_memoize_attr :reuse_build_in_seed_context?, :reuse_build_in_seed_context
end
end
end
diff --git a/lib/gitlab/ci/pipeline/seed/build/cache.rb b/lib/gitlab/ci/pipeline/seed/build/cache.rb
index 78ffaaa7e81..781065a63db 100644
--- a/lib/gitlab/ci/pipeline/seed/build/cache.rb
+++ b/lib/gitlab/ci/pipeline/seed/build/cache.rb
@@ -6,7 +6,7 @@ module Gitlab
module Seed
class Build
class Cache
- def initialize(pipeline, cache)
+ def initialize(pipeline, cache, custom_key_prefix)
@pipeline = pipeline
local_cache = cache.to_h.deep_dup
@key = local_cache.delete(:key)
@@ -14,6 +14,7 @@ module Gitlab
@policy = local_cache.delete(:policy)
@untracked = local_cache.delete(:untracked)
@when = local_cache.delete(:when)
+ @custom_key_prefix = custom_key_prefix
raise ArgumentError, "unknown cache keys: #{local_cache.keys}" if local_cache.any?
end
@@ -45,6 +46,7 @@ module Gitlab
def key_from_files
return unless @key.is_a?(Hash)
+ @key[:prefix] ||= @custom_key_prefix.to_s
[@key[:prefix], files_digest].select(&:present?).join('-')
end
diff --git a/lib/gitlab/ci/pipeline/seed/pipeline.rb b/lib/gitlab/ci/pipeline/seed/pipeline.rb
index 9e609debeed..57ad2546f1c 100644
--- a/lib/gitlab/ci/pipeline/seed/pipeline.rb
+++ b/lib/gitlab/ci/pipeline/seed/pipeline.rb
@@ -38,8 +38,10 @@ module Gitlab
private
+ delegate :logger, to: :@context
+
def stage_seeds
- strong_memoize(:stage_seeds) do
+ logger.instrument(:pipeline_seed_stage_seeds) do
seeds = @stages_attributes.inject([]) do |previous_stages, attributes|
seed = Gitlab::Ci::Pipeline::Seed::Stage.new(@context, attributes, previous_stages)
previous_stages + [seed]
@@ -48,6 +50,7 @@ module Gitlab
seeds.select(&:included?)
end
end
+ strong_memoize_attr :stage_seeds
end
end
end
diff --git a/lib/gitlab/ci/pipeline/seed/stage.rb b/lib/gitlab/ci/pipeline/seed/stage.rb
index 1c4247bd5ee..c3e94529634 100644
--- a/lib/gitlab/ci/pipeline/seed/stage.rb
+++ b/lib/gitlab/ci/pipeline/seed/stage.rb
@@ -10,54 +10,49 @@ module Gitlab
delegate :size, to: :seeds
delegate :dig, to: :seeds
- def initialize(context, attributes, previous_stages)
- @context = context
- @pipeline = context.pipeline
- @attributes = attributes
- @previous_stages = previous_stages
-
- @builds = attributes.fetch(:builds).map do |attributes|
- Seed::Build.new(context, attributes, previous_stages + [self])
+ attr_reader :attributes
+
+ def initialize(context, stage_attributes, previous_stages)
+ pipeline = context.pipeline
+ @attributes = {
+ name: stage_attributes.fetch(:name),
+ position: stage_attributes.fetch(:index),
+ pipeline: pipeline,
+ project: pipeline.project,
+ partition_id: pipeline.partition_id
+ }
+
+ @stage = ::Ci::Stage.new(@attributes)
+
+ @builds = stage_attributes.fetch(:builds).map do |build_attributes|
+ Seed::Build.new(context, build_attributes, previous_stages + [self], @stage)
end
end
- def attributes
- { name: @attributes.fetch(:name),
- position: @attributes.fetch(:index),
- pipeline: @pipeline,
- project: @pipeline.project,
- partition_id: @pipeline.partition_id }
- end
-
def seeds
- strong_memoize(:seeds) do
- @builds.select(&:included?)
- end
+ @builds.select(&:included?)
end
+ strong_memoize_attr :seeds
def errors
- strong_memoize(:errors) do
- @builds.flat_map(&:errors).compact
- end
+ @builds.flat_map(&:errors).compact
end
+ strong_memoize_attr :errors
def seeds_names
- strong_memoize(:seeds_names) do
- seeds.map(&:name).to_set
- end
+ seeds.map(&:name).to_set
end
+ strong_memoize_attr :seeds_names
def included?
seeds.any?
end
def to_resource
- strong_memoize(:stage) do
- ::Ci::Stage.new(attributes).tap do |stage|
- stage.statuses = seeds.map(&:to_resource)
- end
- end
+ @stage.statuses = seeds.map(&:to_resource)
+ @stage
end
+ strong_memoize_attr :to_resource
end
end
end
diff --git a/lib/gitlab/ci/reports/security/finding.rb b/lib/gitlab/ci/reports/security/finding.rb
index dd9b9cc6d55..92a91854358 100644
--- a/lib/gitlab/ci/reports/security/finding.rb
+++ b/lib/gitlab/ci/reports/security/finding.rb
@@ -83,8 +83,8 @@ module Gitlab
message
cve
solution
- ].each_with_object({}) do |key, hash|
- hash[key] = public_send(key) # rubocop:disable GitlabSecurity/PublicSend
+ ].index_with do |key|
+ public_send(key) # rubocop:disable GitlabSecurity/PublicSend
end
end
@@ -98,7 +98,7 @@ module Gitlab
end
def unsafe?(severity_levels, report_types)
- severity.to_s.in?(severity_levels) && (report_types.blank? || report_type.to_s.in?(report_types) )
+ severity.to_s.in?(severity_levels) && (report_types.blank? || report_type.to_s.in?(report_types))
end
def eql?(other)
diff --git a/lib/gitlab/ci/reports/security/finding_key.rb b/lib/gitlab/ci/reports/security/finding_key.rb
index ad047fbf904..d42a0ea5b2e 100644
--- a/lib/gitlab/ci/reports/security/finding_key.rb
+++ b/lib/gitlab/ci/reports/security/finding_key.rb
@@ -15,7 +15,7 @@ module Gitlab
has_fingerprints? && other.has_fingerprints? &&
location_fingerprint == other.location_fingerprint &&
- identifier_fingerprint == other.identifier_fingerprint
+ identifier_fingerprint == other.identifier_fingerprint
end
def hash
diff --git a/lib/gitlab/ci/reports/security/identifier.rb b/lib/gitlab/ci/reports/security/identifier.rb
index 4ba943cdcbc..0ff6be6acc4 100644
--- a/lib/gitlab/ci/reports/security/identifier.rb
+++ b/lib/gitlab/ci/reports/security/identifier.rb
@@ -31,8 +31,8 @@ module Gitlab
fingerprint
name
url
- ].each_with_object({}) do |key, hash|
- hash[key] = public_send(key) # rubocop:disable GitlabSecurity/PublicSend
+ ].index_with do |key|
+ public_send(key) # rubocop:disable GitlabSecurity/PublicSend
end
end
diff --git a/lib/gitlab/ci/reports/security/reports.rb b/lib/gitlab/ci/reports/security/reports.rb
index 5c08381d5cc..8425881a4ab 100644
--- a/lib/gitlab/ci/reports/security/reports.rb
+++ b/lib/gitlab/ci/reports/security/reports.rb
@@ -21,29 +21,6 @@ module Gitlab
def findings
reports.values.flat_map(&:findings)
end
-
- def violates_default_policy_against?(target_reports, vulnerabilities_allowed, severity_levels, vulnerability_states, report_types = [])
- if Feature.enabled?(:require_approval_on_scan_removal, pipeline.project) && scan_removed?(target_reports)
- return true
- end
-
- unsafe_findings_count(target_reports, severity_levels, vulnerability_states, report_types) > vulnerabilities_allowed
- end
-
- def unsafe_findings_uuids(severity_levels, report_types)
- findings.select { |finding| finding.unsafe?(severity_levels, report_types) }.map(&:uuid)
- end
-
- private
-
- def unsafe_findings_count(target_reports, severity_levels, vulnerability_states, report_types)
- new_uuids = unsafe_findings_uuids(severity_levels, report_types) - target_reports&.unsafe_findings_uuids(severity_levels, report_types).to_a
- new_uuids.count
- end
-
- def scan_removed?(target_reports)
- (target_reports&.reports&.keys.to_a - reports.keys).any?
- end
end
end
end
diff --git a/lib/gitlab/ci/reports/test_suite.rb b/lib/gitlab/ci/reports/test_suite.rb
index d0388c65f58..dcc593b4403 100644
--- a/lib/gitlab/ci/reports/test_suite.rb
+++ b/lib/gitlab/ci/reports/test_suite.rb
@@ -96,8 +96,8 @@ module Gitlab
end
def sort_by_execution_time_desc
- @test_cases = @test_cases.keys.each_with_object({}) do |key, hash|
- hash[key] = @test_cases[key].sort_by { |_key, test_case| -test_case.execution_time }.to_h
+ @test_cases = @test_cases.keys.index_with do |key|
+ @test_cases[key].sort_by { |_key, test_case| -test_case.execution_time }.to_h
end
end
end
diff --git a/lib/gitlab/ci/runner_instructions.rb b/lib/gitlab/ci/runner_instructions.rb
index 68c911d3dbb..bcda2fec5ba 100644
--- a/lib/gitlab/ci/runner_instructions.rb
+++ b/lib/gitlab/ci/runner_instructions.rb
@@ -22,7 +22,8 @@ module Gitlab
osx: {
human_readable_name: "macOS",
download_locations: {
- amd64: "https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-darwin-amd64"
+ amd64: "https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-darwin-amd64",
+ arm64: "https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-darwin-arm64"
},
install_script_template_path: "lib/gitlab/ci/runner_instructions/templates/osx/install.sh",
runner_executable: "gitlab-runner"
@@ -61,7 +62,7 @@ module Gitlab
def install_script
with_error_handling [Gitlab::Ci::RunnerInstructions::ArgumentError] do
- raise Gitlab::Ci::RunnerInstructions::ArgumentError, s_('Architecture not found for OS') unless environment[:download_locations].key?(@arch.to_sym)
+ raise Gitlab::Ci::RunnerInstructions::ArgumentError, _('Architecture not found for OS') unless environment[:download_locations].key?(@arch.to_sym)
replace_variables(get_file(environment[:install_script_template_path]))
end
@@ -69,7 +70,7 @@ module Gitlab
def register_command
with_error_handling [Gitlab::Ci::RunnerInstructions::ArgumentError, Gitlab::Access::AccessDeniedError] do
- raise Gitlab::Ci::RunnerInstructions::ArgumentError, s_('No runner executable') unless environment[:runner_executable]
+ raise Gitlab::Ci::RunnerInstructions::ArgumentError, _('No runner executable') unless environment[:runner_executable]
server_url = Gitlab::Routing.url_helpers.root_url(only_path: false)
runner_executable = environment[:runner_executable]
@@ -90,12 +91,12 @@ module Gitlab
end
def environment
- @environment ||= OS[@os.to_sym] || ( raise Gitlab::Ci::RunnerInstructions::ArgumentError, s_('Invalid OS') )
+ @environment ||= OS[@os.to_sym] || (raise Gitlab::Ci::RunnerInstructions::ArgumentError, _('Invalid OS'))
end
def validate_params
- @errors << s_('Missing OS') unless @os.present?
- @errors << s_('Missing arch') unless @arch.present?
+ @errors << _('Missing OS') unless @os.present?
+ @errors << _('Missing arch') unless @arch.present?
end
def replace_variables(expression)
diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
index fddcc1492a8..11420b05dfb 100644
--- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
@@ -177,11 +177,11 @@ include:
- template: Jobs/Browser-Performance-Testing.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml
- template: Jobs/Helm-2to3.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Helm-2to3.gitlab-ci.yml
- template: Security/DAST.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml
- - template: Security/Container-Scanning.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml
- - template: Security/Dependency-Scanning.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml
- - template: Security/License-Scanning.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Security/License-Scanning.gitlab-ci.yml
- - template: Security/SAST.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
- - template: Security/Secret-Detection.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Security/Secret-Detection.gitlab-ci.yml
+ - template: Jobs/Container-Scanning.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Container-Scanning.gitlab-ci.yml
+ - template: Jobs/Dependency-Scanning.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Dependency-Scanning.gitlab-ci.yml
+ - template: Jobs/License-Scanning.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/License-Scanning.gitlab-ci.yml
+ - template: Jobs/SAST.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/SAST.gitlab-ci.yml
+ - template: Jobs/Secret-Detection.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Secret-Detection.gitlab-ci.yml
# The latest build job generates a dotenv report artifact with a CI_APPLICATION_TAG
# that also includes the image digest. This configures Auto Deploy to receive
diff --git a/lib/gitlab/ci/templates/Gradle.gitlab-ci.yml b/lib/gitlab/ci/templates/Gradle.gitlab-ci.yml
index 671925c5df6..16ce85548df 100644
--- a/lib/gitlab/ci/templates/Gradle.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Gradle.gitlab-ci.yml
@@ -11,13 +11,6 @@
image: gradle:alpine
-# Disable the Gradle daemon for Continuous Integration servers as correctness
-# is usually a priority over speed in CI environments. Using a fresh
-# runtime for each build is more reliable since the runtime is completely
-# isolated from any previous builds.
-variables:
- GRADLE_OPTS: "-Dorg.gradle.daemon=false"
-
before_script:
- GRADLE_USER_HOME="$(pwd)/.gradle"
- export GRADLE_USER_HOME
diff --git a/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml
index fcf2ac7de7a..026ddf4a17a 100644
--- a/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml
@@ -7,7 +7,7 @@ browser_performance:
variables:
DOCKER_TLS_CERTDIR: ""
SITESPEED_IMAGE: sitespeedio/sitespeed.io
- SITESPEED_VERSION: 14.1.0
+ SITESPEED_VERSION: 26.1.0
SITESPEED_OPTIONS: ''
services:
- name: 'docker:20.10.12-dind'
diff --git a/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.latest.gitlab-ci.yml
index 04b7dacf2dd..218c2f79e6a 100644
--- a/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.latest.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.latest.gitlab-ci.yml
@@ -7,7 +7,7 @@ browser_performance:
variables:
DOCKER_TLS_CERTDIR: ""
SITESPEED_IMAGE: sitespeedio/sitespeed.io
- SITESPEED_VERSION: 14.1.0
+ SITESPEED_VERSION: latest
SITESPEED_OPTIONS: ''
services:
- name: 'docker:20.10.12-dind'
diff --git a/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml
index 23efed212f8..b4beeb60dfd 100644
--- a/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml
@@ -8,7 +8,8 @@ code_quality:
variables:
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: ""
- CODE_QUALITY_IMAGE: "$CI_TEMPLATE_REGISTRY_HOST/gitlab-org/ci-cd/codequality:0.87.0"
+ CODE_QUALITY_IMAGE_TAG: "0.87.3"
+ CODE_QUALITY_IMAGE: "$CI_TEMPLATE_REGISTRY_HOST/gitlab-org/ci-cd/codequality:$CODE_QUALITY_IMAGE_TAG"
needs: []
script:
- export SOURCE_CODE=$PWD
diff --git a/lib/gitlab/ci/templates/Jobs/Container-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Container-Scanning.gitlab-ci.yml
new file mode 100644
index 00000000000..fa609afc5a8
--- /dev/null
+++ b/lib/gitlab/ci/templates/Jobs/Container-Scanning.gitlab-ci.yml
@@ -0,0 +1,54 @@
+# 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/Jobs/Container-Scanning.gitlab-ci.yml
+
+# Use this template to enable container scanning in your project.
+# You should add this template to an existing `.gitlab-ci.yml` file by using the `include:`
+# keyword.
+# The template should work without modifications but you can customize the template settings if
+# needed: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings
+#
+# Requirements:
+# - A `test` stage to be present in the pipeline.
+# - You must define the image to be scanned in the CS_IMAGE variable. If CS_IMAGE is the
+# same as $CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG, you can skip this.
+# - Container registry credentials defined by `CS_REGISTRY_USER` and `CS_REGISTRY_PASSWORD` variables if the
+# image to be scanned is in a private registry.
+# - For auto-remediation, a readable Dockerfile in the root of the project or as defined by the
+# CS_DOCKERFILE_PATH variable.
+#
+# Configure container 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/container_scanning/#available-variables
+
+variables:
+ CS_ANALYZER_IMAGE: "$CI_TEMPLATE_REGISTRY_HOST/security-products/container-scanning:5"
+
+container_scanning:
+ image: "$CS_ANALYZER_IMAGE$CS_IMAGE_SUFFIX"
+ stage: test
+ variables:
+ # To provide a `vulnerability-allowlist.yml` file, override the GIT_STRATEGY variable in your
+ # `.gitlab-ci.yml` file and set it to `fetch`.
+ # For details, see the following links:
+ # https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html#overriding-the-container-scanning-template
+ # https://docs.gitlab.com/ee/user/application_security/container_scanning/#vulnerability-allowlisting
+ GIT_STRATEGY: none
+ allow_failure: true
+ artifacts:
+ reports:
+ container_scanning: 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
+ rules:
+ - if: $CONTAINER_SCANNING_DISABLED
+ when: never
+ - if: $CI_COMMIT_BRANCH &&
+ $CI_GITLAB_FIPS_MODE == "true" &&
+ $CS_ANALYZER_IMAGE !~ /-(fips|ubi)\z/
+ variables:
+ CS_IMAGE_SUFFIX: -fips
+ - if: $CI_COMMIT_BRANCH
diff --git a/lib/gitlab/ci/templates/Jobs/Container-Scanning.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Container-Scanning.latest.gitlab-ci.yml
new file mode 100644
index 00000000000..f750bda2a3f
--- /dev/null
+++ b/lib/gitlab/ci/templates/Jobs/Container-Scanning.latest.gitlab-ci.yml
@@ -0,0 +1,68 @@
+# 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/Jobs/Container-Scanning.latest.gitlab-ci.yml
+
+# Use this template to enable container scanning in your project.
+# You should add this template to an existing `.gitlab-ci.yml` file by using the `include:`
+# keyword.
+# The template should work without modifications but you can customize the template settings if
+# needed: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings
+#
+# Requirements:
+# - A `test` stage to be present in the pipeline.
+# - You must define the image to be scanned in the CS_IMAGE variable. If CS_IMAGE is the
+# same as $CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG, you can skip this.
+# - Container registry credentials defined by `CS_REGISTRY_USER` and `CS_REGISTRY_PASSWORD` variables if the
+# image to be scanned is in a private registry.
+# - For auto-remediation, a readable Dockerfile in the root of the project or as defined by the
+# CS_DOCKERFILE_PATH variable.
+#
+# Configure container 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/container_scanning/#available-variables
+
+variables:
+ CS_ANALYZER_IMAGE: "$CI_TEMPLATE_REGISTRY_HOST/security-products/container-scanning:5"
+
+container_scanning:
+ image: "$CS_ANALYZER_IMAGE$CS_IMAGE_SUFFIX"
+ stage: test
+ variables:
+ # To provide a `vulnerability-allowlist.yml` file, override the GIT_STRATEGY variable in your
+ # `.gitlab-ci.yml` file and set it to `fetch`.
+ # For details, see the following links:
+ # https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html#overriding-the-container-scanning-template
+ # https://docs.gitlab.com/ee/user/application_security/container_scanning/#vulnerability-allowlisting
+ GIT_STRATEGY: none
+ allow_failure: true
+ artifacts:
+ reports:
+ container_scanning: 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
+ rules:
+ - if: $CONTAINER_SCANNING_DISABLED
+ when: never
+
+ # Add the job to merge request pipelines if there's an open merge request.
+ - if: $CI_PIPELINE_SOURCE == "merge_request_event" &&
+ $CI_GITLAB_FIPS_MODE == "true" &&
+ $CS_ANALYZER_IMAGE !~ /-(fips|ubi)\z/
+ variables:
+ CS_IMAGE_SUFFIX: -fips
+ - if: $CI_PIPELINE_SOURCE == "merge_request_event"
+
+ # Don't add it to a *branch* pipeline if it's already in a merge request pipeline.
+ - if: $CI_OPEN_MERGE_REQUESTS
+ when: never
+
+ # Add the job to branch pipelines.
+ - if: $CI_COMMIT_BRANCH &&
+ $CI_GITLAB_FIPS_MODE == "true" &&
+ $CS_ANALYZER_IMAGE !~ /-(fips|ubi)\z/
+ variables:
+ CS_IMAGE_SUFFIX: -fips
+ - if: $CI_COMMIT_BRANCH
diff --git a/lib/gitlab/ci/templates/Jobs/Load-Performance-Testing.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Load-Performance-Testing.gitlab-ci.yml
index 936d8751fe1..12105e0e95d 100644
--- a/lib/gitlab/ci/templates/Jobs/Load-Performance-Testing.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Load-Performance-Testing.gitlab-ci.yml
@@ -4,8 +4,8 @@ load_performance:
allow_failure: true
variables:
DOCKER_TLS_CERTDIR: ""
- K6_IMAGE: loadimpact/k6
- K6_VERSION: 0.27.0
+ K6_IMAGE: grafana/k6
+ K6_VERSION: 0.41.0
K6_TEST_FILE: raw.githubusercontent.com/grafana/k6/master/samples/http_get.js
K6_OPTIONS: ''
K6_DOCKER_OPTIONS: ''
diff --git a/lib/gitlab/ci/templates/Jobs/SAST.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/SAST.gitlab-ci.yml
index a6d47e31de2..2c5027cdb43 100644
--- a/lib/gitlab/ci/templates/Jobs/SAST.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/SAST.gitlab-ci.yml
@@ -238,6 +238,8 @@ semgrep-sast:
- '**/*.java'
- '**/*.cs'
- '**/*.html'
+ - '**/*.scala'
+ - '**/*.sc'
sobelow-sast:
extends: .sast-analyzer
diff --git a/lib/gitlab/ci/templates/Jobs/SAST.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/SAST.latest.gitlab-ci.yml
index 4600468ef30..58709d3ab62 100644
--- a/lib/gitlab/ci/templates/Jobs/SAST.latest.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/SAST.latest.gitlab-ci.yml
@@ -299,6 +299,8 @@ semgrep-sast:
- '**/*.java'
- '**/*.html'
- '**/*.cs'
+ - '**/*.scala'
+ - '**/*.sc'
- if: $CI_OPEN_MERGE_REQUESTS # Don't add it to a *branch* pipeline if it's already in a merge request pipeline.
when: never
- if: $CI_COMMIT_BRANCH # If there's no open merge request, add it to a *branch* pipeline instead.
@@ -313,6 +315,8 @@ semgrep-sast:
- '**/*.java'
- '**/*.html'
- '**/*.cs'
+ - '**/*.scala'
+ - '**/*.sc'
sobelow-sast:
extends: .sast-analyzer
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 79a08c33fdf..879d6a7a468 100644
--- a/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml
@@ -1,54 +1,5 @@
-# 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/Container-Scanning.gitlab-ci.yml
+# This template moved to Jobs/Container-Scanning.gitlab-ci.yml in GitLab 15.6
+# Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/381665
-# Use this template to enable container scanning in your project.
-# You should add this template to an existing `.gitlab-ci.yml` file by using the `include:`
-# keyword.
-# The template should work without modifications but you can customize the template settings if
-# needed: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings
-#
-# Requirements:
-# - A `test` stage to be present in the pipeline.
-# - You must define the image to be scanned in the CS_IMAGE variable. If CS_IMAGE is the
-# same as $CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG, you can skip this.
-# - Container registry credentials defined by `CS_REGISTRY_USER` and `CS_REGISTRY_PASSWORD` variables if the
-# image to be scanned is in a private registry.
-# - For auto-remediation, a readable Dockerfile in the root of the project or as defined by the
-# CS_DOCKERFILE_PATH variable.
-#
-# Configure container 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/container_scanning/#available-variables
-
-variables:
- CS_ANALYZER_IMAGE: "$CI_TEMPLATE_REGISTRY_HOST/security-products/container-scanning:5"
-
-container_scanning:
- image: "$CS_ANALYZER_IMAGE$CS_IMAGE_SUFFIX"
- stage: test
- variables:
- # To provide a `vulnerability-allowlist.yml` file, override the GIT_STRATEGY variable in your
- # `.gitlab-ci.yml` file and set it to `fetch`.
- # For details, see the following links:
- # https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html#overriding-the-container-scanning-template
- # https://docs.gitlab.com/ee/user/application_security/container_scanning/#vulnerability-allowlisting
- GIT_STRATEGY: none
- allow_failure: true
- artifacts:
- reports:
- container_scanning: 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
- rules:
- - if: $CONTAINER_SCANNING_DISABLED
- when: never
- - if: $CI_COMMIT_BRANCH &&
- $CI_GITLAB_FIPS_MODE == "true" &&
- $CS_ANALYZER_IMAGE !~ /-(fips|ubi)\z/
- variables:
- CS_IMAGE_SUFFIX: -fips
- - if: $CI_COMMIT_BRANCH
+include:
+ template: Jobs/Container-Scanning.gitlab-ci.yml
diff --git a/lib/gitlab/ci/templates/Security/Container-Scanning.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Container-Scanning.latest.gitlab-ci.yml
index f7b1d12b3b3..7a4f451314e 100644
--- a/lib/gitlab/ci/templates/Security/Container-Scanning.latest.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/Container-Scanning.latest.gitlab-ci.yml
@@ -1,68 +1,5 @@
-# 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/Container-Scanning.gitlab-ci.yml
+# This template moved to Jobs/Container-Scanning.latest.gitlab-ci.yml in GitLab 15.6
+# Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/381665
-# Use this template to enable container scanning in your project.
-# You should add this template to an existing `.gitlab-ci.yml` file by using the `include:`
-# keyword.
-# The template should work without modifications but you can customize the template settings if
-# needed: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings
-#
-# Requirements:
-# - A `test` stage to be present in the pipeline.
-# - You must define the image to be scanned in the CS_IMAGE variable. If CS_IMAGE is the
-# same as $CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG, you can skip this.
-# - Container registry credentials defined by `CS_REGISTRY_USER` and `CS_REGISTRY_PASSWORD` variables if the
-# image to be scanned is in a private registry.
-# - For auto-remediation, a readable Dockerfile in the root of the project or as defined by the
-# CS_DOCKERFILE_PATH variable.
-#
-# Configure container 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/container_scanning/#available-variables
-
-variables:
- CS_ANALYZER_IMAGE: "$CI_TEMPLATE_REGISTRY_HOST/security-products/container-scanning:5"
-
-container_scanning:
- image: "$CS_ANALYZER_IMAGE$CS_IMAGE_SUFFIX"
- stage: test
- variables:
- # To provide a `vulnerability-allowlist.yml` file, override the GIT_STRATEGY variable in your
- # `.gitlab-ci.yml` file and set it to `fetch`.
- # For details, see the following links:
- # https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html#overriding-the-container-scanning-template
- # https://docs.gitlab.com/ee/user/application_security/container_scanning/#vulnerability-allowlisting
- GIT_STRATEGY: none
- allow_failure: true
- artifacts:
- reports:
- container_scanning: 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
- rules:
- - if: $CONTAINER_SCANNING_DISABLED
- when: never
-
- # Add the job to merge request pipelines if there's an open merge request.
- - if: $CI_PIPELINE_SOURCE == "merge_request_event" &&
- $CI_GITLAB_FIPS_MODE == "true" &&
- $CS_ANALYZER_IMAGE !~ /-(fips|ubi)\z/
- variables:
- CS_IMAGE_SUFFIX: -fips
- - if: $CI_PIPELINE_SOURCE == "merge_request_event"
-
- # Don't add it to a *branch* pipeline if it's already in a merge request pipeline.
- - if: $CI_OPEN_MERGE_REQUESTS
- when: never
-
- # Add the job to branch pipelines.
- - if: $CI_COMMIT_BRANCH &&
- $CI_GITLAB_FIPS_MODE == "true" &&
- $CS_ANALYZER_IMAGE !~ /-(fips|ubi)\z/
- variables:
- CS_IMAGE_SUFFIX: -fips
- - if: $CI_COMMIT_BRANCH
+include:
+ template: Jobs/Container-Scanning.latest.gitlab-ci.yml
diff --git a/lib/gitlab/ci/templates/Security/Coverage-Fuzzing.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Coverage-Fuzzing.gitlab-ci.yml
index d933007ec61..89944e347f6 100644
--- a/lib/gitlab/ci/templates/Security/Coverage-Fuzzing.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/Coverage-Fuzzing.gitlab-ci.yml
@@ -16,7 +16,7 @@ variables:
COVFUZZ_VERSION: v3
# This is for users who have an offline environment and will have to replicate gitlab-cov-fuzz release binaries
# to their own servers
- COVFUZZ_URL_PREFIX: "https://gitlab.com/gitlab-org/security-products/analyzers/gitlab-cov-fuzz/-/raw"
+ COVFUZZ_URL_PREFIX: "https://gitlab.com/security-products/gitlab-cov-fuzz/-/raw"
coverage_fuzzing_unlicensed:
diff --git a/lib/gitlab/ci/templates/Security/Coverage-Fuzzing.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Coverage-Fuzzing.latest.gitlab-ci.yml
index feed4c47157..4f6ba427058 100644
--- a/lib/gitlab/ci/templates/Security/Coverage-Fuzzing.latest.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/Coverage-Fuzzing.latest.gitlab-ci.yml
@@ -16,7 +16,7 @@ variables:
COVFUZZ_VERSION: v3
# This is for users who have an offline environment and will have to replicate gitlab-cov-fuzz release binaries
# to their own servers
- COVFUZZ_URL_PREFIX: "https://gitlab.com/gitlab-org/security-products/analyzers/gitlab-cov-fuzz/-/raw"
+ COVFUZZ_URL_PREFIX: "https://gitlab.com/security-products/gitlab-cov-fuzz/-/raw"
coverage_fuzzing_unlicensed:
diff --git a/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml
index 40060e96dff..c43296b5865 100644
--- a/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml
@@ -51,7 +51,4 @@ dast:
$REVIEW_DISABLED
when: never
- if: $CI_COMMIT_BRANCH &&
- ($CI_KUBERNETES_ACTIVE || $KUBECONFIG) &&
- $GITLAB_FEATURES =~ /\bdast\b/
- - if: $CI_COMMIT_BRANCH &&
$GITLAB_FEATURES =~ /\bdast\b/
diff --git a/lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml
index 50e9bb5431d..27bcc14bcf5 100644
--- a/lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml
@@ -55,9 +55,6 @@ dast:
# Add the job to merge request pipelines if there's an open merge request.
- if: $CI_PIPELINE_SOURCE == "merge_request_event" &&
- ($CI_KUBERNETES_ACTIVE || $KUBECONFIG) &&
- $GITLAB_FEATURES =~ /\bdast\b/
- - if: $CI_PIPELINE_SOURCE == "merge_request_event" &&
$GITLAB_FEATURES =~ /\bdast\b/
# Don't add it to a *branch* pipeline if it's already in a merge request pipeline.
@@ -66,9 +63,6 @@ dast:
# Add the job to branch pipelines.
- if: $CI_COMMIT_BRANCH &&
- ($CI_KUBERNETES_ACTIVE || $KUBECONFIG) &&
- $GITLAB_FEATURES =~ /\bdast\b/
- - if: $CI_COMMIT_BRANCH &&
$GITLAB_FEATURES =~ /\bdast\b/
after_script:
# Remove any debug.log files because they might contain secrets.
diff --git a/lib/gitlab/ci/templates/Security/Secure-Binaries.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Secure-Binaries.gitlab-ci.yml
index fd04c86e6c7..631f6cecddf 100644
--- a/lib/gitlab/ci/templates/Security/Secure-Binaries.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/Secure-Binaries.gitlab-ci.yml
@@ -9,9 +9,9 @@
# Usage:
#
# include:
-# - template: Secure-Binaries.gitlab-ci.yml
+# - template: Security/Secure-Binaries.gitlab-ci.yml
#
-# Docs: https://docs.gitlab.com/ee/topics/airgap/
+# Docs: https://docs.gitlab.com/ee/user/application_security/offline_deployments/
variables:
# Setting this variable will affect all Security templates
@@ -38,7 +38,7 @@ variables:
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: ""
services:
- - docker:stable-dind
+ - docker:dind
script:
- docker info
- env
diff --git a/lib/gitlab/ci/templates/Verify/Browser-Performance.gitlab-ci.yml b/lib/gitlab/ci/templates/Verify/Browser-Performance.gitlab-ci.yml
index c3113ffebf3..c1a90955f7f 100644
--- a/lib/gitlab/ci/templates/Verify/Browser-Performance.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Verify/Browser-Performance.gitlab-ci.yml
@@ -17,10 +17,10 @@ browser_performance:
variables:
URL: ''
SITESPEED_IMAGE: sitespeedio/sitespeed.io
- SITESPEED_VERSION: 14.1.0
+ SITESPEED_VERSION: 26.1.0
SITESPEED_OPTIONS: ''
services:
- - docker:stable-dind
+ - docker:dind
script:
- mkdir gitlab-exporter
# Busybox wget does not support proxied HTTPS, get the real thing.
diff --git a/lib/gitlab/ci/templates/Verify/Browser-Performance.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Verify/Browser-Performance.latest.gitlab-ci.yml
index c9f0c173692..adc92fde5ae 100644
--- a/lib/gitlab/ci/templates/Verify/Browser-Performance.latest.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Verify/Browser-Performance.latest.gitlab-ci.yml
@@ -17,10 +17,10 @@ browser_performance:
variables:
URL: ''
SITESPEED_IMAGE: sitespeedio/sitespeed.io
- SITESPEED_VERSION: 14.1.0
+ SITESPEED_VERSION: latest
SITESPEED_OPTIONS: ''
services:
- - docker:stable-dind
+ - docker:dind
script:
- mkdir gitlab-exporter
# Busybox wget does not support proxied HTTPS, get the real thing.
diff --git a/lib/gitlab/ci/templates/Verify/Load-Performance-Testing.gitlab-ci.yml b/lib/gitlab/ci/templates/Verify/Load-Performance-Testing.gitlab-ci.yml
index bf5cfbb519d..a907915587a 100644
--- a/lib/gitlab/ci/templates/Verify/Load-Performance-Testing.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Verify/Load-Performance-Testing.gitlab-ci.yml
@@ -15,13 +15,13 @@ load_performance:
stage: performance
image: docker:git
variables:
- K6_IMAGE: loadimpact/k6
- K6_VERSION: 0.27.0
+ K6_IMAGE: grafana/k6
+ K6_VERSION: 0.41.0
K6_TEST_FILE: raw.githubusercontent.com/grafana/k6/master/samples/http_get.js
K6_OPTIONS: ''
K6_DOCKER_OPTIONS: ''
services:
- - docker:stable-dind
+ - docker:dind
script:
- docker run --rm -v "$(pwd)":/k6 -w /k6 $K6_DOCKER_OPTIONS $K6_IMAGE:$K6_VERSION run $K6_TEST_FILE --summary-export=load-performance.json $K6_OPTIONS
artifacts:
diff --git a/lib/gitlab/ci/variables/builder.rb b/lib/gitlab/ci/variables/builder.rb
index 8db8ea3a720..8e18d57b724 100644
--- a/lib/gitlab/ci/variables/builder.rb
+++ b/lib/gitlab/ci/variables/builder.rb
@@ -53,7 +53,7 @@ module Gitlab
# https://gitlab.com/groups/gitlab-org/configure/-/epics/8
# Until then, we need to make both the old and the new KUBECONFIG contexts available
collection.concat(deployment_variables(environment: environment, job: job))
- template = ::Ci::GenerateKubeconfigService.new(pipeline, token: job.try(:token)).execute
+ template = ::Ci::GenerateKubeconfigService.new(pipeline, token: job.try(:token), environment: environment).execute
kubeconfig_yaml = collection['KUBECONFIG']&.value
template.merge_yaml(kubeconfig_yaml) if kubeconfig_yaml.present?
@@ -135,6 +135,9 @@ module Gitlab
variables.append(key: 'CI_NODE_INDEX', value: job.options[:instance].to_s) if job.options&.include?(:instance)
variables.append(key: 'CI_NODE_TOTAL', value: ci_node_total_value(job).to_s)
+ # Set environment name here so we can access it when evaluating the job's rules
+ variables.append(key: 'CI_ENVIRONMENT_NAME', value: job.environment) if job.environment
+
# legacy variables
variables.append(key: 'CI_BUILD_NAME', value: job.name)
variables.append(key: 'CI_BUILD_STAGE', value: job.stage_name)
diff --git a/lib/gitlab/ci/yaml_processor/result.rb b/lib/gitlab/ci/yaml_processor/result.rb
index ff255543d3b..f2c1ad0575d 100644
--- a/lib/gitlab/ci/yaml_processor/result.rb
+++ b/lib/gitlab/ci/yaml_processor/result.rb
@@ -107,6 +107,7 @@ module Gitlab
cache: job[:cache],
resource_group_key: job[:resource_group],
scheduling_type: job[:scheduling_type],
+ id_tokens: job[:id_tokens],
options: {
image: job[:image],
services: job[:services],
@@ -118,6 +119,7 @@ module Gitlab
before_script: job[:before_script],
script: job[:script],
after_script: job[:after_script],
+ hooks: job[:hooks],
environment: job[:environment],
resource_group_key: job[:resource_group],
retry: job[:retry],
diff --git a/lib/gitlab/cluster/rack_timeout_observer.rb b/lib/gitlab/cluster/rack_timeout_observer.rb
index 5182b2be148..15dd6a59e19 100644
--- a/lib/gitlab/cluster/rack_timeout_observer.rb
+++ b/lib/gitlab/cluster/rack_timeout_observer.rb
@@ -3,6 +3,7 @@
module Gitlab
module Cluster
class RackTimeoutObserver
+ include ActionView::Helpers::SanitizeHelper
TRANSITION_STATES = %i(ready active).freeze
def initialize
@@ -28,9 +29,9 @@ module Gitlab
params = controller_params(env) || grape_params(env) || {}
{
- controller: params['controller'],
- action: params['action'],
- route: params['route'],
+ controller: sanitize(params['controller']),
+ action: sanitize(params['action']),
+ route: sanitize(params['route']),
state: info.state
}
end
diff --git a/lib/gitlab/color.rb b/lib/gitlab/color.rb
index 01c534c15a0..7d9280ddba2 100644
--- a/lib/gitlab/color.rb
+++ b/lib/gitlab/color.rb
@@ -215,13 +215,11 @@ module Gitlab
def rgb
return [] unless valid?
- @rgb ||= begin
- if @value.length == 4
- @value[1, 4].scan(/./).map { |v| (v * 2).hex }
- else
- @value[1, 7].scan(/.{2}/).map(&:hex)
- end
- end
+ @rgb ||= if @value.length == 4
+ @value[1, 4].scan(/./).map { |v| (v * 2).hex }
+ else
+ @value[1, 7].scan(/.{2}/).map(&:hex)
+ end
end
end
end
diff --git a/lib/gitlab/config/entry/attributable.rb b/lib/gitlab/config/entry/attributable.rb
index d266d5218de..c8ad2521574 100644
--- a/lib/gitlab/config/entry/attributable.rb
+++ b/lib/gitlab/config/entry/attributable.rb
@@ -7,19 +7,21 @@ module Gitlab
extend ActiveSupport::Concern
class_methods do
- def attributes(*attributes)
+ def attributes(*attributes, prefix: nil)
attributes.flatten.each do |attribute|
- if method_defined?(attribute)
- raise ArgumentError, "Method '#{attribute}' already defined in '#{name}'"
+ attribute_method = prefix ? "#{prefix}_#{attribute}" : attribute
+
+ if method_defined?(attribute_method)
+ raise ArgumentError, "Method '#{attribute_method}' already defined in '#{name}'"
end
- define_method(attribute) do
+ define_method(attribute_method) do
return unless config.is_a?(Hash)
config[attribute]
end
- define_method("has_#{attribute}?") do
+ define_method("has_#{attribute_method}?") do
config.is_a?(Hash) && config.key?(attribute)
end
end
diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb
index d40a6323d4f..7bcbcf84a4e 100644
--- a/lib/gitlab/conflict/file.rb
+++ b/lib/gitlab/conflict/file.rb
@@ -236,14 +236,12 @@ module Gitlab
else
:modified_target_removed_source
end
+ elsif our_path.present? && their_path.present?
+ :both_added
+ elsif their_path.blank?
+ diff_file.renamed_file? ? :renamed_same_file : :removed_target_renamed_source
else
- if our_path.present? && their_path.present?
- :both_added
- elsif their_path.blank?
- diff_file.renamed_file? ? :renamed_same_file : :removed_target_renamed_source
- else
- :removed_source_renamed_target
- end
+ :removed_source_renamed_target
end
end
diff --git a/lib/gitlab/content_security_policy/config_loader.rb b/lib/gitlab/content_security_policy/config_loader.rb
index 29e8e631fb7..8b1298d0561 100644
--- a/lib/gitlab/content_security_policy/config_loader.rb
+++ b/lib/gitlab/content_security_policy/config_loader.rb
@@ -43,7 +43,10 @@ module Gitlab
allow_websocket_connections(directives)
allow_cdn(directives, Settings.gitlab.cdn_host) if Settings.gitlab.cdn_host.present?
- allow_sentry(directives) if Gitlab.config.sentry&.enabled && Gitlab.config.sentry&.clientside_dsn
+ # Support for Sentry setup via configuration files will be removed in 16.0
+ # in favor of Gitlab::CurrentSettings.
+ allow_legacy_sentry(directives) if Gitlab.config.sentry&.enabled && Gitlab.config.sentry&.clientside_dsn
+ allow_sentry(directives) if Gitlab::CurrentSettings.try(:sentry_enabled) && Gitlab::CurrentSettings.try(:sentry_clientside_dsn)
allow_framed_gitlab_paths(directives)
allow_customersdot(directives) if ENV['CUSTOMER_PORTAL_URL'].present?
allow_review_apps(directives) if ENV['REVIEW_APPS_ENABLED']
@@ -135,13 +138,22 @@ module Gitlab
append_to_directive(directives, 'frame_src', customersdot_host)
end
- def self.allow_sentry(directives)
+ def self.allow_legacy_sentry(directives)
+ # Support for Sentry setup via configuration files will be removed in 16.0
+ # in favor of Gitlab::CurrentSettings.
sentry_dsn = Gitlab.config.sentry.clientside_dsn
sentry_uri = URI(sentry_dsn)
append_to_directive(directives, 'connect_src', "#{sentry_uri.scheme}://#{sentry_uri.host}")
end
+ def self.allow_sentry(directives)
+ sentry_dsn = Gitlab::CurrentSettings.sentry_clientside_dsn
+ sentry_uri = URI(sentry_dsn)
+
+ append_to_directive(directives, 'connect_src', "#{sentry_uri.scheme}://#{sentry_uri.host}")
+ end
+
def self.allow_letter_opener(directives)
append_to_directive(directives, 'frame_src', Gitlab::Utils.append_path(Gitlab.config.gitlab.url, '/rails/letter_opener/'))
end
diff --git a/lib/gitlab/contributions_calendar.rb b/lib/gitlab/contributions_calendar.rb
index a45380aca6c..2068a9ae7d5 100644
--- a/lib/gitlab/contributions_calendar.rb
+++ b/lib/gitlab/contributions_calendar.rb
@@ -31,7 +31,7 @@ module Gitlab
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")
+ .where(action: [:created, :closed], target_type: %w[Issue WorkItem])
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)
diff --git a/lib/gitlab/counters/buffered_counter.rb b/lib/gitlab/counters/buffered_counter.rb
new file mode 100644
index 00000000000..56593b642a9
--- /dev/null
+++ b/lib/gitlab/counters/buffered_counter.rb
@@ -0,0 +1,113 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Counters
+ class BufferedCounter
+ include Gitlab::ExclusiveLeaseHelpers
+
+ WORKER_DELAY = 10.minutes
+ WORKER_LOCK_TTL = 10.minutes
+
+ LUA_FLUSH_INCREMENT_SCRIPT = <<~LUA
+ local increment_key, flushed_key = KEYS[1], KEYS[2]
+ local increment_value = redis.call("get", increment_key) or 0
+ local flushed_value = redis.call("incrby", flushed_key, increment_value)
+ if flushed_value == 0 then
+ redis.call("del", increment_key, flushed_key)
+ else
+ redis.call("del", increment_key)
+ end
+ return flushed_value
+ LUA
+
+ def initialize(counter_record, attribute)
+ @counter_record = counter_record
+ @attribute = attribute
+ end
+
+ def get
+ redis_state do |redis|
+ redis.get(key).to_i
+ end
+ end
+
+ def increment(amount)
+ result = redis_state do |redis|
+ redis.incrby(key, amount)
+ end
+
+ FlushCounterIncrementsWorker.perform_in(WORKER_DELAY, counter_record.class.name, counter_record.id, attribute)
+
+ result
+ end
+
+ def reset!
+ counter_record.update!(attribute => 0)
+
+ redis_state do |redis|
+ redis.del(key)
+ end
+ end
+
+ def commit_increment!
+ with_exclusive_lease do
+ flush_amount = amount_to_be_flushed
+ next if flush_amount == 0
+
+ counter_record.transaction do
+ counter_record.update_counters_with_lease({ attribute => flush_amount })
+ remove_flushed_key
+ end
+
+ counter_record.execute_after_commit_callbacks
+ end
+
+ counter_record.reset.read_attribute(attribute)
+ end
+
+ # amount_to_be_flushed returns the total value to be flushed.
+ # The total value is the sum of the following:
+ # - current value in the increment_key
+ # - any existing value in the flushed_key that has not been flushed
+ def amount_to_be_flushed
+ redis_state do |redis|
+ redis.eval(LUA_FLUSH_INCREMENT_SCRIPT, keys: [key, flushed_key])
+ end
+ end
+
+ def key
+ project_id = counter_record.project.id
+ record_name = counter_record.class
+ record_id = counter_record.id
+
+ "project:{#{project_id}}:counters:#{record_name}:#{record_id}:#{attribute}"
+ end
+
+ def flushed_key
+ "#{key}:flushed"
+ end
+
+ private
+
+ attr_reader :counter_record, :attribute
+
+ def remove_flushed_key
+ redis_state do |redis|
+ redis.del(flushed_key)
+ end
+ end
+
+ def redis_state(&block)
+ Gitlab::Redis::SharedState.with(&block)
+ end
+
+ def with_exclusive_lease(&block)
+ lock_key = "#{key}:locked"
+
+ in_lock(lock_key, ttl: WORKER_LOCK_TTL, &block)
+ rescue Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError
+ # a worker is already updating the counters
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/counters/legacy_counter.rb b/lib/gitlab/counters/legacy_counter.rb
new file mode 100644
index 00000000000..06951514ec3
--- /dev/null
+++ b/lib/gitlab/counters/legacy_counter.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Counters
+ # This class is a wrapper over ActiveRecord counter
+ # for attributes that have not adopted Redis-backed BufferedCounter.
+ class LegacyCounter
+ def initialize(counter_record, attribute)
+ @counter_record = counter_record
+ @attribute = attribute
+ @current_value = counter_record.method(attribute).call
+ end
+
+ def increment(amount)
+ updated = counter_record.class.update_counters(counter_record.id, { attribute => amount })
+
+ if updated == 1
+ counter_record.execute_after_commit_callbacks
+ @current_value += amount
+ end
+
+ @current_value
+ end
+
+ def reset!
+ counter_record.update!(attribute => 0)
+ end
+
+ private
+
+ attr_reader :counter_record, :attribute
+ end
+ end
+end
diff --git a/lib/gitlab/data_builder/deployment.rb b/lib/gitlab/data_builder/deployment.rb
index a9c69e3f997..7055f64937d 100644
--- a/lib/gitlab/data_builder/deployment.rb
+++ b/lib/gitlab/data_builder/deployment.rb
@@ -35,6 +35,8 @@ module Gitlab
deployable_id: deployment.deployable_id,
deployable_url: deployable_url,
environment: deployment.environment.name,
+ environment_slug: deployment.environment.slug,
+ environment_external_url: deployment.environment.external_url,
project: deployment.project.hook_attrs,
short_sha: deployment.short_sha,
user: deployment.deployed_by&.hook_attrs,
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index 04cf056199c..51d5bfcee38 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -101,7 +101,8 @@ module Gitlab
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: database_base_models_with_gitlab_shared.values, # all models
- gitlab_internal: database_base_models.values # all models
+ gitlab_internal: database_base_models.values, # all models
+ gitlab_pm: [self.database_base_models.fetch(:main)] # package metadata models
}.with_indifferent_access.freeze
end
diff --git a/lib/gitlab/database/bulk_update.rb b/lib/gitlab/database/bulk_update.rb
index d68be19047e..4b4a9b38fd8 100644
--- a/lib/gitlab/database/bulk_update.rb
+++ b/lib/gitlab/database/bulk_update.rb
@@ -157,7 +157,7 @@ module Gitlab
def self.execute(columns, mapping, &to_class)
raise ArgumentError if mapping.blank?
- entries_by_class = mapping.group_by { |k, v| to_class ? to_class.call(k) : k.class }
+ entries_by_class = mapping.group_by { |k, v| to_class ? yield(k) : k.class }
entries_by_class.each do |model, entries|
Setter.new(model, columns, entries).update!
diff --git a/lib/gitlab/database/count/exact_count_strategy.rb b/lib/gitlab/database/count/exact_count_strategy.rb
index 345c7e44b05..11c83786aa4 100644
--- a/lib/gitlab/database/count/exact_count_strategy.rb
+++ b/lib/gitlab/database/count/exact_count_strategy.rb
@@ -18,9 +18,7 @@ module Gitlab
end
def count
- models.each_with_object({}) do |model, data|
- data[model] = model.count
- end
+ models.index_with(&:count)
rescue *CONNECTION_ERRORS
{}
end
diff --git a/lib/gitlab/database/gitlab_schema.rb b/lib/gitlab/database/gitlab_schema.rb
index 365a4283d4c..0f848ed40fb 100644
--- a/lib/gitlab/database/gitlab_schema.rb
+++ b/lib/gitlab/database/gitlab_schema.rb
@@ -6,14 +6,16 @@
# Each table / view needs to have assigned gitlab_schema. Names supported today:
#
# - gitlab_shared - defines a set of tables that are found on all databases (data accessed is dependent on connection)
-# - gitlab_main / gitlab_ci - defines a set of tables that can only exist on a given database
+# - gitlab_main / gitlab_ci - defines a set of tables that can only exist on a given application database
+# - gitlab_geo - defines a set of tables that can only exist on the geo database
+# - gitlab_internal - defines all internal tables of Rails and PostgreSQL
#
# Tables for the purpose of tests should be prefixed with `_test_my_table_name`
module Gitlab
module Database
module GitlabSchema
- GITLAB_SCHEMAS_FILE = 'lib/gitlab/database/gitlab_schemas.yml'
+ DICTIONARY_PATH = 'db/docs/'
# These tables are deleted/renamed, but still referenced by migrations.
# This is needed for now, but should be removed in the future
@@ -55,7 +57,7 @@ module Gitlab
tables.map { |table| table_schema(table) }.to_set
end
- def self.table_schema(name)
+ def self.table_schema(name, undefined: true)
schema_name, table_name = name.split('.', 2) # Strip schema name like: `public.`
# Most of names do not have schemas, ensure that this is table
@@ -68,7 +70,7 @@ module Gitlab
table_name.gsub!(/_[0-9]+$/, '')
# Tables that are properly mapped
- if gitlab_schema = tables_to_schema[table_name]
+ if gitlab_schema = views_and_tables_to_schema[table_name]
return gitlab_schema
end
@@ -84,6 +86,8 @@ module Gitlab
return :gitlab_ci if table_name.start_with?('_test_gitlab_ci_')
+ return :gitlab_geo if table_name.start_with?('_test_gitlab_geo_')
+
# All tables that start with `_test_` without a following schema are shared and ignored
return :gitlab_shared if table_name.start_with?('_test_')
@@ -91,15 +95,39 @@ module Gitlab
return :gitlab_internal if table_name.start_with?('pg_')
# When undefined it's best to return a unique name so that we don't incorrectly assume that 2 undefined schemas belong on the same database
- :"undefined_#{table_name}"
+ undefined ? :"undefined_#{table_name}" : nil
+ end
+
+ def self.dictionary_path_globs
+ [Rails.root.join(DICTIONARY_PATH, '*.yml')]
+ end
+
+ def self.view_path_globs
+ [Rails.root.join(DICTIONARY_PATH, 'views', '*.yml')]
+ end
+
+ def self.views_and_tables_to_schema
+ @views_and_tables_to_schema ||= self.tables_to_schema.merge(self.views_to_schema)
end
def self.tables_to_schema
- @tables_to_schema ||= YAML.load_file(Rails.root.join(GITLAB_SCHEMAS_FILE))
+ @tables_to_schema ||= Dir.glob(self.dictionary_path_globs).each_with_object({}) do |file_path, dic|
+ data = YAML.load_file(file_path)
+
+ dic[data['table_name']] = data['gitlab_schema'].to_sym
+ end
+ end
+
+ def self.views_to_schema
+ @views_to_schema ||= Dir.glob(self.view_path_globs).each_with_object({}) do |file_path, dic|
+ data = YAML.load_file(file_path)
+
+ dic[data['view_name']] = data['gitlab_schema'].to_sym
+ end
end
def self.schema_names
- @schema_names ||= self.tables_to_schema.values.to_set
+ @schema_names ||= self.views_and_tables_to_schema.values.to_set
end
end
end
diff --git a/lib/gitlab/database/gitlab_schemas.yml b/lib/gitlab/database/gitlab_schemas.yml
deleted file mode 100644
index bf6ebb21f7d..00000000000
--- a/lib/gitlab/database/gitlab_schemas.yml
+++ /dev/null
@@ -1,606 +0,0 @@
-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
-alert_management_alerts: :gitlab_main
-alert_management_alert_metric_images: :gitlab_main
-alert_management_alert_user_mentions: :gitlab_main
-alert_management_http_integrations: :gitlab_main
-allowed_email_domains: :gitlab_main
-analytics_cycle_analytics_aggregations: :gitlab_main
-analytics_cycle_analytics_group_stages: :gitlab_main
-analytics_cycle_analytics_group_value_streams: :gitlab_main
-analytics_cycle_analytics_issue_stage_events: :gitlab_main
-analytics_cycle_analytics_merge_request_stage_events: :gitlab_main
-analytics_cycle_analytics_project_stages: :gitlab_main
-analytics_cycle_analytics_project_value_streams: :gitlab_main
-analytics_cycle_analytics_stage_event_hashes: :gitlab_main
-analytics_devops_adoption_segments: :gitlab_main
-analytics_devops_adoption_snapshots: :gitlab_main
-analytics_language_trend_repository_languages: :gitlab_main
-analytics_usage_trends_measurements: :gitlab_main
-appearances: :gitlab_main
-application_settings: :gitlab_main
-application_setting_terms: :gitlab_main
-approval_merge_request_rules_approved_approvers: :gitlab_main
-approval_merge_request_rules: :gitlab_main
-approval_merge_request_rules_groups: :gitlab_main
-approval_merge_request_rule_sources: :gitlab_main
-approval_merge_request_rules_users: :gitlab_main
-approval_project_rules: :gitlab_main
-approval_project_rules_groups: :gitlab_main
-approval_project_rules_protected_branches: :gitlab_main
-approval_project_rules_users: :gitlab_main
-approvals: :gitlab_main
-approver_groups: :gitlab_main
-approvers: :gitlab_main
-ar_internal_metadata: :gitlab_internal
-atlassian_identities: :gitlab_main
-audit_events_external_audit_event_destinations: :gitlab_main
-audit_events: :gitlab_main
-audit_events_streaming_headers: :gitlab_main
-audit_events_streaming_event_type_filters: :gitlab_main
-authentication_events: :gitlab_main
-award_emoji: :gitlab_main
-aws_roles: :gitlab_main
-background_migration_jobs: :gitlab_shared
-badges: :gitlab_main
-banned_users: :gitlab_main
-batched_background_migration_jobs: :gitlab_shared
-batched_background_migrations: :gitlab_shared
-board_assignees: :gitlab_main
-board_group_recent_visits: :gitlab_main
-board_labels: :gitlab_main
-board_project_recent_visits: :gitlab_main
-boards_epic_board_labels: :gitlab_main
-boards_epic_board_positions: :gitlab_main
-boards_epic_board_recent_visits: :gitlab_main
-boards_epic_boards: :gitlab_main
-boards_epic_lists: :gitlab_main
-boards_epic_list_user_preferences: :gitlab_main
-boards_epic_user_preferences: :gitlab_main
-boards: :gitlab_main
-board_user_preferences: :gitlab_main
-broadcast_messages: :gitlab_main
-bulk_import_configurations: :gitlab_main
-bulk_import_entities: :gitlab_main
-bulk_import_exports: :gitlab_main
-bulk_import_export_uploads: :gitlab_main
-bulk_import_failures: :gitlab_main
-bulk_imports: :gitlab_main
-bulk_import_trackers: :gitlab_main
-chat_names: :gitlab_main
-chat_teams: :gitlab_main
-ci_build_needs: :gitlab_ci
-ci_build_pending_states: :gitlab_ci
-ci_build_report_results: :gitlab_ci
-ci_builds: :gitlab_ci
-ci_builds_metadata: :gitlab_ci
-ci_builds_runner_session: :gitlab_ci
-ci_build_trace_chunks: :gitlab_ci
-ci_build_trace_metadata: :gitlab_ci
-ci_daily_build_group_report_results: :gitlab_ci
-ci_deleted_objects: :gitlab_ci
-ci_freeze_periods: :gitlab_ci
-ci_group_variables: :gitlab_ci
-ci_instance_variables: :gitlab_ci
-ci_job_artifacts: :gitlab_ci
-ci_job_token_project_scope_links: :gitlab_ci
-ci_job_variables: :gitlab_ci
-ci_job_artifact_states: :gitlab_ci
-ci_minutes_additional_packs: :gitlab_ci
-ci_namespace_monthly_usages: :gitlab_ci
-ci_namespace_mirrors: :gitlab_ci
-ci_partitions: :gitlab_ci
-ci_pending_builds: :gitlab_ci
-ci_pipeline_artifacts: :gitlab_ci
-ci_pipeline_chat_data: :gitlab_ci
-ci_pipeline_messages: :gitlab_ci
-ci_pipeline_schedules: :gitlab_ci
-ci_pipeline_schedule_variables: :gitlab_ci
-ci_pipelines_config: :gitlab_ci
-ci_pipeline_metadata: :gitlab_ci
-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
-ci_runner_namespaces: :gitlab_ci
-ci_runner_projects: :gitlab_ci
-ci_runner_versions: :gitlab_ci
-ci_runners: :gitlab_ci
-ci_running_builds: :gitlab_ci
-ci_sources_pipelines: :gitlab_ci
-ci_secure_files: :gitlab_ci
-ci_secure_file_states: :gitlab_ci
-ci_sources_projects: :gitlab_ci
-ci_stages: :gitlab_ci
-ci_subscriptions_projects: :gitlab_ci
-ci_trigger_requests: :gitlab_ci
-ci_triggers: :gitlab_ci
-ci_unit_test_failures: :gitlab_ci
-ci_unit_tests: :gitlab_ci
-ci_variables: :gitlab_ci
-cluster_agents: :gitlab_main
-cluster_agent_tokens: :gitlab_main
-cluster_enabled_grants: :gitlab_main
-cluster_groups: :gitlab_main
-cluster_platforms_kubernetes: :gitlab_main
-cluster_projects: :gitlab_main
-cluster_providers_aws: :gitlab_main
-cluster_providers_gcp: :gitlab_main
-clusters_applications_cert_managers: :gitlab_main
-clusters_applications_cilium: :gitlab_main
-clusters_applications_crossplane: :gitlab_main
-clusters_applications_helm: :gitlab_main
-clusters_applications_ingress: :gitlab_main
-clusters_applications_jupyter: :gitlab_main
-clusters_applications_knative: :gitlab_main
-clusters_applications_prometheus: :gitlab_main
-clusters_applications_runners: :gitlab_main
-clusters: :gitlab_main
-clusters_integration_prometheus: :gitlab_main
-clusters_kubernetes_namespaces: :gitlab_main
-commit_user_mentions: :gitlab_main
-compliance_management_frameworks: :gitlab_main
-container_expiration_policies: :gitlab_main
-container_repositories: :gitlab_main
-content_blocked_states: :gitlab_main
-conversational_development_index_metrics: :gitlab_main
-coverage_fuzzing_corpuses: :gitlab_main
-csv_issue_imports: :gitlab_main
-custom_emoji: :gitlab_main
-customer_relations_contacts: :gitlab_main
-customer_relations_organizations: :gitlab_main
-dast_profile_schedules: :gitlab_main
-dast_profiles: :gitlab_main
-dast_profiles_pipelines: :gitlab_main
-dast_scanner_profiles_builds: :gitlab_main
-dast_scanner_profiles: :gitlab_main
-dast_site_profiles_builds: :gitlab_main
-dast_site_profile_secret_variables: :gitlab_main
-dast_site_profiles: :gitlab_main
-dast_site_profiles_pipelines: :gitlab_main
-dast_sites: :gitlab_main
-dast_site_tokens: :gitlab_main
-dast_site_validations: :gitlab_main
-dependency_proxy_blob_states: :gitlab_main
-dependency_proxy_blobs: :gitlab_main
-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
-deploy_tokens: :gitlab_main
-description_versions: :gitlab_main
-design_management_designs: :gitlab_main
-design_management_designs_versions: :gitlab_main
-design_management_versions: :gitlab_main
-design_user_mentions: :gitlab_main
-detached_partitions: :gitlab_shared
-diff_note_positions: :gitlab_main
-dora_configurations: :gitlab_main
-dora_daily_metrics: :gitlab_main
-draft_notes: :gitlab_main
-elastic_index_settings: :gitlab_main
-elastic_reindexing_slices: :gitlab_main
-elastic_reindexing_subtasks: :gitlab_main
-elastic_reindexing_tasks: :gitlab_main
-elasticsearch_indexed_namespaces: :gitlab_main
-elasticsearch_indexed_projects: :gitlab_main
-emails: :gitlab_main
-environments: :gitlab_main
-epic_issues: :gitlab_main
-epic_metrics: :gitlab_main
-epics: :gitlab_main
-epic_user_mentions: :gitlab_main
-error_tracking_client_keys: :gitlab_main
-error_tracking_error_events: :gitlab_main
-error_tracking_errors: :gitlab_main
-events: :gitlab_main
-evidences: :gitlab_main
-experiments: :gitlab_main
-experiment_subjects: :gitlab_main
-external_approval_rules: :gitlab_main
-external_approval_rules_protected_branches: :gitlab_main
-external_pull_requests: :gitlab_ci
-external_status_checks: :gitlab_main
-external_status_checks_protected_branches: :gitlab_main
-feature_gates: :gitlab_main
-features: :gitlab_main
-fork_network_members: :gitlab_main
-fork_networks: :gitlab_main
-geo_cache_invalidation_events: :gitlab_main
-geo_container_repository_updated_events: :gitlab_main
-geo_event_log: :gitlab_main
-geo_events: :gitlab_main
-geo_hashed_storage_attachments_events: :gitlab_main
-geo_hashed_storage_migrated_events: :gitlab_main
-geo_node_namespace_links: :gitlab_main
-geo_nodes: :gitlab_main
-geo_node_statuses: :gitlab_main
-geo_repositories_changed_events: :gitlab_main
-geo_repository_created_events: :gitlab_main
-geo_repository_deleted_events: :gitlab_main
-geo_repository_renamed_events: :gitlab_main
-geo_repository_updated_events: :gitlab_main
-geo_reset_checksum_events: :gitlab_main
-ghost_user_migrations: :gitlab_main
-gitlab_subscription_histories: :gitlab_main
-gitlab_subscriptions: :gitlab_main
-gpg_keys: :gitlab_main
-gpg_key_subkeys: :gitlab_main
-gpg_signatures: :gitlab_main
-grafana_integrations: :gitlab_main
-group_custom_attributes: :gitlab_main
-group_crm_settings: :gitlab_main
-group_deletion_schedules: :gitlab_main
-group_deploy_keys: :gitlab_main
-group_deploy_keys_groups: :gitlab_main
-group_deploy_tokens: :gitlab_main
-group_features: :gitlab_main
-group_group_links: :gitlab_main
-group_import_states: :gitlab_main
-group_merge_request_approval_settings: :gitlab_main
-group_repository_storage_moves: :gitlab_main
-group_wiki_repositories: :gitlab_main
-historical_data: :gitlab_main
-identities: :gitlab_main
-import_export_uploads: :gitlab_main
-import_failures: :gitlab_main
-incident_management_escalation_policies: :gitlab_main
-incident_management_escalation_rules: :gitlab_main
-incident_management_issuable_escalation_statuses: :gitlab_main
-incident_management_oncall_participants: :gitlab_main
-incident_management_oncall_rotations: :gitlab_main
-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
-incident_management_timeline_event_tags: :gitlab_main
-incident_management_timeline_event_tag_links: :gitlab_main
-index_statuses: :gitlab_main
-in_product_marketing_emails: :gitlab_main
-insights: :gitlab_main
-integrations: :gitlab_main
-internal_ids: :gitlab_main
-ip_restrictions: :gitlab_main
-issuable_metric_images: :gitlab_main
-issuable_resource_links: :gitlab_main
-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
-issue_search_data: :gitlab_main
-issues: :gitlab_main
-issues_prometheus_alert_events: :gitlab_main
-issues_self_managed_prometheus_alert_events: :gitlab_main
-issue_tracker_data: :gitlab_main
-issue_user_mentions: :gitlab_main
-iterations_cadences: :gitlab_main
-jira_connect_installations: :gitlab_main
-jira_connect_subscriptions: :gitlab_main
-jira_imports: :gitlab_main
-jira_tracker_data: :gitlab_main
-keys: :gitlab_main
-label_links: :gitlab_main
-label_priorities: :gitlab_main
-labels: :gitlab_main
-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
-loose_foreign_keys_deleted_records: :gitlab_shared
-member_roles: :gitlab_main
-member_tasks: :gitlab_main
-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
-merge_request_diff_commit_users: :gitlab_main
-merge_request_diff_details: :gitlab_main
-merge_request_diff_files: :gitlab_main
-merge_request_diffs: :gitlab_main
-merge_request_metrics: :gitlab_main
-merge_request_predictions: :gitlab_main
-merge_request_reviewers: :gitlab_main
-merge_requests_closing_issues: :gitlab_main
-merge_requests: :gitlab_main
-merge_request_user_mentions: :gitlab_main
-merge_trains: :gitlab_main
-metrics_dashboard_annotations: :gitlab_main
-metrics_users_starred_dashboards: :gitlab_main
-milestone_releases: :gitlab_main
-milestones: :gitlab_main
-ml_candidates: :gitlab_main
-ml_experiments: :gitlab_main
-ml_candidate_metrics: :gitlab_main
-ml_candidate_params: :gitlab_main
-namespace_admin_notes: :gitlab_main
-namespace_aggregation_schedules: :gitlab_main
-namespace_bans: :gitlab_main
-namespace_limits: :gitlab_main
-namespace_package_settings: :gitlab_main
-namespace_root_storage_statistics: :gitlab_main
-namespace_ci_cd_settings: :gitlab_main
-namespace_commit_emails: :gitlab_main
-namespace_settings: :gitlab_main
-namespace_details: :gitlab_main
-namespaces: :gitlab_main
-namespaces_sync_events: :gitlab_main
-namespace_statistics: :gitlab_main
-note_diff_files: :gitlab_main
-notes: :gitlab_main
-notification_settings: :gitlab_main
-oauth_access_grants: :gitlab_main
-oauth_access_tokens: :gitlab_main
-oauth_applications: :gitlab_main
-oauth_openid_requests: :gitlab_main
-onboarding_progresses: :gitlab_main
-operations_feature_flags_clients: :gitlab_main
-operations_feature_flag_scopes: :gitlab_main
-operations_feature_flags: :gitlab_main
-operations_feature_flags_issues: :gitlab_main
-operations_scopes: :gitlab_main
-operations_strategies: :gitlab_main
-operations_strategies_user_lists: :gitlab_main
-operations_user_lists: :gitlab_main
-p_ci_builds_metadata: :gitlab_ci
-packages_build_infos: :gitlab_main
-packages_cleanup_policies: :gitlab_main
-packages_composer_cache_files: :gitlab_main
-packages_composer_metadata: :gitlab_main
-packages_conan_file_metadata: :gitlab_main
-packages_conan_metadata: :gitlab_main
-packages_debian_file_metadata: :gitlab_main
-packages_debian_group_architectures: :gitlab_main
-packages_debian_group_component_files: :gitlab_main
-packages_debian_group_components: :gitlab_main
-packages_debian_group_distribution_keys: :gitlab_main
-packages_debian_group_distributions: :gitlab_main
-packages_debian_project_architectures: :gitlab_main
-packages_debian_project_component_files: :gitlab_main
-packages_debian_project_components: :gitlab_main
-packages_debian_project_distribution_keys: :gitlab_main
-packages_debian_project_distributions: :gitlab_main
-packages_debian_publications: :gitlab_main
-packages_dependencies: :gitlab_main
-packages_dependency_links: :gitlab_main
-packages_events: :gitlab_main
-packages_helm_file_metadata: :gitlab_main
-packages_maven_metadata: :gitlab_main
-packages_npm_metadata: :gitlab_main
-packages_rpm_metadata: :gitlab_main
-packages_nuget_dependency_link_metadata: :gitlab_main
-packages_nuget_metadata: :gitlab_main
-packages_package_file_build_infos: :gitlab_main
-packages_package_files: :gitlab_main
-packages_rpm_repository_files: :gitlab_main
-packages_packages: :gitlab_main
-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
-path_locks: :gitlab_main
-personal_access_tokens: :gitlab_main
-plan_limits: :gitlab_main
-plans: :gitlab_main
-pool_repositories: :gitlab_main
-postgres_async_indexes: :gitlab_shared
-postgres_autovacuum_activity: :gitlab_shared
-postgres_constraints: :gitlab_shared
-postgres_foreign_keys: :gitlab_shared
-postgres_index_bloat_estimates: :gitlab_shared
-postgres_indexes: :gitlab_shared
-postgres_partitioned_tables: :gitlab_shared
-postgres_partitions: :gitlab_shared
-postgres_reindex_actions: :gitlab_shared
-postgres_reindex_queued_actions: :gitlab_shared
-product_analytics_events_experimental: :gitlab_main
-programming_languages: :gitlab_main
-project_access_tokens: :gitlab_main
-project_alerting_settings: :gitlab_main
-project_aliases: :gitlab_main
-project_authorizations: :gitlab_main
-project_auto_devops: :gitlab_main
-project_build_artifacts_size_refreshes: :gitlab_main
-project_ci_cd_settings: :gitlab_main
-project_ci_feature_usages: :gitlab_main
-project_compliance_framework_settings: :gitlab_main
-project_custom_attributes: :gitlab_main
-project_daily_statistics: :gitlab_main
-project_deploy_tokens: :gitlab_main
-project_error_tracking_settings: :gitlab_main
-project_export_jobs: :gitlab_main
-project_features: :gitlab_main
-project_feature_usages: :gitlab_main
-project_group_links: :gitlab_main
-project_import_data: :gitlab_main
-project_incident_management_settings: :gitlab_main
-project_metrics_settings: :gitlab_main
-project_mirror_data: :gitlab_main
-project_pages_metadata: :gitlab_main
-project_relation_export_uploads: :gitlab_main
-project_relation_exports: :gitlab_main
-project_repositories: :gitlab_main
-project_repository_states: :gitlab_main
-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_wiki_repositories: :gitlab_main
-project_wiki_repository_states: :gitlab_main
-prometheus_alert_events: :gitlab_main
-prometheus_alerts: :gitlab_main
-prometheus_metrics: :gitlab_main
-protected_branches: :gitlab_main
-protected_branch_merge_access_levels: :gitlab_main
-protected_branch_push_access_levels: :gitlab_main
-protected_branch_unprotect_access_levels: :gitlab_main
-protected_environment_approval_rules: :gitlab_main
-protected_environment_deploy_access_levels: :gitlab_main
-protected_environments: :gitlab_main
-protected_tag_create_access_levels: :gitlab_main
-protected_tags: :gitlab_main
-push_event_payloads: :gitlab_main
-push_rules: :gitlab_main
-raw_usage_data: :gitlab_main
-redirect_routes: :gitlab_main
-related_epic_links: :gitlab_main
-release_links: :gitlab_main
-releases: :gitlab_main
-remote_mirrors: :gitlab_main
-repository_languages: :gitlab_main
-required_code_owners_sections: :gitlab_main
-requirements: :gitlab_main
-requirements_management_test_reports: :gitlab_main
-resource_iteration_events: :gitlab_main
-resource_label_events: :gitlab_main
-resource_milestone_events: :gitlab_main
-resource_state_events: :gitlab_main
-resource_weight_events: :gitlab_main
-reviews: :gitlab_main
-routes: :gitlab_main
-saml_group_links: :gitlab_main
-saml_providers: :gitlab_main
-saved_replies: :gitlab_main
-sbom_components: :gitlab_main
-sbom_occurrences: :gitlab_main
-sbom_component_versions: :gitlab_main
-sbom_sources: :gitlab_main
-sbom_vulnerable_component_versions: :gitlab_main
-schema_migrations: :gitlab_internal
-scim_identities: :gitlab_main
-scim_oauth_access_tokens: :gitlab_main
-security_findings: :gitlab_main
-security_orchestration_policy_configurations: :gitlab_main
-security_orchestration_policy_rule_schedules: :gitlab_main
-security_scans: :gitlab_main
-security_training_providers: :gitlab_main
-security_trainings: :gitlab_main
-self_managed_prometheus_alert_events: :gitlab_main
-sent_notifications: :gitlab_main
-sentry_issues: :gitlab_main
-serverless_domain_cluster: :gitlab_main
-service_desk_settings: :gitlab_main
-shards: :gitlab_main
-slack_integrations: :gitlab_main
-smartcard_identities: :gitlab_main
-snippet_repositories: :gitlab_main
-snippet_repository_storage_moves: :gitlab_main
-snippets: :gitlab_main
-snippet_statistics: :gitlab_main
-snippet_user_mentions: :gitlab_main
-software_license_policies: :gitlab_main
-software_licenses: :gitlab_main
-spam_logs: :gitlab_main
-sprints: :gitlab_main
-ssh_signatures: :gitlab_main
-status_check_responses: :gitlab_main
-status_page_published_incidents: :gitlab_main
-status_page_settings: :gitlab_main
-subscriptions: :gitlab_main
-suggestions: :gitlab_main
-system_note_metadata: :gitlab_main
-taggings: :gitlab_ci
-tags: :gitlab_ci
-term_agreements: :gitlab_main
-terraform_states: :gitlab_main
-terraform_state_versions: :gitlab_main
-timelogs: :gitlab_main
-timelog_categories: :gitlab_main
-todos: :gitlab_main
-token_with_ivs: :gitlab_main
-topics: :gitlab_main
-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
-user_credit_card_validations: :gitlab_main
-user_custom_attributes: :gitlab_main
-user_details: :gitlab_main
-user_follow_users: :gitlab_main
-user_group_callouts: :gitlab_main
-user_project_callouts: :gitlab_main
-user_highest_roles: :gitlab_main
-user_interacted_projects: :gitlab_main
-user_phone_number_validations: :gitlab_main
-user_permission_export_uploads: :gitlab_main
-user_preferences: :gitlab_main
-users: :gitlab_main
-users_ops_dashboard_projects: :gitlab_main
-users_security_dashboard_projects: :gitlab_main
-users_star_projects: :gitlab_main
-users_statistics: :gitlab_main
-user_statuses: :gitlab_main
-user_synced_attributes_metadata: :gitlab_main
-verification_codes: :gitlab_main
-vulnerabilities: :gitlab_main
-vulnerability_advisories: :gitlab_main
-vulnerability_exports: :gitlab_main
-vulnerability_external_issue_links: :gitlab_main
-vulnerability_feedback: :gitlab_main
-vulnerability_finding_evidences: :gitlab_main
-vulnerability_finding_links: :gitlab_main
-vulnerability_finding_signatures: :gitlab_main
-vulnerability_findings_remediations: :gitlab_main
-vulnerability_flags: :gitlab_main
-vulnerability_historical_statistics: :gitlab_main
-vulnerability_identifiers: :gitlab_main
-vulnerability_issue_links: :gitlab_main
-vulnerability_merge_request_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_state_transitions: :gitlab_main
-vulnerability_statistics: :gitlab_main
-vulnerability_user_mentions: :gitlab_main
-webauthn_registrations: :gitlab_main
-web_hook_logs: :gitlab_main
-web_hooks: :gitlab_main
-wiki_page_meta: :gitlab_main
-wiki_page_slugs: :gitlab_main
-work_item_parent_links: :gitlab_main
-work_item_types: :gitlab_main
-x509_certificates: :gitlab_main
-x509_commit_signatures: :gitlab_main
-x509_issuers: :gitlab_main
-zentao_tracker_data: :gitlab_main
-# dingtalk_tracker_data JiHu-specific, see https://jihulab.com/gitlab-cn/gitlab/-/merge_requests/417
-dingtalk_tracker_data: :gitlab_main
-zoom_meetings: :gitlab_main
-batched_background_migration_job_transition_logs: :gitlab_shared
-user_namespace_callouts: :gitlab_main
diff --git a/lib/gitlab/database/load_balancing/connection_proxy.rb b/lib/gitlab/database/load_balancing/connection_proxy.rb
index 8799f8d8af8..f0343f9d8b5 100644
--- a/lib/gitlab/database/load_balancing/connection_proxy.rb
+++ b/lib/gitlab/database/load_balancing/connection_proxy.rb
@@ -95,7 +95,7 @@ module Gitlab
# name - The name of the method to call on a connection object.
def read_using_load_balancer(...)
if current_session.use_primary? &&
- !current_session.use_replicas_for_read_queries?
+ !current_session.use_replicas_for_read_queries?
@load_balancer.read_write do |connection|
connection.send(...)
end
diff --git a/lib/gitlab/database/load_balancing/service_discovery.rb b/lib/gitlab/database/load_balancing/service_discovery.rb
index 52a9e8798d4..3295301a2d7 100644
--- a/lib/gitlab/database/load_balancing/service_discovery.rb
+++ b/lib/gitlab/database/load_balancing/service_discovery.rb
@@ -125,13 +125,6 @@ module Gitlab
old_host_list_length: current.length
)
replace_hosts(from_dns)
- else
- ::Gitlab::Database::LoadBalancing::Logger.info(
- event: :host_list_unchanged,
- message: "Unchanged host list for service discovery",
- host_list_length: from_dns.length,
- old_host_list_length: current.length
- )
end
interval
diff --git a/lib/gitlab/database/load_balancing/sidekiq_client_middleware.rb b/lib/gitlab/database/load_balancing/sidekiq_client_middleware.rb
index 13afbd8fd37..619f11ae890 100644
--- a/lib/gitlab/database/load_balancing/sidekiq_client_middleware.rb
+++ b/lib/gitlab/database/load_balancing/sidekiq_client_middleware.rb
@@ -57,7 +57,7 @@ module Gitlab
if uses_primary?
load_balancer.primary_write_location
else
- load_balancer.host.database_replica_location
+ load_balancer.host&.database_replica_location || load_balancer.primary_write_location
end
end
diff --git a/lib/gitlab/database/load_balancing/sidekiq_server_middleware.rb b/lib/gitlab/database/load_balancing/sidekiq_server_middleware.rb
index 737852d5ccb..f7b8d2514ba 100644
--- a/lib/gitlab/database/load_balancing/sidekiq_server_middleware.rb
+++ b/lib/gitlab/database/load_balancing/sidekiq_server_middleware.rb
@@ -97,14 +97,8 @@ module Gitlab
end
def databases_in_sync?(wal_locations)
- locations = if Feature.enabled?(:indifferent_wal_location_keys)
- wal_locations.with_indifferent_access
- else
- wal_locations
- end
-
::Gitlab::Database::LoadBalancing.each_load_balancer.all? do |lb|
- if (location = locations[lb.name])
+ if (location = wal_locations.with_indifferent_access[lb.name])
lb.select_up_to_date_host(location)
else
# If there's no entry for a load balancer it means the Sidekiq
diff --git a/lib/gitlab/database/lock_writes_manager.rb b/lib/gitlab/database/lock_writes_manager.rb
index 2594ee04b35..e3ae2892668 100644
--- a/lib/gitlab/database/lock_writes_manager.rb
+++ b/lib/gitlab/database/lock_writes_manager.rb
@@ -10,18 +10,34 @@ module Gitlab
# See https://www.postgresql.org/message-id/16934.1568989957%40sss.pgh.pa.us
EXPECTED_TRIGGER_RECORD_COUNT = 3
+ def self.tables_to_lock(connection)
+ Gitlab::Database::GitlabSchema.tables_to_schema.each do |table_name, schema_name|
+ yield table_name, schema_name
+ end
+
+ Gitlab::Database::SharedModel.using_connection(connection) do
+ Postgresql::DetachedPartition.find_each do |detached_partition|
+ yield detached_partition.fully_qualified_table_name, detached_partition.table_schema
+ end
+ end
+ end
+
def initialize(table_name:, connection:, database_name:, logger: nil, dry_run: false)
@table_name = table_name
@connection = connection
@database_name = database_name
@logger = logger
@dry_run = dry_run
+
+ @table_name_without_schema = ActiveRecord::ConnectionAdapters::PostgreSQL::Utils
+ .extract_schema_qualified_name(table_name)
+ .identifier
end
def table_locked_for_writes?(table_name)
query = <<~SQL
SELECT COUNT(*) from information_schema.triggers
- WHERE event_object_table = '#{table_name}'
+ WHERE event_object_table = '#{table_name_without_schema}'
AND trigger_name = '#{write_trigger_name(table_name)}'
SQL
@@ -56,7 +72,7 @@ module Gitlab
private
- attr_reader :table_name, :connection, :database_name, :logger, :dry_run
+ attr_reader :table_name, :connection, :database_name, :logger, :dry_run, :table_name_without_schema
def execute_sql_statement(sql)
if dry_run
@@ -99,7 +115,7 @@ module Gitlab
end
def write_trigger_name(table_name)
- "gitlab_schema_write_trigger_for_#{table_name}"
+ "gitlab_schema_write_trigger_for_#{table_name_without_schema}"
end
end
end
diff --git a/lib/gitlab/database/migration.rb b/lib/gitlab/database/migration.rb
index ab8b6988c3d..4d38920f571 100644
--- a/lib/gitlab/database/migration.rb
+++ b/lib/gitlab/database/migration.rb
@@ -51,6 +51,10 @@ module Gitlab
include Gitlab::Database::MigrationHelpers::RestrictGitlabSchema
end
+ class V2_1 < V2_0 # rubocop:disable Naming/ClassAndModuleCamelCase
+ include Gitlab::Database::MigrationHelpers::AutomaticLockWritesOnTables
+ end
+
def self.[](version)
version = version.to_s
name = "V#{version.tr('.', '_')}"
@@ -61,7 +65,7 @@ module Gitlab
# The current version to be used in new migrations
def self.current_version
- 2.0
+ 2.1
end
end
end
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index 16416dd2507..4858a96c173 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -10,6 +10,7 @@ module Gitlab
include Migrations::TimeoutHelpers
include Migrations::ConstraintsHelpers
include Migrations::ExtensionHelpers
+ include Migrations::SidekiqHelpers
include DynamicModelHelpers
include RenameTableHelpers
include AsyncIndexes::MigrationHelpers
@@ -497,17 +498,6 @@ module Gitlab
end
end
- # Adds a column with a default value without locking an entire table.
- #
- # @deprecated With PostgreSQL 11, adding columns with a default does not lead to a table rewrite anymore.
- # As such, this method is not needed anymore and the default `add_column` helper should be used.
- # This helper is subject to be removed in a >13.0 release.
- def add_column_with_default(table, column, type, default:, limit: nil, allow_null: false)
- raise 'Deprecated: add_column_with_default does not support being passed blocks anymore' if block_given?
-
- add_column(table, column, type, default: default, limit: limit, null: allow_null)
- end
-
# Renames a column without requiring downtime.
#
# Concurrent renames work by using database triggers to ensure both the
@@ -1027,38 +1017,6 @@ module Gitlab
rescue ArgumentError
end
- # Remove any instances of deprecated job classes lingering in queues.
- #
- # rubocop:disable Cop/SidekiqApiUsage
- def sidekiq_remove_jobs(job_klass:)
- Sidekiq::Queue.new(job_klass.queue).each do |job|
- job.delete if job.klass == job_klass.to_s
- end
-
- Sidekiq::RetrySet.new.each do |retri|
- retri.delete if retri.klass == job_klass.to_s
- end
-
- Sidekiq::ScheduledSet.new.each do |scheduled|
- scheduled.delete if scheduled.klass == job_klass.to_s
- end
- end
- # rubocop:enable Cop/SidekiqApiUsage
-
- def sidekiq_queue_migrate(queue_from, to:)
- while sidekiq_queue_length(queue_from) > 0
- Sidekiq.redis do |conn|
- conn.rpoplpush "queue:#{queue_from}", "queue:#{to}"
- end
- end
- end
-
- def sidekiq_queue_length(queue_name)
- Sidekiq.redis do |conn|
- conn.llen("queue:#{queue_name}")
- end
- end
-
def check_trigger_permissions!(table)
unless Grant.create_and_execute_trigger?(table)
dbname = ApplicationRecord.database.database_name
diff --git a/lib/gitlab/database/migration_helpers/automatic_lock_writes_on_tables.rb b/lib/gitlab/database/migration_helpers/automatic_lock_writes_on_tables.rb
new file mode 100644
index 00000000000..0aa4b0d01c4
--- /dev/null
+++ b/lib/gitlab/database/migration_helpers/automatic_lock_writes_on_tables.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ module MigrationHelpers
+ module AutomaticLockWritesOnTables
+ extend ActiveSupport::Concern
+
+ included do
+ class_attribute :skip_automatic_lock_on_writes
+ end
+
+ def exec_migration(connection, direction)
+ return super if %w[main ci].exclude?(Gitlab::Database.db_config_name(connection))
+ return super if automatic_lock_on_writes_disabled?
+
+ # This compares the tables only on the `public` schema. Partitions are not affected
+ tables = connection.tables
+ super
+ new_tables = connection.tables - tables
+
+ new_tables.each do |table_name|
+ lock_writes_on_table(connection, table_name) if should_lock_writes_on_table?(table_name)
+ end
+ end
+
+ private
+
+ def automatic_lock_on_writes_disabled?
+ # Feature flags are set on the main database, see tables features/feature_gates.
+ # That is why we switch the ActiveRecord::Base.connection temporarily here back to the 'main' database
+ # for the cases when the migration is targeting another database, like the 'ci' database.
+ with_restored_connection_stack do |_|
+ Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas.with_suppressed do
+ skip_automatic_lock_on_writes ||
+ Gitlab::Utils.to_boolean(ENV['SKIP_AUTOMATIC_LOCK_ON_WRITES']) ||
+ Feature.disabled?(:automatic_lock_writes_on_table, type: :ops)
+ end
+ end
+ end
+
+ def should_lock_writes_on_table?(table_name)
+ # currently gitlab_schema represents only present existing tables, this is workaround for deleted tables
+ # that should be skipped as they will be removed in a future migration.
+ return false if Gitlab::Database::GitlabSchema::DELETED_TABLES[table_name]
+
+ table_schema = Gitlab::Database::GitlabSchema.table_schema(table_name.to_s, undefined: false)
+
+ if table_schema.nil?
+ error_message = <<~ERROR
+ No gitlab_schema is defined for the table #{table_name}. Please consider
+ adding it to the database dictionary.
+ More info: https://docs.gitlab.com/ee/development/database/database_dictionary.html
+ ERROR
+ raise error_message
+ end
+
+ return false unless %i[gitlab_main gitlab_ci].include?(table_schema)
+
+ Gitlab::Database.gitlab_schemas_for_connection(connection).exclude?(table_schema)
+ end
+
+ def lock_writes_on_table(connection, table_name)
+ database_name = Gitlab::Database.db_config_name(connection)
+ LockWritesManager.new(
+ table_name: table_name,
+ connection: connection,
+ database_name: database_name,
+ logger: Logger.new($stdout)
+ ).lock_writes
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database/migrations/batched_migration_last_id.rb b/lib/gitlab/database/migrations/batched_migration_last_id.rb
new file mode 100644
index 00000000000..c77a2e9a375
--- /dev/null
+++ b/lib/gitlab/database/migrations/batched_migration_last_id.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ module Migrations
+ class BatchedMigrationLastId
+ FILE_NAME = 'last-batched-background-migration-id.txt'
+
+ def initialize(connection, base_dir)
+ @connection = connection
+ @base_dir = base_dir
+ end
+
+ def store
+ File.open(file_path, 'wb') { |file| file.write(last_background_migration_id) }
+ end
+
+ # Reads the last id from the file
+ #
+ # @info casts the file content into an +Integer+.
+ # Casts any unexpected content to +nil+
+ #
+ # @example
+ # Integer('4', exception: false) # => 4
+ # Integer('', exception: false) # => nil
+ #
+ # @return [Integer, nil]
+ def read
+ return unless File.exist?(file_path)
+
+ Integer(File.read(file_path).presence, exception: false)
+ end
+
+ private
+
+ attr_reader :connection, :base_dir
+
+ def file_path
+ @file_path ||= base_dir.join(FILE_NAME)
+ end
+
+ def last_background_migration_id
+ Gitlab::Database::SharedModel.using_connection(connection) do
+ Gitlab::Database::BackgroundMigration::BatchedMigration.maximum(:id)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database/migrations/runner.rb b/lib/gitlab/database/migrations/runner.rb
index 27b161419b2..ed55081c9ab 100644
--- a/lib/gitlab/database/migrations/runner.rb
+++ b/lib/gitlab/database/migrations/runner.rb
@@ -29,16 +29,14 @@ module Gitlab
def batched_background_migrations(for_database:, legacy_mode: false)
runner = nil
- result_dir = if legacy_mode
- BASE_RESULT_DIR.join('background_migrations')
- else
- BASE_RESULT_DIR.join(for_database.to_s, 'background_migrations')
- end
+ result_dir = background_migrations_dir(for_database, legacy_mode)
# Only one loop iteration since we pass `only:` here
Gitlab::Database::EachDatabase.each_database_connection(only: for_database) do |connection|
+ from_id = batched_migrations_last_id(for_database).read
+
runner = Gitlab::Database::Migrations::TestBatchedBackgroundRunner
- .new(result_dir: result_dir, connection: connection)
+ .new(result_dir: result_dir, connection: connection, from_id: from_id)
end
runner
@@ -66,6 +64,18 @@ module Gitlab
end
# rubocop:enable Database/MultipleDatabases
+ def batched_migrations_last_id(for_database)
+ runner = nil
+ base_dir = background_migrations_dir(for_database, false)
+
+ Gitlab::Database::EachDatabase.each_database_connection(only: for_database) do |connection|
+ runner = Gitlab::Database::Migrations::BatchedMigrationLastId
+ .new(connection, base_dir)
+ end
+
+ runner
+ end
+
private
def migrations_for_up(database)
@@ -90,6 +100,12 @@ module Gitlab
existing_versions.include?(migration.version) && versions_this_branch.include?(migration.version)
end
end
+
+ def background_migrations_dir(db, legacy_mode)
+ return BASE_RESULT_DIR.join('background_migrations') if legacy_mode
+
+ BASE_RESULT_DIR.join(db.to_s, 'background_migrations')
+ end
end
attr_reader :direction, :result_dir, :migrations
diff --git a/lib/gitlab/database/migrations/sidekiq_helpers.rb b/lib/gitlab/database/migrations/sidekiq_helpers.rb
new file mode 100644
index 00000000000..c536b33bbdf
--- /dev/null
+++ b/lib/gitlab/database/migrations/sidekiq_helpers.rb
@@ -0,0 +1,112 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ module Migrations
+ # rubocop:disable Cop/SidekiqApiUsage
+ # rubocop:disable Cop/SidekiqRedisCall
+ module SidekiqHelpers
+ # Constants for default sidekiq_remove_jobs values
+ DEFAULT_MAX_ATTEMPTS = 5
+ DEFAULT_TIMES_IN_A_ROW = 2
+
+ # Probabilistically removes job_klasses from their specific queues, the
+ # retry set and the scheduled set.
+ #
+ # If jobs are still being processed at the same time, then there is a
+ # small chance it will not remove all instances of job_klass. To
+ # minimize this risk, it repeatedly removes matching jobs from each
+ # until nothing is removed twice in a row.
+ #
+ # Before calling this method, you should make sure that job_klass is no
+ # longer being scheduled within the running application.
+ def sidekiq_remove_jobs(
+ job_klasses:,
+ times_in_a_row: DEFAULT_TIMES_IN_A_ROW,
+ max_attempts: DEFAULT_MAX_ATTEMPTS
+ )
+
+ kwargs = { times_in_a_row: times_in_a_row, max_attempts: max_attempts }
+
+ job_klasses_queues = job_klasses
+ .select { |job_klass| job_klass.to_s.safe_constantize.present? }
+ .map { |job_klass| job_klass.safe_constantize.queue }
+ .uniq
+
+ job_klasses_queues.each do |queue|
+ delete_jobs_for(
+ set: Sidekiq::Queue.new(queue),
+ job_klasses: job_klasses,
+ kwargs: kwargs
+ )
+ end
+
+ delete_jobs_for(
+ set: Sidekiq::RetrySet.new,
+ kwargs: kwargs,
+ job_klasses: job_klasses
+ )
+
+ delete_jobs_for(
+ set: Sidekiq::ScheduledSet.new,
+ kwargs: kwargs,
+ job_klasses: job_klasses
+ )
+ end
+
+ def sidekiq_queue_migrate(queue_from, to:)
+ while sidekiq_queue_length(queue_from) > 0
+ Sidekiq.redis do |conn|
+ conn.rpoplpush "queue:#{queue_from}", "queue:#{to}"
+ end
+ end
+ end
+
+ def sidekiq_queue_length(queue_name)
+ Sidekiq.redis do |conn|
+ conn.llen("queue:#{queue_name}")
+ end
+ end
+
+ private
+
+ # Handle the "jobs deleted" tracking that is needed in order to track
+ # whether a job was deleted or not.
+ def delete_jobs_for(set:, kwargs:, job_klasses:)
+ until_equal_to(0, **kwargs) do
+ set.count do |job|
+ job_klasses.include?(job.klass) && job.delete
+ end
+ end
+ end
+
+ # Control how many times in a row you want to see a job deleted 0
+ # times. The idea is that if you see 0 jobs deleted x number of times
+ # in a row you've *likely* covered the case in which the queue was
+ # mutating while this was running.
+ def until_equal_to(target, times_in_a_row:, max_attempts:)
+ streak = 0
+
+ result = { attempts: 0, success: false }
+
+ 1.upto(max_attempts) do |current_attempt|
+ # yield's return value is a count of "jobs_deleted"
+ if yield == target
+ streak += 1
+ elsif streak > 0
+ streak = 0
+ end
+
+ result[:attempts] = current_attempt
+ result[:success] = streak == times_in_a_row
+
+ break if result[:success]
+ end
+ result
+ end
+ end
+ # rubocop:enable Cop/SidekiqApiUsage
+ # rubocop:enable Cop/SidekiqRedisCall
+ end
+ end
+end
diff --git a/lib/gitlab/database/migrations/test_batched_background_runner.rb b/lib/gitlab/database/migrations/test_batched_background_runner.rb
index 46855ca1921..a16103f452c 100644
--- a/lib/gitlab/database/migrations/test_batched_background_runner.rb
+++ b/lib/gitlab/database/migrations/test_batched_background_runner.rb
@@ -6,16 +6,17 @@ module Gitlab
class TestBatchedBackgroundRunner < BaseBackgroundRunner
include Gitlab::Database::DynamicModelHelpers
- def initialize(result_dir:, connection:)
+ def initialize(result_dir:, connection:, from_id:)
super(result_dir: result_dir, connection: connection)
@connection = connection
+ @from_id = from_id
end
def jobs_by_migration_name
Gitlab::Database::SharedModel.using_connection(connection) do
Gitlab::Database::BackgroundMigration::BatchedMigration
.executable
- .created_after(3.hours.ago) # Simple way to exclude migrations already running before migration testing
+ .where('id > ?', from_id)
.to_h do |migration|
batching_strategy = migration.batch_class.new(connection: connection)
@@ -102,6 +103,10 @@ module Gitlab
end
end
end
+
+ private
+
+ attr_reader :from_id
end
end
end
diff --git a/lib/gitlab/database/obsolete_ignored_columns.rb b/lib/gitlab/database/obsolete_ignored_columns.rb
index ad5473f1b74..2b88ab12380 100644
--- a/lib/gitlab/database/obsolete_ignored_columns.rb
+++ b/lib/gitlab/database/obsolete_ignored_columns.rb
@@ -23,8 +23,8 @@ module Gitlab
private
def ignored_columns_safe_to_remove_for(klass)
- ignores = ignored_and_not_present(klass).each_with_object({}) do |col, h|
- h[col] = klass.ignored_columns_details[col.to_sym]
+ ignores = ignored_and_not_present(klass).index_with do |col|
+ klass.ignored_columns_details[col.to_sym]
end
ignores.select { |_, i| i&.safe_to_remove? }
diff --git a/lib/gitlab/database/partitioning/single_numeric_list_partition.rb b/lib/gitlab/database/partitioning/single_numeric_list_partition.rb
index 4e38eea963b..fd99062974c 100644
--- a/lib/gitlab/database/partitioning/single_numeric_list_partition.rb
+++ b/lib/gitlab/database/partitioning/single_numeric_list_partition.rb
@@ -19,7 +19,7 @@ module Gitlab
attr_reader :table, :value
- def initialize(table, value, partition_name: nil )
+ def initialize(table, value, partition_name: nil)
@table = table
@value = value
@partition_name = partition_name
diff --git a/lib/gitlab/database/postgres_hll/buckets.rb b/lib/gitlab/database/postgres_hll/buckets.rb
index cbc9544d905..3f64eee030e 100644
--- a/lib/gitlab/database/postgres_hll/buckets.rb
+++ b/lib/gitlab/database/postgres_hll/buckets.rb
@@ -61,7 +61,7 @@ module Gitlab
num_uniques = (
((TOTAL_BUCKETS**2) * (0.7213 / (1 + 1.079 / TOTAL_BUCKETS))) /
- (num_zero_buckets + buckets.values.sum { |bucket_hash| 2**(-1 * bucket_hash) } )
+ (num_zero_buckets + buckets.values.sum { |bucket_hash| 2**(-1 * bucket_hash) })
).to_i
if num_zero_buckets > 0 && num_uniques < 2.5 * TOTAL_BUCKETS
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 3b1751c863d..dd10e0d7992 100644
--- a/lib/gitlab/database/query_analyzers/prevent_cross_database_modification.rb
+++ b/lib/gitlab/database/query_analyzers/prevent_cross_database_modification.rb
@@ -165,8 +165,8 @@ module Gitlab
def self.in_factory_bot_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'
+ 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
diff --git a/lib/gitlab/database/query_analyzers/query_recorder.rb b/lib/gitlab/database/query_analyzers/query_recorder.rb
index 88fe829c3d2..b54f3442512 100644
--- a/lib/gitlab/database/query_analyzers/query_recorder.rb
+++ b/lib/gitlab/database/query_analyzers/query_recorder.rb
@@ -4,7 +4,7 @@ module Gitlab
module Database
module QueryAnalyzers
class QueryRecorder < Base
- LOG_FILE = 'rspec/query_recorder.ndjson'
+ LOG_PATH = 'query_recorder/'
class << self
def raw?
@@ -12,8 +12,9 @@ module Gitlab
end
def enabled?
- # Only enable QueryRecorder in CI
- ENV['CI'].present?
+ # Only enable QueryRecorder in CI on database MRs or default branch
+ ENV['CI_MERGE_REQUEST_LABELS']&.include?('database') ||
+ (ENV['CI_COMMIT_REF_NAME'].present? && ENV['CI_COMMIT_REF_NAME'] == ENV['CI_DEFAULT_BRANCH'])
end
def analyze(sql)
@@ -24,11 +25,14 @@ module Gitlab
log_query(payload)
end
+ def log_file
+ Rails.root.join(LOG_PATH, "#{ENV.fetch('CI_JOB_NAME_SLUG', 'rspec')}.ndjson")
+ end
+
private
def log_query(payload)
- log_path = Rails.root.join(LOG_FILE)
- log_dir = File.dirname(log_path)
+ log_dir = Rails.root.join(LOG_PATH)
# Create log directory if it does not exist since it is only created
# ahead of time by certain CI jobs
@@ -36,7 +40,7 @@ module Gitlab
log_line = "#{Gitlab::Json.dump(payload)}\n"
- File.write(log_path, log_line, mode: 'a')
+ File.write(log_file, log_line, mode: 'a')
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 74900dc0d26..6da76803f7c 100644
--- a/lib/gitlab/database/schema_cache_with_renamed_table.rb
+++ b/lib/gitlab/database/schema_cache_with_renamed_table.rb
@@ -40,10 +40,8 @@ module Gitlab
end
def renamed_tables_cache
- @renamed_tables ||= begin
- Gitlab::Database::TABLES_TO_BE_RENAMED.select do |old_name, new_name|
- connection.view_exists?(old_name)
- end
+ @renamed_tables ||= Gitlab::Database::TABLES_TO_BE_RENAMED.select do |old_name, new_name|
+ connection.view_exists?(old_name)
end
end
diff --git a/lib/gitlab/database/schema_cleaner.rb b/lib/gitlab/database/schema_cleaner.rb
index c3cdcf1450d..2c8d0a4eb6d 100644
--- a/lib/gitlab/database/schema_cleaner.rb
+++ b/lib/gitlab/database/schema_cleaner.rb
@@ -25,7 +25,23 @@ module Gitlab
# The intention here is to not introduce an assumption about the standard schema,
# unless we have a good reason to do so.
structure.gsub!(/public\.(\w+)/, '\1')
- structure.gsub!(/CREATE EXTENSION IF NOT EXISTS (\w+) WITH SCHEMA public;/, 'CREATE EXTENSION IF NOT EXISTS \1;')
+ structure.gsub!(
+ /CREATE EXTENSION IF NOT EXISTS (\w+) WITH SCHEMA public;/,
+ 'CREATE EXTENSION IF NOT EXISTS \1;'
+ )
+
+ # Table lock-writes triggers should not be added to the schema
+ # These triggers are added by the rake task gitlab:db:lock_writes for a decomposed database.
+ structure.gsub!(
+ %r{
+ ^CREATE.TRIGGER.gitlab_schema_write_trigger_\w+
+ \s
+ BEFORE.INSERT.OR.DELETE.OR.UPDATE.OR.TRUNCATE.ON.\w+
+ \s
+ FOR.EACH.STATEMENT.EXECUTE.FUNCTION.gitlab_schema_prevent_write\(\);$
+ }x,
+ ''
+ )
structure.gsub!(/\n{3,}/, "\n\n")
diff --git a/lib/gitlab/database/tables_sorted_by_foreign_keys.rb b/lib/gitlab/database/tables_sorted_by_foreign_keys.rb
index 9f096904d31..b2a7f5442e9 100644
--- a/lib/gitlab/database/tables_sorted_by_foreign_keys.rb
+++ b/lib/gitlab/database/tables_sorted_by_foreign_keys.rb
@@ -26,15 +26,32 @@ module Gitlab
# it maps the tables to the tables that depend on it
def tables_dependencies
- @tables.to_h do |table_name|
- [table_name, all_foreign_keys[table_name]&.map(&:from_table).to_a]
+ @tables.index_with do |table_name|
+ all_foreign_keys[table_name]
end
end
def all_foreign_keys
- @all_foreign_keys ||= @tables.flat_map do |table_name|
- @connection.foreign_keys(table_name)
- end.group_by(&:to_table)
+ @all_foreign_keys ||= @tables.each_with_object(Hash.new { |h, k| h[k] = [] }) do |table, hash|
+ foreign_keys_for(table).each do |fk|
+ hash[fk.to_table] << table
+ end
+ end
+ end
+
+ def foreign_keys_for(table)
+ # Detached partitions like gitlab_partitions_dynamic._test_gitlab_partition_20220101
+ # store their foreign keys in the public schema.
+ #
+ # See spec/lib/gitlab/database/tables_sorted_by_foreign_keys_spec.rb
+ # for an example
+ name = ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(table)
+
+ if name.schema == ::Gitlab::Database::DYNAMIC_PARTITIONS_SCHEMA.to_s
+ @connection.foreign_keys(name.identifier)
+ else
+ @connection.foreign_keys(table)
+ end
end
end
end
diff --git a/lib/gitlab/database/tables_truncate.rb b/lib/gitlab/database/tables_truncate.rb
index 8380bf23899..807ecdb862a 100644
--- a/lib/gitlab/database/tables_truncate.rb
+++ b/lib/gitlab/database/tables_truncate.rb
@@ -19,24 +19,32 @@ module Gitlab
logger&.info "DRY RUN:" if dry_run
- connection = Gitlab::Database.database_base_models[database_name].connection
-
schemas_for_connection = Gitlab::Database.gitlab_schemas_for_connection(connection)
tables_to_truncate = Gitlab::Database::GitlabSchema.tables_to_schema.reject do |_, schema_name|
- (GITLAB_SCHEMAS_TO_IGNORE.union(schemas_for_connection)).include?(schema_name)
+ GITLAB_SCHEMAS_TO_IGNORE.union(schemas_for_connection).include?(schema_name)
end.keys
+ Gitlab::Database::SharedModel.using_connection(connection) do
+ Postgresql::DetachedPartition.find_each do |detached_partition|
+ next if GITLAB_SCHEMAS_TO_IGNORE.union(schemas_for_connection).include?(detached_partition.table_schema)
+
+ tables_to_truncate << detached_partition.fully_qualified_table_name
+ end
+ end
+
tables_sorted = Gitlab::Database::TablesSortedByForeignKeys.new(connection, tables_to_truncate).execute
# Checking if all the tables have the write-lock triggers
# to make sure we are deleting the right tables on the right database.
tables_sorted.flatten.each do |table_name|
- query = <<~SQL
- SELECT COUNT(*) from information_schema.triggers
- WHERE event_object_table = '#{table_name}'
- AND trigger_name = 'gitlab_schema_write_trigger_for_#{table_name}'
- SQL
-
- if connection.select_value(query) == 0
+ lock_writes_manager = Gitlab::Database::LockWritesManager.new(
+ table_name: table_name,
+ connection: connection,
+ database_name: database_name,
+ logger: logger,
+ dry_run: dry_run
+ )
+
+ unless lock_writes_manager.table_locked_for_writes?(table_name)
raise "Table '#{table_name}' is not locked for writes. Run the rake task gitlab:db:lock_writes first"
end
end
@@ -51,18 +59,26 @@ module Gitlab
# min_batch_size is the minimum number of new tables to truncate at each stage.
# But in each stage we have also have to truncate the already truncated tables in the previous stages
logger&.info "Truncating legacy tables for the database #{database_name}"
- truncate_tables_in_batches(connection, tables_sorted, min_batch_size)
+ truncate_tables_in_batches(tables_sorted)
end
private
attr_accessor :database_name, :min_batch_size, :logger, :dry_run, :until_table
- def truncate_tables_in_batches(connection, tables_sorted, min_batch_size)
+ def connection
+ @connection ||= Gitlab::Database.database_base_models[database_name].connection
+ end
+
+ def truncate_tables_in_batches(tables_sorted)
truncated_tables = []
tables_sorted.flatten.each do |table|
- sql_statement = "SELECT set_config('lock_writes.#{table}', 'false', false)"
+ table_name_without_schema = ActiveRecord::ConnectionAdapters::PostgreSQL::Utils
+ .extract_schema_qualified_name(table)
+ .identifier
+
+ sql_statement = "SELECT set_config('lock_writes.#{table_name_without_schema}', 'false', false)"
logger&.info(sql_statement)
connection.execute(sql_statement) unless dry_run
end
diff --git a/lib/gitlab/database/type/indifferent_jsonb.rb b/lib/gitlab/database/type/indifferent_jsonb.rb
new file mode 100644
index 00000000000..69bbcb383ba
--- /dev/null
+++ b/lib/gitlab/database/type/indifferent_jsonb.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ module Type
+ # Extends Rails' Jsonb data type to deserialize it into indifferent access Hash.
+ #
+ # Example:
+ #
+ # class SomeModel < ApplicationRecord
+ # # some_model.a_field is of type `jsonb`
+ # attribute :a_field, :ind_jsonb
+ # end
+ class IndifferentJsonb < ::ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Jsonb
+ def type
+ :ind_jsonb
+ end
+
+ def deserialize(value)
+ data = super
+ return unless data
+
+ ::Gitlab::Utils.deep_indifferent_access(data)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database_importers/work_items/hierarchy_restrictions_importer.rb b/lib/gitlab/database_importers/work_items/hierarchy_restrictions_importer.rb
new file mode 100644
index 00000000000..1181c259a5c
--- /dev/null
+++ b/lib/gitlab/database_importers/work_items/hierarchy_restrictions_importer.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module DatabaseImporters
+ module WorkItems
+ module HierarchyRestrictionsImporter
+ def self.upsert_restrictions
+ objective = find_or_create_type(::WorkItems::Type::TYPE_NAMES[:objective])
+ key_result = find_or_create_type(::WorkItems::Type::TYPE_NAMES[:key_result])
+ issue = find_or_create_type(::WorkItems::Type::TYPE_NAMES[:issue])
+ task = find_or_create_type(::WorkItems::Type::TYPE_NAMES[:task])
+ incident = find_or_create_type(::WorkItems::Type::TYPE_NAMES[:incident])
+
+ restrictions = [
+ { parent_type_id: objective.id, child_type_id: objective.id, maximum_depth: 9 },
+ { parent_type_id: objective.id, child_type_id: key_result.id, maximum_depth: 1 },
+ { parent_type_id: issue.id, child_type_id: task.id, maximum_depth: 1 },
+ { parent_type_id: incident.id, child_type_id: task.id, maximum_depth: 1 }
+ ]
+
+ ::WorkItems::HierarchyRestriction.upsert_all(
+ restrictions,
+ unique_by: :index_work_item_hierarchy_restrictions_on_parent_and_child
+ )
+ end
+
+ def self.find_or_create_type(name)
+ type = ::WorkItems::Type.find_by_name_and_namespace_id(name, nil)
+ return type if type
+
+ Gitlab::DatabaseImporters::WorkItems::BaseTypeImporter.upsert_types
+ ::WorkItems::Type.find_by_name_and_namespace_id(name, nil)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/diff/file_collection/compare.rb b/lib/gitlab/diff/file_collection/compare.rb
index 6d8395d048d..df0d71f3db2 100644
--- a/lib/gitlab/diff/file_collection/compare.rb
+++ b/lib/gitlab/diff/file_collection/compare.rb
@@ -4,7 +4,15 @@ module Gitlab
module Diff
module FileCollection
class Compare < Base
+ delegate :limit_value, :current_page, :next_page, :prev_page, :total_count, :total_pages, to: :@pagination
+
def initialize(compare, project:, diff_options:, diff_refs: nil)
+ @pagination = Gitlab::PaginationDelegate.new(
+ page: diff_options&.delete(:page),
+ per_page: diff_options&.delete(:per_page),
+ count: diff_options&.delete(:count)
+ )
+
super(compare,
project: project,
diff_options: diff_options,
diff --git a/lib/gitlab/diff/file_collection/merge_request_diff_batch.rb b/lib/gitlab/diff/file_collection/merge_request_diff_batch.rb
index 0a601bde612..56027d6a4de 100644
--- a/lib/gitlab/diff/file_collection/merge_request_diff_batch.rb
+++ b/lib/gitlab/diff/file_collection/merge_request_diff_batch.rb
@@ -10,6 +10,8 @@ module Gitlab
# separate file keys (https://gitlab.com/gitlab-org/gitlab/issues/30550).
#
class MergeRequestDiffBatch < MergeRequestDiffBase
+ include PaginatedDiffs
+
DEFAULT_BATCH_PAGE = 1
DEFAULT_BATCH_SIZE = 30
@@ -25,41 +27,8 @@ module Gitlab
}
end
- override :diffs
- def diffs
- strong_memoize(:diffs) do
- @merge_request_diff.opening_external_diff do
- # Avoiding any extra queries.
- collection = @paginated_collection.to_a
-
- # The offset collection and calculation is required so that we
- # know how much has been loaded in previous batches, collapsing
- # the current paginated set accordingly (collection limit calculation).
- # See: https://docs.gitlab.com/ee/development/diffs.html#diff-collection-limits
- #
- offset_index = collection.first&.index
- options = diff_options.dup
-
- collection =
- if offset_index && offset_index > 0
- offset_collection = relation.limit(offset_index) # rubocop:disable CodeReuse/ActiveRecord
- options[:offset_index] = offset_index
- offset_collection + collection
- else
- collection
- end
-
- Gitlab::Git::DiffCollection.new(collection.map(&:to_hash), options)
- end
- end
- end
-
private
- def relation
- @merge_request_diff.merge_request_diff_files
- end
-
# rubocop: disable CodeReuse/ActiveRecord
def load_paginated_collection(batch_page, batch_size, diff_options)
batch_page ||= DEFAULT_BATCH_PAGE
diff --git a/lib/gitlab/diff/file_collection/paginated_diffs.rb b/lib/gitlab/diff/file_collection/paginated_diffs.rb
new file mode 100644
index 00000000000..63c186affe9
--- /dev/null
+++ b/lib/gitlab/diff/file_collection/paginated_diffs.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Diff
+ module FileCollection
+ module PaginatedDiffs
+ include Gitlab::Utils::StrongMemoize
+ extend ::Gitlab::Utils::Override
+
+ override :diffs
+ def diffs
+ merge_request_diff.opening_external_diff do
+ # Avoiding any extra queries.
+ collection = paginated_collection.to_a
+
+ # The offset collection and calculation is required so that we
+ # know how much has been loaded in previous batches, collapsing
+ # the current paginated set accordingly (collection limit calculation).
+ # See: https://docs.gitlab.com/ee/development/diffs.html#diff-collection-limits
+ #
+ offset_index = collection.first&.index
+ options = diff_options.dup
+
+ collection =
+ if offset_index && offset_index > 0
+ offset_collection = relation.limit(offset_index) # rubocop:disable CodeReuse/ActiveRecord
+ options[:offset_index] = offset_index
+ offset_collection + collection
+ else
+ collection
+ end
+
+ Gitlab::Git::DiffCollection.new(collection.map(&:to_hash), options)
+ end
+ end
+ strong_memoize_attr :diffs
+
+ private
+
+ attr_reader :merge_request_diff, :paginated_collection
+
+ def relation
+ merge_request_diff.merge_request_diff_files
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/diff/file_collection/paginated_merge_request_diff.rb b/lib/gitlab/diff/file_collection/paginated_merge_request_diff.rb
new file mode 100644
index 00000000000..37abad81305
--- /dev/null
+++ b/lib/gitlab/diff/file_collection/paginated_merge_request_diff.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Diff
+ module FileCollection
+ # Builds a traditional paginated diff file collection using Kaminari
+ # `per` and `per_page` which is different from how `MergeRequestDiffBatch`
+ # works (e.g. supports gradual loading).
+ class PaginatedMergeRequestDiff < MergeRequestDiffBase
+ include PaginatedDiffs
+
+ DEFAULT_PAGE = 1
+ DEFAULT_PER_PAGE = 30
+
+ delegate :limit_value, :current_page, :next_page, :prev_page, :total_count,
+ :total_pages, to: :paginated_collection
+
+ def initialize(merge_request_diff, page, per_page)
+ super(merge_request_diff, diff_options: nil)
+
+ @paginated_collection = load_paginated_collection(page, per_page)
+ end
+
+ private
+
+ def load_paginated_collection(page, per_page)
+ page ||= DEFAULT_PAGE
+ per_page ||= DEFAULT_PER_PAGE
+
+ relation.page(page).per([per_page.to_i, DEFAULT_PER_PAGE].min)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/diff/parser.rb b/lib/gitlab/diff/parser.rb
index 924c28e3db5..b29c75ed467 100644
--- a/lib/gitlab/diff/parser.rb
+++ b/lib/gitlab/diff/parser.rb
@@ -73,7 +73,7 @@ module Gitlab
private
def filename?(line)
- line.start_with?( '--- /dev/null', '+++ /dev/null', '--- a', '+++ b',
+ line.start_with?('--- /dev/null', '+++ /dev/null', '--- a', '+++ b',
'+++ a', # The line will start with `+++ a` in the reverse diff of an orphan commit
'--- /tmp/diffy', '+++ /tmp/diffy')
end
diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb
index 1e03f5d17ee..32794a6c99d 100644
--- a/lib/gitlab/email/receiver.rb
+++ b/lib/gitlab/email/receiver.rb
@@ -192,7 +192,7 @@ module Gitlab
auto_submitted = mail.header['Auto-Submitted']&.value
# Mail::Field#value would strip leading and trailing whitespace
- # See also https://tools.ietf.org/html/rfc3834
+ # See also https://www.rfc-editor.org/rfc/rfc3834
auto_submitted && auto_submitted != 'no'
end
diff --git a/lib/gitlab/error_tracking/error_repository/open_api_strategy.rb b/lib/gitlab/error_tracking/error_repository/open_api_strategy.rb
index cc822e4c10b..e168fa10630 100644
--- a/lib/gitlab/error_tracking/error_repository/open_api_strategy.rb
+++ b/lib/gitlab/error_tracking/error_repository/open_api_strategy.rb
@@ -228,7 +228,7 @@ module Gitlab
def configured_api_url
url = Gitlab::CurrentSettings.current_application_settings.error_tracking_api_url ||
- 'http://localhost:8080'
+ 'http://localhost:8080'
Gitlab::UrlBlocker.validate!(url, schemes: %w[http https], allow_localhost: true)
diff --git a/lib/gitlab/favicon.rb b/lib/gitlab/favicon.rb
index 721518c6fcc..8e48b482462 100644
--- a/lib/gitlab/favicon.rb
+++ b/lib/gitlab/favicon.rb
@@ -34,11 +34,9 @@ module Gitlab
end
def available_status_names
- @available_status_names ||= begin
- Dir.glob(Rails.root.join('app', 'assets', 'images', 'ci_favicons', '*.png'))
+ @available_status_names ||= Dir.glob(Rails.root.join('app', 'assets', 'images', 'ci_favicons', '*.png'))
.map { |file| File.basename(file, '.png') }
.sort
- end
end
private
diff --git a/lib/gitlab/gfm/uploads_rewriter.rb b/lib/gitlab/gfm/uploads_rewriter.rb
index 58b46a85aae..05d680c139c 100644
--- a/lib/gitlab/gfm/uploads_rewriter.rb
+++ b/lib/gitlab/gfm/uploads_rewriter.rb
@@ -54,8 +54,6 @@ module Gitlab
file = find_file(match[:secret], match[:file])
# No file will be returned for a path traversal
- return '' if file.nil?
-
return markdown unless file.try(:exists?)
klass = @target_parent.is_a?(Namespace) ? NamespaceFileUploader : FileUploader
diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb
index 4b877bf44da..8e1b51fcec5 100644
--- a/lib/gitlab/git.rb
+++ b/lib/gitlab/git.rb
@@ -16,6 +16,7 @@ module Gitlab
CommitError = Class.new(BaseError)
OSError = Class.new(BaseError)
UnknownRef = Class.new(BaseError)
+ AmbiguousRef = Class.new(BaseError)
CommandTimedOut = Class.new(CommandError)
InvalidPageToken = Class.new(BaseError)
InvalidRefFormatError = Class.new(BaseError)
diff --git a/lib/gitlab/git/base_error.rb b/lib/gitlab/git/base_error.rb
index a7eaa82b347..0b0fdef54cc 100644
--- a/lib/gitlab/git/base_error.rb
+++ b/lib/gitlab/git/base_error.rb
@@ -1,20 +1,50 @@
# frozen_string_literal: true
+require 'grpc'
module Gitlab
module Git
class BaseError < StandardError
DEBUG_ERROR_STRING_REGEX = /(.*?) debug_error_string:.*$/m.freeze
+ GRPC_CODES = {
+ '0' => 'ok',
+ '1' => 'cancelled',
+ '2' => 'unknown',
+ '3' => 'invalid_argument',
+ '4' => 'deadline_exceeded',
+ '5' => 'not_found',
+ '6' => 'already_exists',
+ '7' => 'permission_denied',
+ '8' => 'resource_exhausted',
+ '9' => 'failed_precondition',
+ '10' => 'aborted',
+ '11' => 'out_of_range',
+ '12' => 'unimplemented',
+ '13' => 'internal',
+ '14' => 'unavailable',
+ '15' => 'data_loss',
+ '16' => 'unauthenticated'
+ }.freeze
+
+ attr_reader :status, :code, :service
def initialize(msg = nil)
- if msg
- raw_message = msg.to_s
- match = DEBUG_ERROR_STRING_REGEX.match(raw_message)
- raw_message = match[1] if match
+ super && return if msg.nil?
+
+ set_grpc_error_code(msg) if msg.is_a?(::GRPC::BadStatus)
+
+ super(build_raw_message(msg))
+ end
+
+ def build_raw_message(message)
+ raw_message = message.to_s
+ match = DEBUG_ERROR_STRING_REGEX.match(raw_message)
+ match ? match[1] : raw_message
+ end
- super(raw_message)
- else
- super
- end
+ def set_grpc_error_code(grpc_error)
+ @status = grpc_error.code
+ @code = GRPC_CODES[@status.to_s]
+ @service = 'git'
end
end
end
diff --git a/lib/gitlab/git/cross_repo_comparer.rb b/lib/gitlab/git/cross_repo.rb
index d42b2a3bd98..d44657e7db1 100644
--- a/lib/gitlab/git/cross_repo_comparer.rb
+++ b/lib/gitlab/git/cross_repo.rb
@@ -2,7 +2,7 @@
module Gitlab
module Git
- class CrossRepoComparer
+ class CrossRepo
attr_reader :source_repo, :target_repo
def initialize(source_repo, target_repo)
@@ -10,15 +10,8 @@ module Gitlab
@target_repo = target_repo
end
- def compare(source_ref, target_ref, straight:)
- ensuring_ref_in_source(target_ref) do |target_commit_id|
- Gitlab::Git::Compare.new(
- source_repo,
- target_commit_id,
- source_ref,
- straight: straight
- )
- end
+ def execute(target_ref, &blk)
+ ensuring_ref_in_source(target_ref, &blk)
end
private
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 3b5151ef4f2..2f9cfe3e764 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -116,9 +116,9 @@ module Gitlab
# Returns an Array of branch names
# sorted by name ASC
def branch_names
- wrapped_gitaly_errors do
- gitaly_ref_client.branch_names
- end
+ refs = list_refs([Gitlab::Git::BRANCH_REF_PREFIX])
+
+ refs.map { |ref| Gitlab::Git.branch_name(ref.name) }
end
# Returns an Array of Branches
@@ -134,6 +134,10 @@ module Gitlab
wrapped_gitaly_errors do
gitaly_ref_client.find_branch(name)
end
+ rescue Gitlab::Git::AmbiguousRef
+ # Gitaly returns "reference is ambiguous" error in case when users request
+ # branch "my-branch", when another branch "my-branch/branch" exists.
+ # We handle this error here and return nil for this case.
end
def find_tag(name)
@@ -158,9 +162,7 @@ module Gitlab
# Returns the number of valid branches
def branch_count
- wrapped_gitaly_errors do
- gitaly_ref_client.count_branch_names
- end
+ branch_names.count
end
def rename(new_relative_path)
@@ -202,16 +204,14 @@ module Gitlab
# Returns the number of valid tags
def tag_count
- wrapped_gitaly_errors do
- gitaly_ref_client.count_tag_names
- end
+ tag_names.count
end
# Returns an Array of tag names
def tag_names
- wrapped_gitaly_errors do
- gitaly_ref_client.tag_names
- end
+ refs = list_refs([Gitlab::Git::TAG_REF_PREFIX])
+
+ refs.map { |ref| Gitlab::Git.tag_name(ref.name) }
end
# Returns an Array of Tags
@@ -385,6 +385,12 @@ module Gitlab
end
end
+ def check_objects_exist(refs)
+ wrapped_gitaly_errors do
+ gitaly_commit_client.object_existence_map(Array.wrap(refs))
+ end
+ end
+
def new_blobs(newrevs, dynamic_timeout: nil)
newrevs = Array.wrap(newrevs).reject { |rev| rev.blank? || rev == ::Gitlab::Git::BLANK_SHA }
return [] if newrevs.empty?
@@ -823,9 +829,14 @@ module Gitlab
end
def compare_source_branch(target_branch_name, source_repository, source_branch_name, straight:)
- CrossRepoComparer
- .new(source_repository, self)
- .compare(source_branch_name, target_branch_name, straight: straight)
+ CrossRepo.new(source_repository, self).execute(target_branch_name) do |target_commit_id|
+ Gitlab::Git::Compare.new(
+ source_repository,
+ target_commit_id,
+ source_branch_name,
+ straight: straight
+ )
+ end
end
def write_ref(ref_path, ref, old_ref: nil)
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index da2a81983ec..344dd27589c 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -157,10 +157,10 @@ module Gitlab
# for deploy tokens and builds
def can_download?
deploy_key_can_download_code? ||
- deploy_token_can_download? ||
- build_can_download? ||
- user_can_download? ||
- guest_can_download?
+ deploy_token_can_download? ||
+ build_can_download? ||
+ user_can_download? ||
+ guest_can_download?
end
def check_container!
@@ -339,7 +339,7 @@ module Gitlab
def check_change_access!
if changes == ANY
can_push = deploy_key? ||
- user_can_push? ||
+ user_can_push? ||
project&.any_branch_allows_collaboration?(user_access.user)
unless can_push
diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb
index 6bcf4802fbe..de66ca7305f 100644
--- a/lib/gitlab/gitaly_client/commit_service.rb
+++ b/lib/gitlab/gitaly_client/commit_service.rb
@@ -571,7 +571,7 @@ module Gitlab
end
def encode_repeated(array)
- Google::Protobuf::RepeatedField.new(:bytes, array.map { |s| encode_binary(s) } )
+ Google::Protobuf::RepeatedField.new(:bytes, array.map { |s| encode_binary(s) })
end
def call_find_commit(revision)
diff --git a/lib/gitlab/gitaly_client/namespace_service.rb b/lib/gitlab/gitaly_client/namespace_service.rb
index dbcebec3aa2..05aee2fa55d 100644
--- a/lib/gitlab/gitaly_client/namespace_service.rb
+++ b/lib/gitlab/gitaly_client/namespace_service.rb
@@ -40,6 +40,13 @@ module Gitlab
gitaly_client_call(:rename_namespace, request, timeout: GitalyClient.fast_timeout)
end
+ def exists?(name)
+ request = Gitaly::NamespaceExistsRequest.new(storage_name: @storage, name: name)
+
+ response = gitaly_client_call(:namespace_exists, request, timeout: GitalyClient.fast_timeout)
+ response.exists
+ end
+
private
def gitaly_client_call(type, request, timeout: nil)
diff --git a/lib/gitlab/gitaly_client/operation_service.rb b/lib/gitlab/gitaly_client/operation_service.rb
index 2312def5efc..66f70ed9dc6 100644
--- a/lib/gitlab/gitaly_client/operation_service.rb
+++ b/lib/gitlab/gitaly_client/operation_service.rb
@@ -452,6 +452,14 @@ module Gitlab
when :index_update
raise Gitlab::Git::Index::IndexError, index_error_message(detailed_error.index_update)
else
+ # Some invalid path errors are caught by Gitaly directly and returned
+ # as an :index_update error, while others are found by libgit2 and
+ # come as generic errors. We need to convert the latter as IndexErrors
+ # as well.
+ if e.to_status.details.start_with?('invalid path')
+ raise Gitlab::Git::Index::IndexError, e.to_status.details
+ end
+
raise e
end
end
@@ -600,17 +608,17 @@ module Gitlab
case index_error.error_type
when :ERROR_TYPE_EMPTY_PATH
- "Received empty path"
+ "You must provide a file path"
when :ERROR_TYPE_INVALID_PATH
- "Invalid path: #{encoded_path}"
+ "invalid path: '#{encoded_path}'"
when :ERROR_TYPE_DIRECTORY_EXISTS
- "Directory already exists: #{encoded_path}"
+ "A directory with this name already exists"
when :ERROR_TYPE_DIRECTORY_TRAVERSAL
- "Directory traversal in path escapes repository: #{encoded_path}"
+ "Path cannot include directory traversal"
when :ERROR_TYPE_FILE_EXISTS
- "File already exists: #{encoded_path}"
+ "A file with this name already exists"
when :ERROR_TYPE_FILE_NOT_FOUND
- "File not found: #{encoded_path}"
+ "A file with this name doesn't exist"
else
"Unknown error performing git operation"
end
diff --git a/lib/gitlab/gitaly_client/ref_service.rb b/lib/gitlab/gitaly_client/ref_service.rb
index de76ade76cb..da579276101 100644
--- a/lib/gitlab/gitaly_client/ref_service.rb
+++ b/lib/gitlab/gitaly_client/ref_service.rb
@@ -17,6 +17,8 @@ module Gitlab
'desc' => Gitaly::SortDirection::DESCENDING
}.freeze
+ AMBIGUOUS_REFERENCE = 'reference is ambiguous'
+
# 'repository' is a Gitlab::Git::Repository
def initialize(repository)
@repository = repository
@@ -54,26 +56,6 @@ module Gitlab
Gitlab::Git.branch_name(response.name)
end
- def branch_names
- request = Gitaly::FindAllBranchNamesRequest.new(repository: @gitaly_repo)
- response = gitaly_client_call(@storage, :ref_service, :find_all_branch_names, request, timeout: GitalyClient.fast_timeout)
- consume_refs_response(response) { |name| Gitlab::Git.branch_name(name) }
- end
-
- def tag_names
- request = Gitaly::FindAllTagNamesRequest.new(repository: @gitaly_repo)
- response = gitaly_client_call(@storage, :ref_service, :find_all_tag_names, request, timeout: GitalyClient.fast_timeout)
- consume_refs_response(response) { |name| Gitlab::Git.tag_name(name) }
- end
-
- def count_tag_names
- tag_names.count
- end
-
- def count_branch_names
- branch_names.count
- end
-
def local_branches(sort_by: nil, pagination_params: nil)
request = Gitaly::FindLocalBranchesRequest.new(repository: @gitaly_repo, pagination_params: pagination_params)
request.sort_by = sort_local_branches_by_param(sort_by) if sort_by
@@ -109,6 +91,10 @@ module Gitlab
target_commit = Gitlab::Git::Commit.decorate(@repository, branch.target_commit)
Gitlab::Git::Branch.new(@repository, branch.name.dup, branch.target_commit.id, target_commit)
+ rescue GRPC::BadStatus => e
+ raise e unless e.message.include?(AMBIGUOUS_REFERENCE)
+
+ raise Gitlab::Git::AmbiguousRef, "branch is ambiguous: #{branch_name}"
end
def find_tag(tag_name)
diff --git a/lib/gitlab/gitaly_client/with_feature_flag_actors.rb b/lib/gitlab/gitaly_client/with_feature_flag_actors.rb
index 92fc524b724..3d81292da16 100644
--- a/lib/gitlab/gitaly_client/with_feature_flag_actors.rb
+++ b/lib/gitlab/gitaly_client/with_feature_flag_actors.rb
@@ -16,8 +16,6 @@ module Gitlab
# gitaly_client_call performs Gitaly calls including collected feature flag actors. The actors are retrieved
# from repository actor and memoized. The service must set `self.repository_actor = a_repository` beforehand.
def gitaly_client_call(*args, **kargs)
- return GitalyClient.call(*args, **kargs) unless actors_aware_gitaly_calls?
-
unless repository_actor
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(
Feature::InvalidFeatureFlagError.new("gitaly_client_call called without setting repository_actor")
@@ -34,11 +32,8 @@ module Gitlab
end
end
- # gitaly_feature_flag_actors returns a hash of actors implied from input repository. If actors_aware_gitaly_calls
- # flag is not on, this method returns an empty hash.
+ # gitaly_feature_flag_actors returns a hash of actors implied from input repository.
def gitaly_feature_flag_actors(repository)
- return {} unless actors_aware_gitaly_calls?
-
container = find_repository_container(repository)
{
repository: repository,
@@ -92,10 +87,6 @@ module Gitlab
repository.container
end
end
-
- def actors_aware_gitaly_calls?
- Feature.enabled?(:actors_aware_gitaly_calls)
- end
end
end
end
diff --git a/lib/gitlab/github_gists_import/importer/gist_importer.rb b/lib/gitlab/github_gists_import/importer/gist_importer.rb
new file mode 100644
index 00000000000..a5e87d3cf7d
--- /dev/null
+++ b/lib/gitlab/github_gists_import/importer/gist_importer.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module GithubGistsImport
+ module Importer
+ class GistImporter
+ attr_reader :gist, :user
+
+ FileCountLimitError = Class.new(StandardError)
+
+ # gist - An instance of `Gitlab::GithubGistsImport::Representation::Gist`.
+ def initialize(gist, user_id)
+ @gist = gist
+ @user = User.find(user_id)
+ end
+
+ def execute
+ snippet = build_snippet
+ import_repository(snippet) if snippet.save!
+
+ return ServiceResponse.success unless max_snippet_files_count_exceeded?(snippet)
+
+ fail_and_track(snippet)
+ end
+
+ private
+
+ def build_snippet
+ attrs = {
+ title: gist.truncated_title,
+ visibility_level: gist.visibility_level,
+ content: gist.first_file[:file_content],
+ file_name: gist.first_file[:file_name],
+ author: user,
+ created_at: gist.created_at,
+ updated_at: gist.updated_at
+ }
+
+ PersonalSnippet.new(attrs)
+ end
+
+ def import_repository(snippet)
+ resolved_address = get_resolved_address
+
+ snippet.create_repository
+ snippet.repository.fetch_as_mirror(gist.git_pull_url, forced: true, resolved_address: resolved_address)
+ rescue StandardError
+ remove_snippet_and_repository(snippet)
+
+ raise
+ end
+
+ def get_resolved_address
+ validated_pull_url, host = Gitlab::UrlBlocker.validate!(gist.git_pull_url,
+ schemes: Project::VALID_IMPORT_PROTOCOLS,
+ ports: Project::VALID_IMPORT_PORTS,
+ allow_localhost: allow_local_requests?,
+ allow_local_network: allow_local_requests?)
+
+ host.present? ? validated_pull_url.host.to_s : ''
+ end
+
+ def max_snippet_files_count_exceeded?(snippet)
+ snippet.all_files.size > Snippet.max_file_limit
+ end
+
+ def remove_snippet_and_repository(snippet)
+ snippet.repository.remove if snippet.repository_exists?
+ snippet.destroy
+ end
+
+ def allow_local_requests?
+ Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
+ end
+
+ def fail_and_track(snippet)
+ remove_snippet_and_repository(snippet)
+
+ ServiceResponse.error(message: 'Snippet max file count exceeded').track_exception(as: FileCountLimitError)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/github_gists_import/importer/gists_importer.rb b/lib/gitlab/github_gists_import/importer/gists_importer.rb
new file mode 100644
index 00000000000..08744dbaf5f
--- /dev/null
+++ b/lib/gitlab/github_gists_import/importer/gists_importer.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module GithubGistsImport
+ module Importer
+ class GistsImporter
+ attr_reader :user, :client, :already_imported_cache_key
+
+ ALREADY_IMPORTED_CACHE_KEY = 'github-gists-importer/already-imported/%{user}'
+ RESULT_CONTEXT = Struct.new(:success?, :error, :waiter, :next_attempt_in, keyword_init: true)
+
+ def initialize(user, token)
+ @user = user
+ @client = Gitlab::GithubImport::Client.new(token, parallel: true)
+ @already_imported_cache_key = format(ALREADY_IMPORTED_CACHE_KEY, user: user.id)
+ end
+
+ def execute
+ waiter = spread_parallel_import
+
+ expire_already_imported_cache!
+
+ RESULT_CONTEXT.new(success?: true, waiter: waiter)
+ rescue Gitlab::GithubImport::RateLimitError => e
+ RESULT_CONTEXT.new(success?: false, error: e, next_attempt_in: client.rate_limit_resets_in)
+ rescue StandardError => e
+ RESULT_CONTEXT.new(success?: false, error: e)
+ end
+
+ private
+
+ def spread_parallel_import
+ waiter = JobWaiter.new
+ worker_arguments = fetch_gists_to_import.map { |gist_hash| [user.id, gist_hash, waiter.key] }
+ waiter.jobs_remaining = worker_arguments.size
+
+ schedule_bulk_perform(worker_arguments)
+ waiter
+ end
+
+ def fetch_gists_to_import
+ page_counter = Gitlab::GithubImport::PageCounter.new(user, :gists, 'github-gists-importer')
+ collection = []
+
+ client.each_page(:gists, nil, page: page_counter.current) do |page|
+ next unless page_counter.set(page.number)
+
+ collection += gists_from(page)
+ end
+
+ page_counter.expire!
+
+ collection
+ end
+
+ def gists_from(page)
+ page.objects.each.with_object([]) do |gist, page_collection|
+ gist = gist.to_h
+ next if already_imported?(gist)
+
+ page_collection << ::Gitlab::GithubGistsImport::Representation::Gist.from_api_response(gist).to_hash
+
+ mark_as_imported(gist)
+ end
+ end
+
+ def schedule_bulk_perform(worker_arguments)
+ # rubocop:disable Scalability/BulkPerformWithContext
+ Gitlab::ApplicationContext.with_context(user: user) do
+ Gitlab::GithubGistsImport::ImportGistWorker.bulk_perform_in(
+ 1.second,
+ worker_arguments,
+ batch_size: 1000,
+ batch_delay: 1.minute
+ )
+ end
+ # rubocop:enable Scalability/BulkPerformWithContext
+ end
+
+ def already_imported?(gist)
+ Gitlab::Cache::Import::Caching.set_includes?(already_imported_cache_key, gist[:id])
+ end
+
+ def mark_as_imported(gist)
+ Gitlab::Cache::Import::Caching.set_add(already_imported_cache_key, gist[:id])
+ end
+
+ def expire_already_imported_cache!
+ Gitlab::Cache::Import::Caching
+ .expire(already_imported_cache_key, Gitlab::Cache::Import::Caching::SHORTER_TIMEOUT)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/github_gists_import/representation/gist.rb b/lib/gitlab/github_gists_import/representation/gist.rb
new file mode 100644
index 00000000000..0d309a98f38
--- /dev/null
+++ b/lib/gitlab/github_gists_import/representation/gist.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module GithubGistsImport
+ module Representation
+ class Gist
+ include Gitlab::GithubImport::Representation::ToHash
+ include Gitlab::GithubImport::Representation::ExposeAttribute
+
+ attr_reader :attributes
+
+ expose_attribute :id, :description, :is_public, :created_at, :updated_at, :files, :git_pull_url
+
+ # Builds a gist from a GitHub API response.
+ #
+ # gist - An instance of `Hash` containing the gist
+ # details.
+ def self.from_api_response(gist, additional_data = {})
+ hash = {
+ id: gist[:id],
+ description: gist[:description],
+ is_public: gist[:public],
+ files: gist[:files],
+ git_pull_url: gist[:git_pull_url],
+ created_at: gist[:created_at],
+ updated_at: gist[:updated_at]
+ }
+
+ new(hash)
+ end
+
+ # Builds a new gist using a Hash that was built from a JSON payload.
+ def self.from_json_hash(raw_hash)
+ new(Gitlab::GithubImport::Representation.symbolize_hash(raw_hash))
+ end
+
+ # attributes - A hash containing the raw gist details. The keys of this
+ # Hash (and any nested hashes) must be symbols.
+ def initialize(attributes)
+ @attributes = attributes
+ end
+
+ # Gist description can be an empty string, so we returning nil to use first file
+ # name as a title in such case on snippet creation
+ # Gist description has a limit of 256, while the snippet's title can be up to 255
+ def truncated_title
+ title = description.presence || first_file[:file_name]
+
+ title.truncate(255)
+ end
+
+ def visibility_level
+ is_public ? Gitlab::VisibilityLevel::PUBLIC : Gitlab::VisibilityLevel::PRIVATE
+ end
+
+ def first_file
+ _key, value = files.first
+
+ {
+ file_name: value[:filename],
+ file_content: Gitlab::HTTP.try_get(value[:raw_url])&.body
+ }
+ end
+
+ def github_identifiers
+ { id: id }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/github_gists_import/status.rb b/lib/gitlab/github_gists_import/status.rb
new file mode 100644
index 00000000000..e997eb0bf88
--- /dev/null
+++ b/lib/gitlab/github_gists_import/status.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module GithubGistsImport
+ class Status
+ IMPORT_STATUS_KEY = 'gitlab:github-gists-import:%{user_id}'
+ EXPIRATION_TIME = 24.hours
+
+ def initialize(user_id)
+ @user_id = user_id
+ end
+
+ def start!
+ change_status('started')
+ end
+
+ def fail!
+ change_status('failed')
+ end
+
+ def finish!
+ change_status('finished')
+ end
+
+ def started?
+ Gitlab::Redis::SharedState.with { |redis| redis.get(import_status_key) == 'started' }
+ end
+
+ private
+
+ def change_status(status_name)
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.set(import_status_key, status_name)
+ redis.expire(import_status_key, EXPIRATION_TIME) unless status_name == 'started'
+ end
+ end
+
+ def import_status_key
+ format(IMPORT_STATUS_KEY, user_id: @user_id)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/github_import/bulk_importing.rb b/lib/gitlab/github_import/bulk_importing.rb
index 28a39128ec9..0c91eff1d10 100644
--- a/lib/gitlab/github_import/bulk_importing.rb
+++ b/lib/gitlab/github_import/bulk_importing.rb
@@ -10,25 +10,38 @@ module Gitlab
def initialize(project, client)
@project = project
@client = client
+ @validation_errors = []
end
# Builds and returns an Array of objects to bulk insert into the
- # database.
+ # database and array of validation errors if object is invalid.
#
# enum - An Enumerable that returns the objects to turn into database
# rows.
def build_database_rows(enum)
+ errors = []
rows = enum.each_with_object([]) do |(object, _), result|
- result << build(object) unless already_imported?(object)
+ next if already_imported?(object)
+
+ attrs = build_attributes(object)
+ build_record = model.new(attrs)
+
+ if build_record.invalid?
+ log_error(object[:id], build_record.errors.full_messages)
+ errors << build_record.errors
+ next
+ end
+
+ result << attrs
end
log_and_increment_counter(rows.size, :fetched)
- rows
+ [rows, errors]
end
# Bulk inserts the given rows into the database.
- def bulk_insert(model, rows, batch_size: 100)
+ def bulk_insert(rows, batch_size: 100)
rows.each_slice(batch_size) do |slice|
ApplicationRecord.legacy_bulk_insert(model.table_name, slice) # rubocop:disable Gitlab/BulkInsert
@@ -40,6 +53,23 @@ module Gitlab
raise NotImplementedError
end
+ def bulk_insert_failures(validation_errors)
+ rows = validation_errors.map do |error|
+ correlation_id_value = Labkit::Correlation::CorrelationId.current_or_new_id
+
+ {
+ source: self.class.name,
+ exception_class: 'ActiveRecord::RecordInvalid',
+ exception_message: error.full_messages.first.truncate(255),
+ correlation_id_value: correlation_id_value,
+ retry_count: nil,
+ created_at: Time.zone.now
+ }
+ end
+
+ project.import_failures.insert_all(rows)
+ end
+
private
def log_and_increment_counter(value, operation)
@@ -57,6 +87,16 @@ module Gitlab
value: value
)
end
+
+ def log_error(object_id, messages)
+ Gitlab::Import::Logger.error(
+ import_type: :github,
+ project_id: project.id,
+ importer: self.class.name,
+ message: messages,
+ github_identifier: object_id
+ )
+ end
end
end
end
diff --git a/lib/gitlab/github_import/client.rb b/lib/gitlab/github_import/client.rb
index d6060141bce..065410693e5 100644
--- a/lib/gitlab/github_import/client.rb
+++ b/lib/gitlab/github_import/client.rb
@@ -15,6 +15,7 @@ module Gitlab
# end
class Client
include ::Gitlab::Utils::StrongMemoize
+ include ::Gitlab::GithubImport::Clients::SearchRepos
attr_reader :octokit
@@ -182,19 +183,6 @@ module Gitlab
end
end
- def search_repos_by_name(name, options = {})
- with_retry { octokit.search_repositories(search_query(str: name, type: :name), options).to_h }
- end
-
- def search_query(str:, type:, include_collaborations: true, include_orgs: true)
- query = "#{str} in:#{type} is:public,private user:#{octokit.user.to_h[:login]}"
-
- query = [query, collaborations_subquery].join(' ') if include_collaborations
- query = [query, organizations_subquery].join(' ') if include_orgs
-
- query
- end
-
# Returns `true` if we're still allowed to perform API calls.
# Search API has rate limit of 30, use lowered threshold when search is used.
def requests_remaining?
diff --git a/lib/gitlab/github_import/clients/proxy.rb b/lib/gitlab/github_import/clients/proxy.rb
new file mode 100644
index 00000000000..f6d1c8ed23c
--- /dev/null
+++ b/lib/gitlab/github_import/clients/proxy.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module GithubImport
+ module Clients
+ class Proxy
+ attr_reader :client
+
+ def initialize(access_token, client_options)
+ @client = pick_client(access_token, client_options)
+ end
+
+ def repos(search_text, pagination_options)
+ return { repos: filtered(client.repos, search_text) } if use_legacy?
+
+ if use_graphql?
+ fetch_repos_via_graphql(search_text, pagination_options)
+ else
+ fetch_repos_via_rest(search_text, pagination_options)
+ end
+ end
+
+ private
+
+ def fetch_repos_via_rest(search_text, pagination_options)
+ { repos: client.search_repos_by_name(search_text, pagination_options)[:items] }
+ end
+
+ def fetch_repos_via_graphql(search_text, pagination_options)
+ response = client.search_repos_by_name_graphql(search_text, pagination_options)
+ {
+ repos: response.dig(:data, :search, :nodes),
+ page_info: response.dig(:data, :search, :pageInfo)
+ }
+ end
+
+ def pick_client(access_token, client_options)
+ return Gitlab::GithubImport::Client.new(access_token) unless use_legacy?
+
+ Gitlab::LegacyGithubImport::Client.new(access_token, **client_options)
+ end
+
+ def filtered(collection, search_text)
+ return collection if search_text.blank?
+
+ collection.select { |item| item[:name].to_s.downcase.include?(search_text) }
+ end
+
+ def use_legacy?
+ Feature.disabled?(:remove_legacy_github_client)
+ end
+
+ def use_graphql?
+ Feature.enabled?(:github_client_fetch_repos_via_graphql)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/github_import/clients/search_repos.rb b/lib/gitlab/github_import/clients/search_repos.rb
new file mode 100644
index 00000000000..bcd226087e7
--- /dev/null
+++ b/lib/gitlab/github_import/clients/search_repos.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module GithubImport
+ module Clients
+ module SearchRepos
+ def search_repos_by_name_graphql(name, options = {})
+ with_retry do
+ octokit.post(
+ '/graphql',
+ { query: graphql_search_repos_body(name, options) }.to_json
+ ).to_h
+ end
+ end
+
+ def search_repos_by_name(name, options = {})
+ with_retry do
+ octokit.search_repositories(
+ search_repos_query(str: name, type: :name),
+ options
+ ).to_h
+ end
+ end
+
+ private
+
+ def graphql_search_repos_body(name, options)
+ query = search_repos_query(str: name, type: :name)
+ query = "query: \"#{query}\""
+ first = options[:first].present? ? ", first: #{options[:first]}" : ''
+ after = options[:after].present? ? ", after: \"#{options[:after]}\"" : ''
+ <<-TEXT
+ {
+ search(type: REPOSITORY, #{query}#{first}#{after}) {
+ nodes {
+ __typename
+ ... on Repository {
+ id: databaseId
+ name
+ full_name: nameWithOwner
+ owner { login }
+ }
+ }
+ pageInfo {
+ startCursor
+ endCursor
+ hasNextPage
+ hasPreviousPage
+ }
+ }
+ }
+ TEXT
+ end
+
+ def search_repos_query(str:, type:, include_collaborations: true, include_orgs: true)
+ query = "#{str} in:#{type} is:public,private user:#{octokit.user.to_h[:login]}"
+
+ query = [query, collaborations_subquery].join(' ') if include_collaborations
+ query = [query, organizations_subquery].join(' ') if include_orgs
+
+ query
+ 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 a9f8483d8c3..44ffcd7a1e4 100644
--- a/lib/gitlab/github_import/importer/diff_note_importer.rb
+++ b/lib/gitlab/github_import/importer/diff_note_importer.rb
@@ -18,7 +18,6 @@ module Gitlab
def execute
return if merge_request_id.blank?
- note.project = project
note.merge_request = merge_request
build_author_attributes
@@ -65,7 +64,7 @@ module Gitlab
# To work around this we're using bulk_insert with a single row. This
# allows us to efficiently insert data (even if it's just 1 row)
# without having to use all sorts of hacks to disable callbacks.
- ApplicationRecord.legacy_bulk_insert(LegacyDiffNote.table_name, [{
+ attributes = {
noteable_type: note.noteable_type,
system: false,
type: 'LegacyDiffNote',
@@ -79,7 +78,12 @@ module Gitlab
created_at: note.created_at,
updated_at: note.updated_at,
st_diff: note.diff_hash.to_yaml
- }])
+ }
+
+ diff_note = LegacyDiffNote.new(attributes.merge(importing: true))
+ diff_note.validate!
+
+ ApplicationRecord.legacy_bulk_insert(LegacyDiffNote.table_name, [attributes])
end
# rubocop:enabled Gitlab/BulkInsert
diff --git a/lib/gitlab/github_import/importer/issue_importer.rb b/lib/gitlab/github_import/importer/issue_importer.rb
index d964bae3dd2..b477468d327 100644
--- a/lib/gitlab/github_import/importer/issue_importer.rb
+++ b/lib/gitlab/github_import/importer/issue_importer.rb
@@ -60,6 +60,9 @@ module Gitlab
work_item_type_id: issue.work_item_type_id
}
+ issue = project.issues.new(attributes.merge(importing: true))
+ issue.validate!
+
insert_and_return_id(attributes, project.issues)
rescue ActiveRecord::InvalidForeignKey
# It's possible the project has been deleted since scheduling this
diff --git a/lib/gitlab/github_import/importer/label_links_importer.rb b/lib/gitlab/github_import/importer/label_links_importer.rb
index 5e248c7cfc5..52c87dda347 100644
--- a/lib/gitlab/github_import/importer/label_links_importer.rb
+++ b/lib/gitlab/github_import/importer/label_links_importer.rb
@@ -22,7 +22,7 @@ module Gitlab
def create_labels
time = Time.zone.now
- rows = []
+ items = []
target_id = find_target_id
issue.label_names.each do |label_name|
@@ -31,16 +31,16 @@ module Gitlab
# the project's labels.
next unless (label_id = label_finder.id_for(label_name))
- rows << {
+ items << LabelLink.new(
label_id: label_id,
target_id: target_id,
target_type: issue.issuable_type,
created_at: time,
updated_at: time
- }
+ )
end
- ApplicationRecord.legacy_bulk_insert(LabelLink.table_name, rows) # rubocop:disable Gitlab/BulkInsert
+ LabelLink.bulk_insert!(items)
end
def find_target_id
diff --git a/lib/gitlab/github_import/importer/labels_importer.rb b/lib/gitlab/github_import/importer/labels_importer.rb
index 9a011f17a18..d5d1cd28b7c 100644
--- a/lib/gitlab/github_import/importer/labels_importer.rb
+++ b/lib/gitlab/github_import/importer/labels_importer.rb
@@ -13,7 +13,10 @@ module Gitlab
# rubocop: enable CodeReuse/ActiveRecord
def execute
- bulk_insert(Label, build_labels)
+ rows, validation_errors = build_labels
+
+ bulk_insert(rows)
+ bulk_insert_failures(validation_errors) if validation_errors.any?
build_labels_cache
end
@@ -29,7 +32,7 @@ module Gitlab
LabelFinder.new(project).build_cache
end
- def build(label)
+ def build_attributes(label)
time = Time.zone.now
{
@@ -49,6 +52,10 @@ module Gitlab
def object_type
:label
end
+
+ def model
+ Label
+ end
end
end
end
diff --git a/lib/gitlab/github_import/importer/lfs_objects_importer.rb b/lib/gitlab/github_import/importer/lfs_objects_importer.rb
index 775afd5f53a..d064278e4a0 100644
--- a/lib/gitlab/github_import/importer/lfs_objects_importer.rb
+++ b/lib/gitlab/github_import/importer/lfs_objects_importer.rb
@@ -27,9 +27,9 @@ module Gitlab
end
def each_object_to_import
- lfs_objects = Projects::LfsPointers::LfsObjectDownloadListService.new(project).execute
+ download_service = Projects::LfsPointers::LfsObjectDownloadListService.new(project)
- lfs_objects.each do |object|
+ download_service.each_list_item do |object|
Gitlab::GithubImport::ObjectCounter.increment(project, object_type, :fetched)
yield object
diff --git a/lib/gitlab/github_import/importer/milestones_importer.rb b/lib/gitlab/github_import/importer/milestones_importer.rb
index 1a3a54d0053..560fbdc66e3 100644
--- a/lib/gitlab/github_import/importer/milestones_importer.rb
+++ b/lib/gitlab/github_import/importer/milestones_importer.rb
@@ -13,7 +13,10 @@ module Gitlab
# rubocop: enable CodeReuse/ActiveRecord
def execute
- bulk_insert(Milestone, build_milestones)
+ rows, validation_errors = build_milestones
+
+ bulk_insert(rows)
+ bulk_insert_failures(validation_errors) if validation_errors.any?
build_milestones_cache
end
@@ -29,7 +32,7 @@ module Gitlab
MilestoneFinder.new(project).build_cache
end
- def build(milestone)
+ def build_attributes(milestone)
{
iid: milestone[:number],
title: milestone[:title],
@@ -53,6 +56,10 @@ module Gitlab
def object_type
:milestone
end
+
+ def model
+ Milestone
+ end
end
end
end
diff --git a/lib/gitlab/github_import/importer/note_importer.rb b/lib/gitlab/github_import/importer/note_importer.rb
index 69b7b2c2a38..04da015a33f 100644
--- a/lib/gitlab/github_import/importer/note_importer.rb
+++ b/lib/gitlab/github_import/importer/note_importer.rb
@@ -33,6 +33,9 @@ module Gitlab
updated_at: note.updated_at
}
+ note = Note.new(attributes.merge(importing: true))
+ note.validate!
+
# We're using bulk_insert here so we can bypass any validations and
# callbacks. Running these would result in a lot of unnecessary SQL
# queries being executed when importing large projects.
diff --git a/lib/gitlab/github_import/importer/pull_request_importer.rb b/lib/gitlab/github_import/importer/pull_request_importer.rb
index 3c17ea1195e..5690a2cc997 100644
--- a/lib/gitlab/github_import/importer/pull_request_importer.rb
+++ b/lib/gitlab/github_import/importer/pull_request_importer.rb
@@ -61,6 +61,9 @@ module Gitlab
updated_at: pull_request.updated_at
}
+ mr = project.merge_requests.new(attributes.merge(importing: true))
+ mr.validate!
+
create_merge_request_without_hooks(project, attributes, pull_request.iid)
end
@@ -93,7 +96,7 @@ module Gitlab
return if project.repository.branch_exists?(source_branch)
project.repository.add_branch(project.creator, source_branch, pull_request.source_branch_sha)
- rescue Gitlab::Git::CommandError => e
+ rescue Gitlab::Git::PreReceiveError, Gitlab::Git::CommandError => e
Gitlab::ErrorTracking.track_exception(e,
source_branch: source_branch,
project_id: merge_request.project.id,
diff --git a/lib/gitlab/github_import/importer/pull_request_merged_by_importer.rb b/lib/gitlab/github_import/importer/pull_request_merged_by_importer.rb
index 640914acf4d..f05aa26a449 100644
--- a/lib/gitlab/github_import/importer/pull_request_merged_by_importer.rb
+++ b/lib/gitlab/github_import/importer/pull_request_merged_by_importer.rb
@@ -4,42 +4,62 @@ module Gitlab
module GithubImport
module Importer
class PullRequestMergedByImporter
+ # pull_request - An instance of
+ # `Gitlab::GithubImport::Representation::PullRequest`
+ # project - An instance of `Project`
+ # client - An instance of `Gitlab::GithubImport::Client`
def initialize(pull_request, project, client)
- @project = project
@pull_request = pull_request
+ @project = project
@client = client
end
def execute
- merge_request = project.merge_requests.find_by_iid(pull_request.iid)
- timestamp = Time.new.utc
- merged_at = pull_request.merged_at
user_finder = GithubImport::UserFinder.new(project, client)
- gitlab_user_id = user_finder.user_id_for(pull_request.merged_by)
+ gitlab_user_id = begin
+ user_finder.user_id_for(pull_request.merged_by)
+ rescue ::Octokit::NotFound
+ nil
+ end
+
+ metrics_upsert(gitlab_user_id)
+
+ add_note!
+ end
+
+ private
+
+ attr_reader :project, :pull_request, :client
+
+ def metrics_upsert(gitlab_user_id)
MergeRequest::Metrics.upsert({
target_project_id: project.id,
merge_request_id: merge_request.id,
merged_by_id: gitlab_user_id,
- merged_at: merged_at,
+ merged_at: pull_request.merged_at,
created_at: timestamp,
updated_at: timestamp
}, unique_by: :merge_request_id)
+ end
- unless gitlab_user_id
- merge_request.notes.create!(
- importing: true,
- note: missing_author_note,
- author_id: project.creator_id,
- project: project,
- created_at: merged_at
- )
- end
+ def add_note!
+ merge_request.notes.create!(
+ importing: true,
+ note: missing_author_note,
+ author_id: project.creator_id,
+ project: project,
+ created_at: pull_request.merged_at
+ )
end
- private
+ def merge_request
+ @merge_request ||= project.merge_requests.find_by_iid(pull_request.iid)
+ end
- attr_reader :project, :pull_request, :client
+ def timestamp
+ @timestamp ||= Time.new.utc
+ end
def missing_author_note
s_("GitHubImporter|*Merged by: %{author} at %{timestamp}*") % {
diff --git a/lib/gitlab/github_import/importer/pull_request_review_importer.rb b/lib/gitlab/github_import/importer/pull_request_review_importer.rb
index b11af90aa6f..de66f310edf 100644
--- a/lib/gitlab/github_import/importer/pull_request_review_importer.rb
+++ b/lib/gitlab/github_import/importer/pull_request_review_importer.rb
@@ -4,6 +4,9 @@ module Gitlab
module GithubImport
module Importer
class PullRequestReviewImporter
+ # review - An instance of `Gitlab::GithubImport::Representation::PullRequestReview`
+ # project - An instance of `Project`
+ # client - An instance of `Gitlab::GithubImport::Client`
def initialize(review, project, client)
@review = review
@project = project
@@ -13,7 +16,12 @@ module Gitlab
def execute
user_finder = GithubImport::UserFinder.new(project, client)
- gitlab_user_id = user_finder.user_id_for(review.author)
+
+ gitlab_user_id = begin
+ user_finder.user_id_for(review.author)
+ rescue ::Octokit::NotFound
+ nil
+ end
if gitlab_user_id
add_review_note!(gitlab_user_id)
diff --git a/lib/gitlab/github_import/importer/releases_importer.rb b/lib/gitlab/github_import/importer/releases_importer.rb
index fe6da30bbf8..62d579fda08 100644
--- a/lib/gitlab/github_import/importer/releases_importer.rb
+++ b/lib/gitlab/github_import/importer/releases_importer.rb
@@ -16,7 +16,10 @@ module Gitlab
# to generate HTML version - you also need to regenerate it in
# Gitlab::GithubImport::Importer::NoteAttachmentsImporter.
def execute
- bulk_insert(Release, build_releases)
+ rows, validation_errors = build_releases
+
+ bulk_insert(rows)
+ bulk_insert_failures(validation_errors) if validation_errors.any?
end
def build_releases
@@ -27,7 +30,7 @@ module Gitlab
existing_tags.include?(release[:tag_name]) || release[:tag_name].nil?
end
- def build(release)
+ def build_attributes(release)
existing_tags.add(release[:tag_name])
{
@@ -66,6 +69,10 @@ module Gitlab
def user_finder
@user_finder ||= GithubImport::UserFinder.new(project, client)
end
+
+ def model
+ Release
+ end
end
end
end
diff --git a/lib/gitlab/github_import/markdown/attachment.rb b/lib/gitlab/github_import/markdown/attachment.rb
index a5cf5ffa60e..1c814e34a39 100644
--- a/lib/gitlab/github_import/markdown/attachment.rb
+++ b/lib/gitlab/github_import/markdown/attachment.rb
@@ -28,6 +28,7 @@ module Gitlab
def from_markdown_image(markdown_node)
url = markdown_node.url
+ return unless url
return unless github_url?(url, media: true)
return unless whitelisted_type?(url, media: true)
@@ -37,6 +38,7 @@ module Gitlab
def from_markdown_link(markdown_node)
url = markdown_node.url
+ return unless url
return unless github_url?(url, docs: true)
return unless whitelisted_type?(url, docs: true)
@@ -46,7 +48,7 @@ module Gitlab
def from_inline_html(markdown_node)
img = Nokogiri::HTML.parse(markdown_node.string_content).xpath('//img')[0]
- return unless img
+ return if img.nil? || img[:src].blank?
return unless github_url?(img[:src], media: true)
return unless whitelisted_type?(img[:src], media: true)
diff --git a/lib/gitlab/github_import/page_counter.rb b/lib/gitlab/github_import/page_counter.rb
index 3face4c794b..c238ccb8932 100644
--- a/lib/gitlab/github_import/page_counter.rb
+++ b/lib/gitlab/github_import/page_counter.rb
@@ -9,10 +9,10 @@ module Gitlab
attr_reader :cache_key
# The base cache key to use for storing the last page number.
- CACHE_KEY = 'github-importer/page-counter/%{project}/%{collection}'
+ CACHE_KEY = '%{import_type}/page-counter/%{object}/%{collection}'
- def initialize(project, collection)
- @cache_key = CACHE_KEY % { project: project.id, collection: collection }
+ def initialize(object, collection, import_type = 'github-importer')
+ @cache_key = CACHE_KEY % { import_type: import_type, object: object.id, collection: collection }
end
# Sets the page number to the given value.
diff --git a/lib/gitlab/github_import/representation/diff_note.rb b/lib/gitlab/github_import/representation/diff_note.rb
index f3be90834c7..9259d0295d5 100644
--- a/lib/gitlab/github_import/representation/diff_note.rb
+++ b/lib/gitlab/github_import/representation/diff_note.rb
@@ -4,18 +4,15 @@ module Gitlab
module GithubImport
module Representation
class DiffNote
- include Gitlab::Utils::StrongMemoize
include ToHash
include ExposeAttribute
- NOTEABLE_TYPE = 'MergeRequest'
NOTEABLE_ID_REGEX = %r{/pull/(?<iid>\d+)}i.freeze
- DISCUSSION_CACHE_KEY = 'github-importer/discussion-id-map/%{project_id}/%{noteable_id}/%{original_note_id}'
expose_attribute :noteable_id, :commit_id, :file_path,
:diff_hunk, :author, :created_at, :updated_at,
:original_commit_id, :note_id, :end_line, :start_line,
- :side, :in_reply_to_id
+ :side, :in_reply_to_id, :discussion_id
# Builds a diff note from a GitHub API response.
#
@@ -45,7 +42,8 @@ module Gitlab
end_line: note[:line],
start_line: note[:start_line],
side: note[:side],
- in_reply_to_id: note[:in_reply_to_id]
+ in_reply_to_id: note[:in_reply_to_id],
+ discussion_id: DiffNotes::DiscussionId.new(note).find_or_generate
}
new(hash)
@@ -59,7 +57,7 @@ module Gitlab
new(hash)
end
- attr_accessor :merge_request, :project
+ attr_accessor :merge_request
# attributes - A Hash containing the raw note details. The keys of this
# Hash must be Symbols.
@@ -74,7 +72,7 @@ module Gitlab
end
def noteable_type
- NOTEABLE_TYPE
+ DiffNotes::DiscussionId::NOTEABLE_TYPE
end
def contains_suggestion?
@@ -127,12 +125,6 @@ module Gitlab
}
end
- def discussion_id
- strong_memoize(:discussion_id) do
- (in_reply_to_id.present? && current_discussion_id) || generate_discussion_id
- end
- end
-
private
# Required by ExposeAttribute
@@ -149,32 +141,6 @@ module Gitlab
def addition?
side == 'RIGHT'
end
-
- def generate_discussion_id
- 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
-
- def cache_discussion_id(discussion_id)
- Gitlab::Cache::Import::Caching.write(discussion_id_cache_key(note_id), discussion_id)
- end
-
- def current_discussion_id
- Gitlab::Cache::Import::Caching.read(discussion_id_cache_key(in_reply_to_id))
- end
-
- def discussion_id_cache_key(id)
- DISCUSSION_CACHE_KEY % {
- project_id: project.id,
- noteable_id: merge_request.id,
- original_note_id: id
- }
- end
end
end
end
diff --git a/lib/gitlab/github_import/representation/diff_notes/discussion_id.rb b/lib/gitlab/github_import/representation/diff_notes/discussion_id.rb
new file mode 100644
index 00000000000..38b560f21c0
--- /dev/null
+++ b/lib/gitlab/github_import/representation/diff_notes/discussion_id.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module GithubImport
+ module Representation
+ module DiffNotes
+ class DiscussionId
+ NOTEABLE_TYPE = 'MergeRequest'
+ DISCUSSION_CACHE_REGEX = %r{/(?<repo>[^/]*)/pull/(?<iid>\d+)}i.freeze
+ DISCUSSION_CACHE_KEY = 'github-importer/discussion-id-map/%{project}/%{noteable_id}/%{original_note_id}'
+
+ def initialize(note)
+ @note = note
+ @matches = note[:html_url].match(DISCUSSION_CACHE_REGEX)
+ end
+
+ def find_or_generate
+ (note[:in_reply_to_id].present? && current_discussion_id) || generate_discussion_id
+ end
+
+ private
+
+ attr_reader :note, :matches
+
+ def generate_discussion_id
+ discussion_id = Discussion.discussion_id(
+ Struct
+ .new(:noteable_id, :noteable_type)
+ .new(matches[:iid].to_i, NOTEABLE_TYPE)
+ )
+ cache_discussion_id(discussion_id)
+ end
+
+ def cache_discussion_id(discussion_id)
+ Gitlab::Cache::Import::Caching.write(
+ discussion_id_cache_key(note[:id]), discussion_id
+ )
+ end
+
+ def current_discussion_id
+ Gitlab::Cache::Import::Caching.read(
+ discussion_id_cache_key(note[:in_reply_to_id])
+ )
+ end
+
+ def discussion_id_cache_key(id)
+ format(DISCUSSION_CACHE_KEY,
+ project: matches[:repo],
+ noteable_id: matches[:iid].to_i,
+ original_note_id: id
+ )
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gl_repository/repo_type.rb b/lib/gitlab/gl_repository/repo_type.rb
index 05278b2dd35..7792ef55b28 100644
--- a/lib/gitlab/gl_repository/repo_type.rb
+++ b/lib/gitlab/gl_repository/repo_type.rb
@@ -66,10 +66,10 @@ module Gitlab
def valid?(repository_path)
repository_path.end_with?(path_suffix) &&
- (
- !snippet? ||
- repository_path.match?(Gitlab::PathRegex.full_snippets_repository_path_regex)
- )
+ (
+ !snippet? ||
+ repository_path.match?(Gitlab::PathRegex.full_snippets_repository_path_regex)
+ )
end
private
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index ecb57bfc1a2..12cdcf445f7 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -65,8 +65,10 @@ module Gitlab
push_frontend_feature_flag(:security_auto_fix)
push_frontend_feature_flag(:new_header_search)
push_frontend_feature_flag(:source_editor_toolbar)
+ push_frontend_feature_flag(:vscode_web_ide, current_user)
push_frontend_feature_flag(:integration_slack_app_notifications)
push_frontend_feature_flag(:vue_group_select)
+ push_frontend_feature_flag(:new_fonts, current_user)
end
# Exposes the state of a feature flag to the frontend code.
diff --git a/lib/gitlab/graphql/expose_permissions.rb b/lib/gitlab/graphql/expose_permissions.rb
index ab9ed354673..070d3c188a4 100644
--- a/lib/gitlab/graphql/expose_permissions.rb
+++ b/lib/gitlab/graphql/expose_permissions.rb
@@ -5,11 +5,15 @@ module Gitlab
module ExposePermissions
extend ActiveSupport::Concern
prepended do
- def self.expose_permissions(permission_type, description: 'Permissions for the current user on the resource')
+ def self.expose_permissions(
+ permission_type,
+ description: 'Permissions for the current user on the resource',
+ &block)
field :user_permissions, permission_type,
description: description,
null: false,
- method: :itself
+ method: :itself,
+ &block
end
end
end
diff --git a/lib/gitlab/graphql/extensions/forward_only_externally_paginated_array_extension.rb b/lib/gitlab/graphql/extensions/forward_only_externally_paginated_array_extension.rb
new file mode 100644
index 00000000000..651b4266756
--- /dev/null
+++ b/lib/gitlab/graphql/extensions/forward_only_externally_paginated_array_extension.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+module Gitlab
+ module Graphql
+ module Extensions
+ # This extension is meant for resolvers that only support forward looking pagination. So in order to limit
+ # confusion for allowed GraphQL pagination arguments on the field, we limit this to just `first` and `after`.
+ class ForwardOnlyExternallyPaginatedArrayExtension < ExternallyPaginatedArrayExtension
+ def apply
+ field.argument :after, GraphQL::Types::String,
+ description: "Returns the elements in the list that come after the specified cursor.",
+ required: false
+ field.argument :first, GraphQL::Types::Int,
+ description: "Returns the first _n_ elements from the list.",
+ required: false
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/graphql/limit/field_call_count.rb b/lib/gitlab/graphql/limit/field_call_count.rb
index 4165970a2a6..3a02e8abbb5 100644
--- a/lib/gitlab/graphql/limit/field_call_count.rb
+++ b/lib/gitlab/graphql/limit/field_call_count.rb
@@ -14,9 +14,18 @@ module Gitlab
private
def increment_call_count(context)
+ query_id = fetch_query_id(context)
+
context[:call_count] ||= {}
- context[:call_count][field] ||= 0
- context[:call_count][field] += 1
+ context[:call_count][query_id] ||= {}
+ context[:call_count][query_id][field] ||= 0
+ context[:call_count][query_id][field] += 1
+ end
+
+ def fetch_query_id(context)
+ context.query.operation_fingerprint
+ rescue TypeError
+ ''
end
def limit
diff --git a/lib/gitlab/graphql/pagination/keyset/connection.rb b/lib/gitlab/graphql/pagination/keyset/connection.rb
index eca4d42fb9a..208ca5f2d24 100644
--- a/lib/gitlab/graphql/pagination/keyset/connection.rb
+++ b/lib/gitlab/graphql/pagination/keyset/connection.rb
@@ -59,16 +59,7 @@ module Gitlab
if before
true
elsif first
- if Feature.enabled?(:graphql_keyset_pagination_without_next_page_query)
- limited_nodes.size > limit_value
- else
- case sliced_nodes
- when Array
- sliced_nodes.size > limit_value
- else
- sliced_nodes.limit(1).offset(limit_value).exists? # rubocop: disable CodeReuse/ActiveRecord
- end
- end
+ limited_nodes.size > limit_value
else
false
end
@@ -126,15 +117,9 @@ module Gitlab
@has_previous_page = paginated_nodes.count > limit_value
@has_previous_page ? paginated_nodes.last(limit_value) : paginated_nodes
elsif loaded?(sliced_nodes)
- if Feature.enabled?(:graphql_keyset_pagination_without_next_page_query)
- sliced_nodes.take(limit_value + 1) # rubocop: disable CodeReuse/ActiveRecord
- else
- sliced_nodes.take(limit_value) # rubocop: disable CodeReuse/ActiveRecord
- end
- elsif Feature.enabled?(:graphql_keyset_pagination_without_next_page_query)
- sliced_nodes.limit(limit_value + 1).to_a
+ sliced_nodes.take(limit_value + 1) # rubocop: disable CodeReuse/ActiveRecord
else
- sliced_nodes.limit(limit_value)
+ sliced_nodes.limit(limit_value + 1).to_a
end
end
end
diff --git a/lib/gitlab/group_search_results.rb b/lib/gitlab/group_search_results.rb
index 4eea96f8344..b112740c4ad 100644
--- a/lib/gitlab/group_search_results.rb
+++ b/lib/gitlab/group_search_results.rb
@@ -12,23 +12,11 @@ module Gitlab
# rubocop:disable CodeReuse/ActiveRecord
def users
- # get all groups the current user has access to
- # ignore order inherited from GroupsFinder to improve performance
- current_user_groups = GroupsFinder.new(current_user).execute.unscope(:order)
+ groups = group.self_and_hierarchy_intersecting_with_user_groups(current_user)
+ members = GroupMember.where(group: groups).non_invite
- # the hierarchy of the current group
- group_groups = @group.self_and_hierarchy.unscope(:order)
-
- # the groups where the above hierarchies intersect
- intersect_groups = group_groups.where(id: current_user_groups)
-
- # members of @group hierarchy where the user has access to the groups
- members = GroupMember.where(group: intersect_groups).non_invite
-
- # get all users the current user has access to (-> `SearchResults#users`), which also applies the query
users = super
- # filter users that belong to the previously selected groups
users.where(id: members.select(:user_id))
end
# rubocop:enable CodeReuse/ActiveRecord
diff --git a/lib/gitlab/http.rb b/lib/gitlab/http.rb
index 567c4dc899f..b05767c7ed4 100644
--- a/lib/gitlab/http.rb
+++ b/lib/gitlab/http.rb
@@ -59,7 +59,7 @@ module Gitlab
raise ReadTotalTimeout, "Request timed out after #{elapsed} seconds"
end
- block.call fragment if block
+ yield fragment if block
end
rescue HTTParty::RedirectionTooDeep
raise RedirectionTooDeep
diff --git a/lib/gitlab/http_connection_adapter.rb b/lib/gitlab/http_connection_adapter.rb
index 7b1657d3854..3ef60be67a9 100644
--- a/lib/gitlab/http_connection_adapter.rb
+++ b/lib/gitlab/http_connection_adapter.rb
@@ -44,7 +44,8 @@ module Gitlab
Gitlab::UrlBlocker.validate!(url, allow_local_network: allow_local_requests?,
allow_localhost: allow_local_requests?,
allow_object_storage: allow_object_storage?,
- dns_rebind_protection: dns_rebind_protection?)
+ dns_rebind_protection: dns_rebind_protection?,
+ schemes: %w[http https])
rescue Gitlab::UrlBlocker::BlockedUrlError => e
raise Gitlab::HTTP::BlockedUrlError, "URL '#{url}' is blocked: #{e.message}"
end
diff --git a/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb
index a42cac61a55..7a42ffca779 100644
--- a/lib/gitlab/i18n.rb
+++ b/lib/gitlab/i18n.rb
@@ -50,30 +50,30 @@ module Gitlab
'eo' => 0,
'es' => 35,
'fil_PH' => 0,
- 'fr' => 85,
+ 'fr' => 94,
'gl_ES' => 0,
'id_ID' => 0,
'it' => 1,
'ja' => 30,
- 'ko' => 21,
- 'nb_NO' => 25,
+ 'ko' => 20,
+ 'nb_NO' => 24,
'nl_NL' => 0,
'pl_PL' => 3,
- 'pt_BR' => 58,
- 'ro_RO' => 98,
- 'ru' => 25,
+ 'pt_BR' => 57,
+ 'ro_RO' => 96,
+ 'ru' => 26,
'si_LK' => 11,
'tr_TR' => 11,
'uk' => 52,
- 'zh_CN' => 98,
+ 'zh_CN' => 97,
'zh_HK' => 1,
- 'zh_TW' => 100
+ 'zh_TW' => 99
}.freeze
private_constant :TRANSLATION_LEVELS
- def selectable_locales
+ def selectable_locales(minimum_translation_level = MINIMUM_TRANSLATION_LEVEL)
AVAILABLE_LANGUAGES.reject do |code, _name|
- percentage_translated_for(code) < MINIMUM_TRANSLATION_LEVEL
+ percentage_translated_for(code) < minimum_translation_level
end
end
diff --git a/lib/gitlab/import_export/base/relation_factory.rb b/lib/gitlab/import_export/base/relation_factory.rb
index b05d9cb2489..d1fd45882d3 100644
--- a/lib/gitlab/import_export/base/relation_factory.rb
+++ b/lib/gitlab/import_export/base/relation_factory.rb
@@ -211,23 +211,21 @@ module Gitlab
def existing_or_new_object
# Only find existing records to avoid mapping tables such as milestones
# Otherwise always create the record, skipping the extra SELECT clause.
- @existing_or_new_object ||= begin
- if existing_object?
- attribute_hash = attribute_hash_for(['events'])
-
- existing_object.assign_attributes(attribute_hash) if attribute_hash.any?
-
- existing_object
- else
- # Because of single-type inheritance, we need to be careful to use the `type` field
- # See https://gitlab.com/gitlab-org/gitlab/issues/34860#note_235321497
- inheritance_column = relation_class.try(:inheritance_column)
- inheritance_attributes = parsed_relation_hash.slice(inheritance_column)
- object = relation_class.new(inheritance_attributes)
- object.assign_attributes(parsed_relation_hash)
- object
- end
- end
+ @existing_or_new_object ||= if existing_object?
+ attribute_hash = attribute_hash_for(['events'])
+
+ existing_object.assign_attributes(attribute_hash) if attribute_hash.any?
+
+ existing_object
+ else
+ # Because of single-type inheritance, we need to be careful to use the `type` field
+ # See https://gitlab.com/gitlab-org/gitlab/issues/34860#note_235321497
+ inheritance_column = relation_class.try(:inheritance_column)
+ inheritance_attributes = parsed_relation_hash.slice(inheritance_column)
+ object = relation_class.new(inheritance_attributes)
+ object.assign_attributes(parsed_relation_hash)
+ object
+ end
end
def attribute_hash_for(attributes)
diff --git a/lib/gitlab/import_export/decompressed_archive_size_validator.rb b/lib/gitlab/import_export/decompressed_archive_size_validator.rb
index aa66fe8a5ae..564008e7a73 100644
--- a/lib/gitlab/import_export/decompressed_archive_size_validator.rb
+++ b/lib/gitlab/import_export/decompressed_archive_size_validator.rb
@@ -35,7 +35,7 @@ module Gitlab
Timeout.timeout(TIMEOUT_LIMIT) do
stderr_r, stderr_w = IO.pipe
- stdout, wait_threads = Open3.pipeline_r(*command, pgroup: true, err: stderr_w )
+ stdout, wait_threads = Open3.pipeline_r(*command, pgroup: true, err: stderr_w)
# When validation is performed on a small archive (e.g. 100 bytes)
# `wait_thr` finishes before we can get process group id. Do not
diff --git a/lib/gitlab/import_export/group/import_export.yml b/lib/gitlab/import_export/group/import_export.yml
index a08efdf400b..7f3254be3e8 100644
--- a/lib/gitlab/import_export/group/import_export.yml
+++ b/lib/gitlab/import_export/group/import_export.yml
@@ -132,3 +132,23 @@ ee:
- :push_event_payload
- iterations_cadences:
- :iterations
+
+# When associated resources are from outside the group, you might need to
+# validate that a user who is exporting the group can access these
+# associations. `include_if_exportable` accepts an array of associations for a
+# resource. During export, the `exportable_association?` method on the
+# resource is called with the association's name and user to validate if
+# associated resource can be included in the export.
+#
+# This definition will call epic's `exportable_association?(:parent,
+# current_user: current_user)` method and include epic's parent association
+# for each epic only if the method returns true:
+#
+# include_if_exportable:
+# group:
+# epics:
+# - :parent
+include_if_exportable:
+ group:
+ epics:
+ - :parent
diff --git a/lib/gitlab/import_export/json/legacy_reader.rb b/lib/gitlab/import_export/json/legacy_reader.rb
index dc80c92f507..ee360020556 100644
--- a/lib/gitlab/import_export/json/legacy_reader.rb
+++ b/lib/gitlab/import_export/json/legacy_reader.rb
@@ -27,7 +27,7 @@ module Gitlab
end
def read_hash
- Gitlab::Json.parse(IO.read(@path))
+ Gitlab::Json.parse(::File.read(@path))
rescue StandardError => e
Gitlab::ErrorTracking.log_exception(e)
raise Gitlab::ImportExport::Error, 'Incorrect JSON format'
diff --git a/lib/gitlab/import_export/lfs_restorer.rb b/lib/gitlab/import_export/lfs_restorer.rb
index 9931b09e9ca..83aab6d031e 100644
--- a/lib/gitlab/import_export/lfs_restorer.rb
+++ b/lib/gitlab/import_export/lfs_restorer.rb
@@ -71,7 +71,7 @@ module Gitlab
@lfs_json ||=
begin
- json = IO.read(lfs_json_path)
+ json = File.read(lfs_json_path)
Gitlab::Json.parse(json)
rescue StandardError
raise Gitlab::ImportExport::Error, 'Incorrect JSON format'
diff --git a/lib/gitlab/import_export/members_mapper.rb b/lib/gitlab/import_export/members_mapper.rb
index c94549a2b3f..0ad19f82e71 100644
--- a/lib/gitlab/import_export/members_mapper.rb
+++ b/lib/gitlab/import_export/members_mapper.rb
@@ -15,15 +15,13 @@ module Gitlab
def map
@map ||=
- begin
- @exported_members.each_with_object(missing_keys_tracking_hash) do |member, hash|
- if member['user']
- old_user_id = member['user']['id']
- existing_user_id = existing_users_email_map[get_email(member)]
- hash[old_user_id] = existing_user_id if existing_user_id && add_team_member(member, existing_user_id)
- else
- add_team_member(member)
- end
+ @exported_members.each_with_object(missing_keys_tracking_hash) do |member, hash|
+ if member['user']
+ old_user_id = member['user']['id']
+ existing_user_id = existing_users_email_map[get_email(member)]
+ hash[old_user_id] = existing_user_id if existing_user_id && add_team_member(member, existing_user_id)
+ else
+ add_team_member(member)
end
end
end
diff --git a/lib/gitlab/import_export/project/import_export.yml b/lib/gitlab/import_export/project/import_export.yml
index 2d9c8d1108e..cc69ed55744 100644
--- a/lib/gitlab/import_export/project/import_export.yml
+++ b/lib/gitlab/import_export/project/import_export.yml
@@ -763,6 +763,19 @@ excluded_attributes:
- :import_type
- :import_source
- :integrations
+ - :push_hooks_integrations
+ - :tag_push_hooks_integrations
+ - :issue_hooks_integrations
+ - :confidential_issue_hooks_integrations
+ - :merge_request_hooks_integrations
+ - :note_hooks_integrations
+ - :confidential_note_hooks_integrations
+ - :job_hooks_integrations
+ - :archive_trace_hooks_integrations
+ - :pipeline_hooks_integrations
+ - :wiki_page_hooks_integrations
+ - :deployment_hooks_integrations
+ - :alert_hooks_integrations
- :mirror
- :runners_token
- :runners_token_encrypted
@@ -1209,7 +1222,9 @@ ee:
- :description
iterations_cadence:
- :title
-
+ excluded_attributes:
+ project:
+ - :vulnerability_hooks_integrations
preloads:
issues:
epic:
diff --git a/lib/gitlab/import_export/project/tree_saver.rb b/lib/gitlab/import_export/project/tree_saver.rb
index bd34cd3ff6e..05b96f7e8ce 100644
--- a/lib/gitlab/import_export/project/tree_saver.rb
+++ b/lib/gitlab/import_export/project/tree_saver.rb
@@ -81,15 +81,13 @@ module Gitlab
end
def json_writer
- @json_writer ||= begin
- if ::Feature.enabled?(:project_export_as_ndjson, @project.namespace)
- full_path = File.join(@shared.export_path, 'tree')
- Gitlab::ImportExport::Json::NdjsonWriter.new(full_path)
- else
- full_path = File.join(@shared.export_path, ImportExport.project_filename)
- Gitlab::ImportExport::Json::LegacyWriter.new(full_path, allowed_path: 'project')
- end
- end
+ @json_writer ||= if ::Feature.enabled?(:project_export_as_ndjson, @project.namespace)
+ full_path = File.join(@shared.export_path, 'tree')
+ Gitlab::ImportExport::Json::NdjsonWriter.new(full_path)
+ else
+ full_path = File.join(@shared.export_path, ImportExport.project_filename)
+ Gitlab::ImportExport::Json::LegacyWriter.new(full_path, allowed_path: 'project')
+ end
end
end
end
diff --git a/lib/gitlab/import_export/remote_stream_upload.rb b/lib/gitlab/import_export/remote_stream_upload.rb
index f3bd241c0bd..1fb3faf0767 100644
--- a/lib/gitlab/import_export/remote_stream_upload.rb
+++ b/lib/gitlab/import_export/remote_stream_upload.rb
@@ -25,6 +25,7 @@ module Gitlab
end
end
end
+
class StreamError < StandardError
attr_reader :response_body
@@ -33,6 +34,7 @@ module Gitlab
@response_body = response_body
end
end
+
class ChunkStream
DEFAULT_BUFFER_SIZE = 128.kilobytes
diff --git a/lib/gitlab/import_export/repo_restorer.rb b/lib/gitlab/import_export/repo_restorer.rb
index 1c6629cf942..cc214d730fe 100644
--- a/lib/gitlab/import_export/repo_restorer.rb
+++ b/lib/gitlab/import_export/repo_restorer.rb
@@ -46,6 +46,11 @@ module Gitlab
)
Repositories::DestroyService.new(repository).execute
+
+ # Because Gitlab::Git::Repository#remove happens inside a run_after_commit
+ # callback in the Repositories::DestroyService#execute we need to trigger
+ # the callback.
+ repository.project.touch
end
end
end
diff --git a/lib/gitlab/import_sources.rb b/lib/gitlab/import_sources.rb
index 5d3a6b0c6e1..a94ea6f595b 100644
--- a/lib/gitlab/import_sources.rb
+++ b/lib/gitlab/import_sources.rb
@@ -15,7 +15,6 @@ module Gitlab
ImportSource.new('bitbucket', 'Bitbucket Cloud', Gitlab::BitbucketImport::Importer),
ImportSource.new('bitbucket_server', 'Bitbucket Server', Gitlab::BitbucketServerImport::Importer),
ImportSource.new('gitlab', 'GitLab.com', Gitlab::GitlabImport::Importer),
- ImportSource.new('google_code', 'Google Code', nil),
ImportSource.new('fogbugz', 'FogBugz', Gitlab::FogbugzImport::Importer),
ImportSource.new('git', 'Repository by URL', nil),
ImportSource.new('gitlab_project', 'GitLab export', Gitlab::ImportExport::Importer),
diff --git a/lib/gitlab/incident_management/pager_duty/incident_issue_description.rb b/lib/gitlab/incident_management/pager_duty/incident_issue_description.rb
index 6aeeb1d31aa..cbc4f126293 100644
--- a/lib/gitlab/incident_management/pager_duty/incident_issue_description.rb
+++ b/lib/gitlab/incident_management/pager_duty/incident_issue_description.rb
@@ -19,7 +19,7 @@ module Gitlab
"**Incident key:** #{incident_payload['incident_key']}",
"**Created at:** #{markdown_incident_created_at}",
"**Assignees:** #{markdown_assignees.join(', ')}",
- "**Impacted services:** #{markdown_impacted_services.join(', ')}"
+ "**Impacted service:** #{markdown_impacted_service}"
].join(markdown_line_break)
end
@@ -47,10 +47,9 @@ module Gitlab
end
end
- def markdown_impacted_services
- Array(incident_payload['impacted_services']).map do |is|
- markdown_link(is['summary'], is['url'])
- end
+ def markdown_impacted_service
+ service = incident_payload['impacted_service']
+ markdown_link(service['summary'], service['url']) unless service.nil?
end
def markdown_link(label, url)
diff --git a/lib/gitlab/instrumentation/redis.rb b/lib/gitlab/instrumentation/redis.rb
index a371930621d..a664656c467 100644
--- a/lib/gitlab/instrumentation/redis.rb
+++ b/lib/gitlab/instrumentation/redis.rb
@@ -39,7 +39,8 @@ module Gitlab
end
end
- %i[get_request_count query_time read_bytes write_bytes].each do |method|
+ %i[get_request_count get_cross_slot_request_count get_allowed_cross_slot_request_count query_time read_bytes
+ write_bytes].each do |method|
define_method method do
STORAGES.sum(&method)
end
diff --git a/lib/gitlab/instrumentation/redis_base.rb b/lib/gitlab/instrumentation/redis_base.rb
index 268c6cdf459..de24132a28e 100644
--- a/lib/gitlab/instrumentation/redis_base.rb
+++ b/lib/gitlab/instrumentation/redis_base.rb
@@ -45,6 +45,16 @@ module Gitlab
::RequestStore[write_bytes_key] += num_bytes
end
+ def increment_cross_slot_request_count(amount = 1)
+ ::RequestStore[cross_slots_key] ||= 0
+ ::RequestStore[cross_slots_key] += amount
+ end
+
+ def increment_allowed_cross_slot_request_count(amount = 1)
+ ::RequestStore[allowed_cross_slots_key] ||= 0
+ ::RequestStore[allowed_cross_slots_key] += amount
+ end
+
def get_request_count
::RequestStore[request_count_key] || 0
end
@@ -61,13 +71,32 @@ module Gitlab
::RequestStore[call_details_key] ||= []
end
+ def get_cross_slot_request_count
+ ::RequestStore[cross_slots_key] || 0
+ end
+
+ def get_allowed_cross_slot_request_count
+ ::RequestStore[allowed_cross_slots_key] || 0
+ end
+
def query_time
query_time = ::RequestStore[call_duration_key] || 0
query_time.round(::Gitlab::InstrumentationHelper::DURATION_PRECISION)
end
def redis_cluster_validate!(commands)
- ::Gitlab::Instrumentation::RedisClusterValidator.validate!(commands) if @redis_cluster_validation
+ return true unless @redis_cluster_validation
+
+ result = ::Gitlab::Instrumentation::RedisClusterValidator.validate(commands)
+ return true if result.nil?
+
+ if !result[:valid] && !result[:allowed] && (Rails.env.development? || Rails.env.test?)
+ raise RedisClusterValidator::CrossSlotError, "Redis command #{result[:command_name]} arguments hash to different slots. See https://docs.gitlab.com/ee/development/redis.html#multi-key-commands"
+ end
+
+ increment_allowed_cross_slot_request_count if result[:allowed]
+
+ result[:valid]
end
def enable_redis_cluster_validation
@@ -122,6 +151,14 @@ module Gitlab
strong_memoize(:call_details_key) { build_key(:redis_call_details) }
end
+ def cross_slots_key
+ strong_memoize(:cross_slots_key) { build_key(:redis_cross_slot_request_count) }
+ end
+
+ def allowed_cross_slots_key
+ strong_memoize(:allowed_cross_slots_key) { build_key(:redis_allowed_cross_slot_request_count) }
+ end
+
def build_key(namespace)
"#{storage_key}_#{namespace}"
end
diff --git a/lib/gitlab/instrumentation/redis_cluster_validator.rb b/lib/gitlab/instrumentation/redis_cluster_validator.rb
index 36d3e088956..1567e54d8da 100644
--- a/lib/gitlab/instrumentation/redis_cluster_validator.rb
+++ b/lib/gitlab/instrumentation/redis_cluster_validator.rb
@@ -183,19 +183,22 @@ module Gitlab
CrossSlotError = Class.new(StandardError)
class << self
- def validate!(commands)
- return unless Rails.env.development? || Rails.env.test?
- return if allow_cross_slot_commands?
+ def validate(commands)
return if commands.empty?
# early exit for single-command (non-pipelined) if it is a single-key-command
command_name = commands.size > 1 ? "PIPELINE/MULTI" : commands.first.first.to_s.upcase
return if commands.size == 1 && REDIS_COMMANDS.dig(command_name, :single_key)
- key_slots = commands.map { |command| key_slots(command) }.flatten
- if key_slots.uniq.many? # rubocop: disable CodeReuse/ActiveRecord
- raise CrossSlotError, "Redis command #{command_name} arguments hash to different slots. See https://docs.gitlab.com/ee/development/redis.html#multi-key-commands"
- end
+ keys = commands.map { |command| extract_keys(command) }.flatten
+
+ {
+ # calculate key-slots only if not allowed
+ valid: allow_cross_slot_commands? || !has_cross_slot_keys?(keys),
+ command_name: command_name,
+ key_count: keys.size,
+ allowed: allow_cross_slot_commands?
+ }
end
# Keep track of the call stack to allow nested calls to work.
@@ -210,15 +213,17 @@ module Gitlab
private
- def key_slots(command)
+ def extract_keys(command)
argument_positions = REDIS_COMMANDS[command.first.to_s.upcase]
return [] unless argument_positions
arguments = command.flatten[argument_positions[:first]..argument_positions[:last]]
- arguments.each_slice(argument_positions[:step]).map do |args|
- key_slot(args.first)
- end
+ arguments.each_slice(argument_positions[:step]).map(&:first)
+ end
+
+ def has_cross_slot_keys?(keys)
+ keys.map { |key| key_slot(key) }.uniq.many? # rubocop: disable CodeReuse/ActiveRecord
end
def allow_cross_slot_commands?
diff --git a/lib/gitlab/instrumentation/redis_interceptor.rb b/lib/gitlab/instrumentation/redis_interceptor.rb
index f19279df2fe..35dd7cbfeb8 100644
--- a/lib/gitlab/instrumentation/redis_interceptor.rb
+++ b/lib/gitlab/instrumentation/redis_interceptor.rb
@@ -33,7 +33,10 @@ module Gitlab
def instrument_call(commands)
start = Gitlab::Metrics::System.monotonic_time # must come first so that 'start' is always defined
instrumentation_class.instance_count_request(commands.size)
- instrumentation_class.redis_cluster_validate!(commands)
+
+ if !instrumentation_class.redis_cluster_validate!(commands) && ::RequestStore.active?
+ instrumentation_class.increment_cross_slot_request_count
+ end
yield
rescue ::Redis::BaseError => ex
@@ -62,13 +65,11 @@ module Gitlab
# This count is an approximation that omits the Redis protocol overhead
# of type prefixes, length prefixes and line endings.
command.each do |x|
- size += begin
- if x.is_a? Array
- x.inject(0) { |sum, y| sum + y.to_s.bytesize }
- else
- x.to_s.bytesize
- end
- end
+ size += if x.is_a? Array
+ x.inject(0) { |sum, y| sum + y.to_s.bytesize }
+ else
+ x.to_s.bytesize
+ end
end
instrumentation_class.increment_write_bytes(size)
diff --git a/lib/gitlab/instrumentation/redis_payload.rb b/lib/gitlab/instrumentation/redis_payload.rb
index 86a6525c8d0..62a4d1a846f 100644
--- a/lib/gitlab/instrumentation/redis_payload.rb
+++ b/lib/gitlab/instrumentation/redis_payload.rb
@@ -20,6 +20,8 @@ module Gitlab
{
"#{key_prefix}_calls": -> { get_request_count },
+ "#{key_prefix}_cross_slot_calls": -> { get_cross_slot_request_count },
+ "#{key_prefix}_allowed_cross_slot_calls": -> { get_allowed_cross_slot_request_count },
"#{key_prefix}_duration_s": -> { query_time },
"#{key_prefix}_read_bytes": -> { read_bytes },
"#{key_prefix}_write_bytes": -> { write_bytes }
diff --git a/lib/gitlab/instrumentation_helper.rb b/lib/gitlab/instrumentation_helper.rb
index b8a2567b775..15a760fada0 100644
--- a/lib/gitlab/instrumentation_helper.rb
+++ b/lib/gitlab/instrumentation_helper.rb
@@ -35,6 +35,7 @@ module Gitlab
instrument_uploads(payload)
instrument_rate_limiting_gates(payload)
instrument_global_search_api(payload)
+ instrument_ldap(payload)
end
def instrument_gitaly(payload)
@@ -136,6 +137,14 @@ module Gitlab
payload.merge!(::Gitlab::Instrumentation::GlobalSearchApi.payload)
end
+ def instrument_ldap(payload)
+ ldap_count = Gitlab::Metrics::Subscribers::Ldap.count
+
+ return if ldap_count == 0
+
+ payload.merge! Gitlab::Metrics::Subscribers::Ldap.payload
+ end
+
# Returns the queuing duration for a Sidekiq job in seconds, as a float, if the
# `enqueued_at` field or `created_at` field is available.
#
diff --git a/lib/gitlab/issuable_metadata.rb b/lib/gitlab/issuable_metadata.rb
index d0702fb5c7d..12ec6447251 100644
--- a/lib/gitlab/issuable_metadata.rb
+++ b/lib/gitlab/issuable_metadata.rb
@@ -27,8 +27,8 @@ module Gitlab
def data
return {} if issuable_ids.empty?
- issuable_ids.each_with_object({}) do |id, issuable_meta|
- issuable_meta[id] = metadata_for_issuable(id)
+ issuable_ids.index_with do |id|
+ metadata_for_issuable(id)
end
end
diff --git a/lib/gitlab/jira/http_client.rb b/lib/gitlab/jira/http_client.rb
index 02b0c902a70..7abfe8e38e8 100644
--- a/lib/gitlab/jira/http_client.rb
+++ b/lib/gitlab/jira/http_client.rb
@@ -35,12 +35,6 @@ module Gitlab
request_params[:base_uri] = uri.to_s
request_params.merge!(auth_params)
- if Feature.enabled?(:jira_raise_timeouts, type: :ops)
- request_params[:open_timeout] = 2.minutes
- request_params[:read_timeout] = 2.minutes
- request_params[:write_timeout] = 2.minutes
- end
-
result = Gitlab::HTTP.public_send(http_method, path, **request_params) # rubocop:disable GitlabSecurity/PublicSend
@authenticated = result.response.is_a?(Net::HTTPOK)
store_cookies(result) if options[:use_cookies]
diff --git a/lib/gitlab/jira_import/issues_importer.rb b/lib/gitlab/jira_import/issues_importer.rb
index 5057317ae01..7b031c26b72 100644
--- a/lib/gitlab/jira_import/issues_importer.rb
+++ b/lib/gitlab/jira_import/issues_importer.rb
@@ -16,7 +16,7 @@ module Gitlab
@start_at = Gitlab::JiraImport.get_issues_next_start_at(project.id)
@imported_items_cache_key = JiraImport.already_imported_cache_key(:issues, project.id)
@job_waiter = JobWaiter.new
- @issue_type_id = WorkItems::Type.default_issue_type.id
+ @issue_type_id = ::WorkItems::Type.default_issue_type.id
end
def execute
diff --git a/lib/gitlab/jwt_authenticatable.rb b/lib/gitlab/jwt_authenticatable.rb
index 08d9f69497e..7c36bbf3426 100644
--- a/lib/gitlab/jwt_authenticatable.rb
+++ b/lib/gitlab/jwt_authenticatable.rb
@@ -3,7 +3,7 @@
module Gitlab
module JwtAuthenticatable
# Supposedly the effective key size for HMAC-SHA256 is 256 bits, i.e. 32
- # bytes https://tools.ietf.org/html/rfc4868#section-2.6
+ # bytes https://www.rfc-editor.org/rfc/rfc4868#section-2.6
SECRET_LENGTH = 32
def self.included(base)
diff --git a/lib/gitlab/jwt_token.rb b/lib/gitlab/jwt_token.rb
index 11bc5479b6e..83aa7fa4a15 100644
--- a/lib/gitlab/jwt_token.rb
+++ b/lib/gitlab/jwt_token.rb
@@ -42,7 +42,7 @@ module Gitlab
def ==(other)
self.id == other.id &&
- self.payload == other.payload
+ self.payload == other.payload
end
def issued_at=(value)
diff --git a/lib/gitlab/kubernetes/helm/v2/install_command.rb b/lib/gitlab/kubernetes/helm/v2/install_command.rb
index 10e16723e45..c50db6bf177 100644
--- a/lib/gitlab/kubernetes/helm/v2/install_command.rb
+++ b/lib/gitlab/kubernetes/helm/v2/install_command.rb
@@ -36,13 +36,13 @@ module Gitlab
# installation and uprade of applications
def install_command
command = ['helm', 'upgrade', name, chart] +
- install_flag +
- rollback_support_flag +
- reset_values_flag +
- optional_version_flag +
- rbac_create_flag +
- namespace_flag +
- value_flag
+ install_flag +
+ rollback_support_flag +
+ reset_values_flag +
+ optional_version_flag +
+ rbac_create_flag +
+ namespace_flag +
+ value_flag
command.shelljoin
end
diff --git a/lib/gitlab/kubernetes/helm/v2/patch_command.rb b/lib/gitlab/kubernetes/helm/v2/patch_command.rb
index 2855e6444b1..40e56771e47 100644
--- a/lib/gitlab/kubernetes/helm/v2/patch_command.rb
+++ b/lib/gitlab/kubernetes/helm/v2/patch_command.rb
@@ -37,10 +37,10 @@ module Gitlab
def upgrade_command
command = ['helm', 'upgrade', name, chart] +
- reuse_values_flag +
- version_flag +
- namespace_flag +
- value_flag
+ reuse_values_flag +
+ version_flag +
+ namespace_flag +
+ value_flag
command.shelljoin
end
diff --git a/lib/gitlab/kubernetes/helm/v3/install_command.rb b/lib/gitlab/kubernetes/helm/v3/install_command.rb
index 20d17f49115..8d521f0dcd4 100644
--- a/lib/gitlab/kubernetes/helm/v3/install_command.rb
+++ b/lib/gitlab/kubernetes/helm/v3/install_command.rb
@@ -33,13 +33,13 @@ module Gitlab
# installation and uprade of applications
def install_command
command = ['helm', 'upgrade', name, chart] +
- install_flag +
- rollback_support_flag +
- reset_values_flag +
- optional_version_flag +
- rbac_create_flag +
- namespace_flag +
- value_flag
+ install_flag +
+ rollback_support_flag +
+ reset_values_flag +
+ optional_version_flag +
+ rbac_create_flag +
+ namespace_flag +
+ value_flag
command.shelljoin
end
diff --git a/lib/gitlab/kubernetes/helm/v3/patch_command.rb b/lib/gitlab/kubernetes/helm/v3/patch_command.rb
index 00f340591e7..1278e524bd2 100644
--- a/lib/gitlab/kubernetes/helm/v3/patch_command.rb
+++ b/lib/gitlab/kubernetes/helm/v3/patch_command.rb
@@ -34,10 +34,10 @@ module Gitlab
def upgrade_command
command = ['helm', 'upgrade', name, chart] +
- reuse_values_flag +
- version_flag +
- namespace_flag +
- value_flag
+ reuse_values_flag +
+ version_flag +
+ namespace_flag +
+ value_flag
command.shelljoin
end
diff --git a/lib/gitlab/kubernetes/kube_client.rb b/lib/gitlab/kubernetes/kube_client.rb
index 92ffa65fe74..44e53e9ec70 100644
--- a/lib/gitlab/kubernetes/kube_client.rb
+++ b/lib/gitlab/kubernetes/kube_client.rb
@@ -161,7 +161,7 @@ module Gitlab
def validate_url!
return if Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
- Gitlab::UrlBlocker.validate!(api_prefix, allow_local_network: false)
+ Gitlab::UrlBlocker.validate!(api_prefix, allow_local_network: false, schemes: %w[http https])
end
def service_account_exists?(resource)
diff --git a/lib/gitlab/memory/jemalloc.rb b/lib/gitlab/memory/jemalloc.rb
index e20e186cab9..6025e6ab6f2 100644
--- a/lib/gitlab/memory/jemalloc.rb
+++ b/lib/gitlab/memory/jemalloc.rb
@@ -14,41 +14,22 @@ module Gitlab
STATS_DEFAULT_FORMAT = :json
- FILENAME_PREFIX = 'jemalloc_stats'
-
# Return jemalloc stats as a string.
def stats(format: STATS_DEFAULT_FORMAT)
- verify_format!(format)
-
- with_malloc_stats_print do |stats_print|
- StringIO.new.tap { |io| write_stats(stats_print, io, STATS_FORMATS[format]) }.string
- end
+ dump_stats(StringIO.new, format: format).string
end
- # Write jemalloc stats to the given directory
- # @param [String] path Directory path the dump will be put into
- # @param [String] tmp_dir Directory path the dump will be streaming to. It is moved to `path` when finished.
- # @param [String] format `json` or `txt`
- # @param [String] filename_label Optional custom string that will be injected into the file name, e.g. `worker_0`
- # @return [String] Full path to the resulting dump file
- def dump_stats(path:, tmp_dir: Dir.tmpdir, format: STATS_DEFAULT_FORMAT, filename_label: nil)
+ # Streams jemalloc stats to the given IO object.
+ def dump_stats(io, format: STATS_DEFAULT_FORMAT)
verify_format!(format)
format_settings = STATS_FORMATS[format]
- tmp_file_path = File.join(tmp_dir, file_name(format_settings[:extension], filename_label))
- file_path = File.join(path, file_name(format_settings[:extension], filename_label))
with_malloc_stats_print do |stats_print|
- File.open(tmp_file_path, 'wb') do |io|
- write_stats(stats_print, io, format_settings)
- end
+ write_stats(stats_print, io, format_settings)
end
- # On OSX, `with_malloc_stats_print` is no-op, and, as result, no file will be written
- return unless File.exist?(tmp_file_path)
-
- FileUtils.mv(tmp_file_path, file_path)
- file_path
+ io
end
private
@@ -95,10 +76,6 @@ module Gitlab
stats_print.call(callback, nil, format[:options])
end
-
- def file_name(extension, filename_label)
- [FILENAME_PREFIX, $$, filename_label, Time.current.to_i, extension].reject(&:blank?).join('.')
- end
end
end
end
diff --git a/lib/gitlab/memory/reporter.rb b/lib/gitlab/memory/reporter.rb
new file mode 100644
index 00000000000..710c89c6216
--- /dev/null
+++ b/lib/gitlab/memory/reporter.rb
@@ -0,0 +1,130 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Memory
+ class Reporter
+ attr_reader :reports_path
+
+ def initialize(reports_path: nil, logger: Gitlab::AppLogger)
+ @reports_path = reports_path || ENV["GITLAB_DIAGNOSTIC_REPORTS_PATH"] || Dir.mktmpdir
+ @logger = logger
+
+ @worker_id = ::Prometheus::PidProvider.worker_id
+ @worker_uuid = SecureRandom.uuid
+
+ init_prometheus_metrics
+ end
+
+ def run_report(report)
+ return false unless report.active?
+
+ @logger.info(
+ log_labels(
+ message: 'started',
+ perf_report: report.name
+ ))
+
+ start_monotonic_time = Gitlab::Metrics::System.monotonic_time
+ start_thread_cpu_time = Gitlab::Metrics::System.thread_cpu_time
+
+ report_file = store_report(report)
+
+ cpu_s = Gitlab::Metrics::System.thread_cpu_duration(start_thread_cpu_time)
+ duration_s = Gitlab::Metrics::System.monotonic_time - start_monotonic_time
+
+ @logger.info(
+ log_labels(
+ message: 'finished',
+ perf_report: report.name,
+ cpu_s: cpu_s.round(2),
+ duration_s: duration_s.round(2),
+ perf_report_file: report_file,
+ perf_report_size_bytes: file_size(report_file)
+ ))
+
+ @report_duration_counter.increment({ report: report.name }, duration_s)
+
+ true
+ rescue StandardError => e
+ @logger.error(
+ log_labels(
+ message: 'failed',
+ perf_report: report.name,
+ error: e.inspect
+ ))
+
+ false
+ end
+
+ private
+
+ def store_report(report)
+ # Store report in tmp subdir while it is still streaming.
+ # This will clearly separate finished reports from the files we are still writing to.
+ tmp_dir = File.join(@reports_path, 'tmp')
+ FileUtils.mkdir_p(tmp_dir)
+
+ report_file = file_name(report)
+ tmp_file_path = File.join(tmp_dir, report_file)
+
+ io_r, io_w = IO.pipe
+ pid = nil
+ File.open(tmp_file_path, 'wb') do |file|
+ extras = {
+ in: io_r,
+ out: file,
+ err: $stderr
+ }
+ pid = Process.spawn('gzip', '--fast', **extras)
+ io_r.close
+
+ report.run(io_w)
+ io_w.close
+
+ Process.waitpid(pid)
+ end
+
+ File.join(@reports_path, report_file).tap do |report_file_path|
+ FileUtils.mv(tmp_file_path, report_file_path)
+ end
+ ensure
+ [io_r, io_w].each(&:close)
+
+ # Make sure we don't leave any running processes behind.
+ Gitlab::ProcessManagement.signal(pid, :KILL) if pid
+ end
+
+ def log_labels(**extra_labels)
+ {
+ pid: $$,
+ worker_id: @worker_id,
+ perf_report_worker_uuid: @worker_uuid
+ }.merge(extra_labels)
+ end
+
+ def file_name(report)
+ timestamp = Time.current.strftime('%Y-%m-%d.%H:%M:%S:%L')
+
+ report_id = [@worker_id, @worker_uuid].join(".")
+
+ [report.name, timestamp, report_id, 'gz'].compact_blank.join('.')
+ end
+
+ def file_size(file_path)
+ File.size(file_path.to_s)
+ rescue Errno::ENOENT
+ 0
+ end
+
+ def init_prometheus_metrics
+ default_labels = { pid: @worker_id }
+
+ @report_duration_counter = Gitlab::Metrics.counter(
+ :gitlab_diag_report_duration_seconds_total,
+ 'Total time elapsed for running diagnostic report',
+ default_labels
+ )
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/memory/reports/heap_dump.rb b/lib/gitlab/memory/reports/heap_dump.rb
new file mode 100644
index 00000000000..95779407f12
--- /dev/null
+++ b/lib/gitlab/memory/reports/heap_dump.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Memory
+ module Reports
+ class HeapDump
+ class << self
+ def enqueue!
+ @write_heap_dump = true
+ end
+
+ def enqueued?
+ !!@write_heap_dump
+ end
+ end
+
+ def name
+ 'heap_dump'
+ end
+
+ def active?
+ Feature.enabled?(:report_heap_dumps, type: :ops)
+ end
+
+ def run(writer)
+ return false unless self.class.enqueued?
+
+ ObjectSpace.dump_all(output: writer)
+
+ true
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/memory/reports/jemalloc_stats.rb b/lib/gitlab/memory/reports/jemalloc_stats.rb
index 05f0717d7c3..cfda409594f 100644
--- a/lib/gitlab/memory/reports/jemalloc_stats.rb
+++ b/lib/gitlab/memory/reports/jemalloc_stats.rb
@@ -4,70 +4,19 @@ module Gitlab
module Memory
module Reports
class JemallocStats
- # On prod, Jemalloc reports sizes were ~2.5 MB:
- # https://gitlab.com/gitlab-com/gl-infra/reliability/-/issues/15993#note_1014767214
- # We configured 1GB emptyDir per pod:
- # https://gitlab.com/gitlab-com/gl-infra/k8s-workloads/gitlab-com/-/merge_requests/1949
- # The pod will be evicted when the size limit is exceeded. We never want this to happen, for availability.
- #
- # With the default, we have a headroom (250*2.5MB=625<1000 MB) to fit into configured emptyDir.
- # It would allow us to keep 3+ days worth of reports for 6 workers running every 2 hours: 3*6*12=216<250
- #
- # The cleanup logic will be redundant after we'll implement the uploads, which would perform the cleanup.
- DEFAULT_MAX_REPORTS_STORED = 250
-
- def initialize(reports_path:)
- @reports_path = reports_path
-
- # Store report in tmp subdir while it is still streaming.
- # This will clearly separate finished reports from the files we are still writing to.
- @tmp_dir = File.join(@reports_path, 'tmp')
- FileUtils.mkdir_p(@tmp_dir)
+ def name
+ 'jemalloc_stats'
end
- def run
+ def run(writer)
return unless active?
- Gitlab::Memory::Jemalloc.dump_stats(path: reports_path, tmp_dir: @tmp_dir, filename_label: worker_id).tap do
- cleanup
- end
+ Gitlab::Memory::Jemalloc.dump_stats(writer)
end
def active?
Feature.enabled?(:report_jemalloc_stats, type: :ops)
end
-
- private
-
- attr_reader :reports_path
-
- def cleanup
- reports_files_modified_order[0...-max_reports_stored].each do |f|
- File.unlink(f) if File.exist?(f)
- rescue Errno::ENOENT
- # Path does not exist: Ignore. We already check `File.exist?`
- # Rescue to be extra safe, because each worker could perform a cleanup
- end
- end
-
- def reports_files_modified_order
- pattern = File.join(reports_path, "#{Gitlab::Memory::Jemalloc::FILENAME_PREFIX}*")
-
- Dir.glob(pattern).sort_by do |f|
- test('M', f)
- rescue Errno::ENOENT
- # Path does not exist: Return any timestamp to proceed with the sort
- Time.current
- end
- end
-
- def worker_id
- ::Prometheus::PidProvider.worker_id
- end
-
- def max_reports_stored
- ENV["GITLAB_DIAGNOSTIC_REPORTS_JEMALLOC_MAX_REPORTS_STORED"] || DEFAULT_MAX_REPORTS_STORED
- end
end
end
end
diff --git a/lib/gitlab/memory/reports_daemon.rb b/lib/gitlab/memory/reports_daemon.rb
index 0dfc31235e7..9bbfe81116d 100644
--- a/lib/gitlab/memory/reports_daemon.rb
+++ b/lib/gitlab/memory/reports_daemon.rb
@@ -7,9 +7,7 @@ module Gitlab
DEFAULT_SLEEP_MAX_DELTA_S = 600 # 0..10 minutes
DEFAULT_SLEEP_BETWEEN_REPORTS_S = 120 # 2 minutes
- DEFAULT_REPORTS_PATH = Dir.tmpdir
-
- def initialize(**options)
+ def initialize(reporter: nil, reports: nil, **options)
super
@alive = true
@@ -21,31 +19,20 @@ module Gitlab
@sleep_between_reports_s =
ENV['GITLAB_DIAGNOSTIC_REPORTS_SLEEP_BETWEEN_REPORTS_S']&.to_i || DEFAULT_SLEEP_BETWEEN_REPORTS_S
- @reports_path =
- ENV["GITLAB_DIAGNOSTIC_REPORTS_PATH"] || DEFAULT_REPORTS_PATH
-
- @reports = [Gitlab::Memory::Reports::JemallocStats.new(reports_path: reports_path)]
-
- init_prometheus_metrics
+ @reporter = reporter || Reporter.new
+ @reports = reports || [
+ Gitlab::Memory::Reports::JemallocStats.new
+ ]
end
- attr_reader :sleep_s, :sleep_max_delta_s, :sleep_between_reports_s, :reports_path
+ attr_reader :sleep_s, :sleep_max_delta_s, :sleep_between_reports_s
def run_thread
while alive
sleep interval_with_jitter
reports.select(&:active?).each do |report|
- start_monotonic_time = Gitlab::Metrics::System.monotonic_time
- start_thread_cpu_time = Gitlab::Metrics::System.thread_cpu_time
-
- file_path = report.run
-
- cpu_s = Gitlab::Metrics::System.thread_cpu_duration(start_thread_cpu_time)
- duration_s = Gitlab::Metrics::System.monotonic_time - start_monotonic_time
-
- log_report(label: report_label(report), cpu_s: cpu_s, duration_s: duration_s, size: file_size(file_path))
- @report_duration_counter.increment({ report: report_label(report) }, duration_s)
+ @reporter.run_report(report)
sleep sleep_between_reports_s
end
@@ -62,45 +49,9 @@ module Gitlab
sleep_s + rand(sleep_max_delta_s)
end
- def log_report(label:, duration_s:, cpu_s:, size:)
- Gitlab::AppLogger.info(
- message: 'finished',
- pid: $$,
- worker_id: worker_id,
- perf_report: label,
- duration_s: duration_s.round(2),
- cpu_s: cpu_s.round(2),
- perf_report_size_bytes: size
- )
- end
-
- def worker_id
- ::Prometheus::PidProvider.worker_id
- end
-
- def report_label(report)
- report.class.to_s.demodulize.underscore
- end
-
def stop_working
@alive = false
end
-
- def init_prometheus_metrics
- default_labels = { pid: worker_id }
-
- @report_duration_counter = Gitlab::Metrics.counter(
- :gitlab_diag_report_duration_seconds_total,
- 'Total time elapsed for running diagnostic report',
- default_labels
- )
- end
-
- def file_size(file_path)
- File.size(file_path.to_s)
- rescue Errno::ENOENT
- 0
- end
end
end
end
diff --git a/lib/gitlab/memory/watchdog.rb b/lib/gitlab/memory/watchdog.rb
index 19dfc640b5d..25af5bd781a 100644
--- a/lib/gitlab/memory/watchdog.rb
+++ b/lib/gitlab/memory/watchdog.rb
@@ -50,27 +50,17 @@ module Gitlab
def initialize
@configuration = Configuration.new
@alive = true
-
- init_prometheus_metrics
end
##
- # Configuration for Watchdog, use like:
- #
- # watchdog.configure do |config|
- # config.handler = Gitlab::Memory::Watchdog::TermProcessHandler
- # config.sleep_time_seconds = 60
- # config.logger = Gitlab::AppLogger
- # config.monitors do |stack|
- # stack.push MyMonitorClass, args*, max_strikes:, kwargs**, &block
- # end
- # end
+ # Configuration for Watchdog, see Gitlab::Memory::Watchdog::Configurator
+ # for examples.
def configure
- yield @configuration
+ yield configuration
end
def call
- logger.info(log_labels.merge(message: 'started'))
+ event_reporter.started(log_labels)
while @alive
sleep(sleep_time_seconds)
@@ -78,35 +68,45 @@ module Gitlab
monitor if Feature.enabled?(:gitlab_memory_watchdog, type: :ops)
end
- logger.info(log_labels.merge(message: 'stopped'))
+ event_reporter.stopped(log_labels(memwd_reason: @reason).compact)
end
- def stop
+ def stop(reason: nil)
+ @reason = reason
@alive = false
end
private
+ attr_reader :configuration
+
+ delegate :event_reporter, :monitors, :sleep_time_seconds, to: :configuration
+
def monitor
- @configuration.monitors.call_each do |result|
+ if monitors.empty?
+ stop(reason: 'monitors are not configured')
+ return
+ end
+
+ monitors.call_each do |result|
break unless @alive
next unless result.threshold_violated?
- @counter_violations.increment(reason: result.monitor_name)
+ event_reporter.threshold_violated(result.monitor_name)
next unless result.strikes_exceeded?
- @alive = !memory_limit_exceeded_callback(result.monitor_name, result.payload)
+ strike_exceeded_callback(result.monitor_name, result.payload)
end
end
- def memory_limit_exceeded_callback(monitor_name, monitor_payload)
- all_labels = log_labels.merge(monitor_payload)
- logger.warn(all_labels)
- @counter_violations_handled.increment(reason: monitor_name)
+ def strike_exceeded_callback(monitor_name, monitor_payload)
+ event_reporter.strikes_exceeded(monitor_name, log_labels(monitor_payload))
+
+ Gitlab::Memory::Reports::HeapDump.enqueue!
- handler.call
+ stop(reason: 'successfully handled') if handler.call
end
def handler
@@ -114,46 +114,13 @@ module Gitlab
# all that happens is we collect logs and Prometheus events for fragmentation violations.
return NullHandler.instance unless Feature.enabled?(:enforce_memory_watchdog, type: :ops)
- @configuration.handler
- end
-
- def logger
- @configuration.logger
+ configuration.handler
end
- def sleep_time_seconds
- @configuration.sleep_time_seconds
- end
-
- def log_labels
- {
- pid: $$,
- worker_id: worker_id,
+ def log_labels(extra = {})
+ extra.merge(
memwd_handler_class: handler.class.name,
- memwd_sleep_time_s: sleep_time_seconds,
- memwd_rss_bytes: process_rss_bytes
- }
- end
-
- def process_rss_bytes
- Gitlab::Metrics::System.memory_usage_rss[:total]
- end
-
- def worker_id
- ::Prometheus::PidProvider.worker_id
- end
-
- def init_prometheus_metrics
- default_labels = { pid: worker_id }
- @counter_violations = Gitlab::Metrics.counter(
- :gitlab_memwd_violations_total,
- 'Total number of times a Ruby process violated a memory threshold',
- default_labels
- )
- @counter_violations_handled = Gitlab::Metrics.counter(
- :gitlab_memwd_violations_handled_total,
- 'Total number of times Ruby process memory violations were handled',
- default_labels
+ memwd_sleep_time_s: sleep_time_seconds
)
end
end
diff --git a/lib/gitlab/memory/watchdog/configuration.rb b/lib/gitlab/memory/watchdog/configuration.rb
index 793f75adf59..5c459220be8 100644
--- a/lib/gitlab/memory/watchdog/configuration.rb
+++ b/lib/gitlab/memory/watchdog/configuration.rb
@@ -10,7 +10,6 @@ module Gitlab
end
def push(monitor_class, *args, **kwargs, &block)
- remove(monitor_class)
@monitors.push(build_monitor_state(monitor_class, *args, **kwargs, &block))
end
@@ -20,16 +19,17 @@ module Gitlab
end
end
- private
-
- def remove(monitor_class)
- @monitors.delete_if { |monitor| monitor.monitor_class == monitor_class }
+ def empty?
+ @monitors.empty?
end
- def build_monitor_state(monitor_class, *args, max_strikes:, **kwargs, &block)
+ private
+
+ def build_monitor_state(monitor_class, *args, max_strikes:, monitor_name: nil, **kwargs, &block)
monitor = build_monitor(monitor_class, *args, **kwargs, &block)
+ monitor_name ||= monitor_class.name.demodulize.underscore
- Gitlab::Memory::Watchdog::MonitorState.new(monitor, max_strikes: max_strikes)
+ Gitlab::Memory::Watchdog::MonitorState.new(monitor, max_strikes: max_strikes, monitor_name: monitor_name)
end
def build_monitor(monitor_class, *args, **kwargs, &block)
@@ -39,7 +39,7 @@ module Gitlab
DEFAULT_SLEEP_TIME_SECONDS = 60
- attr_writer :logger, :handler, :sleep_time_seconds
+ attr_writer :event_reporter, :handler, :sleep_time_seconds
def monitors
@monitor_stack ||= MonitorStack.new
@@ -51,8 +51,8 @@ module Gitlab
@handler ||= NullHandler.instance
end
- def logger
- @logger ||= Gitlab::Logger.new($stdout)
+ def event_reporter
+ @event_reporter ||= EventReporter.new
end
# Used to control the frequency with which the watchdog will wake up and poll the GC.
diff --git a/lib/gitlab/memory/watchdog/configurator.rb b/lib/gitlab/memory/watchdog/configurator.rb
index 82b1b02b63f..04c04cbde02 100644
--- a/lib/gitlab/memory/watchdog/configurator.rb
+++ b/lib/gitlab/memory/watchdog/configurator.rb
@@ -4,36 +4,43 @@ module Gitlab
module Memory
class Watchdog
class Configurator
+ DEFAULT_PUMA_WORKER_RSS_LIMIT_MB = 1200
+ DEFAULT_SLEEP_INTERVAL_S = 60
+ DEFAULT_SIDEKIQ_SLEEP_INTERVAL_S = 3
+ MIN_SIDEKIQ_SLEEP_INTERVAL_S = 2
+ DEFAULT_MAX_STRIKES = 5
+ DEFAULT_MAX_HEAP_FRAG = 0.5
+ DEFAULT_MAX_MEM_GROWTH = 3.0
+ # grace_time / sleep_interval = max_strikes allowed for Sidekiq process to violate defined limits.
+ DEFAULT_SIDEKIQ_GRACE_TIME_S = 300
+
class << self
def configure_for_puma
- lambda do |config|
- sleep_time_seconds = ENV.fetch('GITLAB_MEMWD_SLEEP_TIME_SEC', 60).to_i
- config.logger = Gitlab::AppLogger
+ ->(config) do
config.handler = Gitlab::Memory::Watchdog::PumaHandler.new
- config.sleep_time_seconds = sleep_time_seconds
+ config.sleep_time_seconds = ENV.fetch('GITLAB_MEMWD_SLEEP_TIME_SEC', DEFAULT_SLEEP_INTERVAL_S).to_i
config.monitors(&configure_monitors_for_puma)
end
end
def configure_for_sidekiq
- lambda do |config|
- sleep_time_seconds = [ENV.fetch('SIDEKIQ_MEMORY_KILLER_CHECK_INTERVAL', 3).to_i, 2].max
- config.logger = Sidekiq.logger
+ ->(config) do
config.handler = Gitlab::Memory::Watchdog::TermProcessHandler.new
- config.sleep_time_seconds = sleep_time_seconds
+ config.sleep_time_seconds = sidekiq_sleep_time
config.monitors(&configure_monitors_for_sidekiq)
+ config.event_reporter = SidekiqEventReporter.new
end
end
private
def configure_monitors_for_puma
- lambda do |stack|
- max_strikes = ENV.fetch('GITLAB_MEMWD_MAX_STRIKES', 5).to_i
+ ->(stack) do
+ max_strikes = ENV.fetch('GITLAB_MEMWD_MAX_STRIKES', DEFAULT_MAX_STRIKES).to_i
if Gitlab::Utils.to_boolean(ENV['DISABLE_PUMA_WORKER_KILLER'])
- max_heap_frag = ENV.fetch('GITLAB_MEMWD_MAX_HEAP_FRAG', 0.5).to_f
- max_mem_growth = ENV.fetch('GITLAB_MEMWD_MAX_MEM_GROWTH', 3.0).to_f
+ max_heap_frag = ENV.fetch('GITLAB_MEMWD_MAX_HEAP_FRAG', DEFAULT_MAX_HEAP_FRAG).to_f
+ max_mem_growth = ENV.fetch('GITLAB_MEMWD_MAX_MEM_GROWTH', DEFAULT_MAX_MEM_GROWTH).to_f
# stack.push MonitorClass, args*, max_strikes:, kwargs**, &block
stack.push Gitlab::Memory::Watchdog::Monitor::HeapFragmentation,
@@ -44,17 +51,44 @@ module Gitlab
max_mem_growth: max_mem_growth,
max_strikes: max_strikes
else
- memory_limit = ENV.fetch('PUMA_WORKER_MAX_MEMORY', 1200).to_i
+ memory_limit = ENV.fetch('PUMA_WORKER_MAX_MEMORY', DEFAULT_PUMA_WORKER_RSS_LIMIT_MB).to_i
stack.push Gitlab::Memory::Watchdog::Monitor::RssMemoryLimit,
- memory_limit: memory_limit.megabytes,
+ memory_limit_bytes: memory_limit.megabytes,
max_strikes: max_strikes
end
end
end
+ def sidekiq_sleep_time
+ [
+ ENV.fetch('SIDEKIQ_MEMORY_KILLER_CHECK_INTERVAL', DEFAULT_SIDEKIQ_SLEEP_INTERVAL_S).to_i,
+ MIN_SIDEKIQ_SLEEP_INTERVAL_S
+ ].max
+ end
+
def configure_monitors_for_sidekiq
- # NOP - At the moment we don't run watchdog for Sidekiq
+ ->(stack) do
+ if ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS'].to_i.nonzero?
+ soft_limit_bytes = ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS'].to_i.kilobytes
+ grace_time = ENV.fetch('SIDEKIQ_MEMORY_KILLER_GRACE_TIME', DEFAULT_SIDEKIQ_GRACE_TIME_S).to_i
+ max_strikes = grace_time / sidekiq_sleep_time
+
+ stack.push Gitlab::Memory::Watchdog::Monitor::RssMemoryLimit,
+ memory_limit_bytes: soft_limit_bytes,
+ max_strikes: max_strikes.to_i,
+ monitor_name: :rss_memory_soft_limit
+ end
+
+ if ENV['SIDEKIQ_MEMORY_KILLER_HARD_LIMIT_RSS'].to_i.nonzero?
+ hard_limit_bytes = ENV['SIDEKIQ_MEMORY_KILLER_HARD_LIMIT_RSS'].to_i.kilobytes
+
+ stack.push Gitlab::Memory::Watchdog::Monitor::RssMemoryLimit,
+ memory_limit_bytes: hard_limit_bytes,
+ max_strikes: 0,
+ monitor_name: :rss_memory_hard_limit
+ end
+ end
end
end
end
diff --git a/lib/gitlab/memory/watchdog/event_reporter.rb b/lib/gitlab/memory/watchdog/event_reporter.rb
new file mode 100644
index 00000000000..c37426cb660
--- /dev/null
+++ b/lib/gitlab/memory/watchdog/event_reporter.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Memory
+ class Watchdog
+ class EventReporter
+ include ::Gitlab::Utils::StrongMemoize
+
+ attr_reader :logger
+
+ def initialize(logger: Gitlab::AppLogger)
+ @logger = logger
+ init_prometheus_metrics
+ end
+
+ def started(labels = {})
+ logger.info(message: 'started', **log_labels(labels))
+ end
+
+ def stopped(labels = {})
+ logger.info(message: 'stopped', **log_labels(labels))
+ end
+
+ def threshold_violated(monitor_name)
+ @counter_violations.increment(reason: monitor_name)
+ end
+
+ def strikes_exceeded(monitor_name, labels = {})
+ logger.warn(log_labels(labels))
+
+ @counter_violations_handled.increment(reason: monitor_name)
+ end
+
+ private
+
+ def log_labels(extra = {})
+ extra.merge(
+ pid: $$,
+ worker_id: worker_id,
+ memwd_rss_bytes: process_rss_bytes
+ )
+ end
+
+ def process_rss_bytes
+ Gitlab::Metrics::System.memory_usage_rss[:total]
+ end
+
+ def worker_id
+ ::Prometheus::PidProvider.worker_id
+ end
+
+ def init_prometheus_metrics
+ default_labels = { pid: worker_id }
+ @counter_violations = Gitlab::Metrics.counter(
+ :gitlab_memwd_violations_total,
+ 'Total number of times a Ruby process violated a memory threshold',
+ default_labels
+ )
+ @counter_violations_handled = Gitlab::Metrics.counter(
+ :gitlab_memwd_violations_handled_total,
+ 'Total number of times Ruby process memory violations were handled',
+ default_labels
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/memory/watchdog/monitor/heap_fragmentation.rb b/lib/gitlab/memory/watchdog/monitor/heap_fragmentation.rb
index 8f230980eac..ce99b68464e 100644
--- a/lib/gitlab/memory/watchdog/monitor/heap_fragmentation.rb
+++ b/lib/gitlab/memory/watchdog/monitor/heap_fragmentation.rb
@@ -4,10 +4,7 @@ module Gitlab
module Memory
class Watchdog
module Monitor
- # A monitor that observes Ruby heap fragmentation and calls
- # memory_violation_callback when the Ruby heap has been fragmented for an extended
- # period of time.
- #
+ # A monitor that observes Ruby heap fragmentation.
# See Gitlab::Metrics::Memory for how heap fragmentation is defined.
class HeapFragmentation
attr_reader :max_heap_fragmentation
diff --git a/lib/gitlab/memory/watchdog/monitor/rss_memory_limit.rb b/lib/gitlab/memory/watchdog/monitor/rss_memory_limit.rb
index 3e7de024630..ac71592294c 100644
--- a/lib/gitlab/memory/watchdog/monitor/rss_memory_limit.rb
+++ b/lib/gitlab/memory/watchdog/monitor/rss_memory_limit.rb
@@ -5,29 +5,38 @@ module Gitlab
class Watchdog
module Monitor
class RssMemoryLimit
- attr_reader :memory_limit
+ attr_reader :memory_limit_bytes
- def initialize(memory_limit:)
- @memory_limit = memory_limit
+ def initialize(memory_limit_bytes:)
+ @memory_limit_bytes = memory_limit_bytes
+ init_memory_limit_metrics
end
def call
- worker_rss = Gitlab::Metrics::System.memory_usage_rss[:total]
+ worker_rss_bytes = Gitlab::Metrics::System.memory_usage_rss[:total]
- return { threshold_violated: false, payload: {} } if worker_rss <= memory_limit
+ return { threshold_violated: false, payload: {} } if worker_rss_bytes <= memory_limit_bytes
- { threshold_violated: true, payload: payload(worker_rss, memory_limit) }
+ { threshold_violated: true, payload: payload(worker_rss_bytes, memory_limit_bytes) }
end
private
- def payload(worker_rss, memory_limit)
+ def payload(worker_rss_bytes, memory_limit_bytes)
{
message: 'rss memory limit exceeded',
- memwd_rss_bytes: worker_rss,
- memwd_max_rss_bytes: memory_limit
+ memwd_rss_bytes: worker_rss_bytes,
+ memwd_max_rss_bytes: memory_limit_bytes
}
end
+
+ def init_memory_limit_metrics
+ rss_memory_limit = Gitlab::Metrics.gauge(
+ :gitlab_memwd_max_memory_limit,
+ 'The configured fixed limit for rss memory'
+ )
+ rss_memory_limit.set({}, memory_limit_bytes)
+ end
end
end
end
diff --git a/lib/gitlab/memory/watchdog/monitor_state.rb b/lib/gitlab/memory/watchdog/monitor_state.rb
index 73be5de3e45..bb083fedf2c 100644
--- a/lib/gitlab/memory/watchdog/monitor_state.rb
+++ b/lib/gitlab/memory/watchdog/monitor_state.rb
@@ -5,12 +5,12 @@ module Gitlab
class Watchdog
class MonitorState
class Result
- attr_reader :payload
+ attr_reader :payload, :monitor_name
- def initialize(strikes_exceeded:, threshold_violated:, monitor_class:, payload: )
+ def initialize(strikes_exceeded:, threshold_violated:, monitor_name:, payload:)
@strikes_exceeded = strikes_exceeded
@threshold_violated = threshold_violated
- @monitor_class = monitor_class
+ @monitor_name = monitor_name.to_s.to_sym
@payload = payload
end
@@ -21,15 +21,12 @@ module Gitlab
def threshold_violated?
@threshold_violated
end
-
- def monitor_name
- @monitor_class.name.demodulize.underscore.to_sym
- end
end
- def initialize(monitor, max_strikes:)
+ def initialize(monitor, max_strikes:, monitor_name:)
@monitor = monitor
@max_strikes = max_strikes
+ @monitor_name = monitor_name
@strikes = 0
end
@@ -47,16 +44,12 @@ module Gitlab
build_result(monitor_result)
end
- def monitor_class
- @monitor.class
- end
-
private
def build_result(monitor_result)
Result.new(
strikes_exceeded: strikes_exceeded?,
- monitor_class: monitor_class,
+ monitor_name: @monitor_name,
threshold_violated: monitor_result[:threshold_violated],
payload: payload.merge(monitor_result[:payload]))
end
diff --git a/lib/gitlab/memory/watchdog/sidekiq_event_reporter.rb b/lib/gitlab/memory/watchdog/sidekiq_event_reporter.rb
new file mode 100644
index 00000000000..473ed1b8094
--- /dev/null
+++ b/lib/gitlab/memory/watchdog/sidekiq_event_reporter.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Memory
+ class Watchdog
+ class SidekiqEventReporter
+ include ::Gitlab::Utils::StrongMemoize
+
+ delegate :threshold_violated, :started, :stopped, :logger, to: :event_reporter
+
+ def initialize(logger: ::Sidekiq.logger)
+ @event_reporter = EventReporter.new(logger: logger)
+ @sidekiq_daemon_monitor = Gitlab::SidekiqDaemon::Monitor.instance
+ init_prometheus_metrics
+ end
+
+ def strikes_exceeded(monitor_name, labels = {})
+ running_jobs = fetch_running_jobs
+ labels[:running_jobs] = running_jobs
+ increment_worker_counters(running_jobs)
+
+ event_reporter.strikes_exceeded(monitor_name, labels)
+ end
+
+ private
+
+ attr_reader :event_reporter
+
+ def fetch_running_jobs
+ @sidekiq_daemon_monitor.jobs.map do |jid, job|
+ {
+ jid: jid,
+ worker_class: job[:worker_class].name
+ }
+ end
+ end
+
+ def increment_worker_counters(running_jobs)
+ running_jobs.each do |job|
+ @sidekiq_watchdog_running_jobs_counter.increment({ worker_class: job[:worker_class] })
+ end
+ end
+
+ def init_prometheus_metrics
+ @sidekiq_watchdog_running_jobs_counter = ::Gitlab::Metrics.counter(
+ :sidekiq_watchdog_running_jobs_total,
+ 'Current running jobs when limit was reached'
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/merge_requests/commit_message_generator.rb b/lib/gitlab/merge_requests/message_generator.rb
index ef5c63925c2..5113fbdcd7b 100644
--- a/lib/gitlab/merge_requests/commit_message_generator.rb
+++ b/lib/gitlab/merge_requests/message_generator.rb
@@ -1,28 +1,41 @@
# frozen_string_literal: true
module Gitlab
module MergeRequests
- class CommitMessageGenerator
+ class MessageGenerator
def initialize(merge_request:, current_user:)
@merge_request = merge_request
@current_user = @merge_request.metrics&.merged_by || @merge_request.merge_user || current_user
end
- def merge_message
+ def merge_commit_message
return unless @merge_request.target_project.merge_commit_template.present?
- replace_placeholders(@merge_request.target_project.merge_commit_template)
+ replace_placeholders(@merge_request.target_project.merge_commit_template, allowed_placeholders: PLACEHOLDERS)
end
- def squash_message
+ def squash_commit_message
return unless @merge_request.target_project.squash_commit_template.present?
- replace_placeholders(@merge_request.target_project.squash_commit_template, squash: true)
+ replace_placeholders(
+ @merge_request.target_project.squash_commit_template,
+ allowed_placeholders: PLACEHOLDERS,
+ squash: true
+ )
+ end
+
+ def new_mr_description
+ return unless @merge_request.description.present?
+
+ replace_placeholders(
+ @merge_request.description,
+ allowed_placeholders: ALLOWED_NEW_MR_PLACEHOLDERS,
+ keep_carriage_return: true
+ )
end
private
- attr_reader :merge_request
- attr_reader :current_user
+ attr_reader :merge_request, :current_user
PLACEHOLDERS = {
'source_branch' => ->(merge_request, _, _) { merge_request.source_branch.to_s },
@@ -38,10 +51,25 @@ module Gitlab
end,
'description' => ->(merge_request, _, _) { merge_request.description },
'reference' => ->(merge_request, _, _) { merge_request.to_reference(full: true) },
- 'first_commit' => -> (merge_request, _, _) { merge_request.first_commit&.safe_message&.strip },
- 'first_multiline_commit' => -> (merge_request, _, _) { merge_request.first_multiline_commit&.safe_message&.strip.presence || merge_request.title },
+ 'first_commit' => -> (merge_request, _, _) {
+ return unless merge_request.persisted? || merge_request.compare_commits.present?
+
+ merge_request.first_commit&.safe_message&.strip
+ },
+ 'first_multiline_commit' => -> (merge_request, _, _) {
+ merge_request.first_multiline_commit&.safe_message&.strip.presence || merge_request.title
+ },
'url' => ->(merge_request, _, _) { Gitlab::UrlBuilder.build(merge_request) },
- 'approved_by' => ->(merge_request, _, _) { merge_request.approved_by_users.map { |user| "Approved-by: #{user.name} <#{user.commit_email_or_default}>" }.join("\n") },
+ 'reviewed_by' => ->(merge_request, _, _) {
+ merge_request.reviewed_by_users
+ .map { |user| "Reviewed-by: #{user.name} <#{user.commit_email_or_default}>" }
+ .join("\n")
+ },
+ 'approved_by' => ->(merge_request, _, _) {
+ merge_request.approved_by_users
+ .map { |user| "Approved-by: #{user.name} <#{user.commit_email_or_default}>" }
+ .join("\n")
+ },
'merged_by' => ->(_, user, _) { "#{user&.name} <#{user&.commit_email_or_default}>" },
'co_authored_by' => ->(merge_request, merged_by, squash) do
commit_author = squash ? merge_request.author : merged_by
@@ -66,15 +94,34 @@ module Gitlab
end
}.freeze
+ # A new merge request that is in the process of being created and hasn't
+ # been persisted to the database.
+ #
+ # Limit the placeholders to a subset of the available ones where the
+ # placeholders wouldn't make sense in context. Disallowed placeholders
+ # will be replaced with an empty string.
+ ALLOWED_NEW_MR_PLACEHOLDERS = %w[
+ source_branch
+ target_branch
+ first_commit
+ first_multiline_commit
+ co_authored_by
+ all_commits
+ ].freeze
+
PLACEHOLDERS_COMBINED_REGEX = /%{(#{Regexp.union(PLACEHOLDERS.keys)})}/.freeze
- def replace_placeholders(message, squash: false)
+ def replace_placeholders(message, allowed_placeholders: [], squash: false, keep_carriage_return: false)
# Convert CRLF to LF.
- message = message.delete("\r")
+ message = message.delete("\r") unless keep_carriage_return
used_variables = message.scan(PLACEHOLDERS_COMBINED_REGEX).map { |value| value[0] }.uniq
values = used_variables.to_h do |variable_name|
- ["%{#{variable_name}}", PLACEHOLDERS[variable_name].call(merge_request, current_user, squash)]
+ replacement = if allowed_placeholders.include?(variable_name)
+ PLACEHOLDERS[variable_name].call(merge_request, current_user, squash)
+ end
+
+ ["%{#{variable_name}}", replacement]
end
names_of_empty_variables = values.filter_map { |name, value| name if value.blank? }
diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb
index 6d7ecb53ec3..e99761a0459 100644
--- a/lib/gitlab/metrics.rb
+++ b/lib/gitlab/metrics.rb
@@ -20,6 +20,10 @@ module Gitlab
status.to_i.between?(200, 499)
end
+ def self.server_error?(status)
+ status.to_i >= 500
+ end
+
# Tracks an event.
#
# See `Gitlab::Metrics::Transaction#add_event` for more details.
diff --git a/lib/gitlab/metrics/dashboard/importers/prometheus_metrics.rb b/lib/gitlab/metrics/dashboard/importers/prometheus_metrics.rb
index e2b43798b22..531e4079632 100644
--- a/lib/gitlab/metrics/dashboard/importers/prometheus_metrics.rb
+++ b/lib/gitlab/metrics/dashboard/importers/prometheus_metrics.rb
@@ -63,13 +63,11 @@ module Gitlab
end
def prometheus_metrics_attributes
- @prometheus_metrics_attributes ||= begin
- Dashboard::Transformers::Yml::V1::PrometheusMetrics.new(
- dashboard_hash,
+ @prometheus_metrics_attributes ||= Dashboard::Transformers::Yml::V1::PrometheusMetrics.new(
+ dashboard_hash,
project: project,
dashboard_path: dashboard_path
- ).execute
- end
+ ).execute
end
end
end
diff --git a/lib/gitlab/metrics/dashboard/validator.rb b/lib/gitlab/metrics/dashboard/validator.rb
index 1e8dc059968..57b4b5c068d 100644
--- a/lib/gitlab/metrics/dashboard/validator.rb
+++ b/lib/gitlab/metrics/dashboard/validator.rb
@@ -16,6 +16,8 @@ module Gitlab
errors.empty? || raise(errors.first)
end
+ private
+
def errors(content, schema_path = DASHBOARD_SCHEMA_PATH, dashboard_path: nil, project: nil)
Validator::Client
.new(content, schema_path, dashboard_path: dashboard_path, project: project)
diff --git a/lib/gitlab/metrics/global_search_slis.rb b/lib/gitlab/metrics/global_search_slis.rb
index 200c6eb4043..fc2e805047a 100644
--- a/lib/gitlab/metrics/global_search_slis.rb
+++ b/lib/gitlab/metrics/global_search_slis.rb
@@ -14,9 +14,6 @@ module Gitlab
def initialize_slis!
Gitlab::Metrics::Sli::Apdex.initialize_sli(:global_search, possible_labels)
-
- return unless Feature.enabled?(:global_search_error_rate_sli)
-
Gitlab::Metrics::Sli::ErrorRate.initialize_sli(:global_search, possible_labels)
end
@@ -28,8 +25,6 @@ module Gitlab
end
def record_error_rate(error:, search_type:, search_level:, search_scope:)
- return unless Feature.enabled?(:global_search_error_rate_sli)
-
Gitlab::Metrics::Sli::ErrorRate[:global_search].increment(
labels: labels(search_type: search_type, search_level: search_level, search_scope: search_scope),
error: error
diff --git a/lib/gitlab/metrics/rails_slis.rb b/lib/gitlab/metrics/rails_slis.rb
index 71da0085c8c..9fd4eec479e 100644
--- a/lib/gitlab/metrics/rails_slis.rb
+++ b/lib/gitlab/metrics/rails_slis.rb
@@ -6,6 +6,7 @@ module Gitlab
class << self
def initialize_request_slis!
Gitlab::Metrics::Sli::Apdex.initialize_sli(:rails_request, possible_request_labels)
+ initialize_rails_request_error_rate
Gitlab::Metrics::Sli::Apdex.initialize_sli(:graphql_query, possible_graphql_query_labels)
end
@@ -13,6 +14,10 @@ module Gitlab
Gitlab::Metrics::Sli::Apdex[:rails_request]
end
+ def request_error_rate
+ Gitlab::Metrics::Sli::ErrorRate[:rails_request]
+ end
+
def graphql_query_apdex
Gitlab::Metrics::Sli::Apdex[:graphql_query]
end
@@ -58,6 +63,12 @@ module Gitlab
}
end
end
+
+ def initialize_rails_request_error_rate
+ return unless Feature.enabled?(:gitlab_metrics_error_rate_sli, type: :development)
+
+ Gitlab::Metrics::Sli::ErrorRate.initialize_sli(:rails_request, possible_request_labels)
+ end
end
end
end
diff --git a/lib/gitlab/metrics/requests_rack_middleware.rb b/lib/gitlab/metrics/requests_rack_middleware.rb
index d7fe983c553..0172de8731d 100644
--- a/lib/gitlab/metrics/requests_rack_middleware.rb
+++ b/lib/gitlab/metrics/requests_rack_middleware.rb
@@ -75,15 +75,17 @@ module Gitlab
begin
status, headers, body = @app.call(env)
+ return [status, headers, body] if health_endpoint
- elapsed = ::Gitlab::Metrics::System.monotonic_time - started
-
- if !health_endpoint && ::Gitlab::Metrics.record_duration_for_status?(status)
+ urgency = urgency_for_env(env)
+ if ::Gitlab::Metrics.record_duration_for_status?(status)
+ elapsed = ::Gitlab::Metrics::System.monotonic_time - started
self.class.http_request_duration_seconds.observe({ method: method }, elapsed)
-
- record_apdex(env, elapsed)
+ record_apdex(urgency, elapsed)
end
+ record_error(urgency, status)
+
[status, headers, body]
rescue StandardError
self.class.rack_uncaught_errors_count.increment
@@ -115,15 +117,22 @@ module Gitlab
::Gitlab::ApplicationContext.current_context_attribute(:caller_id)
end
- def record_apdex(env, elapsed)
- urgency = urgency_for_env(env)
-
+ def record_apdex(urgency, elapsed)
Gitlab::Metrics::RailsSlis.request_apdex.increment(
labels: labels_from_context.merge(request_urgency: urgency.name),
success: elapsed < urgency.duration
)
end
+ def record_error(urgency, status)
+ return unless Feature.enabled?(:gitlab_metrics_error_rate_sli, type: :development)
+
+ Gitlab::Metrics::RailsSlis.request_error_rate.increment(
+ labels: labels_from_context.merge(request_urgency: urgency.name),
+ error: ::Gitlab::Metrics.server_error?(status)
+ )
+ end
+
def labels_from_context
{
feature_category: feature_category.presence || FEATURE_CATEGORY_DEFAULT,
diff --git a/lib/gitlab/metrics/subscribers/ldap.rb b/lib/gitlab/metrics/subscribers/ldap.rb
new file mode 100644
index 00000000000..9cac5f41090
--- /dev/null
+++ b/lib/gitlab/metrics/subscribers/ldap.rb
@@ -0,0 +1,103 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Metrics
+ module Subscribers
+ class Ldap < ActiveSupport::Subscriber
+ # This namespace is configured in the Net::LDAP library, and appears
+ # at the end of the event key, e.g. `open.net_ldap`
+ attach_to :net_ldap
+
+ COUNTER = :net_ldap_count
+ DURATION = :net_ldap_duration_s
+
+ # Assembled from methods that are instrumented inside Net::LDAP
+ OBSERVABLE_EVENTS = %i[
+ open
+ bind
+ add
+ modify
+ modify_password
+ rename
+ delete
+ search
+ ].freeze
+
+ class << self
+ # @return [Integer] the total number of LDAP requests
+ def count
+ Gitlab::SafeRequestStore[COUNTER].to_i
+ end
+
+ # @return [Float] the total duration spent on LDAP requests
+ def duration
+ Gitlab::SafeRequestStore[DURATION].to_f
+ end
+
+ # Used in Gitlab::InstrumentationHelper to merge the LDAP stats
+ # into the log output
+ #
+ # @return [Hash<Integer, Float>] a hash of the stored statistics
+ def payload
+ {
+ net_ldap_count: count,
+ net_ldap_duration_s: duration
+ }
+ end
+ end
+
+ # Called when an event is triggered in ActiveSupport::Notifications
+ #
+ # This method is aliased to the various events triggered by the
+ # Net::LDAP library, as the method will be called by those names
+ # when triggered.
+ #
+ # It stores statistics in the request for output to logs, and also
+ # resubmits the event data into Prometheus for monitoring purposes.
+ def observe_event(event)
+ add_to_request_store(event)
+ expose_metrics(event)
+ end
+
+ OBSERVABLE_EVENTS.each do |event|
+ alias_method event, :observe_event
+ end
+
+ private
+
+ def current_transaction
+ ::Gitlab::Metrics::WebTransaction.current || ::Gitlab::Metrics::BackgroundTransaction.current
+ end
+
+ # Track these events as statistics for the current requests, for logging purposes
+ def add_to_request_store(event)
+ return unless Gitlab::SafeRequestStore.active?
+
+ Gitlab::SafeRequestStore[COUNTER] = Gitlab::SafeRequestStore[COUNTER].to_i + 1
+ Gitlab::SafeRequestStore[DURATION] = Gitlab::SafeRequestStore[DURATION].to_f + event.duration.to_f
+ end
+
+ # Converts the observed events into Prometheus metrics
+ def expose_metrics(event)
+ return unless current_transaction
+
+ # event.name will be, for example, `search.net_ldap`
+ # and so we only want the first part, which is the
+ # true name of the event
+ labels = { name: event.name.split(".").first }
+
+ current_transaction.increment(:gitlab_net_ldap_total, 1, labels) do
+ docstring 'Net::LDAP calls'
+ label_keys labels.keys
+ end
+
+ current_transaction.observe(:gitlab_net_ldap_duration_seconds, event.duration, labels) do
+ docstring 'Net::LDAP time'
+ buckets [0.001, 0.01, 0.1, 1.0, 2.0, 5.0]
+ label_keys labels.keys
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/subscribers/rails_cache.rb b/lib/gitlab/metrics/subscribers/rails_cache.rb
index b5e087d107b..b12db9df66d 100644
--- a/lib/gitlab/metrics/subscribers/rails_cache.rb
+++ b/lib/gitlab/metrics/subscribers/rails_cache.rb
@@ -8,6 +8,17 @@ module Gitlab
class RailsCache < ActiveSupport::Subscriber
attach_to :active_support
+ def cache_read_multi(event)
+ observe(:read_multi, event.duration)
+
+ return unless current_transaction
+
+ current_transaction.observe(:gitlab_cache_read_multikey_count, event.payload[:key].size) do
+ buckets [10, 50, 100, 1000]
+ docstring 'Number of keys for mget in read_multi/fetch_multi'
+ end
+ end
+
def cache_read(event)
observe(:read, event.duration)
diff --git a/lib/gitlab/middleware/compressed_json.rb b/lib/gitlab/middleware/compressed_json.rb
index f66dfe44054..80916eab5ac 100644
--- a/lib/gitlab/middleware/compressed_json.rb
+++ b/lib/gitlab/middleware/compressed_json.rb
@@ -4,7 +4,18 @@ module Gitlab
module Middleware
class CompressedJson
COLLECTOR_PATH = '/api/v4/error_tracking/collector'
+ PACKAGES_PATH = %r{
+ \A/api/v4/ (?# prefix)
+ (?:projects/
+ (?<project_id>
+ .+ (?# at least one character)
+ )/
+ )? (?# projects segment)
+ packages/npm/-/npm/v1/security/
+ (?:(?:advisories/bulk)|(?:audits/quick))\z (?# end)
+ }xi.freeze
MAXIMUM_BODY_SIZE = 200.kilobytes.to_i
+ UNSAFE_CHARACTERS = %r{[!"#&'()*+,./:;<>=?@\[\]^`{}|~$]}xi.freeze
def initialize(app)
@app = app
@@ -60,7 +71,21 @@ module Gitlab
end
def match_path?(env)
- env['PATH_INFO'].start_with?((File.join(relative_url, COLLECTOR_PATH)))
+ env['PATH_INFO'].start_with?((File.join(relative_url, COLLECTOR_PATH))) ||
+ match_packages_path?(env)
+ end
+
+ def match_packages_path?(env)
+ match_data = env['PATH_INFO'].delete_prefix(relative_url).match(PACKAGES_PATH)
+ return false unless match_data
+
+ return true unless match_data[:project_id] # instance level endpoint was matched
+
+ url_encoded?(match_data[:project_id])
+ end
+
+ def url_encoded?(project_id)
+ project_id !~ UNSAFE_CHARACTERS
end
end
end
diff --git a/lib/gitlab/middleware/go.rb b/lib/gitlab/middleware/go.rb
index dcbb4557377..13f7ab36823 100644
--- a/lib/gitlab/middleware/go.rb
+++ b/lib/gitlab/middleware/go.rb
@@ -72,8 +72,8 @@ module Gitlab
"#{project_url}.git"
end
- meta_import_tag = tag :meta, name: 'go-import', content: "#{import_prefix} git #{repository_url}"
- meta_source_tag = tag :meta, name: 'go-source', content: "#{import_prefix} #{project_url} #{project_url}/-/tree/#{branch}{/dir} #{project_url}/-/blob/#{branch}{/dir}/{file}#L{line}"
+ meta_import_tag = tag.meta(name: 'go-import', content: "#{import_prefix} git #{repository_url}")
+ meta_source_tag = tag.meta(name: 'go-source', content: "#{import_prefix} #{project_url} #{project_url}/-/tree/#{branch}{/dir} #{project_url}/-/blob/#{branch}{/dir}/{file}#L{line}")
head_tag = content_tag :head, meta_import_tag + meta_source_tag
html_tag = content_tag :html, head_tag + body_tag
[html_tag, 200]
diff --git a/lib/gitlab/other_markup.rb b/lib/gitlab/other_markup.rb
index 0dd6b8a809c..2368ea3ad28 100644
--- a/lib/gitlab/other_markup.rb
+++ b/lib/gitlab/other_markup.rb
@@ -3,18 +3,34 @@
module Gitlab
# Parser/renderer for markups without other special support code.
module OtherMarkup
+ RENDER_TIMEOUT = 10.seconds
+
# Public: Converts the provided markup into HTML.
#
# input - the source text in a markup format
#
def self.render(file_name, input, context)
- html = GitHub::Markup.render(file_name, input)
- .force_encoding(input.encoding)
+ html = render_markup(file_name, input, context).force_encoding(input.encoding)
+
context[:pipeline] ||= :markup
html = Banzai.render(html, context)
-
html.html_safe
end
+
+ def self.render_markup(file_name, input, context)
+ Gitlab::RenderTimeout.timeout(foreground: RENDER_TIMEOUT) { GitHub::Markup.render(file_name, input) }
+ rescue Timeout::Error => e
+ class_name = name.demodulize
+ timeout_counter.increment(source: class_name)
+ Gitlab::ErrorTracking.track_exception(e, project_id: context[:project]&.id, class_name: class_name,
+ file_name: file_name)
+
+ ActionController::Base.helpers.simple_format(input)
+ end
+
+ def self.timeout_counter
+ Gitlab::Metrics.counter(:banzai_filter_timeouts_total, 'Count of the Banzai filters that time out')
+ end
end
end
diff --git a/lib/gitlab/pages/cache_control.rb b/lib/gitlab/pages/cache_control.rb
index 187d5f907e4..be39e52b342 100644
--- a/lib/gitlab/pages/cache_control.rb
+++ b/lib/gitlab/pages/cache_control.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require 'set'
+
module Gitlab
module Pages
class CacheControl
@@ -9,7 +11,9 @@ module Gitlab
# To avoid delivering expired deployment URL in the cached payload,
# use a longer expiration time in the deployment URL
DEPLOYMENT_EXPIRATION = (EXPIRE + 12.hours)
- CACHE_KEY_FORMAT = 'pages_domain_for_%{type}_%{id}_%{settings}'
+
+ SETTINGS_CACHE_KEY = 'pages_domain_for_%{type}_%{id}'
+ PAYLOAD_CACHE_KEY = '%{settings_cache_key}_%{settings_hash}'
class << self
def for_project(project_id)
@@ -29,29 +33,63 @@ module Gitlab
end
def cache_key
- strong_memoize(:cache_key) do
- CACHE_KEY_FORMAT % {
- type: @type,
- id: @id,
- settings: settings
- }
+ strong_memoize(:payload_cache_key) do
+ cache_settings_hash!
+
+ payload_cache_key_for(settings_hash)
end
end
+ # Invalidates the cache.
+ #
+ # Since rails nodes and sidekiq nodes have different application settings,
+ # and the invalidation happens in a sidekiq node, we have to use the
+ # cached settings hash to build the payload cache key to be invalidated.
def clear_cache
- Rails.cache.delete(cache_key)
+ keys = cached_settings_hashes
+ .map { |hash| payload_cache_key_for(hash) }
+ .push(settings_cache_key)
+
+ Rails.cache.delete_multi(keys)
end
private
- def settings
- values = ::Gitlab.config.pages.dup
+ # Since rails nodes and sidekiq nodes have different application settings,
+ # we cache the application settings hash when creating the payload cache
+ # so we can use these values to invalidate the cache in a sidekiq node later.
+ def cache_settings_hash!
+ cached = cached_settings_hashes.to_set
+ Rails.cache.write(settings_cache_key, cached.add(settings_hash))
+ end
+
+ def cached_settings_hashes
+ Rails.cache.read(settings_cache_key) || []
+ end
+
+ def payload_cache_key_for(settings_hash)
+ PAYLOAD_CACHE_KEY % {
+ settings_cache_key: settings_cache_key,
+ settings_hash: settings_hash
+ }
+ end
- values['app_settings'] = ::Gitlab::CurrentSettings.attributes.slice(
- 'force_pages_access_control'
- )
+ def settings_cache_key
+ strong_memoize(:settings_cache_key) do
+ SETTINGS_CACHE_KEY % { type: @type, id: @id }
+ end
+ end
- ::Digest::SHA256.hexdigest(values.inspect)
+ def settings_hash
+ strong_memoize(:settings_hash) do
+ values = ::Gitlab.config.pages.dup
+
+ values['app_settings'] = ::Gitlab::CurrentSettings.attributes.slice(
+ 'force_pages_access_control'
+ )
+
+ ::Digest::SHA256.hexdigest(values.inspect)
+ end
end
end
end
diff --git a/lib/gitlab/pagination/cursor_based_keyset.rb b/lib/gitlab/pagination/cursor_based_keyset.rb
index 2d9fb0a50fc..199ec16d4df 100644
--- a/lib/gitlab/pagination/cursor_based_keyset.rb
+++ b/lib/gitlab/pagination/cursor_based_keyset.rb
@@ -22,7 +22,7 @@ module Gitlab
def self.available?(cursor_based_request_context, relation)
available_for_type?(relation) &&
- order_satisfied?(relation, cursor_based_request_context)
+ order_satisfied?(relation, cursor_based_request_context)
end
def self.enforced_for_type?(relation)
diff --git a/lib/gitlab/pagination/offset_pagination.rb b/lib/gitlab/pagination/offset_pagination.rb
index 00304f48dc5..a98199dae2e 100644
--- a/lib/gitlab/pagination/offset_pagination.rb
+++ b/lib/gitlab/pagination/offset_pagination.rb
@@ -11,15 +11,17 @@ module Gitlab
@request_context = request_context
end
- def paginate(relation, exclude_total_headers: false, skip_default_order: false)
- paginate_with_limit_optimization(add_default_order(relation, skip_default_order: skip_default_order)).tap do |data|
+ def paginate(relation, exclude_total_headers: false, skip_default_order: false, without_count: false)
+ ordered_relation = add_default_order(relation, skip_default_order: skip_default_order)
+
+ paginate_with_limit_optimization(ordered_relation, without_count: without_count).tap do |data|
add_pagination_headers(data, exclude_total_headers)
end
end
private
- def paginate_with_limit_optimization(relation)
+ def paginate_with_limit_optimization(relation, without_count:)
pagination_data = if needs_pagination?(relation)
relation.page(params[:page]).per(params[:per_page])
else
@@ -28,8 +30,7 @@ module Gitlab
return pagination_data unless pagination_data.is_a?(ActiveRecord::Relation)
- limited_total_count = pagination_data.total_count_with_limit
- if limited_total_count > Kaminari::ActiveRecordRelationMethods::MAX_COUNT_LIMIT
+ if without_count || exceeeds_count?(pagination_data)
# 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
@@ -78,6 +79,12 @@ module Gitlab
# Ensure there is in total at least 1 page
[paginated_data.total_pages, 1].max
end
+
+ def exceeeds_count?(paginated_data)
+ limited_total_count = paginated_data.total_count_with_limit
+
+ limited_total_count > Kaminari::ActiveRecordRelationMethods::MAX_COUNT_LIMIT
+ end
end
end
end
diff --git a/lib/gitlab/patch/prependable.rb b/lib/gitlab/patch/prependable.rb
index 1ed341e1c26..b974c0b2c7f 100644
--- a/lib/gitlab/patch/prependable.rb
+++ b/lib/gitlab/patch/prependable.rb
@@ -26,7 +26,7 @@ module Gitlab
# https://github.com/rails/rails/pull/42067
#
# Let's keep our own implementation, until the issue is fixed
- Module.instance_method(:prepend_features).bind(self).call(base)
+ Module.instance_method(:prepend_features).bind_call(self, base)
if const_defined?(:ClassMethods)
klass_methods = const_get(:ClassMethods, false)
diff --git a/lib/gitlab/phabricator_import/project_creator.rb b/lib/gitlab/phabricator_import/project_creator.rb
index c842798ca74..4de9eaa9500 100644
--- a/lib/gitlab/phabricator_import/project_creator.rb
+++ b/lib/gitlab/phabricator_import/project_creator.rb
@@ -55,13 +55,11 @@ module Gitlab
end
def project_feature_attributes
+ # everything disabled except for issues
@project_features_attributes ||=
- begin
- # everything disabled except for issues
- ProjectFeature::FEATURES.to_h do |feature|
- [ProjectFeature.access_level_attribute(feature), ProjectFeature::DISABLED]
- end.merge(ProjectFeature.access_level_attribute(:issues) => ProjectFeature::ENABLED)
- end
+ ProjectFeature::FEATURES.to_h do |feature|
+ [ProjectFeature.access_level_attribute(feature), ProjectFeature::DISABLED]
+ end.merge(ProjectFeature.access_level_attribute(:issues) => ProjectFeature::ENABLED)
end
def import_data
diff --git a/lib/gitlab/process_management.rb b/lib/gitlab/process_management.rb
index f8a1a3a97de..89ffd71c2d8 100644
--- a/lib/gitlab/process_management.rb
+++ b/lib/gitlab/process_management.rb
@@ -40,15 +40,6 @@ module Gitlab
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 StandardError
- nil # There is no reason to return `Errno::ECHILD` if it catches a `TypeError`
- end
- end
-
# Returns true if all the processes are alive.
def self.all_alive?(pids)
pids.each do |pid|
diff --git a/lib/gitlab/process_supervisor.rb b/lib/gitlab/process_supervisor.rb
index 714034f043d..09e923d1449 100644
--- a/lib/gitlab/process_supervisor.rb
+++ b/lib/gitlab/process_supervisor.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require_relative './daemon'
+
module Gitlab
# Given a set of process IDs, the supervisor can monitor processes
# for being alive and invoke a callback if some or all should go away.
diff --git a/lib/gitlab/profiler.rb b/lib/gitlab/profiler.rb
index f8a85f693bc..5af06e82c55 100644
--- a/lib/gitlab/profiler.rb
+++ b/lib/gitlab/profiler.rb
@@ -123,18 +123,18 @@ module Gitlab
def self.with_custom_logger(logger)
original_colorize_logging = ActiveSupport::LogSubscriber.colorize_logging
- original_activerecord_logger = ApplicationRecord.logger
+ original_activerecord_logger = ActiveRecord::Base.logger
original_actioncontroller_logger = ActionController::Base.logger
if logger
ActiveSupport::LogSubscriber.colorize_logging = false
- ApplicationRecord.logger = logger
+ ActiveRecord::Base.logger = logger
ActionController::Base.logger = logger
end
yield.tap do
ActiveSupport::LogSubscriber.colorize_logging = original_colorize_logging
- ApplicationRecord.logger = original_activerecord_logger
+ ActiveRecord::Base.logger = original_activerecord_logger
ActionController::Base.logger = original_actioncontroller_logger
end
end
diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb
index 8cc96970ebd..13718e63b25 100644
--- a/lib/gitlab/project_search_results.rb
+++ b/lib/gitlab/project_search_results.rb
@@ -125,17 +125,15 @@ module Gitlab
def wiki_blobs(limit: count_limit)
return [] unless Ability.allowed?(@current_user, :read_wiki, @project)
- @wiki_blobs ||= begin
- if project.wiki_enabled? && query.present?
- if project.wiki.empty?
- []
- else
- Gitlab::WikiFileFinder.new(project, repository_wiki_ref).find(query, content_match_cutoff: limit)
- end
- else
- []
- end
- end
+ @wiki_blobs ||= if project.wiki_enabled? && query.present?
+ if project.wiki.empty?
+ []
+ else
+ Gitlab::WikiFileFinder.new(project, repository_wiki_ref).find(query, content_match_cutoff: limit)
+ end
+ else
+ []
+ end
end
def notes
@@ -195,3 +193,5 @@ module Gitlab
end
end
end
+
+Gitlab::ProjectSearchResults.prepend_mod_with('Gitlab::ProjectSearchResults')
diff --git a/lib/gitlab/project_template.rb b/lib/gitlab/project_template.rb
index 51a5bedc44b..9bc0001be81 100644
--- a/lib/gitlab/project_template.rb
+++ b/lib/gitlab/project_template.rb
@@ -60,6 +60,7 @@ module Gitlab
ProjectTemplate.new('dotnetcore', '.NET Core', _('A .NET Core console application template, customizable for any .NET Core project'), 'https://gitlab.com/gitlab-org/project-templates/dotnetcore', 'illustrations/third-party-logos/dotnet.svg'),
ProjectTemplate.new('android', 'Android', _('A ready-to-go template for use with Android apps'), 'https://gitlab.com/gitlab-org/project-templates/android', 'illustrations/logos/android.svg'),
ProjectTemplate.new('gomicro', 'Go Micro', _('Go Micro is a framework for micro service development'), 'https://gitlab.com/gitlab-org/project-templates/go-micro', 'illustrations/logos/gomicro.svg'),
+ ProjectTemplate.new('bridgetown', 'Pages/Bridgetown', _('Everything you need to create a GitLab Pages site using Bridgetown'), 'https://gitlab.com/gitlab-org/project-templates/bridgetown'),
ProjectTemplate.new('gatsby', 'Pages/Gatsby', _('Everything you need to create a GitLab Pages site using Gatsby'), 'https://gitlab.com/pages/gatsby', 'illustrations/third-party-logos/gatsby.svg'),
ProjectTemplate.new('hugo', 'Pages/Hugo', _('Everything you need to create a GitLab Pages site using Hugo'), 'https://gitlab.com/pages/hugo', 'illustrations/logos/hugo.svg'),
ProjectTemplate.new('pelican', 'Pages/Pelican', _('Everything you need to create a GitLab Pages site using Pelican'), 'https://gitlab.com/pages/pelican', 'illustrations/third-party-logos/pelican.svg'),
@@ -79,7 +80,8 @@ module Gitlab
ProjectTemplate.new('tencent_serverless_framework', 'Tencent Serverless Framework/NextjsSSR', _('A project boilerplate for Tencent Serverless Framework that uses Next.js SSR'), 'https://gitlab.com/gitlab-org/project-templates/nextjsssr_demo', 'illustrations/logos/tencent_serverless_framework.svg'),
ProjectTemplate.new('jsonnet', 'Jsonnet for Dynamic Child Pipelines', _('An example showing how to use Jsonnet with GitLab dynamic child pipelines'), 'https://gitlab.com/gitlab-org/project-templates/jsonnet'),
ProjectTemplate.new('cluster_management', 'GitLab Cluster Management', _('An example project for managing Kubernetes clusters integrated with GitLab'), 'https://gitlab.com/gitlab-org/project-templates/cluster-management'),
- ProjectTemplate.new('kotlin_native_linux', 'Kotlin Native Linux', _('A basic template for developing Linux programs using Kotlin Native'), 'https://gitlab.com/gitlab-org/project-templates/kotlin-native-linux')
+ ProjectTemplate.new('kotlin_native_linux', 'Kotlin Native Linux', _('A basic template for developing Linux programs using Kotlin Native'), 'https://gitlab.com/gitlab-org/project-templates/kotlin-native-linux'),
+ ProjectTemplate.new('typo3_distribution', 'TYPO3 Distribution', _('A template for starting a new TYPO3 project'), 'https://gitlab.com/ochorocho/typo3-distribution', 'illustrations/logos/typo3.svg')
].freeze
end
# rubocop:enable Metrics/AbcSize
diff --git a/lib/gitlab/prometheus_client.rb b/lib/gitlab/prometheus_client.rb
index dda28ffdf90..6a5613ddd98 100644
--- a/lib/gitlab/prometheus_client.rb
+++ b/lib/gitlab/prometheus_client.rb
@@ -150,7 +150,7 @@ module Gitlab
end
def get(path, args)
- Gitlab::HTTP.get(path, { query: args }.merge(http_options) )
+ Gitlab::HTTP.get(path, { query: args }.merge(http_options))
rescue *Gitlab::HTTP::HTTP_ERRORS => e
raise PrometheusClient::ConnectionError, e.message
end
diff --git a/lib/gitlab/quick_actions/issuable_actions.rb b/lib/gitlab/quick_actions/issuable_actions.rb
index 0b37c80dc5f..a12457d89c9 100644
--- a/lib/gitlab/quick_actions/issuable_actions.rb
+++ b/lib/gitlab/quick_actions/issuable_actions.rb
@@ -84,7 +84,7 @@ module Gitlab
current_user.can?(:"set_#{quick_action_target.to_ability_name}_metadata", quick_action_target) &&
find_labels.any?
end
- command :label do |labels_param|
+ command :label, :labels do |labels_param|
run_label_command(labels: find_labels(labels_param), command: :label, updates_key: :add_label_ids)
end
diff --git a/lib/gitlab/quick_actions/issue_actions.rb b/lib/gitlab/quick_actions/issue_actions.rb
index e74c58e45b1..14e9e66e037 100644
--- a/lib/gitlab/quick_actions/issue_actions.rb
+++ b/lib/gitlab/quick_actions/issue_actions.rb
@@ -80,7 +80,7 @@ module Gitlab
desc { _('Mark this issue as a duplicate of another issue') }
explanation do |duplicate_reference|
- _("Marks this issue as a duplicate of %{duplicate_reference}.") % { duplicate_reference: duplicate_reference }
+ _("Closes this issue. Marks as related to, and a duplicate of, %{duplicate_reference}.") % { duplicate_reference: duplicate_reference }
end
params '#issue'
types Issue
@@ -94,7 +94,7 @@ module Gitlab
if canonical_issue.present?
@updates[:canonical_issue_id] = canonical_issue.id
- message = _("Marked this issue as a duplicate of %{duplicate_param}.") % { duplicate_param: duplicate_param }
+ message = _("Closed this issue. Marked as related to, and a duplicate of, %{duplicate_param}.") % { duplicate_param: duplicate_param }
else
message = _('Failed to mark this issue as a duplicate because referenced issue was not found.')
end
diff --git a/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb b/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb
index 8b1ff5d298a..e549ee2e43a 100644
--- a/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb
+++ b/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb
@@ -92,7 +92,7 @@ module Gitlab
types Issue, MergeRequest
condition do
quick_action_target.supports_milestone? &&
- current_user.can?(:"set_#{quick_action_target.to_ability_name}_metadata", quick_action_target) &&
+ current_user.can?(:"set_#{quick_action_target.to_ability_name}_metadata", quick_action_target) &&
find_milestones(project, state: 'active').any?
end
parse_params do |milestone_param|
@@ -156,7 +156,7 @@ module Gitlab
types Issue, MergeRequest
condition do
quick_action_target.supports_time_tracking? &&
- current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
end
parse_params do |raw_duration|
Gitlab::TimeTrackingFormatter.parse(raw_duration)
@@ -179,7 +179,7 @@ module Gitlab
types Issue, MergeRequest
condition do
quick_action_target.supports_time_tracking? &&
- current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target)
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target)
end
parse_params do |raw_time_date|
Gitlab::QuickActions::SpendTimeAndDateSeparator.new(raw_time_date).execute
diff --git a/lib/gitlab/rack_attack.rb b/lib/gitlab/rack_attack.rb
index f5fb6b5af3d..bedbe9c0bff 100644
--- a/lib/gitlab/rack_attack.rb
+++ b/lib/gitlab/rack_attack.rb
@@ -49,7 +49,7 @@ module Gitlab
#
# - Retry-After: the remaining duration in seconds until the quota is
# reset. This is a standardized HTTP header:
- # https://tools.ietf.org/html/rfc7231#page-69
+ # https://www.rfc-editor.org/rfc/rfc7231#page-69
#
# - RateLimit-Reset: the point of time that the request quota is reset, in Unix time
#
diff --git a/lib/gitlab/rack_attack/request.rb b/lib/gitlab/rack_attack/request.rb
index 08a5ddb6ad1..d7abacb5b67 100644
--- a/lib/gitlab/rack_attack/request.rb
+++ b/lib/gitlab/rack_attack/request.rb
@@ -79,96 +79,96 @@ module Gitlab
def throttle_unauthenticated_api?
api_request? &&
- !should_be_skipped? &&
- !frontend_request? &&
- !throttle_unauthenticated_packages_api? &&
- !throttle_unauthenticated_files_api? &&
- !throttle_unauthenticated_deprecated_api? &&
- Gitlab::Throttle.settings.throttle_unauthenticated_api_enabled &&
- unauthenticated?
+ !should_be_skipped? &&
+ !frontend_request? &&
+ !throttle_unauthenticated_packages_api? &&
+ !throttle_unauthenticated_files_api? &&
+ !throttle_unauthenticated_deprecated_api? &&
+ Gitlab::Throttle.settings.throttle_unauthenticated_api_enabled &&
+ unauthenticated?
end
def throttle_unauthenticated_web?
(web_request? || frontend_request?) &&
- !should_be_skipped? &&
- # TODO: Column will be renamed in https://gitlab.com/gitlab-org/gitlab/-/issues/340031
- Gitlab::Throttle.settings.throttle_unauthenticated_enabled &&
- unauthenticated?
+ !should_be_skipped? &&
+ # TODO: Column will be renamed in https://gitlab.com/gitlab-org/gitlab/-/issues/340031
+ Gitlab::Throttle.settings.throttle_unauthenticated_enabled &&
+ unauthenticated?
end
def throttle_authenticated_api?
api_request? &&
- !frontend_request? &&
- !throttle_authenticated_packages_api? &&
- !throttle_authenticated_files_api? &&
- !throttle_authenticated_deprecated_api? &&
- Gitlab::Throttle.settings.throttle_authenticated_api_enabled
+ !frontend_request? &&
+ !throttle_authenticated_packages_api? &&
+ !throttle_authenticated_files_api? &&
+ !throttle_authenticated_deprecated_api? &&
+ Gitlab::Throttle.settings.throttle_authenticated_api_enabled
end
def throttle_authenticated_web?
(web_request? || frontend_request?) &&
- !throttle_authenticated_git_lfs? &&
- Gitlab::Throttle.settings.throttle_authenticated_web_enabled
+ !throttle_authenticated_git_lfs? &&
+ Gitlab::Throttle.settings.throttle_authenticated_web_enabled
end
def throttle_unauthenticated_protected_paths?
post? &&
- !should_be_skipped? &&
- protected_path? &&
- Gitlab::Throttle.protected_paths_enabled? &&
- unauthenticated?
+ !should_be_skipped? &&
+ protected_path? &&
+ Gitlab::Throttle.protected_paths_enabled? &&
+ unauthenticated?
end
def throttle_authenticated_protected_paths_api?
post? &&
- api_request? &&
- protected_path? &&
- Gitlab::Throttle.protected_paths_enabled?
+ api_request? &&
+ protected_path? &&
+ Gitlab::Throttle.protected_paths_enabled?
end
def throttle_authenticated_protected_paths_web?
post? &&
- web_request? &&
- protected_path? &&
- Gitlab::Throttle.protected_paths_enabled?
+ web_request? &&
+ protected_path? &&
+ Gitlab::Throttle.protected_paths_enabled?
end
def throttle_unauthenticated_packages_api?
packages_api_path? &&
- Gitlab::Throttle.settings.throttle_unauthenticated_packages_api_enabled &&
- unauthenticated?
+ Gitlab::Throttle.settings.throttle_unauthenticated_packages_api_enabled &&
+ unauthenticated?
end
def throttle_authenticated_packages_api?
packages_api_path? &&
- Gitlab::Throttle.settings.throttle_authenticated_packages_api_enabled
+ Gitlab::Throttle.settings.throttle_authenticated_packages_api_enabled
end
def throttle_authenticated_git_lfs?
git_lfs_path? &&
- Gitlab::Throttle.settings.throttle_authenticated_git_lfs_enabled
+ Gitlab::Throttle.settings.throttle_authenticated_git_lfs_enabled
end
def throttle_unauthenticated_files_api?
files_api_path? &&
- Gitlab::Throttle.settings.throttle_unauthenticated_files_api_enabled &&
- unauthenticated?
+ Gitlab::Throttle.settings.throttle_unauthenticated_files_api_enabled &&
+ unauthenticated?
end
def throttle_authenticated_files_api?
files_api_path? &&
- Gitlab::Throttle.settings.throttle_authenticated_files_api_enabled
+ Gitlab::Throttle.settings.throttle_authenticated_files_api_enabled
end
def throttle_unauthenticated_deprecated_api?
deprecated_api_request? &&
- Gitlab::Throttle.settings.throttle_unauthenticated_deprecated_api_enabled &&
- unauthenticated?
+ Gitlab::Throttle.settings.throttle_unauthenticated_deprecated_api_enabled &&
+ unauthenticated?
end
def throttle_authenticated_deprecated_api?
deprecated_api_request? &&
- Gitlab::Throttle.settings.throttle_authenticated_deprecated_api_enabled
+ Gitlab::Throttle.settings.throttle_authenticated_deprecated_api_enabled
end
private
diff --git a/lib/gitlab/redis/multi_store.rb b/lib/gitlab/redis/multi_store.rb
index 12cb1fc6153..4f58bee49d0 100644
--- a/lib/gitlab/redis/multi_store.rb
+++ b/lib/gitlab/redis/multi_store.rb
@@ -10,6 +10,7 @@ module Gitlab
'Value not found on the redis primary store. Read from the redis secondary store successful.'
end
end
+
class PipelinedDiffError < StandardError
def initialize(result_primary, result_secondary)
@result_primary = result_primary
@@ -22,6 +23,7 @@ module Gitlab
"Result from the secondary: #{@result_secondary.inspect}."
end
end
+
class MethodMissingError < StandardError
def message
'Method missing. Falling back to execute method on the redis secondary store.'
diff --git a/lib/gitlab/redis/wrapper.rb b/lib/gitlab/redis/wrapper.rb
index 75dbccb965d..0e5389dc995 100644
--- a/lib/gitlab/redis/wrapper.rb
+++ b/lib/gitlab/redis/wrapper.rb
@@ -144,11 +144,20 @@ module Gitlab
def redis_store_options
config = raw_config_hash
+ config[:instrumentation_class] ||= self.class.instrumentation_class
+
+ if config[:cluster].present?
+ config[:db] = 0 # Redis Cluster only supports db 0
+ config
+ else
+ parse_redis_url(config)
+ end
+ end
+
+ def parse_redis_url(config)
redis_url = config.delete(:url)
redis_uri = URI.parse(redis_url)
- config[:instrumentation_class] ||= self.class.instrumentation_class
-
if redis_uri.scheme == 'unix'
# Redis::Store does not handle Unix sockets well, so let's do it for them
config[:path] = redis_uri.path
@@ -178,7 +187,7 @@ module Gitlab
{ url: '' }
end
- if config_hash[:url].blank?
+ if config_hash[:url].blank? && config_hash[:cluster].blank?
config_hash[:url] = legacy_fallback_urls[self.class.store_name] || legacy_fallback_urls[self.class.config_fallback.store_name]
end
diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb
index c5798bec0d7..540394f04bd 100644
--- a/lib/gitlab/reference_extractor.rb
+++ b/lib/gitlab/reference_extractor.rb
@@ -4,7 +4,8 @@ module Gitlab
# Extract possible GFM references from an arbitrary String for further processing.
class ReferenceExtractor < Banzai::ReferenceExtractor
REFERABLES = %i(user issue label milestone mentioned_user mentioned_group mentioned_project
- merge_request snippet commit commit_range directly_addressed_user epic iteration vulnerability).freeze
+ merge_request snippet commit commit_range directly_addressed_user epic iteration vulnerability
+ alert).freeze
attr_accessor :project, :current_user, :author
def initialize(project, current_user = nil)
@@ -71,7 +72,7 @@ module Gitlab
return @pattern if @pattern
patterns = REFERABLES.map do |type|
- Banzai::ReferenceParser[type].reference_type.to_s.classify.constantize.try(:reference_pattern)
+ Banzai::ReferenceParser[type].reference_class.try(:reference_pattern)
end.uniq
@pattern = Regexp.union(patterns.compact)
diff --git a/lib/gitlab/repository_size_error_message.rb b/lib/gitlab/repository_size_error_message.rb
index 8da840779c9..f5d82e61187 100644
--- a/lib/gitlab/repository_size_error_message.rb
+++ b/lib/gitlab/repository_size_error_message.rb
@@ -6,7 +6,7 @@ module Gitlab
delegate :current_size, :limit, :exceeded_size, :additional_repo_storage_available?, to: :@checker
- # @param checher [RepositorySizeChecker]
+ # @param checker [RepositorySizeChecker]
def initialize(checker)
@checker = checker
end
diff --git a/lib/gitlab/safe_request_store.rb b/lib/gitlab/safe_request_store.rb
index 664afd1cc21..203d7d10532 100644
--- a/lib/gitlab/safe_request_store.rb
+++ b/lib/gitlab/safe_request_store.rb
@@ -40,7 +40,7 @@ module Gitlab
def self.delete_if(&block)
return unless RequestStore.active?
- storage.delete_if { |k, v| block.call(k) }
+ storage.delete_if { |k, v| yield(k) }
end
end
end
diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb
index bc59d4ce943..ba822955133 100644
--- a/lib/gitlab/shell.rb
+++ b/lib/gitlab/shell.rb
@@ -24,9 +24,7 @@ module Gitlab
#
# @return [String] secret token
def secret_token
- @secret_token ||= begin
- File.read(Gitlab.config.gitlab_shell.secret_file).chomp
- end
+ @secret_token ||= File.read(Gitlab.config.gitlab_shell.secret_file).chomp
end
# Ensure gitlab shell has a secret token stored in the secret_file
diff --git a/lib/gitlab/sidekiq_daemon/memory_killer.rb b/lib/gitlab/sidekiq_daemon/memory_killer.rb
index d5227e7a007..4bf9fd8470a 100644
--- a/lib/gitlab/sidekiq_daemon/memory_killer.rb
+++ b/lib/gitlab/sidekiq_daemon/memory_killer.rb
@@ -35,6 +35,7 @@ module Gitlab
@enabled = true
@metrics = init_metrics
+ @sidekiq_daemon_monitor = Gitlab::SidekiqDaemon::Monitor.instance
end
private
@@ -78,7 +79,7 @@ module Gitlab
rescue StandardError => e
log_exception(e, __method__)
rescue Exception => e # rubocop:disable Lint/RescueException
- log_exception(e, __method__ )
+ log_exception(e, __method__)
raise e
end
end
@@ -188,22 +189,17 @@ module Gitlab
def increment_worker_counters(running_jobs, deadline_exceeded)
running_jobs.each do |job|
- @metrics[:sidekiq_memory_killer_running_jobs].increment( { worker_class: job[:worker_class], deadline_exceeded: deadline_exceeded } )
+ @metrics[:sidekiq_memory_killer_running_jobs].increment({ worker_class: job[:worker_class], deadline_exceeded: deadline_exceeded })
end
end
def fetch_running_jobs
- jobs = []
- Gitlab::SidekiqDaemon::Monitor.instance.jobs_mutex.synchronize do
- jobs = Gitlab::SidekiqDaemon::Monitor.instance.jobs.map do |jid, job|
- {
- jid: jid,
- worker_class: job[:worker_class].name
- }
- end
+ @sidekiq_daemon_monitor.jobs.map do |jid, job|
+ {
+ jid: jid,
+ worker_class: job[:worker_class].name
+ }
end
-
- jobs
end
def out_of_range_description(rss, hard_limit, soft_limit, deadline_exceeded)
@@ -269,10 +265,8 @@ module Gitlab
end
def rss_increase_by_jobs
- Gitlab::SidekiqDaemon::Monitor.instance.jobs_mutex.synchronize do
- Gitlab::SidekiqDaemon::Monitor.instance.jobs.sum do |job|
- rss_increase_by_job(job)
- end
+ @sidekiq_daemon_monitor.jobs.sum do |_, job|
+ rss_increase_by_job(job)
end
end
@@ -297,7 +291,7 @@ module Gitlab
end
def any_jobs?
- Gitlab::SidekiqDaemon::Monitor.instance.jobs.any?
+ @sidekiq_daemon_monitor.jobs.any?
end
end
end
diff --git a/lib/gitlab/sidekiq_daemon/monitor.rb b/lib/gitlab/sidekiq_daemon/monitor.rb
index 655e95c82d3..125402c1e4b 100644
--- a/lib/gitlab/sidekiq_daemon/monitor.rb
+++ b/lib/gitlab/sidekiq_daemon/monitor.rb
@@ -15,9 +15,6 @@ module Gitlab
# that should not be caught by application
CancelledError = Class.new(Exception) # rubocop:disable Lint/InheritException
- attr_reader :jobs
- attr_reader :jobs_mutex
-
def initialize
super
@@ -31,8 +28,8 @@ module Gitlab
end
def within_job(worker_class, jid, queue)
- jobs_mutex.synchronize do
- jobs[jid] = { worker_class: worker_class, thread: Thread.current, started_at: Gitlab::Metrics::System.monotonic_time }
+ @jobs_mutex.synchronize do
+ @jobs[jid] = { worker_class: worker_class, thread: Thread.current, started_at: Gitlab::Metrics::System.monotonic_time }
end
if cancelled?(jid)
@@ -48,8 +45,8 @@ module Gitlab
yield
ensure
- jobs_mutex.synchronize do
- jobs.delete(jid)
+ @jobs_mutex.synchronize do
+ @jobs.delete(jid)
end
end
@@ -65,6 +62,12 @@ module Gitlab
end
end
+ def jobs
+ @jobs_mutex.synchronize do
+ @jobs.dup
+ end
+ end
+
private
def run_thread
@@ -166,14 +169,14 @@ module Gitlab
# This is why it passes thread in block,
# to ensure that we do process this thread
def find_thread_unsafe(jid)
- jobs.dig(jid, :thread)
+ @jobs.dig(jid, :thread)
end
def find_thread_with_lock(jid)
# don't try to lock if we cannot find the thread
return unless find_thread_unsafe(jid)
- jobs_mutex.synchronize do
+ @jobs_mutex.synchronize do
find_thread_unsafe(jid).tap do |thread|
yield(thread) if thread
end
diff --git a/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job.rb b/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job.rb
index 357e9d41187..4f7cd340461 100644
--- a/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job.rb
+++ b/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job.rb
@@ -253,7 +253,7 @@ module Gitlab
def with_redis
if Feature.enabled?(:use_primary_and_secondary_stores_for_duplicate_jobs) ||
- Feature.enabled?(:use_primary_store_as_default_for_duplicate_jobs)
+ Feature.enabled?(:use_primary_store_as_default_for_duplicate_jobs)
# TODO: Swap for Gitlab::Redis::SharedState after store transition
# https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/923
Gitlab::Redis::DuplicateJobs.with { |redis| yield redis }
diff --git a/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies.rb b/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies.rb
index 63e8bee4443..fc6a849da95 100644
--- a/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies.rb
+++ b/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies.rb
@@ -7,9 +7,9 @@ module Gitlab
UnknownStrategyError = Class.new(StandardError)
STRATEGIES = {
- until_executing: UntilExecuting,
- until_executed: UntilExecuted,
- none: None
+ until_executing: ::Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies::UntilExecuting,
+ until_executed: ::Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies::UntilExecuted,
+ none: ::Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies::None
}.freeze
def self.for(name)
diff --git a/lib/gitlab/sidekiq_status.rb b/lib/gitlab/sidekiq_status.rb
index 17234bdf519..778d278146d 100644
--- a/lib/gitlab/sidekiq_status.rb
+++ b/lib/gitlab/sidekiq_status.rb
@@ -120,7 +120,7 @@ module Gitlab
def self.with_redis
if Feature.enabled?(:use_primary_and_secondary_stores_for_sidekiq_status) ||
- Feature.enabled?(:use_primary_store_as_default_for_sidekiq_status)
+ Feature.enabled?(:use_primary_store_as_default_for_sidekiq_status)
# TODO: Swap for Gitlab::Redis::SharedState after store transition
# https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/923
Gitlab::Redis::SidekiqStatus.with { |redis| yield redis }
diff --git a/lib/gitlab/slash_commands/application_help.rb b/lib/gitlab/slash_commands/application_help.rb
index bfdb65a816d..94abc8b4508 100644
--- a/lib/gitlab/slash_commands/application_help.rb
+++ b/lib/gitlab/slash_commands/application_help.rb
@@ -3,6 +3,11 @@
module Gitlab
module SlashCommands
class ApplicationHelp < BaseCommand
+ def initialize(project, params)
+ @project = project
+ @params = params
+ end
+
def execute
Gitlab::SlashCommands::Presenters::Help
.new(project, commands, params)
@@ -16,11 +21,7 @@ module Gitlab
end
def commands
- Gitlab::SlashCommands::Command.new(
- project,
- chat_name,
- params
- ).commands
+ Gitlab::SlashCommands::Command.commands
end
end
end
diff --git a/lib/gitlab/slash_commands/command.rb b/lib/gitlab/slash_commands/command.rb
index 265eda46489..f8b55f1a91d 100644
--- a/lib/gitlab/slash_commands/command.rb
+++ b/lib/gitlab/slash_commands/command.rb
@@ -3,7 +3,7 @@
module Gitlab
module SlashCommands
class Command < BaseCommand
- def commands
+ def self.commands
commands = [
Gitlab::SlashCommands::IssueShow,
Gitlab::SlashCommands::IssueNew,
@@ -15,7 +15,7 @@ module Gitlab
Gitlab::SlashCommands::Run
]
- if Feature.enabled?(:incident_declare_slash_command, current_user)
+ if Feature.enabled?(:incident_declare_slash_command)
commands << Gitlab::SlashCommands::IncidentManagement::IncidentNew
end
@@ -50,7 +50,7 @@ module Gitlab
private
def available_commands
- commands.keep_if do |klass|
+ self.class.commands.keep_if do |klass|
klass.available?(project)
end
end
diff --git a/lib/gitlab/slash_commands/deploy.rb b/lib/gitlab/slash_commands/deploy.rb
index 9fcefd99f81..16a4875be91 100644
--- a/lib/gitlab/slash_commands/deploy.rb
+++ b/lib/gitlab/slash_commands/deploy.rb
@@ -54,7 +54,7 @@ module Gitlab
return unless environment
actions = environment.actions_for(to).select do |action|
- action.starts_environment?
+ action.deployment_job?
end
if actions.many?
diff --git a/lib/gitlab/sql/pattern.rb b/lib/gitlab/sql/pattern.rb
index d13ccde8576..cd5587bbaef 100644
--- a/lib/gitlab/sql/pattern.rb
+++ b/lib/gitlab/sql/pattern.rb
@@ -51,16 +51,14 @@ module Gitlab
if words.any?
words.map { |word| arel_column.matches(to_pattern(word, use_minimum_char_limit: use_minimum_char_limit)) }.reduce(:and)
- else
+ elsif lower_exact_match
# No words of at least 3 chars, but we can search for an exact
# case insensitive match with the query as a whole
- if lower_exact_match
- Arel::Nodes::NamedFunction
+ Arel::Nodes::NamedFunction
.new('LOWER', [arel_column])
.eq(query)
- else
- arel_column.matches(sanitize_sql_like(query))
- end
+ else
+ arel_column.matches(sanitize_sql_like(query))
end
end
diff --git a/lib/gitlab/ssh/signature.rb b/lib/gitlab/ssh/signature.rb
index 3b4df9a8d0c..a654d5b2ff1 100644
--- a/lib/gitlab/ssh/signature.rb
+++ b/lib/gitlab/ssh/signature.rb
@@ -9,6 +9,8 @@ module Gitlab
class Signature
include Gitlab::Utils::StrongMemoize
+ GIT_NAMESPACE = 'git'
+
def initialize(signature_text, signed_text, committer_email)
@signature_text = signature_text
@signed_text = signed_text
@@ -18,11 +20,16 @@ module Gitlab
def verification_status
strong_memoize(:verification_status) do
next :unverified unless all_attributes_present?
- next :unverified unless valid_signature_blob? && committer
+ next :unverified unless valid_signature_blob?
next :unknown_key unless signed_by_key
+ next :other_user unless committer
next :other_user unless signed_by_key.user == committer
- :verified
+ if signed_by_user_email_verified?
+ :verified
+ else
+ :unverified
+ end
end
end
@@ -30,7 +37,7 @@ module Gitlab
strong_memoize(:signed_by_key) do
next unless key_fingerprint
- Key.find_by_fingerprint_sha256(key_fingerprint)
+ Key.signing.find_by_fingerprint_sha256(key_fingerprint)
end
end
@@ -48,6 +55,7 @@ module Gitlab
# still need to check that the key belongs to the committer.
def valid_signature_blob?
return false unless signature
+ return false unless signature.namespace == GIT_NAMESPACE
signature.verify(@signed_text)
end
@@ -55,7 +63,11 @@ module Gitlab
def committer
# Lookup by email because users can push verified commits that were made
# by someone else. For example: Doing a rebase.
- strong_memoize(:committer) { User.find_by_any_email(@committer_email, confirmed: true) }
+ strong_memoize(:committer) { User.find_by_any_email(@committer_email) }
+ end
+
+ def signed_by_user_email_verified?
+ signed_by_key.user.verified_emails.include?(@committer_email)
end
def signature
diff --git a/lib/gitlab/task_helpers.rb b/lib/gitlab/task_helpers.rb
index 54db31ffd6c..9dba8c99b99 100644
--- a/lib/gitlab/task_helpers.rb
+++ b/lib/gitlab/task_helpers.rb
@@ -149,18 +149,6 @@ module Gitlab
end
end
- def all_repos
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- Gitlab.config.repositories.storages.each_value do |repository_storage|
- IO.popen(%W(find #{repository_storage.legacy_disk_path} -mindepth 2 -type d -name *.git)) do |find|
- find.each_line do |path|
- yield path.chomp
- end
- end
- end
- end
- end
-
def repository_storage_paths_args
Gitlab::GitalyClient::StorageSettings.allow_disk_access do
Gitlab.config.repositories.storages.values.map { |rs| rs.legacy_disk_path }
diff --git a/lib/gitlab/template/base_template.rb b/lib/gitlab/template/base_template.rb
index ededc3db18e..223e3d40751 100644
--- a/lib/gitlab/template/base_template.rb
+++ b/lib/gitlab/template/base_template.rb
@@ -121,8 +121,8 @@ module Gitlab
grouped = items.group_by(&:category)
categories = grouped.keys
- categories.each_with_object({}) do |category, hash|
- hash[category] = grouped[category].map do |item|
+ categories.index_with do |category|
+ grouped[category].map do |item|
{ name: item.name, id: item.key, key: item.key, project_id: item.try(:project_id) }
end
end
diff --git a/lib/gitlab/timeless.rb b/lib/gitlab/timeless.rb
index b72d33113dd..ed0b7b4ed87 100644
--- a/lib/gitlab/timeless.rb
+++ b/lib/gitlab/timeless.rb
@@ -8,9 +8,9 @@ module Gitlab
# negative arity means arguments are optional
if block.arity == 1 || block.arity < 0
- block.call(model)
+ yield(model)
else
- block.call
+ yield
end
ensure
diff --git a/lib/gitlab/tracking/destinations/snowplow.rb b/lib/gitlab/tracking/destinations/snowplow.rb
index ddcd4693738..fd877bc0137 100644
--- a/lib/gitlab/tracking/destinations/snowplow.rb
+++ b/lib/gitlab/tracking/destinations/snowplow.rb
@@ -14,7 +14,15 @@ module Gitlab
def event(category, action, label: nil, property: nil, value: nil, context: nil)
return unless enabled?
- tracker.track_struct_event(category, action, label, property, value, context, (Time.now.to_f * 1000).to_i)
+ tracker.track_struct_event(
+ category: category,
+ action: action,
+ label: label,
+ property: property,
+ value: value,
+ context: context,
+ tstamp: (Time.now.to_f * 1000).to_i
+ )
increment_total_events_counter
end
@@ -54,19 +62,21 @@ module Gitlab
def tracker
@tracker ||= SnowplowTracker::Tracker.new(
- emitter,
- SnowplowTracker::Subject.new,
- SNOWPLOW_NAMESPACE,
- app_id
+ emitters: [emitter],
+ subject: SnowplowTracker::Subject.new,
+ namespace: SNOWPLOW_NAMESPACE,
+ app_id: app_id
)
end
def emitter
SnowplowTracker::AsyncEmitter.new(
- hostname,
- protocol: protocol,
- on_success: method(:increment_successful_events_emissions),
- on_failure: method(:failure_callback)
+ endpoint: hostname,
+ options: {
+ protocol: protocol,
+ on_success: method(:increment_successful_events_emissions),
+ on_failure: method(:failure_callback)
+ }
)
end
diff --git a/lib/gitlab/tracking/incident_management.rb b/lib/gitlab/tracking/incident_management.rb
index df2a0658b36..a912fdbaeca 100644
--- a/lib/gitlab/tracking/incident_management.rb
+++ b/lib/gitlab/tracking/incident_management.rb
@@ -17,7 +17,7 @@ module Gitlab
details = label ? { label: label, property: v } : {}
- ::Gitlab::Tracking.event('IncidentManagement::Settings', "#{prefix}_#{key}", **details )
+ ::Gitlab::Tracking.event('IncidentManagement::Settings', "#{prefix}_#{key}", **details)
end
end
diff --git a/lib/gitlab/tracking/service_ping_context.rb b/lib/gitlab/tracking/service_ping_context.rb
index 393cd647e7f..d31ca69a10c 100644
--- a/lib/gitlab/tracking/service_ping_context.rb
+++ b/lib/gitlab/tracking/service_ping_context.rb
@@ -4,21 +4,51 @@ module Gitlab
module Tracking
class ServicePingContext
SCHEMA_URL = 'iglu:com.gitlab/gitlab_service_ping/jsonschema/1-0-0'
- ALLOWED_SOURCES = %i[redis_hll].freeze
+ REDISHLL_SOURCE = :redis_hll
+ REDIS_SOURCE = :redis
- def initialize(data_source:, event:)
+ ALLOWED_SOURCES = [REDISHLL_SOURCE, REDIS_SOURCE].freeze
+
+ def initialize(data_source:, event: nil, key_path: nil)
+ check_configuration(data_source, event, key_path)
+
+ @payload = { data_source: data_source }
+
+ payload[:event_name] = event if data_source.eql? REDISHLL_SOURCE
+ payload[:key_path] = key_path if data_source.eql? REDIS_SOURCE
+ end
+
+ def to_context
+ SnowplowTracker::SelfDescribingJson.new(SCHEMA_URL, payload)
+ end
+
+ def to_h
+ {
+ schema: SCHEMA_URL,
+ data: @payload
+ }
+ end
+
+ private
+
+ attr_reader :payload
+
+ def check_configuration(data_source, event, key_path)
unless ALLOWED_SOURCES.include?(data_source)
- raise ArgumentError, "#{data_source} is not acceptable data source for ServicePingContext"
+ configuration_error("#{data_source} is not acceptable data source for ServicePingContext")
end
- @payload = {
- data_source: data_source,
- event_name: event
- }
+ if REDISHLL_SOURCE.eql?(data_source) && event.nil?
+ configuration_error("event attribute can not be missing for #{REDISHLL_SOURCE} data source")
+ end
+
+ return unless REDIS_SOURCE.eql?(data_source) && key_path.nil?
+
+ configuration_error("key_path attribute can not be missing for #{REDIS_SOURCE} data source")
end
- def to_context
- SnowplowTracker::SelfDescribingJson.new(SCHEMA_URL, @payload)
+ def configuration_error(message)
+ Gitlab::ErrorTracking.track_and_raise_for_dev_exception(ArgumentError.new(message))
end
end
end
diff --git a/lib/gitlab/url_blocker.rb b/lib/gitlab/url_blocker.rb
index 1e447923a39..00e609511f2 100644
--- a/lib/gitlab/url_blocker.rb
+++ b/lib/gitlab/url_blocker.rb
@@ -22,8 +22,8 @@ module Gitlab
# rubocop:disable Metrics/ParameterLists
def validate!(
url,
+ schemes:,
ports: [],
- schemes: [],
allow_localhost: false,
allow_local_network: true,
allow_object_storage: false,
@@ -35,6 +35,8 @@ module Gitlab
return [nil, nil] if url.nil?
+ raise ArgumentError, 'The schemes is a required argument' if schemes.blank?
+
# Param url can be a string, URI or Addressable::URI
uri = parse_url(url)
@@ -204,7 +206,7 @@ module Gitlab
end
def validate_scheme(scheme, schemes)
- if scheme.blank? || (schemes.any? && !schemes.include?(scheme))
+ if scheme.blank? || (schemes.any? && schemes.exclude?(scheme))
raise BlockedUrlError, "Only allowed schemes are #{schemes.join(', ')}"
end
end
diff --git a/lib/gitlab/usage/metrics/aggregates.rb b/lib/gitlab/usage/metrics/aggregates.rb
index 02d9fa74289..4b38809dde4 100644
--- a/lib/gitlab/usage/metrics/aggregates.rb
+++ b/lib/gitlab/usage/metrics/aggregates.rb
@@ -11,6 +11,7 @@ module Gitlab
UnknownAggregationOperator = Class.new(AggregatedMetricError)
UnknownAggregationSource = Class.new(AggregatedMetricError)
DisallowedAggregationTimeFrame = Class.new(AggregatedMetricError)
+ UndefinedEvents = Class.new(AggregatedMetricError)
DATABASE_SOURCE = 'database'
REDIS_SOURCE = 'redis_hll'
diff --git a/lib/gitlab/usage/metrics/aggregates/aggregate.rb b/lib/gitlab/usage/metrics/aggregates/aggregate.rb
index 78f1ddc8a29..8d816c8d902 100644
--- a/lib/gitlab/usage/metrics/aggregates/aggregate.rb
+++ b/lib/gitlab/usage/metrics/aggregates/aggregate.rb
@@ -76,3 +76,5 @@ module Gitlab
end
end
end
+
+Gitlab::Usage::Metrics::Aggregates::Aggregate.prepend_mod
diff --git a/lib/gitlab/usage/metrics/aggregates/sources/calculations/intersection.rb b/lib/gitlab/usage/metrics/aggregates/sources/calculations/intersection.rb
index dabf757c8a7..c8c248905f7 100644
--- a/lib/gitlab/usage/metrics/aggregates/sources/calculations/intersection.rb
+++ b/lib/gitlab/usage/metrics/aggregates/sources/calculations/intersection.rb
@@ -18,10 +18,8 @@ module Gitlab
subset_powers_data = subsets_intersection_powers(metric_names, start_date, end_date, recorded_at, subset_powers_cache)
# calculate last component of the equation |A & B & C & D| = .... - |A + B + C + D|
- power_of_union_of_all_metrics = begin
- subset_powers_cache[metric_names.size][metric_names.join('_+_')] ||= \
- calculate_metrics_union(metric_names: metric_names, start_date: start_date, end_date: end_date, recorded_at: recorded_at)
- end
+ power_of_union_of_all_metrics = subset_powers_cache[metric_names.size][metric_names.join('_+_')] ||= \
+ calculate_metrics_union(metric_names: metric_names, start_date: start_date, end_date: end_date, recorded_at: recorded_at)
# in order to determine if part of equation (|A & B & C|, |A & B & C & D|), that represents the intersection that we need to calculate,
# is positive or negative in particular equation we need to determine if number of subsets is even or odd. Please take a look at two examples below
diff --git a/lib/gitlab/usage/metrics/instrumentations/count_merge_request_authors_metric.rb b/lib/gitlab/usage/metrics/instrumentations/count_merge_request_authors_metric.rb
deleted file mode 100644
index a7f8bca8e08..00000000000
--- a/lib/gitlab/usage/metrics/instrumentations/count_merge_request_authors_metric.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Usage
- module Metrics
- module Instrumentations
- class CountMergeRequestAuthorsMetric < DatabaseMetric
- operation :distinct_count, column: :author_id
-
- relation { MergeRequest }
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/usage/metrics/instrumentations/database_metric.rb b/lib/gitlab/usage/metrics/instrumentations/database_metric.rb
index f0d5298870c..f731057309e 100644
--- a/lib/gitlab/usage/metrics/instrumentations/database_metric.rb
+++ b/lib/gitlab/usage/metrics/instrumentations/database_metric.rb
@@ -117,6 +117,8 @@ module Gitlab
case time_frame
when '28d'
monthly_time_range_db_params(column: self.class.metric_timestamp_column)
+ when '7d'
+ weekly_time_range_db_params(column: self.class.metric_timestamp_column)
when 'all'
{}
when 'none'
diff --git a/lib/gitlab/usage/service_ping/payload_keys_processor.rb b/lib/gitlab/usage/service_ping/payload_keys_processor.rb
index ea2043ffb83..89931d8c012 100644
--- a/lib/gitlab/usage/service_ping/payload_keys_processor.rb
+++ b/lib/gitlab/usage/service_ping/payload_keys_processor.rb
@@ -28,8 +28,8 @@ module Gitlab
payload.map do |key, value|
if has_metric_definition?(key, parents)
parents.dup.append(key).join('.')
- else
- payload_keys(value, parents.dup << key) if value.is_a?(Hash)
+ elsif value.is_a?(Hash)
+ payload_keys(value, parents.dup << key)
end
end
end
diff --git a/lib/gitlab/usage/time_frame.rb b/lib/gitlab/usage/time_frame.rb
index 39b0855b917..e2eed969200 100644
--- a/lib/gitlab/usage/time_frame.rb
+++ b/lib/gitlab/usage/time_frame.rb
@@ -21,6 +21,10 @@ module Gitlab
def monthly_time_range_db_params(column: nil)
{ (column || DEFAULT_TIMESTAMP_COLUMN) => 30.days.ago..2.days.ago }
end
+
+ def weekly_time_range_db_params(column: nil)
+ { (column || DEFAULT_TIMESTAMP_COLUMN) => 9.days.ago..2.days.ago }
+ end
end
end
end
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index 5021dac453f..24f6cc725f6 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -157,7 +157,6 @@ module Gitlab
runners_usage,
integrations_usage,
user_preferences_usage,
- container_expiration_policies_usage,
service_desk_counts
).tap do |data|
data[:snippets] = add(data[:personal_snippets], data[:project_snippets])
@@ -300,7 +299,7 @@ module Gitlab
object_store: {
enabled: alt_usage_data { config['enabled'] },
direct_upload: alt_usage_data { config['direct_upload'] },
- background_upload: alt_usage_data { config['background_upload'] },
+ background_upload: alt_usage_data { false }, # This setting no longer exists
provider: alt_usage_data { config['connection']['provider'] }
}
}
@@ -328,26 +327,6 @@ module Gitlab
end
# rubocop: disable CodeReuse/ActiveRecord
- def container_expiration_policies_usage
- results = {}
- start = minimum_id(Project)
- finish = maximum_id(Project)
-
- # rubocop: disable UsageData/LargeTable
- base = ::ContainerExpirationPolicy.active
- # rubocop: enable UsageData/LargeTable
-
- # rubocop: disable UsageData/LargeTable
- ::ContainerExpirationPolicy.older_than_options.keys.each do |value|
- results["projects_with_expiration_policy_enabled_with_older_than_set_to_#{value}".to_sym] = distinct_count(base.where(older_than: value), :project_id, start: start, finish: finish)
- end
- # rubocop: enable UsageData/LargeTable
-
- results[:projects_with_expiration_policy_enabled_with_older_than_unset] = distinct_count(base.where(older_than: nil), :project_id, start: start, finish: finish)
-
- results
- end
-
def integrations_usage
# rubocop: disable UsageData/LargeTable:
Integration.available_integration_names(include_dev: false).each_with_object({}) do |name, response|
@@ -611,10 +590,6 @@ module Gitlab
{}
end
- def redis_hll_counters
- { redis_hll_counters: ::Gitlab::UsageDataCounters::HLLRedisCounter.unique_events_data }
- end
-
def action_monthly_active_users(time_period)
counter = Gitlab::UsageDataCounters::EditorUniqueCounter
date_range = { date_from: time_period[:created_at].first, date_to: time_period[:created_at].last }
@@ -665,7 +640,6 @@ module Gitlab
.merge(topology_usage_data)
.merge(usage_activity_by_stage)
.merge(usage_activity_by_stage(:usage_activity_by_stage_monthly, monthly_time_range_db_params))
- .merge(redis_hll_counters)
end
def metric_time_period(time_period)
@@ -794,8 +768,8 @@ module Gitlab
# rubocop:disable CodeReuse/ActiveRecord
def distinct_count_user_auth_by_provider(time_period)
- counts = auth_providers_except_ldap.each_with_object({}) do |provider, hash|
- hash[provider] = distinct_count(
+ counts = auth_providers_except_ldap.index_with do |provider|
+ distinct_count(
::AuthenticationEvent.success.for_provider(provider).where(time_period), :user_id)
end
diff --git a/lib/gitlab/usage_data_counters/editor_unique_counter.rb b/lib/gitlab/usage_data_counters/editor_unique_counter.rb
index 5ede840661a..0b448f68153 100644
--- a/lib/gitlab/usage_data_counters/editor_unique_counter.rb
+++ b/lib/gitlab/usage_data_counters/editor_unique_counter.rb
@@ -45,20 +45,23 @@ module Gitlab
private
- def track_unique_action(action, author, time, project = nil)
+ def track_unique_action(event_name, author, time, project = nil)
return unless author
if Feature.enabled?(:route_hll_to_snowplow_phase2)
Gitlab::Tracking.event(
+ name,
'ide_edit',
- action.to_s,
+ property: event_name.to_s,
project: project,
namespace: project&.namespace,
- user: author
+ user: author,
+ label: 'usage_activity_by_stage_monthly.create.action_monthly_active_users_ide_edit',
+ context: [Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll, event: event_name).to_context]
)
end
- Gitlab::UsageDataCounters::HLLRedisCounter.track_event(action, values: author.id, time: time)
+ Gitlab::UsageDataCounters::HLLRedisCounter.track_event(event_name, values: author.id, time: time)
end
def count_unique(actions, date_from, date_to)
diff --git a/lib/gitlab/usage_data_counters/hll_redis_counter.rb b/lib/gitlab/usage_data_counters/hll_redis_counter.rb
index 24a87ae01f4..992cec2d174 100644
--- a/lib/gitlab/usage_data_counters/hll_redis_counter.rb
+++ b/lib/gitlab/usage_data_counters/hll_redis_counter.rb
@@ -25,32 +25,6 @@ module Gitlab
pipeline_authoring
].freeze
- CATEGORIES_COLLECTED_FROM_METRICS_DEFINITIONS = %w[
- analytics
- ci_users
- deploy_token_packages
- code_review
- ecosystem
- error_tracking
- ide_edit
- importer
- incident_management
- incident_management_alerts
- issues_edit
- kubernetes_agent
- manage
- pipeline_authoring
- quickactions
- search
- secure
- snippets
- source_code
- terraform
- testing
- user_packages
- work_items
- ].freeze
-
# Track event on entity_id
# Increment a Redis HLL counter for unique event_name and entity_id
#
@@ -114,41 +88,12 @@ module Gitlab
@categories ||= known_events.map { |event| event[:category] }.uniq
end
- def categories_collected_from_metrics_definitions
- CATEGORIES_COLLECTED_FROM_METRICS_DEFINITIONS
- end
-
# @param category [String] the category name
# @return [Array<String>] list of event names for given category
def events_for_category(category)
known_events.select { |event| event[:category] == category.to_s }.map { |event| event[:name] }
end
- # Recent 7 or 28 days unique events data for events defined in /lib/gitlab/usage_data_counters/known_events/
- #
- # - For metrics for which we store a key per day, we have the last 7 days or last 28 days of data.
- # - For metrics for which we store a key per week, we have the last complete week or last 4 complete weeks
- # daily or weekly information is in the file we have for events definition /lib/gitlab/usage_data_counters/known_events/
- # - Most of the metrics have weekly aggregation. We recommend this as it generates fewer keys in Redis to store.
- # - The aggregation used doesn't affect data granulation.
- def unique_events_data
- categories_pending_migration.each_with_object({}) do |category, category_results|
- events_names = events_for_category(category)
-
- event_results = events_names.each_with_object({}) do |event, hash|
- hash["#{event}_weekly"] = unique_events(**weekly_time_range.merge(event_names: [event])) unless event == "i_package_composer_deploy_token"
- hash["#{event}_monthly"] = unique_events(**monthly_time_range.merge(event_names: [event]))
- end
-
- if eligible_for_totals?(events_names) && CATEGORIES_FOR_TOTALS.include?(category)
- event_results["#{category}_total_unique_counts_weekly"] = unique_events(**weekly_time_range.merge(event_names: events_names))
- event_results["#{category}_total_unique_counts_monthly"] = unique_events(**monthly_time_range.merge(event_names: events_names))
- end
-
- category_results["#{category}"] = event_results
- end
- end
-
def known_event?(event_name)
event_for(event_name).present?
end
@@ -166,16 +111,13 @@ module Gitlab
private
- def categories_pending_migration
- (categories - categories_collected_from_metrics_definitions)
- end
-
def track(values, event_name, context: '', time: Time.zone.now)
return unless ::ServicePing::ServicePingSettings.enabled?
event = event_for(event_name)
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(UnknownEvent.new("Unknown event #{event_name}")) unless event.present?
+ return if event.blank?
return unless feature_enabled?(event)
Gitlab::Redis::HLL.add(key: redis_key(event, time, context), value: values, expiry: expiry(event))
diff --git a/lib/gitlab/usage_data_counters/issue_activity_unique_counter.rb b/lib/gitlab/usage_data_counters/issue_activity_unique_counter.rb
index dda72f7fa3b..477fa288874 100644
--- a/lib/gitlab/usage_data_counters/issue_activity_unique_counter.rb
+++ b/lib/gitlab/usage_data_counters/issue_activity_unique_counter.rb
@@ -173,7 +173,7 @@ module Gitlab
private
- def track_snowplow_action(action, author, project)
+ def track_snowplow_action(event_name, author, project)
return unless Feature.enabled?(:route_hll_to_snowplow_phase2, project.namespace)
return unless author
@@ -181,17 +181,18 @@ module Gitlab
ISSUE_CATEGORY,
ISSUE_ACTION,
label: ISSUE_LABEL,
- property: action,
+ property: event_name,
project: project,
namespace: project.namespace,
- user: author
+ user: author,
+ context: [Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll, event: event_name).to_context]
)
end
- def track_unique_action(action, author)
+ def track_unique_action(event_name, author)
return unless author
- Gitlab::UsageDataCounters::HLLRedisCounter.track_event(action, values: author.id)
+ Gitlab::UsageDataCounters::HLLRedisCounter.track_event(event_name, values: author.id)
end
end
end
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 5b80f6c6c0d..b9f143a3a56 100644
--- a/lib/gitlab/usage_data_counters/known_events/ci_templates.yml
+++ b/lib/gitlab/usage_data_counters/known_events/ci_templates.yml
@@ -347,6 +347,14 @@
category: ci_templates
redis_slot: ci_templates
aggregation: weekly
+- name: p_ci_templates_jobs_container_scanning
+ category: ci_templates
+ redis_slot: ci_templates
+ aggregation: weekly
+- name: p_ci_templates_jobs_container_scanning_latest
+ category: ci_templates
+ redis_slot: ci_templates
+ aggregation: weekly
- name: p_ci_templates_jobs_dependency_scanning_latest
category: ci_templates
redis_slot: ci_templates
@@ -519,6 +527,10 @@
category: ci_templates
redis_slot: ci_templates
aggregation: weekly
+- name: p_ci_templates_implicit_jobs_container_scanning
+ category: ci_templates
+ redis_slot: ci_templates
+ aggregation: weekly
- name: p_ci_templates_implicit_jobs_dast_default_branch_deploy
category: ci_templates
redis_slot: ci_templates
diff --git a/lib/gitlab/usage_data_counters/known_events/code_review_events.yml b/lib/gitlab/usage_data_counters/known_events/code_review_events.yml
index 0bd809f8aa5..3bb6655d762 100644
--- a/lib/gitlab/usage_data_counters/known_events/code_review_events.yml
+++ b/lib/gitlab/usage_data_counters/known_events/code_review_events.yml
@@ -39,6 +39,10 @@
redis_slot: code_review
category: code_review
aggregation: weekly
+- name: i_code_review_create_mr
+ redis_slot: code_review
+ category: code_review
+ aggregation: weekly
- name: i_code_review_user_create_mr
redis_slot: code_review
category: code_review
@@ -256,7 +260,6 @@
redis_slot: code_review
category: code_review
aggregation: weekly
- feature_flag: usage_data_diff_searches
- name: i_code_review_total_suggestions_applied
redis_slot: code_review
category: code_review
@@ -427,3 +430,28 @@
redis_slot: code_review
category: code_review
aggregation: weekly
+## Security Reports
+- name: i_code_review_merge_request_widget_security_reports_view
+ redis_slot: code_review
+ category: code_review
+ aggregation: weekly
+- name: i_code_review_merge_request_widget_security_reports_full_report_clicked
+ redis_slot: code_review
+ category: code_review
+ aggregation: weekly
+- name: i_code_review_merge_request_widget_security_reports_expand
+ redis_slot: code_review
+ category: code_review
+ aggregation: weekly
+- name: i_code_review_merge_request_widget_security_reports_expand_success
+ redis_slot: code_review
+ category: code_review
+ aggregation: weekly
+- name: i_code_review_merge_request_widget_security_reports_expand_warning
+ redis_slot: code_review
+ category: code_review
+ aggregation: weekly
+- name: i_code_review_merge_request_widget_security_reports_expand_failed
+ redis_slot: code_review
+ category: code_review
+ aggregation: weekly
diff --git a/lib/gitlab/usage_data_counters/known_events/common.yml b/lib/gitlab/usage_data_counters/known_events/common.yml
index c1720b26a22..a64b7c4032b 100644
--- a/lib/gitlab/usage_data_counters/known_events/common.yml
+++ b/lib/gitlab/usage_data_counters/known_events/common.yml
@@ -131,7 +131,6 @@
category: testing
redis_slot: testing
aggregation: weekly
- feature_flag: usage_data_ci_i_testing_coverage_report_uploaded
# Project Management group
- name: g_project_management_issue_title_changed
category: issues_edit
diff --git a/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter.rb b/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter.rb
index 93137b762ec..10dae35d0bf 100644
--- a/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter.rb
+++ b/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter.rb
@@ -6,7 +6,8 @@ module Gitlab
MR_DIFFS_ACTION = 'i_code_review_mr_diffs'
MR_DIFFS_SINGLE_FILE_ACTION = 'i_code_review_mr_single_file_diffs'
MR_DIFFS_USER_SINGLE_FILE_ACTION = 'i_code_review_user_single_file_diffs'
- MR_CREATE_ACTION = 'i_code_review_user_create_mr'
+ MR_CREATE_ACTION = 'i_code_review_create_mr'
+ MR_USER_CREATE_ACTION = 'i_code_review_user_create_mr'
MR_CLOSE_ACTION = 'i_code_review_user_close_mr'
MR_REOPEN_ACTION = 'i_code_review_user_reopen_mr'
MR_MERGE_ACTION = 'i_code_review_user_merge_mr'
@@ -62,8 +63,24 @@ module Gitlab
track_unique_action_by_user(MR_DIFFS_USER_SINGLE_FILE_ACTION, user)
end
- def track_create_mr_action(user:)
- track_unique_action_by_user(MR_CREATE_ACTION, user)
+ def track_create_mr_action(user:, merge_request:)
+ track_unique_action_by_user(MR_USER_CREATE_ACTION, user)
+ track_unique_action_by_merge_request(MR_CREATE_ACTION, merge_request)
+
+ project = merge_request.target_project
+ return unless Feature.enabled?(:route_hll_to_snowplow_phase2, project.namespace)
+
+ Gitlab::Tracking.event(
+ name,
+ :create,
+ project: project,
+ namespace: project.namespace,
+ user: user,
+ property: MR_CREATE_ACTION,
+ label: 'redis_hll_counters.code_review.i_code_review_create_mr_monthly',
+ context: [Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll,
+ event: MR_CREATE_ACTION).to_context]
+ )
end
def track_close_mr_action(user:)
@@ -85,11 +102,15 @@ module Gitlab
return unless Feature.enabled?(:route_hll_to_snowplow_phase2, project.namespace)
Gitlab::Tracking.event(
- 'merge_requests',
- MR_APPROVE_ACTION,
+ name,
+ :approve,
project: project,
namespace: project.namespace,
- user: user
+ user: user,
+ property: MR_APPROVE_ACTION,
+ label: 'redis_hll_counters.code_review.i_code_review_user_approve_mr_monthly',
+ context: [Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll,
+ event: MR_APPROVE_ACTION).to_context]
)
end
diff --git a/lib/gitlab/usage_data_counters/merge_request_widget_extension_counter.rb b/lib/gitlab/usage_data_counters/merge_request_widget_extension_counter.rb
index f88bbc41c70..639da9bfee0 100644
--- a/lib/gitlab/usage_data_counters/merge_request_widget_extension_counter.rb
+++ b/lib/gitlab/usage_data_counters/merge_request_widget_extension_counter.rb
@@ -5,7 +5,16 @@ module Gitlab
class MergeRequestWidgetExtensionCounter < BaseCounter
KNOWN_EVENTS = %w[view full_report_clicked expand expand_success expand_warning expand_failed].freeze
PREFIX = 'i_code_review_merge_request_widget'
- WIDGETS = %w[accessibility code_quality license_compliance status_checks terraform test_summary metrics].freeze
+ WIDGETS = %w[
+ accessibility
+ code_quality
+ license_compliance
+ status_checks
+ terraform
+ test_summary
+ metrics
+ security_reports
+ ].freeze
class << self
private
diff --git a/lib/gitlab/utils/delegator_override/validator.rb b/lib/gitlab/utils/delegator_override/validator.rb
index 4449fa75877..7fa5cc4deef 100644
--- a/lib/gitlab/utils/delegator_override/validator.rb
+++ b/lib/gitlab/utils/delegator_override/validator.rb
@@ -67,7 +67,7 @@ module Gitlab
(delegator_class.instance_methods - allowlist).each do |method_name|
target_classes.each do |target_class|
- next unless target_class.instance_methods.include?(method_name)
+ next unless target_class.method_defined?(method_name)
errors << generate_error(method_name, target_class, delegator_class)
end
diff --git a/lib/gitlab/utils/override.rb b/lib/gitlab/utils/override.rb
index 39670a835a6..f83ebba7c3f 100644
--- a/lib/gitlab/utils/override.rb
+++ b/lib/gitlab/utils/override.rb
@@ -67,8 +67,8 @@ module Gitlab
private
def instance_method_defined?(klass, name)
- klass.instance_methods(false).include?(name) ||
- klass.private_instance_methods(false).include?(name)
+ klass.method_defined?(name, false) ||
+ klass.private_method_defined?(name, false)
end
def find_direct_method(klass, name)
diff --git a/lib/gitlab/utils/sanitize_node_link.rb b/lib/gitlab/utils/sanitize_node_link.rb
index b0dfa087fcf..9c34302f75e 100644
--- a/lib/gitlab/utils/sanitize_node_link.rb
+++ b/lib/gitlab/utils/sanitize_node_link.rb
@@ -30,7 +30,7 @@ module Gitlab
# Remove all invalid scheme characters before checking against the
# list of unsafe protocols.
#
- # See https://tools.ietf.org/html/rfc3986#section-3.1
+ # See https://www.rfc-editor.org/rfc/rfc3986#section-3.1
#
def safe_protocol?(scheme)
return false unless scheme
diff --git a/lib/gitlab/utils/strong_memoize.rb b/lib/gitlab/utils/strong_memoize.rb
index 6456ad08924..7e78363dae5 100644
--- a/lib/gitlab/utils/strong_memoize.rb
+++ b/lib/gitlab/utils/strong_memoize.rb
@@ -16,16 +16,6 @@ module Gitlab
# include Gitlab::Utils::StrongMemoize
#
# def trigger_from_token
- # strong_memoize(:trigger) do
- # Ci::Trigger.find_by_token(params[:token].to_s)
- # end
- # end
- #
- # Or like:
- #
- # include Gitlab::Utils::StrongMemoize
- #
- # def trigger_from_token
# Ci::Trigger.find_by_token(params[:token].to_s)
# end
# strong_memoize_attr :trigger_from_token
@@ -99,6 +89,15 @@ module Gitlab
def do_strong_memoize(klass, method_name, member_name)
method = klass.instance_method(method_name)
+ unless method.arity == 0
+ raise <<~ERROR
+ Using `strong_memoize_attr` on methods with parameters is not supported.
+
+ Use `strong_memoize_with` instead.
+ See https://docs.gitlab.com/ee/development/utilities.html#strongmemoize
+ ERROR
+ end
+
# Methods defined within a class method are already public by default, so we don't need to
# explicitly make them public.
scope = %i[private protected].find do |scope|
@@ -106,9 +105,9 @@ module Gitlab
.include? method_name
end
- klass.define_method(method_name) do |*args, &block|
+ klass.define_method(method_name) do |&block|
strong_memoize(member_name) do
- method.bind_call(self, *args, &block)
+ method.bind_call(self, &block)
end
end
diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb
index 7360585df43..8b016a09889 100644
--- a/lib/gitlab/visibility_level.rb
+++ b/lib/gitlab/visibility_level.rb
@@ -11,7 +11,7 @@ module Gitlab
included do
scope :public_only, -> { where(visibility_level: PUBLIC) }
- scope :public_and_internal_only, -> { where(visibility_level: [PUBLIC, INTERNAL] ) }
+ scope :public_and_internal_only, -> { where(visibility_level: [PUBLIC, INTERNAL]) }
scope :private_only, -> { where(visibility_level: PRIVATE) }
scope :non_public_only, -> { where.not(visibility_level: PUBLIC) }
diff --git a/lib/gitlab/work_items/work_item_hierarchy.rb b/lib/gitlab/work_items/work_item_hierarchy.rb
new file mode 100644
index 00000000000..e71bf2bce6a
--- /dev/null
+++ b/lib/gitlab/work_items/work_item_hierarchy.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module WorkItems
+ class WorkItemHierarchy < ObjectHierarchy
+ extend ::Gitlab::Utils::Override
+
+ private
+
+ def middle_table
+ ::WorkItems::ParentLink.arel_table
+ end
+
+ def from_tables(cte)
+ [objects_table, cte.table, middle_table]
+ end
+
+ override :parent_id_column
+ def parent_id_column(cte)
+ middle_table[:work_item_parent_id]
+ end
+
+ override :ancestor_conditions
+ def ancestor_conditions(cte)
+ conditions = middle_table[:work_item_parent_id].eq(objects_table[:id]).and(
+ middle_table[:work_item_id].eq(cte.table[:id])
+ )
+
+ with_type_filter(conditions, cte)
+ end
+
+ override :descendant_conditions
+ def descendant_conditions(cte)
+ conditions = middle_table[:work_item_id].eq(objects_table[:id]).and(
+ middle_table[:work_item_parent_id].eq(cte.table[:id])
+ )
+
+ with_type_filter(conditions, cte)
+ end
+
+ def with_type_filter(conditions, cte)
+ return conditions unless options[:same_type]
+
+ conditions.and(objects_table[:work_item_type_id].eq(cte.table[:work_item_type_id]))
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index 0d5daeefe90..02418c45e73 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -33,7 +33,7 @@ module Gitlab
GitalyServer: {
address: Gitlab::GitalyClient.address(repository.storage),
token: Gitlab::GitalyClient.token(repository.storage),
- features: Feature::Gitaly.server_feature_flags(
+ call_metadata: Feature::Gitaly.server_feature_flags(
user: ::Feature::Gitaly.user_actor(user),
repository: repository,
project: ::Feature::Gitaly.project_actor(repository.container),
@@ -48,6 +48,12 @@ module Gitlab
attrs[:GitConfigOptions] << "receive.maxInputSize=#{receive_max_input_size.megabytes}"
end
+ attrs[:GitalyServer][:call_metadata].merge!(
+ 'user_id' => attrs[:GL_ID].presence,
+ 'username' => attrs[:GL_USERNAME].presence,
+ 'remote_ip' => Gitlab::ApplicationContext.current_context_attribute(:remote_ip).presence
+ ).compact!
+
attrs
end
@@ -257,7 +263,7 @@ module Gitlab
{
address: Gitlab::GitalyClient.address(repository.shard),
token: Gitlab::GitalyClient.token(repository.shard),
- features: Feature::Gitaly.server_feature_flags(
+ call_metadata: Feature::Gitaly.server_feature_flags(
user: ::Feature::Gitaly.user_actor,
repository: repository,
project: ::Feature::Gitaly.project_actor(repository.container),
diff --git a/lib/gitlab/x509/signature.rb b/lib/gitlab/x509/signature.rb
index f8a6980f208..d6bbb8bb2cb 100644
--- a/lib/gitlab/x509/signature.rb
+++ b/lib/gitlab/x509/signature.rb
@@ -6,6 +6,7 @@ module Gitlab
module X509
class Signature
include Gitlab::Utils::StrongMemoize
+ include SignatureType
attr_reader :signature_text, :signed_text, :created_at
@@ -16,14 +17,18 @@ module Gitlab
@created_at = created_at
end
+ def type
+ :x509
+ end
+
def x509_certificate
return if certificate_attributes.nil?
X509Certificate.safe_create!(certificate_attributes) unless verified_signature.nil?
end
- def user
- strong_memoize(:user) { User.find_by_any_email(@email) }
+ def signed_by_user
+ strong_memoize(:signed_by_user) { User.find_by_any_email(@email) }
end
def verified_signature
@@ -33,11 +38,11 @@ module Gitlab
def verification_status
return :unverified if
x509_certificate.nil? ||
- x509_certificate.revoked? ||
- !verified_signature ||
- user.nil?
+ x509_certificate.revoked? ||
+ !verified_signature ||
+ signed_by_user.nil?
- if user.verified_emails.include?(@email.downcase) && certificate_email.casecmp?(@email)
+ if signed_by_user.verified_emails.include?(@email.downcase) && certificate_email.casecmp?(@email)
:verified
else
:unverified