diff options
Diffstat (limited to 'app/models/ci')
-rw-r--r-- | app/models/ci/bridge.rb | 4 | ||||
-rw-r--r-- | app/models/ci/build.rb | 67 | ||||
-rw-r--r-- | app/models/ci/daily_build_group_report_result.rb | 20 | ||||
-rw-r--r-- | app/models/ci/daily_report_result.rb | 22 | ||||
-rw-r--r-- | app/models/ci/freeze_period.rb | 18 | ||||
-rw-r--r-- | app/models/ci/freeze_period_status.rb | 47 | ||||
-rw-r--r-- | app/models/ci/group.rb | 2 | ||||
-rw-r--r-- | app/models/ci/instance_variable.rb | 76 | ||||
-rw-r--r-- | app/models/ci/job_artifact.rb | 61 | ||||
-rw-r--r-- | app/models/ci/legacy_stage.rb | 4 | ||||
-rw-r--r-- | app/models/ci/persistent_ref.rb | 12 | ||||
-rw-r--r-- | app/models/ci/pipeline.rb | 55 | ||||
-rw-r--r-- | app/models/ci/pipeline_schedule.rb | 4 | ||||
-rw-r--r-- | app/models/ci/processable.rb | 12 | ||||
-rw-r--r-- | app/models/ci/stage.rb | 4 |
15 files changed, 342 insertions, 66 deletions
diff --git a/app/models/ci/bridge.rb b/app/models/ci/bridge.rb index 76882dfcb0d..1e92a47ab49 100644 --- a/app/models/ci/bridge.rb +++ b/app/models/ci/bridge.rb @@ -166,6 +166,10 @@ module Ci end end + def dependency_variables + [] + end + private def cross_project_params diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index e515447e394..7f64ea7dd97 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -25,13 +25,16 @@ module Ci RUNNER_FEATURES = { upload_multiple_artifacts: -> (build) { build.publishes_artifacts_reports? }, - refspecs: -> (build) { build.merge_request_ref? } + refspecs: -> (build) { build.merge_request_ref? }, + artifacts_exclude: -> (build) { build.supports_artifacts_exclude? } }.freeze DEFAULT_RETRIES = { scheduler_failure: 2 }.freeze + DEGRADATION_THRESHOLD_VARIABLE_NAME = 'DEGRADATION_THRESHOLD' + has_one :deployment, as: :deployable, class_name: 'Deployment' has_one :resource, class_name: 'Ci::Resource', inverse_of: :build has_many :trace_sections, class_name: 'Ci::BuildTraceSection' @@ -87,8 +90,12 @@ module Ci scope :unstarted, ->() { where(runner_id: nil) } scope :ignore_failures, ->() { where(allow_failure: false) } - scope :with_artifacts_archive, ->() do - where('EXISTS (?)', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').archive) + scope :with_downloadable_artifacts, ->() do + where('EXISTS (?)', + Ci::JobArtifact.select(1) + .where('ci_builds.id = ci_job_artifacts.job_id') + .where(file_type: Ci::JobArtifact::DOWNLOADABLE_TYPES) + ) end scope :with_existing_job_artifacts, ->(query) do @@ -130,8 +137,8 @@ module Ci .includes(:metadata, :job_artifacts_metadata) end - scope :with_artifacts_not_expired, ->() { with_artifacts_archive.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) } - scope :with_expired_artifacts, ->() { with_artifacts_archive.where('artifacts_expire_at < ?', Time.now) } + scope :with_artifacts_not_expired, ->() { with_downloadable_artifacts.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) } + scope :with_expired_artifacts, ->() { with_downloadable_artifacts.where('artifacts_expire_at < ?', Time.now) } scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) } scope :manual_actions, ->() { where(when: :manual, status: COMPLETED_STATUSES + %i[manual]) } scope :scheduled_actions, ->() { where(when: :delayed, status: COMPLETED_STATUSES + %i[scheduled]) } @@ -486,8 +493,7 @@ module Ci end def requires_resource? - Feature.enabled?(:ci_resource_group, project, default_enabled: true) && - self.resource_group_id.present? + self.resource_group_id.present? end def has_environment? @@ -530,6 +536,7 @@ module Ci .concat(job_variables) .concat(environment_changed_page_variables) .concat(persisted_environment_variables) + .concat(deploy_freeze_variables) .to_runner_variables end end @@ -585,6 +592,26 @@ module Ci end end + def deploy_freeze_variables + Gitlab::Ci::Variables::Collection.new.tap do |variables| + break variables unless freeze_period? + + variables.append(key: 'CI_DEPLOY_FREEZE', value: 'true') + end + end + + def freeze_period? + Ci::FreezePeriodStatus.new(project: project).execute + end + + def dependency_variables + return [] if all_dependencies.empty? + + Gitlab::Ci::Variables::Collection.new.concat( + Ci::JobVariable.where(job: all_dependencies).dotenv_source + ) + end + def features { trace_sections: true } end @@ -870,6 +897,14 @@ module Ci end end + def collect_accessibility_reports!(accessibility_report) + each_report(Ci::JobArtifact::ACCESSIBILITY_REPORT_FILE_TYPES) do |file_type, blob| + Gitlab::Ci::Parsers.fabricate!(file_type).parse!(blob, accessibility_report) + end + + accessibility_report + end + def collect_coverage_reports!(coverage_report) each_report(Ci::JobArtifact::COVERAGE_REPORT_FILE_TYPES) do |file_type, blob| Gitlab::Ci::Parsers.fabricate!(file_type).parse!(blob, coverage_report) @@ -878,6 +913,14 @@ module Ci coverage_report end + def collect_terraform_reports!(terraform_reports) + each_report(::Ci::JobArtifact::TERRAFORM_REPORT_FILE_TYPES) do |file_type, blob, report_artifact| + ::Gitlab::Ci::Parsers.fabricate!(file_type).parse!(blob, terraform_reports, artifact: report_artifact) + end + + terraform_reports + end + def report_artifacts job_artifacts.with_reports end @@ -902,6 +945,16 @@ module Ci failure_reason: :data_integrity_failure) end + def supports_artifacts_exclude? + options&.dig(:artifacts, :exclude)&.any? && + Gitlab::Ci::Features.artifacts_exclude_enabled? + end + + def degradation_threshold + var = yaml_variables.find { |v| v[:key] == DEGRADATION_THRESHOLD_VARIABLE_NAME } + var[:value]&.to_i if var + end + private def dependencies diff --git a/app/models/ci/daily_build_group_report_result.rb b/app/models/ci/daily_build_group_report_result.rb new file mode 100644 index 00000000000..3506b27e974 --- /dev/null +++ b/app/models/ci/daily_build_group_report_result.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Ci + class DailyBuildGroupReportResult < ApplicationRecord + extend Gitlab::Ci::Model + + PARAM_TYPES = %w[coverage].freeze + + belongs_to :last_pipeline, class_name: 'Ci::Pipeline', foreign_key: :last_pipeline_id + belongs_to :project + + def self.upsert_reports(data) + upsert_all(data, unique_by: :index_daily_build_group_report_results_unique_columns) if data.any? + end + + def self.recent_results(attrs, limit: nil) + where(attrs).order(date: :desc, group_name: :asc).limit(limit) + end + end +end diff --git a/app/models/ci/daily_report_result.rb b/app/models/ci/daily_report_result.rb deleted file mode 100644 index 3c1c5f11ed4..00000000000 --- a/app/models/ci/daily_report_result.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -module Ci - class DailyReportResult < ApplicationRecord - extend Gitlab::Ci::Model - - belongs_to :last_pipeline, class_name: 'Ci::Pipeline', foreign_key: :last_pipeline_id - belongs_to :project - - # TODO: Refactor this out when BuildReportResult is implemented. - # They both need to share the same enum values for param. - REPORT_PARAMS = { - coverage: 0 - }.freeze - - enum param_type: REPORT_PARAMS - - def self.upsert_reports(data) - upsert_all(data, unique_by: :index_daily_report_results_unique_columns) if data.any? - end - end -end diff --git a/app/models/ci/freeze_period.rb b/app/models/ci/freeze_period.rb new file mode 100644 index 00000000000..bf03b92259a --- /dev/null +++ b/app/models/ci/freeze_period.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Ci + class FreezePeriod < ApplicationRecord + include StripAttribute + self.table_name = 'ci_freeze_periods' + + default_scope { order(created_at: :asc) } + + belongs_to :project, inverse_of: :freeze_periods + + strip_attributes :freeze_start, :freeze_end + + validates :freeze_start, cron: true, presence: true + validates :freeze_end, cron: true, presence: true + validates :cron_timezone, cron_freeze_period_timezone: true, presence: true + end +end diff --git a/app/models/ci/freeze_period_status.rb b/app/models/ci/freeze_period_status.rb new file mode 100644 index 00000000000..befa935e750 --- /dev/null +++ b/app/models/ci/freeze_period_status.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module Ci + class FreezePeriodStatus + attr_reader :project + + def initialize(project:) + @project = project + end + + def execute + project.freeze_periods.any? { |period| within_freeze_period?(period) } + end + + def within_freeze_period?(period) + # previous_freeze_end, ..., previous_freeze_start, ..., NOW, ..., next_freeze_end, ..., next_freeze_start + # Current time is within a freeze period if + # it falls between a previous freeze start and next freeze end + start_freeze = Gitlab::Ci::CronParser.new(period.freeze_start, period.cron_timezone) + end_freeze = Gitlab::Ci::CronParser.new(period.freeze_end, period.cron_timezone) + + previous_freeze_start = previous_time(start_freeze) + previous_freeze_end = previous_time(end_freeze) + next_freeze_start = next_time(start_freeze) + next_freeze_end = next_time(end_freeze) + + previous_freeze_end < previous_freeze_start && + previous_freeze_start <= time_zone_now && + time_zone_now <= next_freeze_end && + next_freeze_end < next_freeze_start + end + + private + + def previous_time(cron_parser) + cron_parser.previous_time_from(time_zone_now) + end + + def next_time(cron_parser) + cron_parser.next_time_from(time_zone_now) + end + + def time_zone_now + @time_zone_now ||= Time.zone.now + end + end +end diff --git a/app/models/ci/group.rb b/app/models/ci/group.rb index 15dc1ca8954..4b2081f2977 100644 --- a/app/models/ci/group.rb +++ b/app/models/ci/group.rb @@ -46,7 +46,7 @@ module Ci end def self.fabricate(project, stage) - stage.statuses.ordered.latest + stage.latest_statuses .sort_by(&:sortable_name).group_by(&:group_name) .map do |group_name, grouped_statuses| self.new(project, stage, name: group_name, jobs: grouped_statuses) diff --git a/app/models/ci/instance_variable.rb b/app/models/ci/instance_variable.rb new file mode 100644 index 00000000000..c674f76d229 --- /dev/null +++ b/app/models/ci/instance_variable.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +module Ci + class InstanceVariable < ApplicationRecord + extend Gitlab::Ci::Model + include Ci::NewHasVariable + include Ci::Maskable + + alias_attribute :secret_value, :value + + validates :key, uniqueness: { + message: "(%{value}) has already been taken" + } + + scope :unprotected, -> { where(protected: false) } + after_commit { self.class.touch_redis_cache_timestamp } + + class << self + def all_cached + cached_data[:all] + end + + def unprotected_cached + cached_data[:unprotected] + end + + def touch_redis_cache_timestamp(time = Time.current.to_f) + shared_backend.write(:ci_instance_variable_changed_at, time) + end + + private + + def cached_data + fetch_memory_cache(:ci_instance_variable_data) do + all_records = unscoped.all.to_a + + { all: all_records, unprotected: all_records.reject(&:protected?) } + end + end + + def fetch_memory_cache(key, &payload) + cache = process_backend.read(key) + + if cache && !stale_cache?(cache) + cache[:data] + else + store_cache(key, &payload) + end + end + + def stale_cache?(cache_info) + shared_timestamp = shared_backend.read(:ci_instance_variable_changed_at) + return true unless shared_timestamp + + shared_timestamp.to_f > cache_info[:cached_at].to_f + end + + def store_cache(key) + data = yield + time = Time.current.to_f + + process_backend.write(key, data: data, cached_at: time) + touch_redis_cache_timestamp(time) + data + end + + def shared_backend + Rails.cache + end + + def process_backend + Gitlab::ProcessMemoryCache.cache_backend + end + end + end +end diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb index ef0701b3874..d931428dccd 100644 --- a/app/models/ci/job_artifact.rb +++ b/app/models/ci/job_artifact.rb @@ -12,7 +12,10 @@ module Ci TEST_REPORT_FILE_TYPES = %w[junit].freeze COVERAGE_REPORT_FILE_TYPES = %w[cobertura].freeze + ACCESSIBILITY_REPORT_FILE_TYPES = %w[accessibility].freeze NON_ERASABLE_FILE_TYPES = %w[trace].freeze + TERRAFORM_REPORT_FILE_TYPES = %w[terraform].freeze + UNSUPPORTED_FILE_TYPES = %i[license_management].freeze DEFAULT_FILE_NAMES = { archive: nil, metadata: nil, @@ -20,6 +23,7 @@ module Ci metrics_referee: nil, network_referee: nil, junit: 'junit.xml', + accessibility: 'gl-accessibility.json', codequality: 'gl-code-quality-report.json', sast: 'gl-sast-report.json', dependency_scanning: 'gl-dependency-scanning-report.json', @@ -32,7 +36,8 @@ module Ci lsif: 'lsif.json', dotenv: '.env', cobertura: 'cobertura-coverage.xml', - terraform: 'tfplan.json' + terraform: 'tfplan.json', + cluster_applications: 'gl-cluster-applications.json' }.freeze INTERNAL_TYPES = { @@ -46,13 +51,15 @@ module Ci metrics: :gzip, metrics_referee: :gzip, network_referee: :gzip, - lsif: :gzip, dotenv: :gzip, cobertura: :gzip, + cluster_applications: :gzip, + lsif: :zip, # All these file formats use `raw` as we need to store them uncompressed # for Frontend to fetch the files and do analysis # When they will be only used by backend, they can be `gzipped`. + accessibility: :raw, codequality: :raw, sast: :raw, dependency_scanning: :raw, @@ -64,15 +71,38 @@ module Ci terraform: :raw }.freeze + DOWNLOADABLE_TYPES = %w[ + accessibility + archive + cobertura + codequality + container_scanning + dast + dependency_scanning + dotenv + junit + license_management + license_scanning + lsif + metrics + performance + sast + ].freeze + TYPE_AND_FORMAT_PAIRS = INTERNAL_TYPES.merge(REPORT_TYPES).freeze + # This is required since we cannot add a default to the database + # https://gitlab.com/gitlab-org/gitlab/-/issues/215418 + attribute :locked, :boolean, default: false + belongs_to :project belongs_to :job, class_name: "Ci::Build", foreign_key: :job_id mount_uploader :file, JobArtifactUploader validates :file_format, presence: true, unless: :trace?, on: :create - validate :valid_file_format?, unless: :trace?, on: :create + validate :validate_supported_file_format!, on: :create + validate :validate_file_format!, unless: :trace?, on: :create before_save :set_size, if: :file_changed? update_project_statistics project_statistics_name: :build_artifacts_size @@ -82,6 +112,7 @@ module Ci scope :with_files_stored_locally, -> { where(file_store: [nil, ::JobArtifactUploader::Store::LOCAL]) } scope :with_files_stored_remotely, -> { where(file_store: ::JobArtifactUploader::Store::REMOTE) } scope :for_sha, ->(sha, project_id) { joins(job: :pipeline).where(ci_pipelines: { sha: sha, project_id: project_id }) } + scope :for_ref, ->(ref, project_id) { joins(job: :pipeline).where(ci_pipelines: { ref: ref, project_id: project_id }) } scope :for_job_name, ->(name) { joins(:job).where(ci_builds: { name: name }) } scope :with_file_types, -> (file_types) do @@ -98,10 +129,18 @@ module Ci with_file_types(TEST_REPORT_FILE_TYPES) end + scope :accessibility_reports, -> do + with_file_types(ACCESSIBILITY_REPORT_FILE_TYPES) + end + scope :coverage_reports, -> do with_file_types(COVERAGE_REPORT_FILE_TYPES) end + scope :terraform_reports, -> do + with_file_types(TERRAFORM_REPORT_FILE_TYPES) + end + scope :erasable, -> do types = self.file_types.reject { |file_type| NON_ERASABLE_FILE_TYPES.include?(file_type) }.values @@ -109,6 +148,8 @@ module Ci end scope :expired, -> (limit) { where('expire_at < ?', Time.now).limit(limit) } + scope :locked, -> { where(locked: true) } + scope :unlocked, -> { where(locked: [false, nil]) } scope :scoped_project, -> { where('ci_job_artifacts.project_id = projects.id') } @@ -133,7 +174,9 @@ module Ci lsif: 15, # LSIF data for code navigation dotenv: 16, cobertura: 17, - terraform: 18 # Transformed json + terraform: 18, # Transformed json + accessibility: 19, + cluster_applications: 20 } enum file_format: { @@ -161,7 +204,15 @@ module Ci raw: Gitlab::Ci::Build::Artifacts::Adapters::RawStream }.freeze - def valid_file_format? + def validate_supported_file_format! + return if Feature.disabled?(:drop_license_management_artifact, project, default_enabled: true) + + if UNSUPPORTED_FILE_TYPES.include?(self.file_type&.to_sym) + errors.add(:base, _("File format is no longer supported")) + end + end + + def validate_file_format! unless TYPE_AND_FORMAT_PAIRS[self.file_type&.to_sym] == self.file_format&.to_sym errors.add(:base, _('Invalid file format with specified file type')) end diff --git a/app/models/ci/legacy_stage.rb b/app/models/ci/legacy_stage.rb index f156219ea81..250306e2be4 100644 --- a/app/models/ci/legacy_stage.rb +++ b/app/models/ci/legacy_stage.rb @@ -41,6 +41,10 @@ module Ci .fabricate! end + def latest_statuses + statuses.ordered.latest + end + def statuses @statuses ||= pipeline.statuses.where(stage: name) end diff --git a/app/models/ci/persistent_ref.rb b/app/models/ci/persistent_ref.rb index 76139f5d676..91163c85a9e 100644 --- a/app/models/ci/persistent_ref.rb +++ b/app/models/ci/persistent_ref.rb @@ -14,16 +14,12 @@ module Ci delegate :ref_exists?, :create_ref, :delete_refs, to: :repository def exist? - return unless enabled? - ref_exists?(path) rescue false end def create - return unless enabled? - create_ref(sha, path) rescue => e Gitlab::ErrorTracking @@ -31,8 +27,6 @@ module Ci end def delete - return unless enabled? - delete_refs(path) rescue Gitlab::Git::Repository::NoRepository # no-op @@ -44,11 +38,5 @@ module Ci def path "refs/#{Repository::REF_PIPELINES}/#{pipeline.id}" end - - private - - def enabled? - Feature.enabled?(:depend_on_persistent_pipeline_ref, project, default_enabled: true) - end end end diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 8a3ca2e758c..5db1635f64d 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -82,7 +82,7 @@ module Ci has_one :pipeline_config, class_name: 'Ci::PipelineConfig', inverse_of: :pipeline - has_many :daily_report_results, class_name: 'Ci::DailyReportResult', foreign_key: :last_pipeline_id + has_many :daily_build_group_report_results, class_name: 'Ci::DailyBuildGroupReportResult', foreign_key: :last_pipeline_id accepts_nested_attributes_for :variables, reject_if: :persisted? @@ -115,8 +115,11 @@ module Ci state_machine :status, initial: :created do event :enqueue do - transition [:created, :waiting_for_resource, :preparing, :skipped, :scheduled] => :pending + transition [:created, :manual, :waiting_for_resource, :preparing, :skipped, :scheduled] => :pending transition [:success, :failed, :canceled] => :running + + # this is needed to ensure tests to be covered + transition [:running] => :running end event :request_resource do @@ -194,7 +197,7 @@ module Ci # We wait a little bit to ensure that all BuildFinishedWorkers finish first # because this is where some metrics like code coverage is parsed and stored # in CI build records which the daily build metrics worker relies on. - pipeline.run_after_commit { Ci::DailyReportResultsWorker.perform_in(10.minutes, pipeline.id) } + pipeline.run_after_commit { Ci::DailyBuildGroupReportResultsWorker.perform_in(10.minutes, pipeline.id) } end after_transition do |pipeline, transition| @@ -393,16 +396,18 @@ module Ci false end - ## - # TODO We do not completely switch to persisted stages because of - # race conditions with setting statuses gitlab-foss#23257. - # def ordered_stages - return legacy_stages unless complete? - - if Feature.enabled?('ci_pipeline_persisted_stages', default_enabled: true) + if Feature.enabled?(:ci_atomic_processing, project, default_enabled: false) + # The `Ci::Stage` contains all up-to date data + # as atomic processing updates all data in-bulk + stages + elsif Feature.enabled?(:ci_pipeline_persisted_stages, default_enabled: true) && complete? + # The `Ci::Stage` contains up-to date data only for `completed` pipelines + # this is due to asynchronous processing of pipeline, and stages possibly + # not updated inline with processing of pipeline stages else + # In other cases, we need to calculate stages dynamically legacy_stages end end @@ -440,7 +445,7 @@ module Ci end def legacy_stages - if Feature.enabled?(:ci_composite_status, default_enabled: false) + if Feature.enabled?(:ci_composite_status, project, default_enabled: false) legacy_stages_using_composite_status else legacy_stages_using_sql @@ -681,6 +686,8 @@ module Ci variables.concat(merge_request.predefined_variables) end + variables.append(key: 'CI_KUBERNETES_ACTIVE', value: 'true') if has_kubernetes_active? + if external_pull_request_event? && external_pull_request variables.concat(external_pull_request.predefined_variables) end @@ -781,7 +788,7 @@ module Ci end def find_job_with_archive_artifacts(name) - builds.latest.with_artifacts_archive.find_by_name(name) + builds.latest.with_downloadable_artifacts.find_by_name(name) end def latest_builds_with_artifacts @@ -809,6 +816,14 @@ module Ci end end + def accessibility_reports + Gitlab::Ci::Reports::AccessibilityReports.new.tap do |accessibility_reports| + builds.latest.with_reports(Ci::JobArtifact.accessibility_reports).each do |build| + build.collect_accessibility_reports!(accessibility_reports) + end + end + end + def coverage_reports Gitlab::Ci::Reports::CoverageReports.new.tap do |coverage_reports| builds.latest.with_reports(Ci::JobArtifact.coverage_reports).each do |build| @@ -817,6 +832,14 @@ module Ci end end + def terraform_reports + ::Gitlab::Ci::Reports::TerraformReports.new.tap do |terraform_reports| + builds.latest.with_reports(::Ci::JobArtifact.terraform_reports).each do |build| + build.collect_terraform_reports!(terraform_reports) + end + end + end + def has_exposed_artifacts? complete? && builds.latest.with_exposed_artifacts.exists? end @@ -938,6 +961,14 @@ module Ci end end + # Set scheduling type of processables if they were created before scheduling_type + # data was deployed (https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22246). + def ensure_scheduling_type! + return unless ::Gitlab::Ci::Features.ensure_scheduling_type_enabled? + + processables.populate_scheduling_type! + end + private def pipeline_data diff --git a/app/models/ci/pipeline_schedule.rb b/app/models/ci/pipeline_schedule.rb index f5785000062..8c9ad343f32 100644 --- a/app/models/ci/pipeline_schedule.rb +++ b/app/models/ci/pipeline_schedule.rb @@ -6,6 +6,10 @@ module Ci include Importable include StripAttribute include Schedulable + include Limitable + + self.limit_name = 'ci_pipeline_schedules' + self.limit_scope = :project belongs_to :project belongs_to :owner, class_name: 'User' diff --git a/app/models/ci/processable.rb b/app/models/ci/processable.rb index c123bd7c33b..cc00500662d 100644 --- a/app/models/ci/processable.rb +++ b/app/models/ci/processable.rb @@ -49,7 +49,7 @@ module Ci end validates :type, presence: true - validates :scheduling_type, presence: true, on: :create, if: :validate_scheduling_type? + validates :scheduling_type, presence: true, on: :create, unless: :importing? delegate :merge_request?, :merge_request_ref?, @@ -83,7 +83,7 @@ module Ci # Overriding scheduling_type enum's method for nil `scheduling_type`s def scheduling_type_dag? - super || find_legacy_scheduling_type == :dag + scheduling_type.nil? ? find_legacy_scheduling_type == :dag : super end # scheduling_type column of previous builds/bridges have not been populated, @@ -100,10 +100,12 @@ module Ci end end - private + def ensure_scheduling_type! + # If this has a scheduling_type, it means all processables in the pipeline already have. + return if scheduling_type - def validate_scheduling_type? - !importing? && Feature.enabled?(:validate_scheduling_type_of_processables, project) + pipeline.ensure_scheduling_type! + reset end end end diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb index 93bd42f8734..a316b4718e0 100644 --- a/app/models/ci/stage.rb +++ b/app/models/ci/stage.rb @@ -13,6 +13,7 @@ module Ci belongs_to :pipeline has_many :statuses, class_name: 'CommitStatus', foreign_key: :stage_id + has_many :latest_statuses, -> { ordered.latest }, class_name: 'CommitStatus', foreign_key: :stage_id has_many :processables, class_name: 'Ci::Processable', foreign_key: :stage_id has_many :builds, foreign_key: :stage_id has_many :bridges, foreign_key: :stage_id @@ -42,8 +43,7 @@ module Ci state_machine :status, initial: :created do event :enqueue do - transition [:created, :waiting_for_resource, :preparing] => :pending - transition [:success, :failed, :canceled, :skipped] => :running + transition any - [:pending] => :pending end event :request_resource do |