gitlab-org--gitlab-foss/app/models/ci/build_dependencies.rb

158 lines
4.3 KiB
Ruby

# frozen_string_literal: true
module Ci
class BuildDependencies
include ::Gitlab::Utils::StrongMemoize
attr_reader :processable
def initialize(processable)
@processable = processable
end
def all
(local + cross_pipeline + cross_project).uniq
end
def invalid_local
local.reject(&:valid_dependency?)
end
def valid?
valid_local? && valid_cross_pipeline? && valid_cross_project?
end
private
# Dependencies can only be of Ci::Build type because only builds
# can create artifacts
def model_class
::Ci::Build
end
# Dependencies local to the given pipeline
def local
strong_memoize(:local) do
next [] if no_local_dependencies_specified?
next [] unless processable.pipeline_id # we don't have any dependency when creating the pipeline
deps = model_class.where(pipeline_id: processable.pipeline_id).latest
deps = from_previous_stages(deps)
deps = from_needs(deps)
from_dependencies(deps).to_a
end
end
# Dependencies from the same parent-pipeline hierarchy excluding
# the current job's pipeline
def cross_pipeline
strong_memoize(:cross_pipeline) do
fetch_dependencies_in_hierarchy
end
end
# Dependencies that are defined by project and ref
def cross_project
[]
end
def fetch_dependencies_in_hierarchy
deps_specifications = specified_cross_pipeline_dependencies
return [] if deps_specifications.empty?
deps_specifications = expand_variables_and_validate(deps_specifications)
jobs_in_pipeline_hierarchy(deps_specifications)
end
def jobs_in_pipeline_hierarchy(deps_specifications)
all_pipeline_ids = []
all_job_names = []
deps_specifications.each do |spec|
all_pipeline_ids << spec[:pipeline]
all_job_names << spec[:job]
end
model_class.latest.success
.in_pipelines(processable.pipeline.same_family_pipeline_ids)
.in_pipelines(all_pipeline_ids.uniq)
.by_name(all_job_names.uniq)
.select do |dependency|
# the query may not return exact matches pipeline-job, so we filter
# them separately.
deps_specifications.find do |spec|
spec[:pipeline] == dependency.pipeline_id &&
spec[:job] == dependency.name
end
end
end
def expand_variables_and_validate(specifications)
specifications.map do |spec|
pipeline = ExpandVariables.expand(spec[:pipeline].to_s, processable_variables).to_i
# current pipeline is not allowed because local dependencies
# should be used instead.
next if pipeline == processable.pipeline_id
job = ExpandVariables.expand(spec[:job], processable_variables)
{ job: job, pipeline: pipeline }
end.compact
end
def valid_cross_pipeline?
cross_pipeline.size == specified_cross_pipeline_dependencies.size
end
def valid_local?
local.all?(&:valid_dependency?)
end
def valid_cross_project?
true
end
def project
processable.project
end
def no_local_dependencies_specified?
processable.options[:dependencies]&.empty?
end
def from_previous_stages(scope)
scope.before_stage(processable.stage_idx)
end
def from_needs(scope)
return scope unless processable.scheduling_type_dag?
needs_names = processable.needs.artifacts.select(:name)
scope.where(name: needs_names)
end
def from_dependencies(scope)
return scope unless processable.options[:dependencies].present?
scope.where(name: processable.options[:dependencies])
end
def processable_variables
-> { processable.simple_variables_without_dependencies }
end
def specified_cross_pipeline_dependencies
strong_memoize(:specified_cross_pipeline_dependencies) do
next [] unless Feature.enabled?(:ci_cross_pipeline_artifacts_download, processable.project, default_enabled: true)
specified_cross_dependencies.select { |dep| dep[:pipeline] && dep[:artifacts] }
end
end
def specified_cross_dependencies
Array(processable.options[:cross_dependencies])
end
end
end
Ci::BuildDependencies.prepend_mod_with('Ci::BuildDependencies')