1# frozen_string_literal: true 2 3module Ci 4 class Processable < ::CommitStatus 5 include Gitlab::Utils::StrongMemoize 6 extend ::Gitlab::Utils::Override 7 8 has_one :resource, class_name: 'Ci::Resource', foreign_key: 'build_id', inverse_of: :processable 9 10 belongs_to :resource_group, class_name: 'Ci::ResourceGroup', inverse_of: :processables 11 12 accepts_nested_attributes_for :needs 13 14 scope :preload_needs, -> { preload(:needs) } 15 16 scope :with_needs, -> (names = nil) do 17 needs = Ci::BuildNeed.scoped_build.select(1) 18 needs = needs.where(name: names) if names 19 where('EXISTS (?)', needs).preload(:needs) 20 end 21 22 scope :without_needs, -> (names = nil) do 23 needs = Ci::BuildNeed.scoped_build.select(1) 24 needs = needs.where(name: names) if names 25 where('NOT EXISTS (?)', needs) 26 end 27 28 state_machine :status do 29 event :enqueue do 30 transition [:created, :skipped, :manual, :scheduled] => :waiting_for_resource, if: :with_resource_group? 31 end 32 33 event :enqueue_scheduled do 34 transition scheduled: :waiting_for_resource, if: :with_resource_group? 35 end 36 37 event :enqueue_waiting_for_resource do 38 transition waiting_for_resource: :preparing, if: :any_unmet_prerequisites? 39 transition waiting_for_resource: :pending 40 end 41 42 before_transition any => :waiting_for_resource do |processable| 43 processable.waiting_for_resource_at = Time.current 44 end 45 46 before_transition on: :enqueue_waiting_for_resource do |processable| 47 next unless processable.with_resource_group? 48 49 processable.resource_group.assign_resource_to(processable) 50 end 51 52 after_transition any => :waiting_for_resource do |processable| 53 processable.run_after_commit do 54 Ci::ResourceGroups::AssignResourceFromResourceGroupWorker 55 .perform_async(processable.resource_group_id) 56 end 57 end 58 59 after_transition any => ::Ci::Processable.completed_statuses do |processable| 60 next unless processable.with_resource_group? 61 62 processable.resource_group.release_resource_from(processable) 63 64 processable.run_after_commit do 65 Ci::ResourceGroups::AssignResourceFromResourceGroupWorker 66 .perform_async(processable.resource_group_id) 67 end 68 end 69 end 70 71 def self.select_with_aggregated_needs(project) 72 aggregated_needs_names = Ci::BuildNeed 73 .scoped_build 74 .select("ARRAY_AGG(name)") 75 .to_sql 76 77 all.select( 78 '*', 79 "(#{aggregated_needs_names}) as aggregated_needs_names" 80 ) 81 end 82 83 # Old processables may have scheduling_type as nil, 84 # so we need to ensure the data exists before using it. 85 def self.populate_scheduling_type! 86 needs = Ci::BuildNeed.scoped_build.select(1) 87 where(scheduling_type: nil).update_all( 88 "scheduling_type = CASE WHEN (EXISTS (#{needs.to_sql})) 89 THEN #{scheduling_types[:dag]} 90 ELSE #{scheduling_types[:stage]} 91 END" 92 ) 93 end 94 95 validates :type, presence: true 96 validates :scheduling_type, presence: true, on: :create, unless: :importing? 97 98 delegate :merge_request?, 99 :merge_request_ref?, 100 :legacy_detached_merge_request_pipeline?, 101 :merge_train_pipeline?, 102 to: :pipeline 103 104 def aggregated_needs_names 105 read_attribute(:aggregated_needs_names) 106 end 107 108 def schedulable? 109 raise NotImplementedError 110 end 111 112 def action? 113 raise NotImplementedError 114 end 115 116 def when 117 read_attribute(:when) || 'on_success' 118 end 119 120 def expanded_environment_name 121 raise NotImplementedError 122 end 123 124 def persisted_environment 125 raise NotImplementedError 126 end 127 128 override :all_met_to_become_pending? 129 def all_met_to_become_pending? 130 super && !with_resource_group? 131 end 132 133 def with_resource_group? 134 self.resource_group_id.present? 135 end 136 137 # Overriding scheduling_type enum's method for nil `scheduling_type`s 138 def scheduling_type_dag? 139 scheduling_type.nil? ? find_legacy_scheduling_type == :dag : super 140 end 141 142 # scheduling_type column of previous builds/bridges have not been populated, 143 # so we calculate this value on runtime when we need it. 144 def find_legacy_scheduling_type 145 strong_memoize(:find_legacy_scheduling_type) do 146 needs.exists? ? :dag : :stage 147 end 148 end 149 150 def needs_attributes 151 strong_memoize(:needs_attributes) do 152 needs.map { |need| need.attributes.except('id', 'build_id') } 153 end 154 end 155 156 def ensure_scheduling_type! 157 # If this has a scheduling_type, it means all processables in the pipeline already have. 158 return if scheduling_type 159 160 pipeline.ensure_scheduling_type! 161 reset 162 end 163 164 def dependency_variables 165 return [] if all_dependencies.empty? 166 167 Gitlab::Ci::Variables::Collection.new.concat( 168 Ci::JobVariable.where(job: all_dependencies).dotenv_source 169 ) 170 end 171 172 def all_dependencies 173 strong_memoize(:all_dependencies) do 174 dependencies.all 175 end 176 end 177 178 private 179 180 def dependencies 181 strong_memoize(:dependencies) do 182 Ci::BuildDependencies.new(self) 183 end 184 end 185 end 186end 187