diff --git a/app/models/packages/package_file.rb b/app/models/packages/package_file.rb index e7844f7b3f9cbbe66954fca9347d359609aa06ac..4fb8342a4ec7b004cc9c67ca80e6983621f9b995 100644 --- a/app/models/packages/package_file.rb +++ b/app/models/packages/package_file.rb @@ -62,7 +62,10 @@ class PackageFile < ApplicationRecord scope :preload_pipelines, -> { preload(pipelines: :user) } scope :preload_pipelines_with_user_project_namespace_route, -> do - preload(pipelines: [:user, { project: { namespace: :route } }]) + preload( + :package_file_state, + pipelines: [:user, { project: { namespace: :route } }] + ) end scope :preload_conan_file_metadata, -> { preload(:conan_file_metadatum) } diff --git a/config/initializers/postgres_partitioning.rb b/config/initializers/postgres_partitioning.rb index 0f6e23f450e1374bf79324da9f0e278dcb57715d..d351b54ac4c038f94b6a09ca186bae5cec303f4d 100644 --- a/config/initializers/postgres_partitioning.rb +++ b/config/initializers/postgres_partitioning.rb @@ -34,6 +34,7 @@ Ci::JobInput, Ci::JobMessage, Ci::Pipeline, + Geo::PipelineArtifactState, Ci::PipelineVariable, Ci::RunnerManagerBuild, Ci::Stage, diff --git a/ee/app/models/concerns/geo/verification_state_definition.rb b/ee/app/models/concerns/geo/verification_state_definition.rb index 98dc96c37808955f36afa4675b739b2bf0202ba5..d931dcefcf8cca6f2bc425be3d42f460085dd512 100644 --- a/ee/app/models/concerns/geo/verification_state_definition.rb +++ b/ee/app/models/concerns/geo/verification_state_definition.rb @@ -137,5 +137,20 @@ def verification_transition_details(object, transition) def verification_state_name_no_prefix verification_state_name.to_s.gsub('verification_', '') end + + # Returns true if all verification fields are in their default state. + # Used during migration from replicable table to state table to determine + # if we should copy the checksum from the replicable table. + # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/208182 + # TODO: Remove after verification data migration is complete (related to moving verification_checksum) + def verification_fields_default? + verification_pending? && + verified_at.nil? && + verification_started_at.nil? && + verification_retry_at.nil? && + verification_checksum.nil? && + verification_failure.blank? && + verification_retry_count.to_i == 0 + end end end diff --git a/ee/app/models/ee/ci/pipeline_artifact.rb b/ee/app/models/ee/ci/pipeline_artifact.rb index 9d416bc438cd267559a8100a46ef290fdeb3130e..f535f11afee4c99917a0d34e50ba6fcbaa969e50 100644 --- a/ee/app/models/ee/ci/pipeline_artifact.rb +++ b/ee/app/models/ee/ci/pipeline_artifact.rb @@ -14,14 +14,38 @@ module PipelineArtifact with_replicator ::Geo::PipelineArtifactReplicator has_one :pipeline_artifact_state, - ->(artifact) { where(partition_id: artifact.partition_id) }, - class_name: 'Geo::PipelineArtifactState', + ->(artifact) { in_partition(artifact) }, + class_name: '::Geo::PipelineArtifactState', partition_foreign_key: :partition_id, inverse_of: :pipeline_artifact, autosave: false + + delegate(*::Geo::VerificationState::VERIFICATION_METHODS, to: :pipeline_artifact_state) + + scope :available_verifiables, -> { left_joins(:pipeline_artifact_state) } + scope :with_verification_state, ->(state) { + left_joins(:pipeline_artifact_state).where( + p_ci_pipeline_artifact_states: { + verification_state: verification_state_value(state) + } + ) + } + + def verification_state_object + pipeline_artifact_state + end + + def pipeline_artifact_state + state = super || build_pipeline_artifact_state + state.partition_id = partition_id if state.partition_id.nil? && partition_id.present? + state.pipeline_artifact ||= self + state + end end class_methods do + extend ::Gitlab::Utils::Override + # Search for a list of projects associated, based on the query given in `query`. # # @param [String] query term that will search over projects :path, :name and :description @@ -33,6 +57,27 @@ def search(query) # This is divided into two separate queries, one for the CI and one for the main database where(project_id: ::Project.search(query).limit(1000).pluck_primary_key) end + + override :verification_state_table_class + def verification_state_table_class + Geo::PipelineArtifactState + end + + override :create_verification_details_for + def create_verification_details_for(primary_keys) + pipeline_artifacts = find(primary_keys) + + rows = pipeline_artifacts.map do |artifact| + { verification_state_model_key => artifact.id, :partition_id => artifact.partition_id } + end + + verification_state_table_class.insert_all(rows, unique_by: %i[pipeline_artifact_id partition_id]) + end + + override :verification_state_model_key + def verification_state_model_key + :pipeline_artifact_id + end end end end diff --git a/ee/app/models/ee/packages/package_file.rb b/ee/app/models/ee/packages/package_file.rb index f5ac7d4ddc30d7cf5dda24ca34731a5698089d81..26b2faad72788f7963dce915dd6d225c02d6c0d6 100644 --- a/ee/app/models/ee/packages/package_file.rb +++ b/ee/app/models/ee/packages/package_file.rb @@ -20,6 +20,27 @@ module PackageFile inverse_of: :package_file, foreign_key: :package_file_id, class_name: '::Geo::PackageFileState' + + delegate(*::Geo::VerificationState::VERIFICATION_METHODS, to: :package_file_state) + + scope :available_verifiables, -> { left_joins(:package_file_state) } + scope :with_verification_state, ->(state) { + left_joins(:package_file_state).where( + packages_package_file_states: { + verification_state: verification_state_value(state) + } + ) + } + def verification_state_object + package_file_state + end + + def package_file_state + state = super || build_package_file_state + state.package_file_id ||= id if id.present? + state.package_file ||= self + state + end end class_methods do @@ -43,6 +64,16 @@ def selective_sync_scope(node, **_params) joins(:package) .where(packages_packages: { project_id: ::Project.selective_sync_scope(node).select(:id) }) end + + override :verification_state_table_class + def verification_state_table_class + Geo::PackageFileState + end + + override :verification_state_model_key + def verification_state_model_key + :package_file_id + end end end end diff --git a/ee/app/models/ee/snippet_repository.rb b/ee/app/models/ee/snippet_repository.rb index 7e9d78aea8cfc7b5f1ef59078c189edbba54be7e..68d87042f02113dbbd28c28e703fca4a5b27dc86 100644 --- a/ee/app/models/ee/snippet_repository.rb +++ b/ee/app/models/ee/snippet_repository.rb @@ -20,6 +20,28 @@ module SnippetRepository inverse_of: :snippet_repository, foreign_key: :snippet_repository_id, class_name: '::Geo::SnippetRepositoryState' + + delegate(*::Geo::VerificationState::VERIFICATION_METHODS, to: :snippet_repository_state) + + scope :available_verifiables, -> { left_joins(:snippet_repository_state) } + scope :with_verification_state, ->(state) { + left_joins(:snippet_repository_state).where( + snippet_repository_states: { + verification_state: verification_state_value(state) + } + ) + } + + def verification_state_object + snippet_repository_state + end + + def snippet_repository_state + state = super || build_snippet_repository_state + state.snippet_repository_id ||= id if id.present? + state.snippet_repository ||= self + state + end end class_methods do @@ -37,7 +59,7 @@ def search(query) end # @return [ActiveRecord::Relation] scope observing selective sync - # settings of the given node + # settings of the given node override :selective_sync_scope def selective_sync_scope(node, **_params) return all unless node.selective_sync? @@ -51,7 +73,7 @@ def snippet_repositories_for_selected_namespaces(node) personal_snippets = self.joins(:snippet).where(snippet: ::Snippet.only_personal_snippets) project_snippets = self.joins(snippet: :project) - .merge(::Snippet.for_projects(::Project.selective_sync_scope(node).select(:id))) + .merge(::Snippet.for_projects(::Project.selective_sync_scope(node).select(:id))) self.from_union([project_snippets, personal_snippets]) end @@ -59,6 +81,16 @@ def snippet_repositories_for_selected_namespaces(node) def snippet_repositories_for_selected_shards(node) self.for_repository_storage(node.selective_sync_shards) end + + override :verification_state_table_class + def verification_state_table_class + Geo::SnippetRepositoryState + end + + override :verification_state_model_key + def verification_state_model_key + :snippet_repository_id + end end end end diff --git a/ee/app/models/ee/terraform/state_version.rb b/ee/app/models/ee/terraform/state_version.rb index ededd0a50129ac8cf33dc93ff8c2897acd41cf0e..6f2810114a282e4526a71aca54f9e95f41fe9992 100644 --- a/ee/app/models/ee/terraform/state_version.rb +++ b/ee/app/models/ee/terraform/state_version.rb @@ -19,6 +19,28 @@ module StateVersion autosave: false scope :project_id_in, ->(ids) { joins(:terraform_state).where('terraform_states.project_id': ids) } + + delegate(*::Geo::VerificationState::VERIFICATION_METHODS, to: :terraform_state_version_state) + + scope :available_verifiables, -> { left_joins(:terraform_state_version_state) } + scope :with_verification_state, ->(state) { + left_joins(:terraform_state_version_state).where( + terraform_state_version_states: { + verification_state: verification_state_value(state) + } + ) + } + + def verification_state_object + terraform_state_version_state + end + + def terraform_state_version_state + state = super || build_terraform_state_version_state + state.terraform_state_version_id ||= id if id.present? + state.terraform_state_version ||= self + state + end end class_methods do @@ -43,6 +65,16 @@ def selective_sync_scope(node, **_params) project_id_in(::Project.selective_sync_scope(node)) end + + override :verification_state_table_class + def verification_state_table_class + Geo::TerraformStateVersionState + end + + override :verification_state_model_key + def verification_state_model_key + :terraform_state_version_id + end end end end diff --git a/ee/app/models/geo/pipeline_artifact_state.rb b/ee/app/models/geo/pipeline_artifact_state.rb index 6a685df9ba750de5fa7391df853781367aa1619f..c8a521054f3d71f3c8942252057a3b17e548f2f8 100644 --- a/ee/app/models/geo/pipeline_artifact_state.rb +++ b/ee/app/models/geo/pipeline_artifact_state.rb @@ -6,6 +6,7 @@ class PipelineArtifactState < Ci::ApplicationRecord include ::Ci::Partitionable self.table_name = 'p_ci_pipeline_artifact_states' + self.primary_key = :pipeline_artifact_id belongs_to :pipeline_artifact, ->(artifact_state) { in_partition(artifact_state) }, @@ -14,5 +15,9 @@ class PipelineArtifactState < Ci::ApplicationRecord class_name: '::Ci::PipelineArtifact' partitionable scope: :pipeline_artifact, partitioned: true + + def model_record_id + pipeline_artifact_id + end end end diff --git a/ee/app/replicators/geo/package_file_replicator.rb b/ee/app/replicators/geo/package_file_replicator.rb index deb23e0336b34a6ed53dfab99e5285c306541694..37185cb3290daae6aace456e8b93b9b66dddb3b2 100644 --- a/ee/app/replicators/geo/package_file_replicator.rb +++ b/ee/app/replicators/geo/package_file_replicator.rb @@ -21,5 +21,17 @@ def self.replicable_title_plural def carrierwave_uploader model_record.file end + + def calculate_checksum + raise 'File is not checksummable' unless checksummable? + + state = model_record.verification_state_object + + # this is a temporary change, to be kept while records are migrated to the states table + # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/208182 + return model_record.read_attribute(:verification_checksum).presence || super if state.verification_fields_default? + + super + end end end diff --git a/ee/app/replicators/geo/pipeline_artifact_replicator.rb b/ee/app/replicators/geo/pipeline_artifact_replicator.rb index 437c35b139db520ce3076a1adb40276cdc1c1007..ce19e13a0fd38e5b8a9db304b2d2f0e8f52709bb 100644 --- a/ee/app/replicators/geo/pipeline_artifact_replicator.rb +++ b/ee/app/replicators/geo/pipeline_artifact_replicator.rb @@ -21,5 +21,17 @@ def self.replicable_title_plural def carrierwave_uploader model_record.file end + + def calculate_checksum + raise 'File is not checksummable' unless checksummable? + + state = model_record.verification_state_object + + # this is a temporary change, to be kept while records are migrated to the states table + # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/208182 + return model_record.read_attribute(:verification_checksum).presence || super if state.verification_fields_default? + + super + end end end diff --git a/ee/app/replicators/geo/terraform_state_version_replicator.rb b/ee/app/replicators/geo/terraform_state_version_replicator.rb index 79a489f9b05589a7e829c1dd3a7b45f0b6f8f590..325bb8db8a9f8ce9ca21a5cbdd0682c8f7bf8240 100644 --- a/ee/app/replicators/geo/terraform_state_version_replicator.rb +++ b/ee/app/replicators/geo/terraform_state_version_replicator.rb @@ -21,5 +21,17 @@ def self.replicable_title def self.replicable_title_plural s_('Geo|Terraform State Versions') end + + def calculate_checksum + raise 'File is not checksummable' unless checksummable? + + state = model_record.verification_state_object + + # this is a temporary change, to be kept while records are migrated to the states table + # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/208182 + return model_record.read_attribute(:verification_checksum).presence || super if state.verification_fields_default? + + super + end end end diff --git a/ee/spec/lib/gitlab/geo/replication/blob_retriever_spec.rb b/ee/spec/lib/gitlab/geo/replication/blob_retriever_spec.rb index d387f52f0b6f429187253f1ec395fa67a364f69d..7087003dbbca0122c02f47c85b7af6d95c0ccdf4 100644 --- a/ee/spec/lib/gitlab/geo/replication/blob_retriever_spec.rb +++ b/ee/spec/lib/gitlab/geo/replication/blob_retriever_spec.rb @@ -2,11 +2,25 @@ require 'spec_helper' RSpec.describe Gitlab::Geo::Replication::BlobRetriever, :aggregate_failures, feature_category: :geo_replication do - let(:package_file) { create(:package_file, :npm) } - let(:package_checksum) { package_file.class.sha256_hexdigest(package_file.file.path) } + include EE::GeoHelpers + + let_it_be(:primary) { create(:geo_node, :primary) } + + let(:package_file) do + file = create(:package_file, :npm) + checksum = file.class.sha256_hexdigest(file.file.path) + file.update_column(:verification_checksum, checksum) + file + end + + let(:package_checksum) { package_file.verification_checksum } let(:replicator_class) { Geo::PackageFileReplicator } let(:replicator) { replicator_class.new(model_record_id: package_file.id) } + before do + stub_current_geo_node(primary) + end + describe '#initialize' do it 'errors out with an invalid replicator' do expect { described_class.new(replicator: Object.new, checksum: nil) }.to raise_error(ArgumentError) diff --git a/ee/spec/models/concerns/geo/verifiable_model_spec.rb b/ee/spec/models/concerns/geo/verifiable_model_spec.rb index 43ebdb12b502a83e0d7c10ca36c33be5de2d48a4..c8decf0271cf19c217cfce8cb4c98983c8ccfa16 100644 --- a/ee/spec/models/concerns/geo/verifiable_model_spec.rb +++ b/ee/spec/models/concerns/geo/verifiable_model_spec.rb @@ -168,7 +168,8 @@ it 'prevents n+1 queries' do create_list(factory, 4) - expect { model_classes.with_state_details.all.map(&:verification_state) }.not_to exceed_query_limit(2) + expect { model_classes.with_state_details.all.map(&:verification_state) } + .not_to exceed_query_limit(2) end end end diff --git a/ee/spec/models/ee/ci/pipeline_artifact_spec.rb b/ee/spec/models/ee/ci/pipeline_artifact_spec.rb index 79ed2bc0ee20a09d7659fd149024a35221a95187..d987d9e643a8d318ec9563c973699bbe270bc6cc 100644 --- a/ee/spec/models/ee/ci/pipeline_artifact_spec.rb +++ b/ee/spec/models/ee/ci/pipeline_artifact_spec.rb @@ -4,6 +4,11 @@ RSpec.describe Ci::PipelineArtifact, feature_category: :geo_replication do include EE::GeoHelpers + include Ci::PartitioningHelpers + + before do + stub_current_partition_id(ci_testing_partition_id) + end describe 'associations' do it { is_expected.to have_one(:pipeline_artifact_state).class_name('Geo::PipelineArtifactState').inverse_of(:pipeline_artifact) } @@ -11,8 +16,18 @@ include_examples 'a verifiable model for verification state' do let(:verifiable_model_record) do - build(:ci_pipeline_artifact, pipeline: create(:ci_pipeline, project: create(:project))) + build( + :ci_pipeline_artifact, + pipeline: create( + :ci_pipeline, + project: create(:project), + partition_id: ci_testing_partition_id + ), + partition_id: ci_testing_partition_id + ) end + + let(:unverifiable_model_record) { nil } end describe '.replicables_for_current_secondary' do @@ -152,4 +167,90 @@ end end end + + describe '.create_verification_details_for' do + let_it_be(:pipeline_with_partition) { create(:ci_pipeline, partition_id: ci_testing_partition_id) } + let_it_be(:artifact1) { create(:ci_pipeline_artifact, pipeline: pipeline_with_partition, partition_id: ci_testing_partition_id, file_type: :code_coverage) } + let_it_be(:artifact2) { create(:ci_pipeline_artifact, pipeline: pipeline_with_partition, partition_id: ci_testing_partition_id, file_type: :code_quality_mr_diff) } + + context 'when creating verification details for multiple artifacts' do + it 'creates verification state records without duplicates' do + primary_keys = [artifact1.id, artifact2.id] + + expect { described_class.create_verification_details_for(primary_keys) } + .to change { Geo::PipelineArtifactState.count }.by(2) + + # Verify the records were created with correct attributes + state1 = Geo::PipelineArtifactState.find_by(pipeline_artifact_id: artifact1.id, partition_id: ci_testing_partition_id) + state2 = Geo::PipelineArtifactState.find_by(pipeline_artifact_id: artifact2.id, partition_id: ci_testing_partition_id) + + expect(state1).to be_present + expect(state2).to be_present + expect(state1.partition_id).to eq(ci_testing_partition_id) + expect(state2.partition_id).to eq(ci_testing_partition_id) + end + + it 'handles duplicate creation attempts gracefully' do + primary_keys = [artifact1.id, artifact2.id] + + # First call should create records + expect { described_class.create_verification_details_for(primary_keys) } + .to change { Geo::PipelineArtifactState.count }.by(2) + + # Second call with same keys should not raise error or create duplicates + expect { described_class.create_verification_details_for(primary_keys) } + .not_to change { Geo::PipelineArtifactState.count } + + # Verify no constraint violations occurred + expect(Geo::PipelineArtifactState.where(pipeline_artifact_id: [artifact1.id, artifact2.id]).count).to eq(2) + end + + it 'handles mixed scenarios with existing and new records' do + # Create verification state for first artifact only + described_class.create_verification_details_for([artifact1.id]) + expect(Geo::PipelineArtifactState.count).to eq(1) + + # Now try to create for both (one existing, one new) + primary_keys = [artifact1.id, artifact2.id] + expect { described_class.create_verification_details_for(primary_keys) } + .to change { Geo::PipelineArtifactState.count }.by(1) + + expect(Geo::PipelineArtifactState.count).to eq(2) + end + + it 'prevents database constraint violations when attempting to insert duplicates' do + primary_keys = [artifact1.id] + + # Create initial record + described_class.create_verification_details_for(primary_keys) + expect(Geo::PipelineArtifactState.count).to eq(1) + + # This test would fail without the unique_by parameter, as it would attempt + # to insert a duplicate record and raise a database constraint violation. + # With unique_by: [:pipeline_artifact_id, :partition_id], the duplicate is ignored. + expect { described_class.create_verification_details_for(primary_keys) } + .not_to raise_error + + expect(Geo::PipelineArtifactState.count).to eq(1) + end + + context 'when demonstrating the need for unique_by parameter' do + it 'shows that insert_all without unique_by fails when duplicates exist' do + # Create the initial record using the method + described_class.create_verification_details_for([artifact1.id]) + + # Try to insert the same record directly without unique_by + rows = [{ pipeline_artifact_id: artifact1.id, partition_id: artifact1.partition_id }] + + # This fails because Rails doesn't know how to handle the conflict + expect { Geo::PipelineArtifactState.insert_all(rows) } + .to raise_error(ArgumentError, /No unique index found/) + + # But with unique_by (as used in the actual method), it works + expect { Geo::PipelineArtifactState.insert_all(rows, unique_by: %i[pipeline_artifact_id partition_id]) } + .not_to raise_error + end + end + end + end end diff --git a/ee/spec/models/ee/packages/package_file_spec.rb b/ee/spec/models/ee/packages/package_file_spec.rb index 6ee37f99c38984fcae9cdb9c23de6de940e64239..368d9ab063d3a79104c98c96c3c6d37c74243560 100644 --- a/ee/spec/models/ee/packages/package_file_spec.rb +++ b/ee/spec/models/ee/packages/package_file_spec.rb @@ -9,6 +9,8 @@ let(:verifiable_model_record) do build(:package_file) end + + let(:unverifiable_model_record) { nil } end describe '.replicables_for_current_secondary' do diff --git a/ee/spec/models/ee/terraform/state_version_spec.rb b/ee/spec/models/ee/terraform/state_version_spec.rb index 421d1b5113d96515d2d749abe2f700115f22900a..50909a655f387209b3a2f2c6ee0cd7fa34b48248 100644 --- a/ee/spec/models/ee/terraform/state_version_spec.rb +++ b/ee/spec/models/ee/terraform/state_version_spec.rb @@ -22,6 +22,8 @@ let(:verifiable_model_record) do build(:terraform_state_version, terraform_state: create(:terraform_state, project: project)) end + + let(:unverifiable_model_record) { nil } end describe '.replicables_for_current_secondary' do diff --git a/ee/spec/models/geo/pipeline_artifact_state_spec.rb b/ee/spec/models/geo/pipeline_artifact_state_spec.rb index 131fb6e887b3bbfe816179ce0d106a1c36697d43..7ce8f36a0cb049898c911d89678278f218a79624 100644 --- a/ee/spec/models/geo/pipeline_artifact_state_spec.rb +++ b/ee/spec/models/geo/pipeline_artifact_state_spec.rb @@ -36,6 +36,12 @@ end it 'includes partition scope when accessing pipeline_artifact_state association' do + create(:geo_pipeline_artifact_state, + pipeline_artifact: pipeline_artifact, + partition_id: ci_testing_partition_id) + + pipeline_artifact.reload + # Verify the association query includes the partition scope association_scope = pipeline_artifact.association(:pipeline_artifact_state).scope diff --git a/ee/spec/models/snippet_repository_spec.rb b/ee/spec/models/snippet_repository_spec.rb index 6c74a86118f21172968355b33769094cd089036f..2293bc02913f2d9aa0e566c8ac653b3e69b17d0a 100644 --- a/ee/spec/models/snippet_repository_spec.rb +++ b/ee/spec/models/snippet_repository_spec.rb @@ -24,6 +24,8 @@ let(:verifiable_model_record) do build(:snippet_repository, snippet: create(:project_snippet, project: create(:project, group: create(:group)))) end + + let(:unverifiable_model_record) { nil } end context 'with 3 groups, 2 projects, and 5 snippets' do diff --git a/ee/spec/services/geo/blob_upload_service_spec.rb b/ee/spec/services/geo/blob_upload_service_spec.rb index 493b05007d9987e09c511d07d1f0104d787db8ac..a2cf84915f61bc9c98ccded24df1256f7e82485c 100644 --- a/ee/spec/services/geo/blob_upload_service_spec.rb +++ b/ee/spec/services/geo/blob_upload_service_spec.rb @@ -3,10 +3,23 @@ require 'spec_helper' RSpec.describe Geo::BlobUploadService, feature_category: :geo_replication do - let(:package_file) { create(:package_file, :npm) } + include EE::GeoHelpers + + let_it_be(:primary) { create(:geo_node, :primary) } + + let(:package_file) do + file = create(:package_file, :npm) + checksum = file.class.sha256_hexdigest(file.file.path) + file.update_column(:verification_checksum, checksum) + file + end subject { described_class.new(replicable_name: 'package_file', replicable_id: package_file.id, decoded_params: {}) } + before do + stub_current_geo_node(primary) + end + describe '#initialize' do it 'initializes with valid attributes' do expect { subject }.not_to raise_error diff --git a/spec/factories/ci/pipeline_artifacts.rb b/spec/factories/ci/pipeline_artifacts.rb index 77b1ac5a9cc88af63fe71913ebe7bfb3dc106d49..3b78ce0940edac5a09811679bf72e40adaf1717b 100644 --- a/spec/factories/ci/pipeline_artifacts.rb +++ b/spec/factories/ci/pipeline_artifacts.rb @@ -4,6 +4,7 @@ factory :ci_pipeline_artifact, class: 'Ci::PipelineArtifact' do pipeline factory: :ci_pipeline project { pipeline.project } + partition_id { pipeline.partition_id } file_format { :raw } file_store { ObjectStorage::SUPPORTED_STORES.first } size { 1.megabyte }