1# frozen_string_literal: true 2 3module Ci 4 class BuildDependencies 5 include ::Gitlab::Utils::StrongMemoize 6 7 attr_reader :processable 8 9 def initialize(processable) 10 @processable = processable 11 end 12 13 def all 14 (local + cross_pipeline + cross_project).uniq 15 end 16 17 def invalid_local 18 local.reject(&:valid_dependency?) 19 end 20 21 def valid? 22 valid_local? && valid_cross_pipeline? && valid_cross_project? 23 end 24 25 private 26 27 # Dependencies can only be of Ci::Build type because only builds 28 # can create artifacts 29 def model_class 30 ::Ci::Build 31 end 32 33 # Dependencies local to the given pipeline 34 def local 35 strong_memoize(:local) do 36 next [] if no_local_dependencies_specified? 37 next [] unless processable.pipeline_id # we don't have any dependency when creating the pipeline 38 39 deps = model_class.where(pipeline_id: processable.pipeline_id).latest 40 deps = find_dependencies(processable, deps) 41 42 from_dependencies(deps).to_a 43 end 44 end 45 46 def find_dependencies(processable, deps) 47 if processable.scheduling_type_dag? 48 from_needs(deps) 49 else 50 from_previous_stages(deps) 51 end 52 end 53 54 # Dependencies from the same parent-pipeline hierarchy excluding 55 # the current job's pipeline 56 def cross_pipeline 57 strong_memoize(:cross_pipeline) do 58 fetch_dependencies_in_hierarchy 59 end 60 end 61 62 # Dependencies that are defined by project and ref 63 def cross_project 64 [] 65 end 66 67 def fetch_dependencies_in_hierarchy 68 deps_specifications = specified_cross_pipeline_dependencies 69 return [] if deps_specifications.empty? 70 71 deps_specifications = expand_variables_and_validate(deps_specifications) 72 jobs_in_pipeline_hierarchy(deps_specifications) 73 end 74 75 def jobs_in_pipeline_hierarchy(deps_specifications) 76 all_pipeline_ids = [] 77 all_job_names = [] 78 79 deps_specifications.each do |spec| 80 all_pipeline_ids << spec[:pipeline] 81 all_job_names << spec[:job] 82 end 83 84 model_class.latest.success 85 .in_pipelines(processable.pipeline.same_family_pipeline_ids) 86 .in_pipelines(all_pipeline_ids.uniq) 87 .by_name(all_job_names.uniq) 88 .select do |dependency| 89 # the query may not return exact matches pipeline-job, so we filter 90 # them separately. 91 deps_specifications.find do |spec| 92 spec[:pipeline] == dependency.pipeline_id && 93 spec[:job] == dependency.name 94 end 95 end 96 end 97 98 def expand_variables_and_validate(specifications) 99 specifications.map do |spec| 100 pipeline = ExpandVariables.expand(spec[:pipeline].to_s, processable_variables).to_i 101 # current pipeline is not allowed because local dependencies 102 # should be used instead. 103 next if pipeline == processable.pipeline_id 104 105 job = ExpandVariables.expand(spec[:job], processable_variables) 106 107 { job: job, pipeline: pipeline } 108 end.compact 109 end 110 111 def valid_cross_pipeline? 112 cross_pipeline.size == specified_cross_pipeline_dependencies.size 113 end 114 115 def valid_local? 116 local.all?(&:valid_dependency?) 117 end 118 119 def valid_cross_project? 120 true 121 end 122 123 def project 124 processable.project 125 end 126 127 def no_local_dependencies_specified? 128 processable.options[:dependencies]&.empty? 129 end 130 131 def from_previous_stages(scope) 132 scope.before_stage(processable.stage_idx) 133 end 134 135 def from_needs(scope) 136 needs_names = processable.needs.artifacts.select(:name) 137 scope.where(name: needs_names) 138 end 139 140 def from_dependencies(scope) 141 return scope unless processable.options[:dependencies].present? 142 143 scope.where(name: processable.options[:dependencies]) 144 end 145 146 def processable_variables 147 -> { processable.simple_variables_without_dependencies } 148 end 149 150 def specified_cross_pipeline_dependencies 151 strong_memoize(:specified_cross_pipeline_dependencies) do 152 specified_cross_dependencies.select { |dep| dep[:pipeline] && dep[:artifacts] } 153 end 154 end 155 156 def specified_cross_dependencies 157 Array(processable.options[:cross_dependencies]) 158 end 159 end 160end 161 162Ci::BuildDependencies.prepend_mod_with('Ci::BuildDependencies') 163