2020-03-31 15:07:53 +00:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
module Ci
|
|
|
|
class BuildDependencies
|
2020-12-08 12:09:53 +00:00
|
|
|
include ::Gitlab::Utils::StrongMemoize
|
|
|
|
|
2020-03-31 15:07:53 +00:00
|
|
|
attr_reader :processable
|
|
|
|
|
|
|
|
def initialize(processable)
|
|
|
|
@processable = processable
|
|
|
|
end
|
|
|
|
|
|
|
|
def all
|
2020-12-08 12:09:53 +00:00
|
|
|
(local + cross_pipeline + cross_project).uniq
|
2020-03-31 15:07:53 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Dependencies local to the given pipeline
|
|
|
|
def local
|
|
|
|
return [] if no_local_dependencies_specified?
|
|
|
|
|
|
|
|
deps = model_class.where(pipeline_id: processable.pipeline_id).latest
|
|
|
|
deps = from_previous_stages(deps)
|
|
|
|
deps = from_needs(deps)
|
|
|
|
deps = from_dependencies(deps)
|
|
|
|
deps
|
|
|
|
end
|
|
|
|
|
2020-12-08 12:09:53 +00:00
|
|
|
# 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
|
|
|
|
|
2020-11-26 12:09:48 +00:00
|
|
|
# Dependencies that are defined by project and ref
|
|
|
|
def cross_project
|
2020-03-31 15:07:53 +00:00
|
|
|
[]
|
|
|
|
end
|
|
|
|
|
|
|
|
def invalid_local
|
|
|
|
local.reject(&:valid_dependency?)
|
|
|
|
end
|
|
|
|
|
|
|
|
def valid?
|
2020-12-08 12:09:53 +00:00
|
|
|
valid_local? && valid_cross_pipeline? && valid_cross_project?
|
2020-03-31 15:07:53 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
# Dependencies can only be of Ci::Build type because only builds
|
|
|
|
# can create artifacts
|
|
|
|
def model_class
|
|
|
|
::Ci::Build
|
|
|
|
end
|
|
|
|
|
2020-12-08 12:09:53 +00:00
|
|
|
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
|
|
|
|
|
2020-03-31 15:07:53 +00:00
|
|
|
def valid_local?
|
2020-06-10 12:08:58 +00:00
|
|
|
return true if Feature.enabled?(:ci_disable_validates_dependencies)
|
2020-03-31 15:07:53 +00:00
|
|
|
|
|
|
|
local.all?(&:valid_dependency?)
|
|
|
|
end
|
|
|
|
|
2020-11-26 12:09:48 +00:00
|
|
|
def valid_cross_project?
|
2020-03-31 15:07:53 +00:00
|
|
|
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
|
2020-12-08 12:09:53 +00:00
|
|
|
|
|
|
|
def processable_variables
|
|
|
|
-> { processable.simple_variables_without_dependencies }
|
|
|
|
end
|
|
|
|
|
|
|
|
def specified_cross_pipeline_dependencies
|
|
|
|
strong_memoize(:specified_cross_pipeline_dependencies) do
|
2020-12-15 18:10:06 +00:00
|
|
|
next [] unless Feature.enabled?(:ci_cross_pipeline_artifacts_download, processable.project, default_enabled: true)
|
2020-12-08 12:09:53 +00:00
|
|
|
|
|
|
|
specified_cross_dependencies.select { |dep| dep[:pipeline] && dep[:artifacts] }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def specified_cross_dependencies
|
|
|
|
Array(processable.options[:cross_dependencies])
|
|
|
|
end
|
2020-03-31 15:07:53 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
Ci::BuildDependencies.prepend_if_ee('EE::Ci::BuildDependencies')
|