ductwork 0.1.0 → 0.2.1
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/CHANGELOG.md +12 -0
- data/README.md +4 -1
- data/app/models/ductwork/execution.rb +3 -3
- data/app/models/ductwork/job.rb +2 -2
- data/app/models/ductwork/pipeline.rb +2 -2
- data/app/models/ductwork/step.rb +1 -1
- data/lib/ductwork/dsl/definition_builder.rb +39 -11
- data/lib/ductwork/engine.rb +10 -0
- data/lib/ductwork/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8ab7c6e79c26352d990680a3913a26ea8bd4bc9999136b4ee1a1846c06e74523
|
|
4
|
+
data.tar.gz: 7adb0603119c80d5edc54e94de44828bba2c927edad418e5944d594ba21f734a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 83be2952cf26ebbee28521df07b68ee29e87ade164a70a87ede7c7bcfc1ef5525097b1346757d76e34d4fc25535c5489f56900fa763006d923bd8491dcba520f
|
|
7
|
+
data.tar.gz: 53cabb6cdfa4f96f847ddc68818a3f0a87d59ca443282700eca5f690a8a01805e46dbd925990565630bb5c45dc728183de251629488d055f8062d136f06dff7e
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Ductwork Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.1]
|
|
4
|
+
|
|
5
|
+
- fix: do not splat arguments when executing a job nor triggering a pipeline
|
|
6
|
+
- fix: do not splat arguments when enqueuing a job and fix related spec
|
|
7
|
+
- fix: add missing `dependent: :destroy` on certain associations
|
|
8
|
+
|
|
9
|
+
## [0.2.0]
|
|
10
|
+
|
|
11
|
+
- feat: validate all pipeline definitions on rails boot
|
|
12
|
+
- feat: validate argument(s) passed to step transition DSL methods to be valid step class
|
|
13
|
+
- fix: allow steps to be chained while pipeline is expanded or divided (before collapsing or combining) - before this incorrectly raised a `CollapseError` or `CombineError`
|
|
14
|
+
|
|
3
15
|
## [0.1.0]
|
|
4
16
|
|
|
5
17
|
- Initial release - see [documentation](https://docs.getductwork.io/) for details
|
data/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# Ductwork
|
|
2
2
|
|
|
3
|
+
[](https://github.com/ductwork/ductwork/actions/workflows/main.yml)
|
|
4
|
+
[](https://rubygems.org/gems/ductwork)
|
|
5
|
+
|
|
3
6
|
A Ruby pipeline framework.
|
|
4
7
|
|
|
5
8
|
Ductwork lets you build complex pipelines quickly and easily using intuitive Ruby tooling and a natural DSL. No need to learn complicated unified object models or stand up separate runner instances—just write Ruby code and let Ductwork handle the orchestration.
|
|
@@ -72,7 +75,7 @@ class UsersRequiringEnrichment
|
|
|
72
75
|
|
|
73
76
|
def execute
|
|
74
77
|
ids = User.where("data_last_refreshed_at < ?", @days_outdated.days.ago).ids
|
|
75
|
-
|
|
78
|
+
Ductwork.logger.info("Enriching #{ids.length} users' data")
|
|
76
79
|
|
|
77
80
|
# Return value becomes input to the next step
|
|
78
81
|
ids
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
module Ductwork
|
|
4
4
|
class Execution < Ductwork::Record
|
|
5
5
|
belongs_to :job, class_name: "Ductwork::Job"
|
|
6
|
-
has_one :availability, class_name: "Ductwork::Availability", foreign_key: "execution_id"
|
|
7
|
-
has_one :run, class_name: "Ductwork::Run", foreign_key: "execution_id"
|
|
8
|
-
has_one :result, class_name: "Ductwork::Result", foreign_key: "execution_id"
|
|
6
|
+
has_one :availability, class_name: "Ductwork::Availability", foreign_key: "execution_id", dependent: :destroy
|
|
7
|
+
has_one :run, class_name: "Ductwork::Run", foreign_key: "execution_id", dependent: :destroy
|
|
8
|
+
has_one :result, class_name: "Ductwork::Result", foreign_key: "execution_id", dependent: :destroy
|
|
9
9
|
|
|
10
10
|
validates :retry_count, presence: true
|
|
11
11
|
validates :started_at, presence: true
|
data/app/models/ductwork/job.rb
CHANGED
|
@@ -60,7 +60,7 @@ module Ductwork
|
|
|
60
60
|
end
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
-
def self.enqueue(step,
|
|
63
|
+
def self.enqueue(step, args)
|
|
64
64
|
Ductwork::Record.transaction do
|
|
65
65
|
job = step.create_job!(
|
|
66
66
|
klass: step.klass,
|
|
@@ -89,7 +89,7 @@ module Ductwork
|
|
|
89
89
|
job_klass: klass
|
|
90
90
|
)
|
|
91
91
|
args = JSON.parse(input_args)["args"]
|
|
92
|
-
instance = Object.const_get(klass).new(
|
|
92
|
+
instance = Object.const_get(klass).new(args)
|
|
93
93
|
run = execution.create_run!(
|
|
94
94
|
started_at: Time.current
|
|
95
95
|
)
|
|
@@ -48,7 +48,7 @@ module Ductwork
|
|
|
48
48
|
Ductwork.defined_pipelines << name.to_s
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
-
def trigger(
|
|
51
|
+
def trigger(args)
|
|
52
52
|
if pipeline_definition.nil?
|
|
53
53
|
raise DefinitionError, "Pipeline must be defined before triggering"
|
|
54
54
|
end
|
|
@@ -71,7 +71,7 @@ module Ductwork
|
|
|
71
71
|
step_type: :start,
|
|
72
72
|
started_at: Time.current
|
|
73
73
|
)
|
|
74
|
-
Ductwork::Job.enqueue(step,
|
|
74
|
+
Ductwork::Job.enqueue(step, args)
|
|
75
75
|
|
|
76
76
|
pipeline
|
|
77
77
|
end
|
data/app/models/ductwork/step.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module Ductwork
|
|
4
4
|
class Step < Ductwork::Record
|
|
5
5
|
belongs_to :pipeline, class_name: "Ductwork::Pipeline"
|
|
6
|
-
has_one :job, class_name: "Ductwork::Job", foreign_key: "step_id"
|
|
6
|
+
has_one :job, class_name: "Ductwork::Job", foreign_key: "step_id", dependent: :destroy
|
|
7
7
|
|
|
8
8
|
validates :klass, presence: true
|
|
9
9
|
validates :status, presence: true
|
|
@@ -12,9 +12,12 @@ module Ductwork
|
|
|
12
12
|
nodes: [],
|
|
13
13
|
edges: {},
|
|
14
14
|
}
|
|
15
|
+
@divisions = 0
|
|
16
|
+
@expansions = 0
|
|
15
17
|
end
|
|
16
18
|
|
|
17
19
|
def start(klass)
|
|
20
|
+
validate_classes!(klass)
|
|
18
21
|
validate_start_once!
|
|
19
22
|
add_new_nodes(klass)
|
|
20
23
|
|
|
@@ -24,6 +27,7 @@ module Ductwork
|
|
|
24
27
|
# NOTE: there is a bug here that does not allow the user to reuse step
|
|
25
28
|
# classes in the same pipeline. i'll fix this later
|
|
26
29
|
def chain(klass)
|
|
30
|
+
validate_classes!(klass)
|
|
27
31
|
validate_definition_started!(action: "chaining")
|
|
28
32
|
add_edge_to_last_node(klass, type: :chain)
|
|
29
33
|
add_new_nodes(klass)
|
|
@@ -32,10 +36,13 @@ module Ductwork
|
|
|
32
36
|
end
|
|
33
37
|
|
|
34
38
|
def divide(to:)
|
|
39
|
+
validate_classes!(to)
|
|
35
40
|
validate_definition_started!(action: "dividing chain")
|
|
36
41
|
add_edge_to_last_node(*to, type: :divide)
|
|
37
42
|
add_new_nodes(*to)
|
|
38
43
|
|
|
44
|
+
@divisions += 1
|
|
45
|
+
|
|
39
46
|
if block_given?
|
|
40
47
|
branches = to.map do |klass|
|
|
41
48
|
Ductwork::DSL::BranchBuilder
|
|
@@ -49,9 +56,12 @@ module Ductwork
|
|
|
49
56
|
end
|
|
50
57
|
|
|
51
58
|
def combine(into:)
|
|
59
|
+
validate_classes!(into)
|
|
52
60
|
validate_definition_started!(action: "combining steps")
|
|
53
61
|
validate_definition_divided!
|
|
54
62
|
|
|
63
|
+
@divisions -= 1
|
|
64
|
+
|
|
55
65
|
last_nodes = definition[:nodes].reverse.select do |node|
|
|
56
66
|
definition[:edges][node].empty?
|
|
57
67
|
end
|
|
@@ -67,23 +77,31 @@ module Ductwork
|
|
|
67
77
|
end
|
|
68
78
|
|
|
69
79
|
def expand(to:)
|
|
80
|
+
validate_classes!(to)
|
|
70
81
|
validate_definition_started!(action: "expanding chain")
|
|
71
82
|
add_edge_to_last_node(to, type: :expand)
|
|
72
83
|
add_new_nodes(to)
|
|
73
84
|
|
|
85
|
+
@expansions += 1
|
|
86
|
+
|
|
74
87
|
self
|
|
75
88
|
end
|
|
76
89
|
|
|
77
90
|
def collapse(into:)
|
|
91
|
+
validate_classes!(into)
|
|
78
92
|
validate_definition_started!(action: "collapsing steps")
|
|
79
93
|
validate_definition_expanded!
|
|
80
94
|
add_edge_to_last_node(into, type: :collapse)
|
|
81
95
|
add_new_nodes(into)
|
|
82
96
|
|
|
97
|
+
@expansions -= 1
|
|
98
|
+
|
|
83
99
|
self
|
|
84
100
|
end
|
|
85
101
|
|
|
86
102
|
def on_halt(klass)
|
|
103
|
+
validate_classes!(klass)
|
|
104
|
+
|
|
87
105
|
definition[:metadata] ||= {}
|
|
88
106
|
definition[:metadata][:on_halt] = {}
|
|
89
107
|
definition[:metadata][:on_halt][:klass] = klass.name
|
|
@@ -99,7 +117,25 @@ module Ductwork
|
|
|
99
117
|
|
|
100
118
|
private
|
|
101
119
|
|
|
102
|
-
attr_reader :definition
|
|
120
|
+
attr_reader :definition, :divisions, :expansions
|
|
121
|
+
|
|
122
|
+
def validate_classes!(klasses)
|
|
123
|
+
valid = Array(klasses).all? do |klass|
|
|
124
|
+
klass.is_a?(Class) &&
|
|
125
|
+
klass.method_defined?(:execute) &&
|
|
126
|
+
klass.instance_method(:execute).arity.zero?
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
if !valid
|
|
130
|
+
word = if Array(klasses).length > 1
|
|
131
|
+
"Arguments"
|
|
132
|
+
else
|
|
133
|
+
"Argument"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
raise ArgumentError, "#{word} must be a valid step class"
|
|
137
|
+
end
|
|
138
|
+
end
|
|
103
139
|
|
|
104
140
|
def validate_start_once!
|
|
105
141
|
if definition[:nodes].any?
|
|
@@ -114,25 +150,17 @@ module Ductwork
|
|
|
114
150
|
end
|
|
115
151
|
|
|
116
152
|
def validate_definition_divided!
|
|
117
|
-
if
|
|
153
|
+
if divisions.zero?
|
|
118
154
|
raise CombineError, "Must divide pipeline definition before combining steps"
|
|
119
155
|
end
|
|
120
156
|
end
|
|
121
157
|
|
|
122
158
|
def validate_definition_expanded!
|
|
123
|
-
if
|
|
159
|
+
if expansions.zero?
|
|
124
160
|
raise CollapseError, "Must expand pipeline definition before collapsing steps"
|
|
125
161
|
end
|
|
126
162
|
end
|
|
127
163
|
|
|
128
|
-
def last_edge
|
|
129
|
-
last_edge_node = definition[:nodes].reverse.find do |node|
|
|
130
|
-
definition[:edges][node].any?
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
definition.dig(:edges, last_edge_node, -1)
|
|
134
|
-
end
|
|
135
|
-
|
|
136
164
|
def add_new_nodes(*klasses)
|
|
137
165
|
definition[:nodes].push(*klasses.map(&:name))
|
|
138
166
|
klasses.each do |klass|
|
data/lib/ductwork/engine.rb
CHANGED
|
@@ -10,5 +10,15 @@ module Ductwork
|
|
|
10
10
|
Ductwork.configuration ||= Ductwork::Configuration.new
|
|
11
11
|
Ductwork.configuration.logger ||= Rails.logger
|
|
12
12
|
end
|
|
13
|
+
|
|
14
|
+
initializer "ductwork.validate_definitions", after: :load_config_initializers do
|
|
15
|
+
ActiveSupport.on_load(:active_record) do
|
|
16
|
+
# Load steps and pipelines so definition validation runs and bugs
|
|
17
|
+
# can be caught simply by booting the app or running tests
|
|
18
|
+
loader = Rails.autoloaders.main
|
|
19
|
+
loader.eager_load_dir(Rails.root.join("app/steps"))
|
|
20
|
+
loader.eager_load_dir(Rails.root.join("app/pipelines"))
|
|
21
|
+
end
|
|
22
|
+
end
|
|
13
23
|
end
|
|
14
24
|
end
|
data/lib/ductwork/version.rb
CHANGED