summaryrefslogtreecommitdiff
path: root/app/services/ci/process_pipeline_service.rb
blob: 3b145a65d79cd33877018b61b664a5810b634af0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# frozen_string_literal: true

module Ci
  class ProcessPipelineService < BaseService
    attr_reader :pipeline

    def execute(pipeline, trigger_build_ids = nil)
      @pipeline = pipeline

      update_retried

      success = process_stages_without_needs

      # we evaluate dependent needs,
      # only when the another job has finished
      success = process_builds_with_needs(trigger_build_ids) || success

      @pipeline.update_status

      success
    end

    private

    def process_stages_without_needs
      stage_indexes_of_created_processables_without_needs.flat_map do |index|
        process_stage_without_needs(index)
      end.any?
    end

    def process_stage_without_needs(index)
      current_status = status_for_prior_stages(index)

      return unless HasStatus::COMPLETED_STATUSES.include?(current_status)

      created_processables_in_stage_without_needs(index).select do |build|
        process_build(build, current_status)
      end
    end

    def process_builds_with_needs(trigger_build_ids)
      return false unless trigger_build_ids.present?
      return false unless Feature.enabled?(:ci_dag_support, project, default_enabled: true)

      # we find processables that are dependent:
      # 1. because of current dependency,
      trigger_build_names = pipeline.processables.latest
        .for_ids(trigger_build_ids).names

      # 2. does not have builds that not yet complete
      incomplete_build_names = pipeline.processables.latest
        .incomplete.names

      # Each found processable is guaranteed here to have completed status
      created_processables
        .with_needs(trigger_build_names)
        .without_needs(incomplete_build_names)
        .find_each
        .map(&method(:process_build_with_needs))
        .any?
    end

    def process_build_with_needs(build)
      current_status = status_for_build_needs(build.needs.map(&:name))

      return unless HasStatus::COMPLETED_STATUSES.include?(current_status)

      process_build(build, current_status)
    end

    def process_build(build, current_status)
      Gitlab::OptimisticLocking.retry_lock(build) do |subject|
        Ci::ProcessBuildService.new(project, @user)
          .execute(subject, current_status)
      end
    end

    def status_for_prior_stages(index)
      pipeline.processables.status_for_prior_stages(index)
    end

    def status_for_build_needs(needs)
      pipeline.processables.status_for_names(needs)
    end

    # rubocop: disable CodeReuse/ActiveRecord
    def stage_indexes_of_created_processables_without_needs
      created_processables_without_needs.order(:stage_idx)
        .pluck(Arel.sql('DISTINCT stage_idx'))
    end
    # rubocop: enable CodeReuse/ActiveRecord

    def created_processables_in_stage_without_needs(index)
      created_processables_without_needs
        .for_stage(index)
    end

    def created_processables_without_needs
      if Feature.enabled?(:ci_dag_support, project, default_enabled: true)
        pipeline.processables.created.without_needs
      else
        pipeline.processables.created
      end
    end

    def created_processables
      pipeline.processables.created
    end

    # This method is for compatibility and data consistency and should be removed with 9.3 version of GitLab
    # This replicates what is db/post_migrate/20170416103934_upate_retried_for_ci_build.rb
    # and ensures that functionality will not be broken before migration is run
    # this updates only when there are data that needs to be updated, there are two groups with no retried flag
    # rubocop: disable CodeReuse/ActiveRecord
    def update_retried
      # find the latest builds for each name
      latest_statuses = pipeline.statuses.latest
        .group(:name)
        .having('count(*) > 1')
        .pluck(Arel.sql('MAX(id)'), 'name')

      # mark builds that are retried
      pipeline.statuses.latest
        .where(name: latest_statuses.map(&:second))
        .where.not(id: latest_statuses.map(&:first))
        .update_all(retried: true) if latest_statuses.any?
    end
    # rubocop: enable CodeReuse/ActiveRecord
  end
end