ductwork 0.26.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.claude/skills/audit-clock-drift/SKILL.md +11 -0
- data/.claude/skills/audit-database-indexes/SKILL.md +11 -0
- data/.claude/skills/audit-database-support/SKILL.md +18 -0
- data/.claude/skills/audit-durability/SKILL.md +17 -0
- data/.saturnci/database.yml +1 -0
- data/CHANGELOG-PRO.md +60 -0
- data/CHANGELOG.md +144 -1
- data/CLAUDE.md +34 -0
- data/README.md +24 -14
- data/app/controllers/ductwork/application_controller.rb +3 -3
- data/app/controllers/ductwork/dashboards_controller.rb +1 -1
- data/app/controllers/ductwork/pipelines_controller.rb +4 -3
- data/app/helpers/ductwork/application_helper.rb +2 -2
- data/app/views/ductwork/dashboards/show.html.erb +12 -12
- data/app/views/ductwork/pipelines/index.html.erb +12 -12
- data/app/views/ductwork/pipelines/show.html.erb +13 -13
- data/app/views/ductwork/step_errors/index.html.erb +2 -2
- data/lib/ductwork/branch_claim.rb +185 -0
- data/lib/ductwork/cli.rb +73 -24
- data/lib/ductwork/configuration.rb +73 -3
- data/lib/ductwork/context.rb +17 -19
- data/lib/ductwork/database_clock.rb +84 -0
- data/lib/ductwork/dsl/branch_builder.rb +6 -1
- data/lib/ductwork/dsl/definition_builder.rb +24 -1
- data/lib/ductwork/{job_claim.rb → execution_claim.rb} +7 -6
- data/lib/ductwork/fault_injection.rb +35 -0
- data/lib/ductwork/migration_helper.rb +1 -1
- data/lib/ductwork/models/advancement.rb +48 -0
- data/lib/ductwork/models/attempt.rb +9 -0
- data/lib/ductwork/models/availability.rb +2 -0
- data/lib/ductwork/models/branch.rb +800 -0
- data/lib/ductwork/models/branch_link.rb +10 -0
- data/lib/ductwork/models/execution.rb +199 -2
- data/lib/ductwork/models/job.rb +8 -121
- data/lib/ductwork/models/pipeline.rb +150 -272
- data/lib/ductwork/models/process.rb +177 -5
- data/lib/ductwork/models/result.rb +2 -1
- data/lib/ductwork/models/run.rb +119 -1
- data/lib/ductwork/models/step.rb +26 -6
- data/lib/ductwork/models/transition.rb +15 -0
- data/lib/ductwork/models/tuple.rb +1 -4
- data/lib/ductwork/models/workflow.rb +3 -0
- data/lib/ductwork/optimistic_locking_execution_claim.rb +97 -0
- data/lib/ductwork/processes/health_check.rb +87 -0
- data/lib/ductwork/processes/job_worker.rb +86 -20
- data/lib/ductwork/processes/job_worker_runner.rb +25 -37
- data/lib/ductwork/processes/pipeline_advancer.rb +48 -76
- data/lib/ductwork/processes/pipeline_advancer_runner.rb +34 -44
- data/lib/ductwork/processes/process_supervisor.rb +87 -10
- data/lib/ductwork/processes/thread_supervisor.rb +55 -21
- data/lib/ductwork/processes/thread_supervisor_runner.rb +8 -5
- data/lib/ductwork/processes/worker_health_check.rb +55 -0
- data/lib/ductwork/row_locking_execution_claim.rb +77 -0
- data/lib/ductwork/testing/rspec.rb +28 -11
- data/lib/ductwork/version.rb +1 -1
- data/lib/ductwork.rb +5 -0
- data/lib/generators/ductwork/install/install_generator.rb +14 -4
- data/lib/generators/ductwork/install/templates/config/ductwork.yml +4 -0
- data/lib/generators/ductwork/install/templates/db/create_ductwork_advancements.rb +29 -0
- data/lib/generators/ductwork/install/templates/db/create_ductwork_attempts.rb +20 -0
- data/lib/generators/ductwork/install/templates/db/create_ductwork_availabilities.rb +18 -5
- data/lib/generators/ductwork/install/templates/db/create_ductwork_branch_links.rb +27 -0
- data/lib/generators/ductwork/install/templates/db/create_ductwork_branches.rb +37 -0
- data/lib/generators/ductwork/install/templates/db/create_ductwork_executions.rb +8 -1
- data/lib/generators/ductwork/install/templates/db/create_ductwork_pipelines.rb +0 -8
- data/lib/generators/ductwork/install/templates/db/create_ductwork_processes.rb +2 -0
- data/lib/generators/ductwork/install/templates/db/create_ductwork_results.rb +1 -0
- data/lib/generators/ductwork/install/templates/db/create_ductwork_runs.rb +45 -6
- data/lib/generators/ductwork/install/templates/db/create_ductwork_steps.rb +20 -5
- data/lib/generators/ductwork/install/templates/db/create_ductwork_transitions.rb +43 -0
- data/lib/generators/ductwork/install/templates/db/create_ductwork_tuples.rb +3 -3
- data/lib/generators/ductwork/update/templates/db/add_crash_count_to_ductwork_advancements.rb +7 -0
- data/lib/generators/ductwork/update/templates/db/add_crash_count_to_ductwork_executions.rb +14 -0
- data/lib/generators/ductwork/update/templates/db/add_indexes_to_ductwork_results.rb +7 -0
- data/lib/generators/ductwork/update/templates/db/add_indexes_to_ductwork_runs.rb +9 -0
- data/lib/generators/ductwork/update/templates/db/add_indexes_to_ductwork_transitions.rb +16 -0
- data/lib/generators/ductwork/update/templates/db/add_pipeline_started_index_to_ductwork_runs.rb +7 -0
- data/lib/generators/ductwork/update/templates/db/add_process_id_to_ductwork_executions.rb +35 -0
- data/lib/generators/ductwork/update/templates/db/add_role_to_ductwork_processes.rb +20 -0
- data/lib/generators/ductwork/update/templates/db/associate_branches_to_runs.rb +74 -0
- data/lib/generators/ductwork/update/templates/db/associate_steps_to_branches.rb +24 -0
- data/lib/generators/ductwork/update/templates/db/associate_steps_to_runs.rb +72 -0
- data/lib/generators/ductwork/update/templates/db/associate_tuples_to_runs.rb +73 -0
- data/lib/generators/ductwork/update/templates/db/backfill_branch_ids_on_steps.rb +21 -0
- data/lib/generators/ductwork/update/templates/db/create_ductwork_advancements.rb +28 -0
- data/lib/generators/ductwork/update/templates/db/create_ductwork_branch_links.rb +27 -0
- data/lib/generators/ductwork/update/templates/db/create_ductwork_branches.rb +37 -0
- data/lib/generators/ductwork/update/templates/db/create_ductwork_runs.rb +55 -0
- data/lib/generators/ductwork/update/templates/db/create_ductwork_transitions.rb +32 -0
- data/lib/generators/ductwork/update/templates/db/denormalize_pipeline_klass_on_availabilities.rb +11 -4
- data/lib/generators/ductwork/update/templates/db/migrate_tables_to_uuid_primary_key.rb +5 -0
- data/lib/generators/ductwork/update/templates/db/rename_runs_to_attempts.rb +7 -0
- data/lib/generators/ductwork/update/templates/db/update_process_associations.rb +29 -0
- data/lib/generators/ductwork/update/update_generator.rb +99 -0
- metadata +47 -6
- data/lib/ductwork/optimistic_locking_job_claim.rb +0 -88
- data/lib/ductwork/row_locking_job_claim.rb +0 -75
|
@@ -2,16 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
module Ductwork
|
|
4
4
|
class Pipeline < Ductwork::Record # rubocop:todo Metrics/ClassLength
|
|
5
|
-
has_many :
|
|
6
|
-
|
|
5
|
+
has_many :runs,
|
|
6
|
+
class_name: "Ductwork::Run",
|
|
7
|
+
foreign_key: "pipeline_id",
|
|
8
|
+
dependent: :destroy
|
|
7
9
|
|
|
8
10
|
validates :klass, presence: true
|
|
9
|
-
validates :definition, presence: true
|
|
10
|
-
validates :definition_sha1, presence: true
|
|
11
11
|
validates :status, presence: true
|
|
12
|
-
validates :started_at, presence: true
|
|
13
|
-
validates :triggered_at, presence: true
|
|
14
|
-
validates :last_advanced_at, presence: true
|
|
15
12
|
|
|
16
13
|
enum :status,
|
|
17
14
|
pending: "pending",
|
|
@@ -31,6 +28,7 @@ module Ductwork
|
|
|
31
28
|
end
|
|
32
29
|
|
|
33
30
|
class DefinitionError < StandardError; end
|
|
31
|
+
class ReviveError < StandardError; end
|
|
34
32
|
|
|
35
33
|
class << self
|
|
36
34
|
attr_reader :pipeline_definition
|
|
@@ -53,31 +51,42 @@ module Ductwork
|
|
|
53
51
|
Ductwork.defined_pipelines << name.to_s
|
|
54
52
|
end
|
|
55
53
|
|
|
56
|
-
def trigger(*args)
|
|
54
|
+
def trigger(*args) # rubocop:todo Metrics
|
|
57
55
|
if pipeline_definition.nil?
|
|
58
56
|
raise DefinitionError, "Pipeline must be defined before triggering"
|
|
59
57
|
end
|
|
60
58
|
|
|
59
|
+
now = Time.current
|
|
61
60
|
node = pipeline_definition.dig(:nodes, 0)
|
|
62
61
|
klass = pipeline_definition.dig(:edges, node, :klass)
|
|
63
62
|
definition = JSON.dump(pipeline_definition)
|
|
64
63
|
|
|
65
|
-
pipeline = Record.transaction do
|
|
64
|
+
pipeline = Record.transaction do # rubocop:todo Metrics/BlockLength
|
|
66
65
|
p = create!(
|
|
67
66
|
klass: name.to_s,
|
|
67
|
+
status: :in_progress
|
|
68
|
+
)
|
|
69
|
+
run = p.runs.create!(
|
|
70
|
+
pipeline_klass: name.to_s,
|
|
68
71
|
status: :in_progress,
|
|
69
72
|
definition: definition,
|
|
70
73
|
definition_sha1: Digest::SHA1.hexdigest(definition),
|
|
71
|
-
triggered_at:
|
|
72
|
-
started_at:
|
|
73
|
-
last_advanced_at: Time.current
|
|
74
|
+
triggered_at: now,
|
|
75
|
+
started_at: now
|
|
74
76
|
)
|
|
75
|
-
|
|
77
|
+
branch = run.branches.create!(
|
|
78
|
+
pipeline_klass: name.to_s,
|
|
79
|
+
status: :in_progress,
|
|
80
|
+
started_at: now,
|
|
81
|
+
last_advanced_at: now
|
|
82
|
+
)
|
|
83
|
+
step = branch.steps.create!(
|
|
84
|
+
run: run,
|
|
76
85
|
node: node,
|
|
77
86
|
klass: klass,
|
|
78
87
|
status: :in_progress,
|
|
79
88
|
to_transition: :start,
|
|
80
|
-
started_at:
|
|
89
|
+
started_at: now
|
|
81
90
|
)
|
|
82
91
|
Ductwork::Job.enqueue(step, *args)
|
|
83
92
|
|
|
@@ -94,306 +103,175 @@ module Ductwork
|
|
|
94
103
|
end
|
|
95
104
|
end
|
|
96
105
|
|
|
97
|
-
def
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
# watch out for here and maybe add in config to use AR relation
|
|
101
|
-
# at certain counts or even memory limits.
|
|
102
|
-
advancing_steps = steps.advancing.pluck(:id, :node, :klass)
|
|
103
|
-
advancing_ids = advancing_steps.map(&:first)
|
|
104
|
-
edges = find_edges(advancing_steps)
|
|
106
|
+
def current_run
|
|
107
|
+
runs.in_progress.sole
|
|
108
|
+
end
|
|
105
109
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
else
|
|
110
|
-
advance_to_next_steps_by_type(edges, advancing_ids)
|
|
111
|
-
end
|
|
110
|
+
def revive!(duplicate_context: false)
|
|
111
|
+
if !halted?
|
|
112
|
+
raise ReviveError, "Cannot revive #{status} pipeline"
|
|
112
113
|
end
|
|
113
|
-
end
|
|
114
114
|
|
|
115
|
-
|
|
116
|
-
@parsed_definition ||= JSON.parse(definition).with_indifferent_access
|
|
117
|
-
end
|
|
115
|
+
last_run = runs.order(started_at: :desc).first
|
|
118
116
|
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
if last_run.blank?
|
|
118
|
+
raise ReviveError, "Cannot revive pipeline without previous run"
|
|
119
|
+
end
|
|
121
120
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
121
|
+
now = Time.current
|
|
122
|
+
new_run = last_run.dup
|
|
123
|
+
new_run.triggered_at = now
|
|
124
|
+
new_run.started_at = now
|
|
125
|
+
new_run.status = "in_progress"
|
|
126
|
+
new_run.on_halt_dispatched_at = nil
|
|
128
127
|
|
|
129
|
-
|
|
130
|
-
|
|
128
|
+
Ductwork::Record.transaction do
|
|
129
|
+
lock!
|
|
130
|
+
new_run.save!
|
|
131
|
+
duplicate_successful_branches_and_steps(new_run, last_run, now)
|
|
132
|
+
duplicate_halted_branches_and_steps(new_run, last_run, now)
|
|
133
|
+
conditionally_duplicate_context(last_run, new_run, now, duplicate_context)
|
|
134
|
+
duplicate_all_branch_links(new_run, last_run)
|
|
135
|
+
in_progress!
|
|
136
|
+
end
|
|
131
137
|
|
|
132
|
-
|
|
133
|
-
msg: "Pipeline halted",
|
|
134
|
-
pipeline_id: id,
|
|
135
|
-
pipeline_klass: klass
|
|
136
|
-
)
|
|
138
|
+
self
|
|
137
139
|
end
|
|
138
140
|
|
|
139
141
|
private
|
|
140
142
|
|
|
141
|
-
def
|
|
142
|
-
status =
|
|
143
|
-
started_at = Time.current
|
|
144
|
-
# NOTE: "chain" is used by ActiveRecord so we have to call
|
|
145
|
-
# this enum value "default" :sad:
|
|
146
|
-
to_transition = edge[:type] == "chain" ? "default" : edge[:type]
|
|
147
|
-
node ||= edge[:to].sole
|
|
148
|
-
klass = parsed_definition.dig(:edges, node, :klass)
|
|
149
|
-
|
|
150
|
-
next_step = steps.create!(node:, klass:, status:, to_transition:, started_at:)
|
|
151
|
-
Ductwork::Job.enqueue(next_step, input_arg)
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
def find_edges(advancing_steps)
|
|
155
|
-
if advancing_steps.any?
|
|
156
|
-
nodes = advancing_steps.map(&:second)
|
|
143
|
+
def duplicate_successful_branches_and_steps(new_run, last_run, now)
|
|
144
|
+
status = %i[advancing waiting completed]
|
|
157
145
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
146
|
+
last_run.branches.where(status:).find_each do |branch|
|
|
147
|
+
new_branch = branch.dup
|
|
148
|
+
new_branch.run = new_run
|
|
149
|
+
new_branch.started_at = now
|
|
150
|
+
new_branch.completed_at = now
|
|
161
151
|
|
|
162
|
-
|
|
163
|
-
steps
|
|
164
|
-
.where(id: advancing_ids)
|
|
165
|
-
.update_all(status: :completed, completed_at: Time.current)
|
|
152
|
+
new_branch.save!
|
|
166
153
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
154
|
+
branch.steps.where(status:).find_each do |step|
|
|
155
|
+
new_step = step.dup
|
|
156
|
+
new_step.source_step = step
|
|
157
|
+
new_step.branch = new_branch
|
|
158
|
+
new_step.run = new_run
|
|
159
|
+
new_step.started_at = now
|
|
160
|
+
new_step.completed_at = now
|
|
171
161
|
|
|
172
|
-
|
|
173
|
-
complete!
|
|
174
|
-
end
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
def advance_to_next_steps_by_type(edges, advancing_ids)
|
|
178
|
-
steps.where(id: advancing_ids).update_all(status: :completed, completed_at: Time.current)
|
|
179
|
-
|
|
180
|
-
if edges.all? { |_, attrs| attrs[:type] == "combine" }
|
|
181
|
-
conditionally_combine_next_steps(edges, advancing_ids)
|
|
182
|
-
else
|
|
183
|
-
edges.each do |node, attrs|
|
|
184
|
-
if attrs[:type] == "collapse"
|
|
185
|
-
conditionally_collapse_next_steps(node, attrs, advancing_ids)
|
|
186
|
-
else
|
|
187
|
-
advance_non_merging_steps(attrs, advancing_ids)
|
|
188
|
-
end
|
|
162
|
+
new_step.save!
|
|
189
163
|
end
|
|
190
164
|
end
|
|
191
|
-
log_pipeline_advanced(edges)
|
|
192
165
|
end
|
|
193
166
|
|
|
194
|
-
def
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
msg: "Invalid To Transition",
|
|
206
|
-
to_transition: to_transition,
|
|
207
|
-
pipeline_id: id,
|
|
208
|
-
role: :pipeline_advancer
|
|
209
|
-
)
|
|
210
|
-
end
|
|
211
|
-
end
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
def advance_to_next_steps(step_id, edge)
|
|
215
|
-
if edge[:type] == "divert"
|
|
216
|
-
advance_to_divert_step(step_id, edge)
|
|
217
|
-
else
|
|
218
|
-
advance_to_array_steps(step_id, edge)
|
|
167
|
+
def duplicate_halted_branches_and_steps(new_run, last_run, now)
|
|
168
|
+
last_run.branches.where(status: :halted).find_each do |branch|
|
|
169
|
+
new_branch = branch.dup
|
|
170
|
+
new_branch.run = new_run
|
|
171
|
+
new_branch.status = "in_progress"
|
|
172
|
+
new_branch.started_at = now
|
|
173
|
+
new_branch.completed_at = nil
|
|
174
|
+
new_branch.last_advanced_at = now
|
|
175
|
+
|
|
176
|
+
new_branch.save!
|
|
177
|
+
revive_branch_steps(branch, new_branch, new_run, now)
|
|
219
178
|
end
|
|
220
179
|
end
|
|
221
180
|
|
|
222
|
-
def
|
|
223
|
-
|
|
224
|
-
node = edge[:to][input_arg.to_s] || edge[:to]["otherwise"]
|
|
181
|
+
def revive_branch_steps(branch, new_branch, new_run, now)
|
|
182
|
+
duplicate_prior_steps_as_completed(branch, new_branch, new_run, now)
|
|
225
183
|
|
|
226
|
-
if
|
|
227
|
-
|
|
184
|
+
if branch.job_retries_exhausted?
|
|
185
|
+
re_enqueue_failed_step(branch, new_branch, new_run, now)
|
|
228
186
|
else
|
|
229
|
-
|
|
187
|
+
re_advance_latest_step(branch, new_branch, new_run, now)
|
|
230
188
|
end
|
|
231
189
|
end
|
|
232
190
|
|
|
233
|
-
def
|
|
234
|
-
|
|
235
|
-
depth = Ductwork
|
|
236
|
-
.configuration
|
|
237
|
-
.steps_max_depth(pipeline: klass, step: to_klass)
|
|
191
|
+
def duplicate_prior_steps_as_completed(branch, new_branch, new_run, now)
|
|
192
|
+
latest_step = branch.steps.order(started_at: :desc).first
|
|
238
193
|
|
|
239
|
-
|
|
240
|
-
end
|
|
194
|
+
return if latest_step.blank?
|
|
241
195
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
else
|
|
245
|
-
edge[:to].each do |node|
|
|
246
|
-
input_arg = Ductwork::Job.find_by(step_id:).return_value
|
|
247
|
-
create_step_and_enqueue_job(edge:, input_arg:, node:)
|
|
248
|
-
end
|
|
249
|
-
end
|
|
250
|
-
end
|
|
196
|
+
status = %i[advancing waiting completed]
|
|
197
|
+
id = latest_step.id
|
|
251
198
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
role: :pipeline_advancer
|
|
260
|
-
)
|
|
261
|
-
end
|
|
262
|
-
end
|
|
199
|
+
branch.steps.where(status:).where.not(id:).find_each do |step|
|
|
200
|
+
new_step = step.dup
|
|
201
|
+
new_step.source_step = step
|
|
202
|
+
new_step.branch = new_branch
|
|
203
|
+
new_step.run = new_run
|
|
204
|
+
new_step.started_at = now
|
|
205
|
+
new_step.completed_at = now
|
|
263
206
|
|
|
264
|
-
|
|
265
|
-
edge = edges.values.sample
|
|
266
|
-
groups = steps
|
|
267
|
-
.where(id: advancing_ids)
|
|
268
|
-
.group(:node)
|
|
269
|
-
.count
|
|
270
|
-
.keys
|
|
271
|
-
.map { |node| steps.where(id: advancing_ids).where(node:) }
|
|
272
|
-
|
|
273
|
-
groups.first.zip(*groups[1..]).each do |group|
|
|
274
|
-
input_arg = Ductwork::Job
|
|
275
|
-
.where(step_id: group.map(&:id))
|
|
276
|
-
.map(&:return_value)
|
|
277
|
-
create_step_and_enqueue_job(edge:, input_arg:)
|
|
207
|
+
new_step.save!
|
|
278
208
|
end
|
|
279
209
|
end
|
|
280
210
|
|
|
281
|
-
def
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
end
|
|
295
|
-
end
|
|
296
|
-
|
|
297
|
-
def bulk_create_steps_and_jobs(edge:, return_value:) # rubocop:todo Metrics
|
|
298
|
-
# NOTE: "chain" is used by ActiveRecord so we have to call
|
|
299
|
-
# this enum value "default" :sad:
|
|
300
|
-
to_transition = edge[:type] == "chain" ? "default" : edge[:type]
|
|
301
|
-
node ||= edge[:to].sole
|
|
302
|
-
step_klass = parsed_definition.dig(:edges, node, :klass)
|
|
303
|
-
now = Time.current
|
|
304
|
-
|
|
305
|
-
Array(return_value).each_slice(1_000).each do |batch| # rubocop:todo Metrics/BlockLength
|
|
306
|
-
step_rows = []
|
|
307
|
-
job_rows = []
|
|
308
|
-
execution_rows = []
|
|
309
|
-
availability_rows = []
|
|
310
|
-
|
|
311
|
-
batch.each do |value| # rubocop:todo Metrics/BlockLength
|
|
312
|
-
step_id = SecureRandom.uuid_v7
|
|
313
|
-
job_id = SecureRandom.uuid_v7
|
|
314
|
-
execution_id = SecureRandom.uuid_v7
|
|
315
|
-
availability_id = SecureRandom.uuid_v7
|
|
316
|
-
|
|
317
|
-
step_rows << {
|
|
318
|
-
id: step_id,
|
|
319
|
-
pipeline_id: id,
|
|
320
|
-
node: node,
|
|
321
|
-
klass: step_klass,
|
|
322
|
-
status: "in_progress",
|
|
323
|
-
to_transition: to_transition,
|
|
324
|
-
started_at: now,
|
|
325
|
-
created_at: now,
|
|
326
|
-
updated_at: now,
|
|
327
|
-
}
|
|
328
|
-
job_rows << {
|
|
329
|
-
id: job_id,
|
|
330
|
-
step_id: step_id,
|
|
331
|
-
input_args: JSON.dump({ args: [value] }),
|
|
332
|
-
klass: step_klass,
|
|
333
|
-
started_at: now,
|
|
334
|
-
created_at: now,
|
|
335
|
-
updated_at: now,
|
|
336
|
-
}
|
|
337
|
-
execution_rows << {
|
|
338
|
-
id: execution_id,
|
|
339
|
-
job_id: job_id,
|
|
340
|
-
retry_count: 0,
|
|
341
|
-
started_at: now,
|
|
342
|
-
created_at: now,
|
|
343
|
-
updated_at: now,
|
|
344
|
-
}
|
|
345
|
-
availability_rows << {
|
|
346
|
-
id: availability_id,
|
|
347
|
-
execution_id: execution_id,
|
|
348
|
-
pipeline_klass: klass,
|
|
349
|
-
started_at: now,
|
|
350
|
-
created_at: now,
|
|
351
|
-
updated_at: now,
|
|
352
|
-
}
|
|
353
|
-
end
|
|
354
|
-
|
|
355
|
-
Ductwork::Step.insert_all!(step_rows)
|
|
356
|
-
Ductwork::Job.insert_all!(job_rows)
|
|
357
|
-
Ductwork::Execution.insert_all!(execution_rows)
|
|
358
|
-
Ductwork::Availability.insert_all!(availability_rows)
|
|
359
|
-
|
|
360
|
-
Ductwork.logger.info(
|
|
361
|
-
msg: "Job batch enqueued",
|
|
362
|
-
count: batch.count,
|
|
363
|
-
job_klass: step_klass
|
|
364
|
-
)
|
|
365
|
-
end
|
|
211
|
+
def re_enqueue_failed_step(branch, new_branch, new_run, now)
|
|
212
|
+
failed_step = branch.steps.find_by(status: :failed)
|
|
213
|
+
args = JSON.parse(failed_step.job.input_args).fetch("args")
|
|
214
|
+
step = new_branch.steps.create!(
|
|
215
|
+
run: new_run,
|
|
216
|
+
source_step: failed_step,
|
|
217
|
+
node: failed_step.node,
|
|
218
|
+
klass: failed_step.klass,
|
|
219
|
+
to_transition: failed_step.to_transition,
|
|
220
|
+
status: :in_progress,
|
|
221
|
+
started_at: now
|
|
222
|
+
)
|
|
223
|
+
Ductwork::Job.enqueue(step, *args)
|
|
366
224
|
end
|
|
367
225
|
|
|
368
|
-
def
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
226
|
+
def re_advance_latest_step(branch, new_branch, new_run, now)
|
|
227
|
+
latest_step = branch.steps.order(started_at: :desc).first
|
|
228
|
+
step = new_branch.steps.create!(
|
|
229
|
+
run: new_run,
|
|
230
|
+
source_step: latest_step,
|
|
231
|
+
node: latest_step.node,
|
|
232
|
+
klass: latest_step.klass,
|
|
233
|
+
to_transition: latest_step.to_transition,
|
|
234
|
+
status: :advancing,
|
|
235
|
+
started_at: now
|
|
236
|
+
)
|
|
237
|
+
job = latest_step.job.dup
|
|
238
|
+
job.step = step
|
|
239
|
+
job.started_at = now
|
|
240
|
+
job.completed_at = now
|
|
241
|
+
job.save!
|
|
378
242
|
end
|
|
379
243
|
|
|
380
|
-
def
|
|
381
|
-
|
|
244
|
+
def conditionally_duplicate_context(last_run, new_run, now, duplicate_context)
|
|
245
|
+
if duplicate_context
|
|
246
|
+
last_run.tuples.find_each do |tuple|
|
|
247
|
+
new_tuple = tuple.dup
|
|
248
|
+
new_tuple.run = new_run
|
|
249
|
+
new_tuple.first_set_at = now
|
|
250
|
+
new_tuple.last_set_at = now
|
|
382
251
|
|
|
383
|
-
|
|
384
|
-
|
|
252
|
+
new_tuple.save!
|
|
253
|
+
end
|
|
385
254
|
end
|
|
386
|
-
|
|
387
|
-
create_step_and_enqueue_job(edge:, input_arg:)
|
|
388
255
|
end
|
|
389
256
|
|
|
390
|
-
def
|
|
391
|
-
Ductwork
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
257
|
+
def duplicate_all_branch_links(new_run, last_run)
|
|
258
|
+
map = Ductwork::Step
|
|
259
|
+
.where(branch_id: new_run.branches.select(:id))
|
|
260
|
+
.where.not(source_step_id: nil)
|
|
261
|
+
.joins("INNER JOIN ductwork_steps src ON src.id = ductwork_steps.source_step_id")
|
|
262
|
+
.pluck("src.branch_id", "ductwork_steps.branch_id")
|
|
263
|
+
.uniq
|
|
264
|
+
.to_h
|
|
265
|
+
old_branch_ids = last_run.branches.select(:id)
|
|
266
|
+
|
|
267
|
+
Ductwork::BranchLink
|
|
268
|
+
.where(parent_branch_id: old_branch_ids, child_branch_id: old_branch_ids)
|
|
269
|
+
.find_each do |old_link|
|
|
270
|
+
Ductwork::BranchLink.create!(
|
|
271
|
+
parent_branch_id: map.fetch(old_link.parent_branch_id),
|
|
272
|
+
child_branch_id: map.fetch(old_link.child_branch_id)
|
|
273
|
+
)
|
|
274
|
+
end
|
|
397
275
|
end
|
|
398
276
|
end
|
|
399
277
|
end
|