From 47970fc5b7f3092fa2e83d09e1faeeeb77bf5df1 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Fri, 24 Jan 2025 00:34:42 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- GITALY_SERVER_VERSION | 2 +- .../check_lfs_file_locks_service.rb | 5 +- app/views/profiles/preferences/show.html.haml | 2 +- config/gitlab_loose_foreign_keys.yml | 7 + ...ci_gitlab_hosted_runner_monthly_usages.yml | 13 ++ db/docs/ci_instance_runner_monthly_usages.yml | 16 +++ ...reate_ci_instance_runner_monthly_usages.rb | 47 +++++++ ..._ci_gitlab_hosted_runner_monthly_usages.rb | 47 +++++++ db/schema_migrations/20250109164501 | 1 + db/schema_migrations/20250109164504 | 1 + db/structure.sql | 71 ++++++++++ .../site_architecture/automation.md | 2 + .../merge_request_approval_policies.md | 12 ++ doc/user/clusters/agent/vulnerabilities.md | 17 +++ .../release_with_release_cli_spec.rb | 122 ++++++++++-------- .../ci_variable/custom_variable_spec.rb | 5 +- ...al_config_file_paths_with_wildcard_spec.rb | 11 +- ...lude_multiple_files_from_a_project_spec.rb | 5 +- ...tiple_files_from_multiple_projects_spec.rb | 19 +-- ...pipelines_independent_relationship_spec.rb | 8 +- ...variables_to_downstream_via_bridge_spec.rb | 14 +- .../pipeline_with_image_pull_policy_spec.rb | 19 +-- .../run_pipeline_with_manual_jobs_spec.rb | 53 +------- ...trigger_child_pipeline_with_manual_spec.rb | 9 +- .../4_verify/pipeline/trigger_matrix_spec.rb | 5 +- .../testing/endpoint_coverage_spec.rb | 9 +- spec/db/schema_spec.rb | 1 + spec/lib/gitlab/import_export/all_models.yml | 2 + spec/rack_servers/puma_spec.rb | 2 +- .../check_lfs_file_locks_service_spec.rb | 37 +++++- 30 files changed, 395 insertions(+), 169 deletions(-) create mode 100644 db/docs/ci_gitlab_hosted_runner_monthly_usages.yml create mode 100644 db/docs/ci_instance_runner_monthly_usages.yml create mode 100644 db/migrate/20250109164501_create_ci_instance_runner_monthly_usages.rb create mode 100644 db/migrate/20250109164504_create_ci_gitlab_hosted_runner_monthly_usages.rb create mode 100644 db/schema_migrations/20250109164501 create mode 100644 db/schema_migrations/20250109164504 diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 4a23e6689b9..63e32a669cd 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -6dc57705f4d70c87ea803faa572e5bb844cbc569 +dd5a7ec67062bdb145fa2f66385a9bea72ccce1f diff --git a/app/services/merge_requests/mergeability/check_lfs_file_locks_service.rb b/app/services/merge_requests/mergeability/check_lfs_file_locks_service.rb index 23c4defe4c9..d6fb5f635f6 100644 --- a/app/services/merge_requests/mergeability/check_lfs_file_locks_service.rb +++ b/app/services/merge_requests/mergeability/check_lfs_file_locks_service.rb @@ -47,7 +47,10 @@ module MergeRequests delegate :project, :author_id, :changed_paths, to: :merge_request def contains_locked_lfs_files? - project.lfs_file_locks.for_paths(changed_paths.map(&:path)).not_for_users(author_id).exists? + return false unless project.lfs_file_locks.exists? + + paths = changed_paths.map(&:path).uniq + project.lfs_file_locks.for_paths(paths).not_for_users(author_id).exists? end def check_inactive? diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index f546fa0caf7..ebfc6b9e692 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -53,7 +53,7 @@ .syntax-theme.row - Gitlab::ColorSchemes.each do |scheme| %label.col-6.col-sm-4.col-md-3.col-lg-auto.gl-mb-5 - .preview= image_tag "#{scheme.css_class}-scheme-preview.png" + .preview= image_tag "#{scheme.css_class}-scheme-preview.png", alt: "#{scheme.css_class}-scheme-preview.png" = f.gitlab_ui_radio_component :color_scheme_id, scheme.id, scheme.name, radio_options: { checked: user_color_schema_id == scheme.id } diff --git a/config/gitlab_loose_foreign_keys.yml b/config/gitlab_loose_foreign_keys.yml index 0c00b378646..86b309c38d0 100644 --- a/config/gitlab_loose_foreign_keys.yml +++ b/config/gitlab_loose_foreign_keys.yml @@ -39,6 +39,13 @@ ci_group_variables: - table: namespaces column: group_id on_delete: async_delete +ci_instance_runner_monthly_usages: + - table: namespaces + column: root_namespace_id + on_delete: async_delete + - table: projects + column: project_id + on_delete: async_nullify ci_job_token_authorizations: - table: projects column: origin_project_id diff --git a/db/docs/ci_gitlab_hosted_runner_monthly_usages.yml b/db/docs/ci_gitlab_hosted_runner_monthly_usages.yml new file mode 100644 index 00000000000..5ce6f6137ec --- /dev/null +++ b/db/docs/ci_gitlab_hosted_runner_monthly_usages.yml @@ -0,0 +1,13 @@ +--- +table_name: ci_gitlab_hosted_runner_monthly_usages +classes: +- Ci::Minutes::GitlabHostedRunnerMonthlyUsage +feature_categories: +- continuous_integration +description: Per month CI usage data at the runner and project level meant to store + GitLab hosted runners data on Dedicated installations only. +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/171336 +milestone: '17.9' +gitlab_schema: gitlab_ci +exempt_from_sharding: true +notes: Exempted from sharding because Dedicated only. diff --git a/db/docs/ci_instance_runner_monthly_usages.yml b/db/docs/ci_instance_runner_monthly_usages.yml new file mode 100644 index 00000000000..95d7e24e901 --- /dev/null +++ b/db/docs/ci_instance_runner_monthly_usages.yml @@ -0,0 +1,16 @@ +--- +table_name: ci_instance_runner_monthly_usages +classes: +- Ci::Minutes::InstanceRunnerMonthlyUsage +feature_categories: +- continuous_integration +description: Per month CI usage data at the runner and project level meant to store + instance runner usage except hosted runners on dedicated +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/171336 +milestone: '17.9' +gitlab_schema: gitlab_ci +sharding_key: + # The table entries belong to a namespace/group only + # We don't shard on projects because the usage is recorded as caused by a project but shouldn't move with that project + # the usage should stay with it's root namespace even if the project moves + root_namespace_id: namespaces \ No newline at end of file diff --git a/db/migrate/20250109164501_create_ci_instance_runner_monthly_usages.rb b/db/migrate/20250109164501_create_ci_instance_runner_monthly_usages.rb new file mode 100644 index 00000000000..eb0bfc14f80 --- /dev/null +++ b/db/migrate/20250109164501_create_ci_instance_runner_monthly_usages.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +class CreateCiInstanceRunnerMonthlyUsages < Gitlab::Database::Migration[2.2] + milestone '17.9' + disable_ddl_transaction! + + def up + # rubocop:disable Migration/EnsureFactoryForTable -- False Positive + create_table :ci_instance_runner_monthly_usages do |t| + # 8 bytes + t.references :runner, + type: :bigint, index: false, null: true, + foreign_key: { to_table: :ci_runners, on_delete: :nullify } + t.bigint :runner_duration_seconds, null: false, default: 0 + t.bigint :project_id, null: true + t.bigint :root_namespace_id, null: false + t.timestamps_with_timezone null: false + # 4 bytes + t.date :billing_month, null: false + t.integer :notification_level, null: false, default: 100 + # variables bytes + t.decimal :compute_minutes_used, precision: 18, scale: 4, null: false, default: 0.0 + end + + add_index :ci_instance_runner_monthly_usages, [:root_namespace_id, :billing_month], + name: 'index_ci_instance_runner_monthly_usages_on_namespace_and_month' + + add_index :ci_instance_runner_monthly_usages, [:project_id, :billing_month], + name: 'index_ci_instance_runner_monthly_usages_on_project_and_month' + + add_index :ci_instance_runner_monthly_usages, + [:runner_id, :billing_month, :root_namespace_id, :project_id], + name: 'idx_instance_runner_usage_unique', + unique: true + + add_check_constraint( + :ci_instance_runner_monthly_usages, + "(billing_month = date_trunc('month', billing_month::timestamp with time zone))", + 'ci_instance_runner_monthly_usages_year_month_constraint' + ) + # rubocop:enable Migration/EnsureFactoryForTable -- False Positive + end + + def down + drop_table :ci_instance_runner_monthly_usages if table_exists? :ci_instance_runner_monthly_usages + end +end diff --git a/db/migrate/20250109164504_create_ci_gitlab_hosted_runner_monthly_usages.rb b/db/migrate/20250109164504_create_ci_gitlab_hosted_runner_monthly_usages.rb new file mode 100644 index 00000000000..d473139bc54 --- /dev/null +++ b/db/migrate/20250109164504_create_ci_gitlab_hosted_runner_monthly_usages.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +# Dedicated only +class CreateCiGitlabHostedRunnerMonthlyUsages < Gitlab::Database::Migration[2.2] + milestone '17.9' + disable_ddl_transaction! + + def up + # rubocop:disable Migration/EnsureFactoryForTable -- False Positive + create_table :ci_gitlab_hosted_runner_monthly_usages do |t| + # 8 bytes + t.references :runner, + type: :bigint, index: false, null: false + t.bigint :runner_duration_seconds, null: false, default: 0 + t.bigint :project_id, null: false + t.bigint :root_namespace_id, null: false + t.timestamps_with_timezone null: false + # 4 bytes + t.date :billing_month, null: false + t.integer :notification_level, null: false, default: 100 + # variables bytes + t.decimal :compute_minutes_used, precision: 18, scale: 4, null: false, default: 0.0 + end + + add_index :ci_gitlab_hosted_runner_monthly_usages, [:root_namespace_id, :billing_month], + name: 'idx_hosted_runner_usage_on_namespace_billing_month' + + add_index :ci_gitlab_hosted_runner_monthly_usages, [:project_id, :billing_month], + name: 'idx_hosted_runner_usage_on_project_billing_month' + + add_index :ci_gitlab_hosted_runner_monthly_usages, + [:runner_id, :billing_month, :root_namespace_id, :project_id], + name: 'idx_hosted_runner_usage_unique', + unique: true + + add_check_constraint( + :ci_gitlab_hosted_runner_monthly_usages, + "(billing_month = date_trunc('month', billing_month::timestamp with time zone))", + 'ci_hosted_runner_monthly_usages_month_constraint' + ) + # rubocop:enable Migration/EnsureFactoryForTable -- False Positive + end + + def down + drop_table :ci_gitlab_hosted_runner_monthly_usages if table_exists? :ci_gitlab_hosted_runner_monthly_usages + end +end diff --git a/db/schema_migrations/20250109164501 b/db/schema_migrations/20250109164501 new file mode 100644 index 00000000000..b1e3472cd64 --- /dev/null +++ b/db/schema_migrations/20250109164501 @@ -0,0 +1 @@ +23937513149f40d61d67db1e2735693dd667bf23197e967d067416135c29374a \ No newline at end of file diff --git a/db/schema_migrations/20250109164504 b/db/schema_migrations/20250109164504 new file mode 100644 index 00000000000..7d4bddd55f9 --- /dev/null +++ b/db/schema_migrations/20250109164504 @@ -0,0 +1 @@ +27ff90b2bc2682e6f5a9a012302e271626a68fa027d2dad4db6f98dcad81f052 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 06114631d3e..935f46c6e16 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -9905,6 +9905,29 @@ CREATE SEQUENCE ci_freeze_periods_id_seq ALTER SEQUENCE ci_freeze_periods_id_seq OWNED BY ci_freeze_periods.id; +CREATE TABLE ci_gitlab_hosted_runner_monthly_usages ( + id bigint NOT NULL, + runner_id bigint NOT NULL, + runner_duration_seconds bigint DEFAULT 0 NOT NULL, + project_id bigint NOT NULL, + root_namespace_id bigint NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + billing_month date NOT NULL, + notification_level integer DEFAULT 100 NOT NULL, + compute_minutes_used numeric(18,4) DEFAULT 0.0 NOT NULL, + CONSTRAINT ci_hosted_runner_monthly_usages_month_constraint CHECK ((billing_month = date_trunc('month'::text, (billing_month)::timestamp with time zone))) +); + +CREATE SEQUENCE ci_gitlab_hosted_runner_monthly_usages_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE ci_gitlab_hosted_runner_monthly_usages_id_seq OWNED BY ci_gitlab_hosted_runner_monthly_usages.id; + CREATE TABLE ci_group_variables ( id bigint NOT NULL, key character varying NOT NULL, @@ -9935,6 +9958,29 @@ CREATE SEQUENCE ci_group_variables_id_seq ALTER SEQUENCE ci_group_variables_id_seq OWNED BY ci_group_variables.id; +CREATE TABLE ci_instance_runner_monthly_usages ( + id bigint NOT NULL, + runner_id bigint, + runner_duration_seconds bigint DEFAULT 0 NOT NULL, + project_id bigint, + root_namespace_id bigint NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + billing_month date NOT NULL, + notification_level integer DEFAULT 100 NOT NULL, + compute_minutes_used numeric(18,4) DEFAULT 0.0 NOT NULL, + CONSTRAINT ci_instance_runner_monthly_usages_year_month_constraint CHECK ((billing_month = date_trunc('month'::text, (billing_month)::timestamp with time zone))) +); + +CREATE SEQUENCE ci_instance_runner_monthly_usages_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE ci_instance_runner_monthly_usages_id_seq OWNED BY ci_instance_runner_monthly_usages.id; + CREATE TABLE ci_instance_variables ( id bigint NOT NULL, variable_type smallint DEFAULT 1 NOT NULL, @@ -24346,8 +24392,12 @@ ALTER TABLE ONLY ci_deleted_objects ALTER COLUMN id SET DEFAULT nextval('ci_dele ALTER TABLE ONLY ci_freeze_periods ALTER COLUMN id SET DEFAULT nextval('ci_freeze_periods_id_seq'::regclass); +ALTER TABLE ONLY ci_gitlab_hosted_runner_monthly_usages ALTER COLUMN id SET DEFAULT nextval('ci_gitlab_hosted_runner_monthly_usages_id_seq'::regclass); + ALTER TABLE ONLY ci_group_variables ALTER COLUMN id SET DEFAULT nextval('ci_group_variables_id_seq'::regclass); +ALTER TABLE ONLY ci_instance_runner_monthly_usages ALTER COLUMN id SET DEFAULT nextval('ci_instance_runner_monthly_usages_id_seq'::regclass); + ALTER TABLE ONLY ci_instance_variables ALTER COLUMN id SET DEFAULT nextval('ci_instance_variables_id_seq'::regclass); ALTER TABLE ONLY ci_job_token_authorizations ALTER COLUMN id SET DEFAULT nextval('ci_job_token_authorizations_id_seq'::regclass); @@ -26434,9 +26484,15 @@ ALTER TABLE ONLY ci_deleted_objects ALTER TABLE ONLY ci_freeze_periods ADD CONSTRAINT ci_freeze_periods_pkey PRIMARY KEY (id); +ALTER TABLE ONLY ci_gitlab_hosted_runner_monthly_usages + ADD CONSTRAINT ci_gitlab_hosted_runner_monthly_usages_pkey PRIMARY KEY (id); + ALTER TABLE ONLY ci_group_variables ADD CONSTRAINT ci_group_variables_pkey PRIMARY KEY (id); +ALTER TABLE ONLY ci_instance_runner_monthly_usages + ADD CONSTRAINT ci_instance_runner_monthly_usages_pkey PRIMARY KEY (id); + ALTER TABLE ONLY ci_instance_variables ADD CONSTRAINT ci_instance_variables_pkey PRIMARY KEY (id); @@ -30033,6 +30089,12 @@ CREATE INDEX idx_group_audit_events_on_project_created_at_id ON ONLY group_audit CREATE INDEX idx_headers_instance_external_audit_event_destination_id ON instance_audit_events_streaming_headers USING btree (instance_external_audit_event_destination_id); +CREATE INDEX idx_hosted_runner_usage_on_namespace_billing_month ON ci_gitlab_hosted_runner_monthly_usages USING btree (root_namespace_id, billing_month); + +CREATE INDEX idx_hosted_runner_usage_on_project_billing_month ON ci_gitlab_hosted_runner_monthly_usages USING btree (project_id, billing_month); + +CREATE UNIQUE INDEX idx_hosted_runner_usage_unique ON ci_gitlab_hosted_runner_monthly_usages USING btree (runner_id, billing_month, root_namespace_id, project_id); + CREATE UNIQUE INDEX idx_import_placeholder_memberships_on_source_user_group_id ON import_placeholder_memberships USING btree (source_user_id, group_id); CREATE INDEX idx_import_placeholder_memberships_on_source_user_id_and_id ON import_placeholder_memberships USING btree (source_user_id, id); @@ -30055,6 +30117,8 @@ CREATE INDEX idx_instance_audit_events_on_author_id_created_at_id ON ONLY instan CREATE UNIQUE INDEX idx_instance_external_audit_event_destination_id_key_uniq ON instance_audit_events_streaming_headers USING btree (instance_external_audit_event_destination_id, key); +CREATE UNIQUE INDEX idx_instance_runner_usage_unique ON ci_instance_runner_monthly_usages USING btree (runner_id, billing_month, root_namespace_id, project_id); + CREATE INDEX idx_issues_on_health_status_not_null ON issues USING btree (health_status) WHERE (health_status IS NOT NULL); CREATE INDEX idx_issues_on_project_id_and_created_at_and_id_and_state_id ON issues USING btree (project_id, created_at, id, state_id); @@ -30961,6 +31025,10 @@ CREATE INDEX index_ci_freeze_periods_on_project_id ON ci_freeze_periods USING bt CREATE UNIQUE INDEX index_ci_group_variables_on_group_id_and_key_and_environment ON ci_group_variables USING btree (group_id, key, environment_scope); +CREATE INDEX index_ci_instance_runner_monthly_usages_on_namespace_and_month ON ci_instance_runner_monthly_usages USING btree (root_namespace_id, billing_month); + +CREATE INDEX index_ci_instance_runner_monthly_usages_on_project_and_month ON ci_instance_runner_monthly_usages USING btree (project_id, billing_month); + CREATE UNIQUE INDEX index_ci_instance_variables_on_key ON ci_instance_variables USING btree (key); CREATE INDEX index_ci_job_artifact_states_on_job_artifact_id_partition_id ON ci_job_artifact_states USING btree (job_artifact_id, partition_id); @@ -39643,6 +39711,9 @@ ALTER TABLE ONLY issue_user_mentions ALTER TABLE ONLY namespace_settings ADD CONSTRAINT fk_rails_3896d4fae5 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE; +ALTER TABLE ONLY ci_instance_runner_monthly_usages + ADD CONSTRAINT fk_rails_38b9dcccc9 FOREIGN KEY (runner_id) REFERENCES ci_runners(id) ON DELETE SET NULL; + ALTER TABLE ONLY packages_cleanup_policies ADD CONSTRAINT fk_rails_393ba98591 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; diff --git a/doc/development/documentation/site_architecture/automation.md b/doc/development/documentation/site_architecture/automation.md index 67273cb8398..9f68ec7098a 100644 --- a/doc/development/documentation/site_architecture/automation.md +++ b/doc/development/documentation/site_architecture/automation.md @@ -43,11 +43,13 @@ that are coded across multiple repositories. |---|---|---| | [All feature flags in GitLab](../../../user/feature_flags.md) | [Generated during docs build](https://gitlab.com/gitlab-org/gitlab-docs/-/blob/main/doc/raketasks.md#generate-the-feature-flag-tables) | [Technical Writing](https://handbook.gitlab.com/handbook/product/ux/technical-writing/) | | [GitLab Runner feature flags](https://docs.gitlab.com/runner/configuration/feature-flags.html) | [Page source](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/ec6e1797d2173a95c8ac7f726bd62f6f110b7211/docs/configuration/feature-flags.md?plain=1#L39) | [Runner](https://handbook.gitlab.com/handbook/engineering/development/ops/verify/runner/) | +| [GitLab Runner Kubernetes API settings](https://docs.gitlab.com/runner/executors/kubernetes/) | Generated with [mage](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/.gitlab/ci/qa.gitlab-ci.yml#L133) | [Runner](https://handbook.gitlab.com/handbook/engineering/development/ops/verify/runner/) | | [Deprecations and removals by version](../../../update/deprecations.md) | [Update the deprecations and removals documentation](../../deprecation_guidelines/index.md#update-the-deprecations-and-removals-documentation) | | | [Breaking change windows](../../../update/breaking_windows.md) | [Update the breaking change windows documentation](../../deprecation_guidelines/index.md#update-the-breaking-change-windows-documentation) | | | [GraphQL API resources](../../../api/graphql/reference/index.md) | [GraphQL API style guide](../../api_graphql_styleguide.md#documentation-and-schema) | [Import and Integrate](https://handbook.gitlab.com/handbook/engineering/development/dev/foundations/import-and-integrate/) | | [Audit event types](../../../user/compliance/audit_event_types.md) | [Audit event development guidelines](../../audit_event_guide/index.md) | [Compliance](https://handbook.gitlab.com/handbook/engineering/development/sec/govern/compliance/) | | [Available custom role permissions](../../../user/custom_roles/abilities.md) | [Generated by Rake task](https://gitlab.com/gitlab-org/gitlab/-/blob/master/tooling/custom_roles/docs/templates/custom_abilities.md.erb) | [Authorization](https://handbook.gitlab.com/handbook/product/categories/#authorization-group)| +| [Application settings analysis](../../cells/application_settings_analysis.md) | [Generated by Ruby script](https://gitlab.com/gitlab-org/gitlab/-/blob/2bb2910c84fad965bde473aa2881d88358b6e96e/scripts/cells/application-settings-analysis.rb#L353) | | | DAST vulnerability check documentation ([Example](../../../user/application_security/dast/browser/checks/798.19.md)) | [How to generate the Markdown](https://gitlab.com/gitlab-org/security-products/dast-cwe-checks/-/blob/main/doc/how-to-generate-the-markdown-documentation.md) | [Dynamic Analysis](https://handbook.gitlab.com/handbook/product/categories/#dynamic-analysis-group) | | [The docs homepage](../../../index.md) | | [Technical Writing](https://handbook.gitlab.com/handbook/product/ux/technical-writing/) | diff --git a/doc/user/application_security/policies/merge_request_approval_policies.md b/doc/user/application_security/policies/merge_request_approval_policies.md index 347cb30a99f..b3f8ac51a77 100644 --- a/doc/user/application_security/policies/merge_request_approval_policies.md +++ b/doc/user/application_security/policies/merge_request_approval_policies.md @@ -631,3 +631,15 @@ If you notice any inconsistencies in your merge request approval rules, you can These actions help ensure that your merge request approval policies are correctly applied and consistent across all merge requests. If you continue to experience issues with merge request approval policies after taking these steps, contact GitLab support for assistance. + +### Merge requests that fix a detected vulnerability require approval + +If your policy configuration includes the `detected` state, merge requests that +fix previously detected vulnerabilities still require approval. The merge request +approval policy evaluates based on vulnerabilities that existed before the changes +in the merge request, which adds an additional layer of review for any changes that affect +known vulnerabilities. + +If you want to allow merge requests that fix vulnerabilities to proceed without +any additional approvals due to a detected vulnerability, consider removing the +`detected` state from your policy configuration. diff --git a/doc/user/clusters/agent/vulnerabilities.md b/doc/user/clusters/agent/vulnerabilities.md index 8b6424db1dc..b511bd231c3 100644 --- a/doc/user/clusters/agent/vulnerabilities.md +++ b/doc/user/clusters/agent/vulnerabilities.md @@ -238,6 +238,23 @@ To do this: - Job ``` +## Configure Trivy report artifact deletion + +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/480845) in GitLab 17.9. + +By default, the GitLab agent deletes the Trivy report artifact after a scan has completed. + +You can configure the GitLab agent to preserve the report artifact, so you can view the report in its raw state. + +To do this: + +- Set `delete_report_artifact` to `false`: + + ```yaml + container_scanning: + delete_report_artifact: false + ``` + ## View cluster vulnerabilities To view vulnerability information in GitLab: diff --git a/qa/qa/specs/features/browser_ui/4_verify/ci_components_catalog/release_with_release_cli_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/ci_components_catalog/release_with_release_cli_spec.rb index 2408f711e58..92977014a10 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/ci_components_catalog/release_with_release_cli_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/ci_components_catalog/release_with_release_cli_spec.rb @@ -20,7 +20,6 @@ module QA Runtime::Feature.disable(:ci_release_cli_catalog_publish_option) Flow::Login.sign_in - Flow::Project.enable_catalog_resource_feature(project) end @@ -34,41 +33,52 @@ module QA project.create_repository_tag('1.0.0') project.visit! - visit_job('create-release-with-existing-tag') + Flow::Pipeline.wait_for_pipeline_creation_via_api(project: project) + project.visit_job('create-release-with-existing-tag') Page::Project::Job::Show.perform do |show| - expect(show.output).to have_content('release created successfully!') - expect(show.output).to have_content('Tag: 1.0.0') - expect(show.output).to have_content('Name: 1.0.0') - expect(show.output).to have_content('Description: A long description of the release') + Support::Waiter.wait_until { show.has_passed? } + + aggregate_failures 'Job has expected contents' do + expect(show.output).to have_content('release created successfully!') + expect(show.output).to have_content('Tag: 1.0.0') + expect(show.output).to have_content('Name: 1.0.0') + expect(show.output).to have_content('Description: A long description of the release') + end end visit_catalog_resource_show_page Page::Explore::CiCdCatalog::Show.perform do |show| - expect(show).to have_version_badge('1.0.0') - expect(show).to have_component_name('new_component') - expect(show).to have_input( - name: 'scanner-output', - required: 'false', - type: 'string', - description: '', - default: 'json' - ) + aggregate_failures 'Catalog component has expected contents' do + expect(show).to have_version_badge('1.0.0') + expect(show).to have_component_name('new_component') + expect(show).to have_input( + name: 'scanner-output', + required: 'false', + type: 'string', + description: '', + default: 'json' + ) + end show.click_latest_version_badge end Page::Project::Tag::Show.perform do |show| - expect(show).to have_tag_name('1.0.0') - expect(show).to have_no_tag_message + aggregate_failures 'Project tag has expected contents' do + expect(show).to have_tag_name('1.0.0') + expect(show).to have_no_tag_message + end show.click_release_link end Page::Project::Release::Show.perform do |show| - expect(show).to have_release_name('1.0.0') - expect(show).to have_release_description('A long description of the release') + aggregate_failures 'Project release has expected contents' do + expect(show).to have_release_name('1.0.0') + expect(show).to have_release_description('A long description of the release') + end end end @@ -78,49 +88,60 @@ module QA project.create_repository_tag('1.0.0') project.visit! - visit_job('create-release-with-new-tag-filled-with-information') + Flow::Pipeline.wait_for_pipeline_creation_via_api(project: project) + project.visit_job('create-release-with-new-tag-filled-with-information') Page::Project::Job::Show.perform do |show| - expect(show.output).to have_content('release created successfully!') - expect(show.output).to have_content('Tag: v9.0.2') - expect(show.output).to have_content('Name: new release v9.0.2') - expect(show.output).to have_content('Description: A long description of the release') - expect(show.output).to have_content('Released At: 2026-01-01 00:00:00 +0000 UTC') - expect(show.output).to have_content('Asset::Link::Name: Download link') - expect(show.output).to have_content('Asset::Link::URL: https://gitlab-runner-downloads.s3.amazonaws.com/v16.9.0-rc2/binaries/gitlab-runner-linux-amd64') - expect(show.output).to have_content('Milestone: v1.0 -') - expect(show.output).to have_content('Milestone: v2.0 -') + Support::Waiter.wait_until { show.has_passed? } + + aggregate_failures 'Job has expected contents' do + expect(show.output).to have_content('release created successfully!') + expect(show.output).to have_content('Tag: v9.0.2') + expect(show.output).to have_content('Name: new release v9.0.2') + expect(show.output).to have_content('Description: A long description of the release') + expect(show.output).to have_content('Released At: 2026-01-01 00:00:00 +0000 UTC') + expect(show.output).to have_content('Asset::Link::Name: Download link') + expect(show.output).to have_content('Asset::Link::URL: https://gitlab-runner-downloads.s3.amazonaws.com/v16.9.0-rc2/binaries/gitlab-runner-linux-amd64') + expect(show.output).to have_content('Milestone: v1.0 -') + expect(show.output).to have_content('Milestone: v2.0 -') + end end visit_catalog_resource_show_page Page::Explore::CiCdCatalog::Show.perform do |show| - expect(show).to have_version_badge('9.0.2') - expect(show).to have_component_name('new_component') - expect(show).to have_input( - name: 'scanner-output', - required: 'false', - type: 'string', - description: '', - default: 'json' - ) + aggregate_failures 'Catalog component has expected contents' do + expect(show).to have_version_badge('9.0.2') + expect(show).to have_component_name('new_component') + expect(show).to have_input( + name: 'scanner-output', + required: 'false', + type: 'string', + description: '', + default: 'json' + ) + end show.click_latest_version_badge end Page::Project::Tag::Show.perform do |show| - expect(show).to have_tag_name('v9.0.2') - expect(show).to have_tag_message('a new tag') + aggregate_failures 'Project tag has expected contents' do + expect(show).to have_tag_name('v9.0.2') + expect(show).to have_tag_message('a new tag') + end show.click_release_link end Page::Project::Release::Show.perform do |show| - expect(show).to have_release_name('new release v9.0.2') - expect(show).to have_release_description('A long description of the release') - expect(show).to have_milestone_title('v1.0') - expect(show).to have_milestone_title('v2.0') - expect(show).to have_asset_link('Download link', '/binaries/gitlab-runner-linux-amd64') + aggregate_failures 'Project release has expected contents' do + expect(show).to have_release_name('new release v9.0.2') + expect(show).to have_release_description('A long description of the release') + expect(show).to have_milestone_title('v1.0') + expect(show).to have_milestone_title('v2.0') + expect(show).to have_asset_link('Download link', '/binaries/gitlab-runner-linux-amd64') + end end end @@ -198,17 +219,6 @@ module QA YAML end - def visit_job(job_name) - Flow::Pipeline.visit_latest_pipeline - - Page::Project::Pipeline::Show.perform do |show| - Support::Waiter.wait_until { show.has_passed? } - - expect(show).to have_job(job_name) - show.click_job(job_name) - end - end - def visit_catalog_resource_show_page Page::Main::Menu.perform do |main| main.go_to_explore diff --git a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/custom_variable_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/custom_variable_spec.rb index d2aac9dab02..de8ed91098b 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/custom_variable_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/custom_variable_spec.rb @@ -29,17 +29,16 @@ module QA end before do - Flow::Login.sign_in - project.change_pipeline_variables_minimum_override_role('developer') + Flow::Login.sign_in project.visit! Page::Project::Menu.perform(&:go_to_pipelines) Page::Project::Pipeline::Index.perform(&:click_run_pipeline_button) end after do - runner&.remove_via_api! + runner.remove_via_api! end it 'manually creates a pipeline and uses the defined custom variable value', diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/include_local_config_file_paths_with_wildcard_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/include_local_config_file_paths_with_wildcard_spec.rb index 225b672f922..10ecc6575c7 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/include_local_config_file_paths_with_wildcard_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/include_local_config_file_paths_with_wildcard_spec.rb @@ -8,15 +8,11 @@ module QA before do Flow::Login.sign_in add_files_to_project - project.visit! - Flow::Pipeline.visit_latest_pipeline + project.visit_latest_pipeline end - after do - project.remove_via_api! - end - - it 'runs the pipeline with composed config', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348002' do + it 'runs the pipeline with composed config', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348002' do Page::Project::Pipeline::Show.perform do |pipeline| aggregate_failures 'pipeline has all expected jobs' do expect(pipeline).to have_job('build') @@ -32,6 +28,7 @@ module QA create(:commit, project: project, commit_message: 'Add CI and local files', actions: [ build_config_file, test_config_file, non_detectable_file, main_ci_file ]) + Flow::Pipeline.wait_for_pipeline_creation_via_api(project: project) end def main_ci_file diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_a_project_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_a_project_spec.rb index 7f284bcb19d..bc2788922af 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_a_project_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_a_project_spec.rb @@ -15,8 +15,7 @@ module QA Flow::Login.sign_in add_included_files add_main_ci_file - project.visit! - Flow::Pipeline.visit_latest_pipeline(status: 'Passed') + project.visit_latest_pipeline end after do @@ -47,6 +46,8 @@ module QA def add_main_ci_file create(:commit, project: project, commit_message: 'Add config file', actions: [main_ci_file]) + Flow::Pipeline.wait_for_pipeline_creation_via_api(project: project) + Flow::Pipeline.wait_for_latest_pipeline_to_have_status(project: project, status: 'success') end def add_included_files diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_multiple_projects_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_multiple_projects_spec.rb index 699ff2465a8..deac86a2e0f 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_multiple_projects_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_multiple_projects_spec.rb @@ -15,10 +15,9 @@ module QA add_included_files_for(main_project) add_included_files_for(project1) add_included_files_for(project2) - add_main_ci_file(main_project) + add_ci_file_to_main_project - main_project.visit! - Flow::Pipeline.visit_latest_pipeline(status: 'Passed') + main_project.visit_latest_pipeline end after do @@ -43,7 +42,11 @@ module QA private def add_included_files_for(project) - files = [ + create(:commit, project: project, commit_message: 'Add files', actions: included_files(project)) + end + + def included_files(project) + [ { action: 'create', file_path: 'file1.yml', @@ -63,12 +66,12 @@ module QA YAML } ] - - create(:commit, project: project, commit_message: 'Add files', actions: files) end - def add_main_ci_file(project) - create(:commit, project: project, commit_message: 'Add config file', actions: [main_ci_file]) + def add_ci_file_to_main_project + create(:commit, project: main_project, commit_message: 'Add config file', actions: [main_ci_file]) + Flow::Pipeline.wait_for_pipeline_creation_via_api(project: main_project) + Flow::Pipeline.wait_for_latest_pipeline_to_have_status(project: main_project, status: 'success') end def main_ci_file diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/parent_child_pipelines_independent_relationship_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/parent_child_pipelines_independent_relationship_spec.rb index a7ee5c83c2a..6a1ab3f5103 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/parent_child_pipelines_independent_relationship_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/parent_child_pipelines_independent_relationship_spec.rb @@ -19,7 +19,7 @@ module QA testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/358059' ) do add_ci_files(success_child_ci_file) - Flow::Pipeline.visit_latest_pipeline + project.visit_latest_pipeline Page::Project::Pipeline::Show.perform do |parent_pipeline| expect(parent_pipeline).to have_child_pipeline @@ -32,7 +32,7 @@ module QA testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/358060' ) do add_ci_files(fail_child_ci_file) - Flow::Pipeline.visit_latest_pipeline + project.visit_latest_pipeline Page::Project::Pipeline::Show.perform do |parent_pipeline| expect(parent_pipeline).to have_child_pipeline @@ -97,7 +97,9 @@ module QA create(:commit, project: project, commit_message: 'Add parent and child pipelines CI files.', - actions: [child_ci_file, parent_ci_file]).project.visit! + actions: [child_ci_file, parent_ci_file] + ) + Flow::Pipeline.wait_for_pipeline_creation_via_api(project: project) end end end diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb index 8b8c0bf6c2f..fae72ca7a84 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb @@ -11,19 +11,21 @@ module QA let!(:runner) { create(:group_runner, group: group, name: executor, tags: [executor]) } before do - Flow::Login.sign_in + upstream_project.change_pipeline_variables_minimum_override_role('developer') + downstream_project.change_pipeline_variables_minimum_override_role('developer') + add_ci_file(downstream_project, downstream_ci_file) add_ci_file(upstream_project, upstream_ci_file) - upstream_project.change_pipeline_variables_minimum_override_role('developer') - downstream_project.change_pipeline_variables_minimum_override_role('developer') - upstream_project.visit! - Flow::Pipeline.visit_latest_pipeline(status: 'Passed') + Flow::Login.sign_in + Flow::Pipeline.wait_for_pipeline_creation_via_api(project: upstream_project) + Flow::Pipeline.wait_for_latest_pipeline_to_have_status(project: upstream_project, status: 'success') + + upstream_project.visit_latest_pipeline end after do runner.remove_via_api! - [upstream_project, downstream_project].each(&:remove_via_api!) end it 'runs the pipeline with composed config', diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_with_image_pull_policy_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_with_image_pull_policy_spec.rb index 998221b4189..a5a89c7eaea 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_with_image_pull_policy_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_with_image_pull_policy_spec.rb @@ -15,11 +15,11 @@ module QA end before do + Flow::Login.sign_in update_runner_policy(allowed_policies) add_ci_file - Flow::Login.sign_in - project.visit! - Flow::Pipeline.visit_latest_pipeline + + project.visit_latest_pipeline end after do @@ -63,7 +63,7 @@ module QA with_them do it 'applies pull policy in job correctly', testcase: params[:testcase] do - visit_job + project.visit_job(job_name) if pull_image expect(job_log).to have_content(message), @@ -93,7 +93,7 @@ module QA issue: "https://gitlab.com/gitlab-org/gitlab/-/issues/462232" } ) do - visit_job + project.visit_job(job_name) expect(job_log).to include(text1, text2), "Expected to find contents #{text1} and #{text2} in #{job_log}, but didn't." @@ -140,14 +140,9 @@ module QA YAML } ]) - end - def visit_job - Page::Project::Pipeline::Show.perform do |show| - Support::Waiter.wait_until(max_duration: 90) { show.completed? } - - show.click_job(job_name) - end + Flow::Pipeline.wait_for_pipeline_creation_via_api(project: project) + Flow::Pipeline.wait_for_latest_pipeline_to_have_status(project: project, status: 'success') end def job_log diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_with_manual_jobs_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_with_manual_jobs_spec.rb index 583f29c8450..f795c5c0611 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_with_manual_jobs_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_with_manual_jobs_spec.rb @@ -57,15 +57,14 @@ module QA end before do - make_sure_to_have_a_skipped_pipeline - Flow::Login.sign_in - project.visit! - Flow::Pipeline.visit_latest_pipeline(status: 'Skipped') + Flow::Pipeline.wait_for_pipeline_creation_via_api(project: project) + Flow::Pipeline.wait_for_latest_pipeline_to_have_status(project: project, status: 'skipped') + project.visit_latest_pipeline end after do - runner&.remove_via_api! + runner.remove_via_api! end it( @@ -74,10 +73,7 @@ module QA ) do Page::Project::Pipeline::Show.perform do |show| show.click_job_action('Prep') # Trigger pipeline manually - - show.wait_until(max_duration: 300, sleep_interval: 2, reload: false) do - project.latest_pipeline[:status] == 'success' - end + Flow::Pipeline.wait_for_latest_pipeline_to_have_status(project: project, status: 'success', wait: 300) aggregate_failures do expect(show).to have_build('Test', status: :success) @@ -91,45 +87,6 @@ module QA end end end - - private - - # Wait for first pipeline to finish and have "skipped" status - # If it takes too long, create new pipeline and retry (2 times) - def make_sure_to_have_a_skipped_pipeline - attempts ||= 1 - Runtime::Logger.info('Waiting for pipeline to have status "skipped"...') - Support::Waiter.wait_until(max_duration: 120, sleep_interval: 3, retry_on_exception: true) do - project.latest_pipeline[:status] == 'skipped' - end - rescue Support::Repeater::WaitExceededError - raise 'Failed to create skipped pipeline after 3 attempts.' unless (attempts += 1) < 4 - - Runtime::Logger.debug( - "Previous pipeline took too long to finish. Potential jobs with problems:\n#{problematic_jobs}" - ) - Runtime::Logger.info("Triggering a new pipeline...") - trigger_new_pipeline - retry - end - - def trigger_new_pipeline - original_count = project.pipelines.length - create(:pipeline, project: project) - - Support::Waiter.wait_until(sleep_interval: 1) { project.pipelines.length > original_count } - end - - # We know that all the jobs in pipeline are purposely skipped - # The pipeline should have status "skipped" almost right away after being created - # If pipeline is held up, likely because there are some jobs that - # doesn't have either "skipped" or "manual" status - def problematic_jobs - pipeline = create(:pipeline, project: project, id: project.latest_pipeline[:id]) - - acceptable_statuses = %w[skipped manual] - pipeline.jobs.select { |job| !(acceptable_statuses.include? job[:status]) } - end end end end diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/trigger_child_pipeline_with_manual_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/trigger_child_pipeline_with_manual_spec.rb index 26d7ef55ded..8527a309027 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/trigger_child_pipeline_with_manual_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/trigger_child_pipeline_with_manual_spec.rb @@ -10,8 +10,7 @@ module QA before do Flow::Login.sign_in add_ci_files - project.visit! - Flow::Pipeline.visit_latest_pipeline(status: 'Passed') + project.visit_latest_pipeline end after do @@ -29,9 +28,9 @@ module QA expect(parent_pipeline).not_to have_child_pipeline parent_pipeline.click_job_action('trigger') - Support::Waiter.wait_until { parent_pipeline.has_child_pipeline? } - parent_pipeline.expand_child_pipeline + Support::Waiter.wait_until(max_duration: 240) { parent_pipeline.has_child_pipeline? } + parent_pipeline.expand_child_pipeline expect(parent_pipeline).to have_build('child_build', status: nil) end end @@ -42,6 +41,8 @@ module QA create(:commit, project: project, commit_message: 'Add parent and child pipelines CI files.', actions: [ child_ci_file, parent_ci_file ]) + Flow::Pipeline.wait_for_pipeline_creation_via_api(project: project) + Flow::Pipeline.wait_for_latest_pipeline_to_have_status(project: project, status: 'success') end def parent_ci_file diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/trigger_matrix_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/trigger_matrix_spec.rb index cb2fa5d2399..166d374ae96 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/trigger_matrix_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/trigger_matrix_spec.rb @@ -10,8 +10,7 @@ module QA before do Flow::Login.sign_in add_ci_files - project.visit! - Flow::Pipeline.visit_latest_pipeline(status: 'Passed') + project.visit_latest_pipeline end after do @@ -47,6 +46,8 @@ module QA def add_ci_files create(:commit, project: project, commit_message: 'todo', actions: [child_ci_file, parent_ci_file]) + Flow::Pipeline.wait_for_pipeline_creation_via_api(project: project) + Flow::Pipeline.wait_for_latest_pipeline_to_have_status(project: project, status: 'success') end def parent_ci_file diff --git a/qa/qa/specs/features/browser_ui/4_verify/testing/endpoint_coverage_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/testing/endpoint_coverage_spec.rb index 87ebeb24666..80781129e46 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/testing/endpoint_coverage_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/testing/endpoint_coverage_spec.rb @@ -54,13 +54,8 @@ module QA push.commit_message = 'Commit .gitlab-ci.yml' end - # observe pipeline creation - project.visit! - Flow::Pipeline.visit_latest_pipeline - - Page::Project::Pipeline::Show.perform do |show| - show.click_job('test') - end + Flow::Pipeline.wait_for_pipeline_creation_via_api(project: project) + project.visit_job('test') Page::Project::Job::Show.perform do |show| # user views job succeeding diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb index c9ba8d42c29..58227c03b39 100644 --- a/spec/db/schema_spec.rb +++ b/spec/db/schema_spec.rb @@ -101,6 +101,7 @@ RSpec.describe 'Database schema', ci_builds_runner_session: %w[project_id], ci_daily_build_group_report_results: %w[partition_id], ci_deleted_objects: %w[project_id], + ci_gitlab_hosted_runner_monthly_usages: %w[root_namespace_id project_id runner_id], ci_job_artifacts: %w[partition_id project_id job_id], ci_namespace_monthly_usages: %w[namespace_id], ci_pipeline_artifacts: %w[partition_id], diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index b92e8cc150d..112a5b3218c 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -564,6 +564,8 @@ container_repositories: - project - name project: +- instance_runner_monthly_usages +- hosted_runner_monthly_usages - catalog_resource - catalog_resource_sync_events - catalog_resource_versions diff --git a/spec/rack_servers/puma_spec.rb b/spec/rack_servers/puma_spec.rb index 1d7efe67564..54b2c0ef2cb 100644 --- a/spec/rack_servers/puma_spec.rb +++ b/spec/rack_servers/puma_spec.rb @@ -25,7 +25,7 @@ RSpec.describe 'Puma' do WebMock.allow_net_connect! end - %w[SIGQUIT SIGTERM SIGKILL].each do |signal| + %w[SIGTERM SIGKILL].each do |signal| it "has a worker that self-terminates on signal #{signal}" do response = Excon.get('unix://', socket: @socket_path) expect(response.status).to eq(200) diff --git a/spec/services/merge_requests/mergeability/check_lfs_file_locks_service_spec.rb b/spec/services/merge_requests/mergeability/check_lfs_file_locks_service_spec.rb index 7202bc0abf7..b8be397b129 100644 --- a/spec/services/merge_requests/mergeability/check_lfs_file_locks_service_spec.rb +++ b/spec/services/merge_requests/mergeability/check_lfs_file_locks_service_spec.rb @@ -24,40 +24,63 @@ RSpec.describe MergeRequests::Mergeability::CheckLfsFileLocksService, feature_ca context 'when lfs is enabled' do let(:only_allow_merge_if_pipeline_succeeds) { true } - let(:changed_path) { instance_double('Gitlab::Git::ChangedPath', path: 'README.md') } + let(:changed_paths) do + [ + instance_double('Gitlab::Git::ChangedPath', path: 'README.md'), + instance_double('Gitlab::Git::ChangedPath', path: 'conflict.rb'), + instance_double('Gitlab::Git::ChangedPath', path: 'README.md') + ] + end before do - allow(merge_request).to receive(:changed_paths).and_return([changed_path]) + allow(merge_request).to receive(:changed_paths).and_return(changed_paths) + allow(project.lfs_file_locks).to receive(:exists?).and_call_original + allow(project.lfs_file_locks).to receive(:for_paths).and_call_original end context 'when there are no lfs files locks for this project' do it 'returns a check result with status success' do expect(execute.status).to eq Gitlab::MergeRequests::Mergeability::CheckResult::SUCCESS_STATUS end + + it 'returns early before querying for matching file locks' do + execute + expect(project.lfs_file_locks).to have_received(:exists?) + expect(project.lfs_file_locks).not_to have_received(:for_paths) + end end context 'when there are lfs files locked by the merge request author' do - let(:user) { create(:user) } - before do - allow(merge_request).to receive(:author_id).and_return(user.id) - create(:lfs_file_lock, project: project, path: changed_path.path, user: user) + create(:lfs_file_lock, project: project, path: changed_paths.first.path, user: merge_request.author) end it 'returns a check result with status success' do expect(execute.status).to eq Gitlab::MergeRequests::Mergeability::CheckResult::SUCCESS_STATUS end + + it 'deduplicates the changed paths' do + execute + expect(project.lfs_file_locks).to have_received(:exists?) + expect(project.lfs_file_locks).to have_received(:for_paths).with(changed_paths.map(&:path).uniq) + end end context 'when there are lfs files locked by another user' do before do allow(merge_request).to receive(:author_id).and_return(0) - create(:lfs_file_lock, project: project, path: changed_path.path) + create(:lfs_file_lock, project: project, path: changed_paths.second.path) end it 'returns a check result with status failure' do expect(execute.status).to eq Gitlab::MergeRequests::Mergeability::CheckResult::FAILED_STATUS end + + it 'deduplicates the changed paths' do + execute + expect(project.lfs_file_locks).to have_received(:exists?) + expect(project.lfs_file_locks).to have_received(:for_paths).with(changed_paths.map(&:path).uniq) + end end end