ductr 0.1.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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +14 -0
- data/.vscode/settings.json +18 -0
- data/COPYING +674 -0
- data/COPYING.LESSER +165 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +121 -0
- data/README.md +37 -0
- data/Rakefile +37 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/ductr.gemspec +50 -0
- data/exe/ductr +24 -0
- data/lib/ductr/adapter.rb +94 -0
- data/lib/ductr/cli/default.rb +25 -0
- data/lib/ductr/cli/main.rb +60 -0
- data/lib/ductr/cli/new_project_generator.rb +72 -0
- data/lib/ductr/cli/templates/project/bin_ductr.rb +7 -0
- data/lib/ductr/cli/templates/project/config_app.rb +5 -0
- data/lib/ductr/cli/templates/project/config_development.yml +8 -0
- data/lib/ductr/cli/templates/project/config_environment_development.rb +18 -0
- data/lib/ductr/cli/templates/project/gemfile.rb +6 -0
- data/lib/ductr/cli/templates/project/rubocop.yml +14 -0
- data/lib/ductr/cli/templates/project/tool-versions +1 -0
- data/lib/ductr/configuration.rb +145 -0
- data/lib/ductr/etl/controls/buffered_destination.rb +65 -0
- data/lib/ductr/etl/controls/buffered_transform.rb +76 -0
- data/lib/ductr/etl/controls/control.rb +46 -0
- data/lib/ductr/etl/controls/destination.rb +28 -0
- data/lib/ductr/etl/controls/paginated_source.rb +47 -0
- data/lib/ductr/etl/controls/source.rb +21 -0
- data/lib/ductr/etl/controls/transform.rb +28 -0
- data/lib/ductr/etl/fiber_control.rb +136 -0
- data/lib/ductr/etl/fiber_runner.rb +68 -0
- data/lib/ductr/etl/kiba_runner.rb +26 -0
- data/lib/ductr/etl/parser.rb +115 -0
- data/lib/ductr/etl/runner.rb +37 -0
- data/lib/ductr/etl_job.rb +161 -0
- data/lib/ductr/job.rb +58 -0
- data/lib/ductr/job_etl_runner.rb +37 -0
- data/lib/ductr/job_status.rb +56 -0
- data/lib/ductr/kiba_job.rb +130 -0
- data/lib/ductr/log/formatters/color_formatter.rb +48 -0
- data/lib/ductr/log/logger.rb +169 -0
- data/lib/ductr/log/outputs/file_output.rb +30 -0
- data/lib/ductr/log/outputs/standard_output.rb +39 -0
- data/lib/ductr/pipeline.rb +133 -0
- data/lib/ductr/pipeline_runner.rb +95 -0
- data/lib/ductr/pipeline_step.rb +92 -0
- data/lib/ductr/registry.rb +55 -0
- data/lib/ductr/rufus_trigger.rb +106 -0
- data/lib/ductr/scheduler.rb +117 -0
- data/lib/ductr/store/job_serializer.rb +59 -0
- data/lib/ductr/store/job_store.rb +59 -0
- data/lib/ductr/store/pipeline_serializer.rb +106 -0
- data/lib/ductr/store/pipeline_store.rb +48 -0
- data/lib/ductr/store.rb +81 -0
- data/lib/ductr/trigger.rb +49 -0
- data/lib/ductr/version.rb +6 -0
- data/lib/ductr.rb +143 -0
- data/sig/ductr.rbs +1107 -0
- metadata +292 -0
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ductr
|
4
|
+
#
|
5
|
+
# Representation of a pipeline's step.
|
6
|
+
# Hold a fiber to execute steps concurrently.
|
7
|
+
#
|
8
|
+
class PipelineStep
|
9
|
+
extend Forwardable
|
10
|
+
|
11
|
+
#
|
12
|
+
# @!method resume
|
13
|
+
# Resumes the step's fiber.
|
14
|
+
# @return [void]
|
15
|
+
# @!method alive?
|
16
|
+
# Check if the step's fiber is running.
|
17
|
+
# @return [Boolean] True if step's fiber is running
|
18
|
+
#
|
19
|
+
def_delegators :fiber, :resume, :alive?
|
20
|
+
|
21
|
+
# @return [Pipeline] The step's pipeline
|
22
|
+
attr_reader :pipeline
|
23
|
+
# @return [Symbol] The step's name
|
24
|
+
attr_reader :name
|
25
|
+
# @return [Array<Job>] The step's queued jobs
|
26
|
+
attr_reader :jobs
|
27
|
+
|
28
|
+
# @return [PipelineStep] The previous step
|
29
|
+
attr_accessor :left
|
30
|
+
|
31
|
+
#
|
32
|
+
# Creates a step for the given pipeline.
|
33
|
+
#
|
34
|
+
# @param [Pipeline] pipeline The pipeline containing step's method
|
35
|
+
# @param [Symbol] The name of the pipeline's step method
|
36
|
+
#
|
37
|
+
def initialize(pipeline, name)
|
38
|
+
@pipeline = pipeline
|
39
|
+
@name = name
|
40
|
+
|
41
|
+
@jobs = []
|
42
|
+
@left = []
|
43
|
+
end
|
44
|
+
|
45
|
+
#
|
46
|
+
# Track, registers and enqueues the given job.
|
47
|
+
#
|
48
|
+
# @param [Job] job The job to enqueue
|
49
|
+
#
|
50
|
+
# @return [void]
|
51
|
+
#
|
52
|
+
def enqueue_job(job)
|
53
|
+
jobs.push(job)
|
54
|
+
Store.register_job(job)
|
55
|
+
job.enqueue
|
56
|
+
end
|
57
|
+
|
58
|
+
#
|
59
|
+
# Check if the step is done.
|
60
|
+
#
|
61
|
+
# @return [Boolean] True if the step is done
|
62
|
+
#
|
63
|
+
def done?
|
64
|
+
!fiber.alive?
|
65
|
+
end
|
66
|
+
|
67
|
+
#
|
68
|
+
# Waits until all step's jobs are stopped.
|
69
|
+
#
|
70
|
+
# @return [void]
|
71
|
+
#
|
72
|
+
def flush_jobs
|
73
|
+
return if jobs.empty?
|
74
|
+
|
75
|
+
Fiber.yield until Store.read_jobs(*jobs).all?(&:stopped?)
|
76
|
+
end
|
77
|
+
|
78
|
+
#
|
79
|
+
# The step's fiber instance, invokes the pipeline's step method.
|
80
|
+
#
|
81
|
+
# @return [Fiber] The step's fiber
|
82
|
+
#
|
83
|
+
def fiber
|
84
|
+
@fiber ||= Fiber.new do
|
85
|
+
Fiber.yield until left.all?(&:done?)
|
86
|
+
|
87
|
+
pipeline.send(name)
|
88
|
+
flush_jobs
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ductr
|
4
|
+
class NotFoundInRegistryError < StandardError; end
|
5
|
+
|
6
|
+
#
|
7
|
+
# The registry pattern to store adapters, controls and triggers.
|
8
|
+
#
|
9
|
+
class Registry
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
#
|
13
|
+
# @!method values
|
14
|
+
# Get all registered adapters, controls or triggers.
|
15
|
+
# @return [Array<Class<Adapter, ETL::Control, Trigger>>] The registered adapters, controls or triggers
|
16
|
+
#
|
17
|
+
def_delegators :@items, :values
|
18
|
+
|
19
|
+
#
|
20
|
+
# Initialize the registry as an empty hash with the given name.
|
21
|
+
#
|
22
|
+
# @param name [Symbol] The registry name, used in error message
|
23
|
+
#
|
24
|
+
def initialize(name)
|
25
|
+
@name = name
|
26
|
+
@items = {}
|
27
|
+
end
|
28
|
+
|
29
|
+
#
|
30
|
+
# Allow to add one class into the registry.
|
31
|
+
#
|
32
|
+
# @param item [Class<Adapter, ETL::Control, Trigger>] The class to add in the registry
|
33
|
+
# @param as: [Symbol] The adapter, control or trigger type
|
34
|
+
#
|
35
|
+
# @return [void]
|
36
|
+
#
|
37
|
+
def add(item, as:)
|
38
|
+
@items[as.to_sym] = item
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# Find an adapter, control or trigger based on its type.
|
43
|
+
#
|
44
|
+
# @param type [Symbol] The adapter, control or trigger type
|
45
|
+
#
|
46
|
+
# @raise [NotFoundInRegistryError] If nothing matches the given type
|
47
|
+
# @return [Class<Adapter, ETL::Control, Trigger>] The found adapter
|
48
|
+
#
|
49
|
+
def find(type)
|
50
|
+
@items.fetch(type.to_sym) do
|
51
|
+
raise NotFoundInRegistryError, "The #{@name} of type \"#{type}\" does not exist"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rufus-scheduler"
|
4
|
+
|
5
|
+
module Ductr
|
6
|
+
#
|
7
|
+
# A time related trigger, used to trigger jobs or pipelines based on temporal events.
|
8
|
+
# The trigger is registered as `:schedule`:
|
9
|
+
#
|
10
|
+
# trigger :schedule, every: "1min"
|
11
|
+
# def every_minute
|
12
|
+
# MyPipeline.perform_later
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# This trigger is based on the `rufus-scheduler` gem.
|
16
|
+
# Under the hood, the internal method `Rufus::Scheduler#do_schedule` is used.
|
17
|
+
#
|
18
|
+
# There are 4 types of options available:
|
19
|
+
#
|
20
|
+
# - `:once`, used to schedule at a given time
|
21
|
+
#
|
22
|
+
# trigger :schedule, once: "10d" # Will run in ten days
|
23
|
+
# trigger :schedule, once: "2042/12/12 23:59:00" # Will run at the given date
|
24
|
+
#
|
25
|
+
# - `:every`, triggers following the given frequency
|
26
|
+
#
|
27
|
+
# trigger :schedule, every: "1min" # will run every minute.
|
28
|
+
#
|
29
|
+
# - `:interval`, run the trigger then waits the given interval before running again
|
30
|
+
#
|
31
|
+
# # Will run every 4 + 1 seconds
|
32
|
+
# trigger :schedule, interval: "4s"
|
33
|
+
# def every_interval
|
34
|
+
# sleep(1)
|
35
|
+
# MyPipeline.perform_later
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# - `:cron`, run the trigger following the given crontab pattern
|
39
|
+
#
|
40
|
+
# trigger :schedule, cron: "00 01 * * *" # Will run every day at 1am.
|
41
|
+
#
|
42
|
+
class RufusTrigger < Trigger
|
43
|
+
Ductr.trigger_registry.add self, as: :schedule
|
44
|
+
|
45
|
+
#
|
46
|
+
# Adds a new trigger into rufus-scheduler.
|
47
|
+
#
|
48
|
+
# @param [Symbol] method The scheduler method to be called by rufus-scheduler
|
49
|
+
# @param [Hash<Symbol: String>] options The options to configure rufus-scheduler
|
50
|
+
#
|
51
|
+
# @return [void]
|
52
|
+
#
|
53
|
+
def add(method, options)
|
54
|
+
rufus_type = options.keys.first
|
55
|
+
rufus_value = options.values.first
|
56
|
+
|
57
|
+
do_schedule(rufus_type, rufus_value, method)
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
# Shutdown rufus-scheduler
|
62
|
+
#
|
63
|
+
# @return [void]
|
64
|
+
#
|
65
|
+
def stop
|
66
|
+
rufus.shutdown
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
#
|
72
|
+
# Shortcut to get the rufus-scheduler singleton
|
73
|
+
#
|
74
|
+
# @return [Rufus::Scheduler] The rufus-scheduler instance
|
75
|
+
#
|
76
|
+
def rufus
|
77
|
+
Rufus::Scheduler.singleton
|
78
|
+
end
|
79
|
+
|
80
|
+
#
|
81
|
+
# Returns a callable object based on given scheduler, method_name and options.
|
82
|
+
#
|
83
|
+
# @param [Scheduler] scheduler The scheduler instance
|
84
|
+
# @param [Symbol] method_name The scheduler's method name
|
85
|
+
# @param [Hash] ** The option passed to the trigger annotation
|
86
|
+
#
|
87
|
+
# @return [#call] A callable object
|
88
|
+
#
|
89
|
+
def callable(scheduler, method_name, **)
|
90
|
+
scheduler.method(method_name)
|
91
|
+
end
|
92
|
+
|
93
|
+
#
|
94
|
+
# Calls the Rufus::Scheduler#do_schedule private method with rufus-scheduler type, value and the callable object.
|
95
|
+
#
|
96
|
+
# @param [Symbol] rufus_type The rufus-scheduler type (`:once`, `:every`, `:interval` or `:cron`)
|
97
|
+
# @param [String] rufus_value The rufus-scheduler value (e.g. `"10min"`)
|
98
|
+
# @param [#call] method The callable object (the scheduler's method in this case)
|
99
|
+
#
|
100
|
+
# @return [void]
|
101
|
+
#
|
102
|
+
def do_schedule(rufus_type, rufus_value, method)
|
103
|
+
rufus.send(:do_schedule, rufus_type, rufus_value, nil, {}, false, method)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ductr
|
4
|
+
#
|
5
|
+
# Base class to declare event driven scheduling.
|
6
|
+
# Example using the schedule trigger:
|
7
|
+
#
|
8
|
+
# class MyScheduler < Ductr::Scheduler
|
9
|
+
# trigger :schedule, every: "10min"
|
10
|
+
# def every_ten_minutes
|
11
|
+
# MyJob.perform_later
|
12
|
+
# end
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
class Scheduler
|
16
|
+
extend Annotable
|
17
|
+
|
18
|
+
#
|
19
|
+
# @!method self.trigger(trigger_type, adapter_name = nil, **trigger_options)
|
20
|
+
# Annotation to define a trigger method
|
21
|
+
# @param trigger_type [Symbol] The trigger's type
|
22
|
+
# @param adapter_name [Symbol] The trigger's adapter (if any)
|
23
|
+
# @param **trigger_options [Hash<Symbol: Object>] The options to pass to the trigger
|
24
|
+
#
|
25
|
+
# @example A schedule trigger
|
26
|
+
# trigger :schedule, every: "10min"
|
27
|
+
# def every_ten_minutes
|
28
|
+
# MyJob.perform_later
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# @see The chosen trigger documentation for further information.
|
32
|
+
#
|
33
|
+
# @return [void]
|
34
|
+
#
|
35
|
+
annotable :trigger
|
36
|
+
|
37
|
+
class << self
|
38
|
+
#
|
39
|
+
# All trigger instances are stored in a singleton hash, avoiding to create the same trigger multiple times.
|
40
|
+
#
|
41
|
+
# @return [Hash<Symbol: Trigger>] The singleton hash containing all trigger instances
|
42
|
+
#
|
43
|
+
def triggers
|
44
|
+
@triggers ||= {}
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# Calls #start on all created triggers.
|
49
|
+
#
|
50
|
+
# @return [void]
|
51
|
+
#
|
52
|
+
def start
|
53
|
+
triggers.values.each(&:start)
|
54
|
+
end
|
55
|
+
|
56
|
+
#
|
57
|
+
# Calls #stop on all created triggers.
|
58
|
+
#
|
59
|
+
# @return [void]
|
60
|
+
#
|
61
|
+
def stop
|
62
|
+
triggers.values.each(&:stop)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
# Parses trigger annotations, creates triggers if needed and calls #add on trigger instances.
|
68
|
+
#
|
69
|
+
def initialize
|
70
|
+
self.class.annotated_methods.each do |method|
|
71
|
+
annotation = method.find_annotation(:trigger)
|
72
|
+
trigger = find_trigger(*annotation.params.reverse)
|
73
|
+
callable = self.method(method.name)
|
74
|
+
|
75
|
+
trigger.add(callable, annotation.options)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
#
|
82
|
+
# Finds a trigger in the according trigger registry based on its type and adapter name if given.
|
83
|
+
#
|
84
|
+
# @param [Symbol] trigger_type The trigger type, e.g. `:schedule`
|
85
|
+
# @param [Symbol] adapter_name The adapter name, e.g. `:my_adapter`
|
86
|
+
#
|
87
|
+
# @return [Trigger] The found trigger
|
88
|
+
#
|
89
|
+
def find_trigger(trigger_type, adapter_name = nil)
|
90
|
+
return find_or_create_trigger(Ductr.trigger_registry, trigger_type) unless adapter_name
|
91
|
+
|
92
|
+
adapter = Ductr.config.adapter(adapter_name)
|
93
|
+
trigger_registry = adapter.class.trigger_registry
|
94
|
+
registry_key = "#{adapter_name}_#{trigger_type}".to_sym
|
95
|
+
|
96
|
+
find_or_create_trigger(trigger_registry, registry_key, trigger_type, adapter)
|
97
|
+
end
|
98
|
+
|
99
|
+
#
|
100
|
+
# Find a trigger in the singleton hash. if none found, find the trigger class in the given registry,
|
101
|
+
# initializes it and store the instance in the singleton hash.
|
102
|
+
#
|
103
|
+
# @param [Registry] trigger_registry The registry containing the required trigger class
|
104
|
+
# @param [Symbol] registry_key The key in which to store the trigger instance inside the singleton hash
|
105
|
+
# @param [Symbol] trigger_type The registry's key in which to find the trigger class
|
106
|
+
# @param [Adapter, Nil] adapter The adapter to pass to the trigger
|
107
|
+
#
|
108
|
+
# @return [Trigger] The found or created trigger
|
109
|
+
#
|
110
|
+
def find_or_create_trigger(trigger_registry, registry_key, trigger_type = registry_key, adapter = nil)
|
111
|
+
trigger = Scheduler.triggers[registry_key]
|
112
|
+
return trigger if trigger
|
113
|
+
|
114
|
+
Scheduler.triggers[registry_key] = trigger_registry.find(trigger_type).new(adapter)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ductr
|
4
|
+
module Store
|
5
|
+
#
|
6
|
+
# Convert jobs into active job serializable structs.
|
7
|
+
#
|
8
|
+
module JobSerializer
|
9
|
+
#
|
10
|
+
# @!parse
|
11
|
+
# #
|
12
|
+
# # The job representation as a struct.
|
13
|
+
# #
|
14
|
+
# # @!attribute [r] job_id
|
15
|
+
# # @return [String] The active job's job id
|
16
|
+
# #
|
17
|
+
# # @!attribute [r] status
|
18
|
+
# # @return [Symbol] The job's status
|
19
|
+
# #
|
20
|
+
# # @!attribute [r] error
|
21
|
+
# # @return [Exception, nil] The job's error if any
|
22
|
+
# #
|
23
|
+
# class SerializedJob < Struct
|
24
|
+
# #
|
25
|
+
# # @param [String] job_id Active job's job id
|
26
|
+
# # @param [Symbol] status Job's status
|
27
|
+
# # @param [Exception, nil] error Job's error
|
28
|
+
# #
|
29
|
+
# def initialize(job_id, status, error)
|
30
|
+
# @job_id = job_id
|
31
|
+
# @status = status
|
32
|
+
# @error = error
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
SerializedJob = Struct.new(:job_id, :status, :error) do
|
37
|
+
#
|
38
|
+
# Determines whether the job has a `completed` or `failed` status.
|
39
|
+
#
|
40
|
+
# @return [Boolean] True when the status is `completed` or `failed`
|
41
|
+
#
|
42
|
+
def stopped?
|
43
|
+
%i[completed failed].include? status
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# Convert the given job into a `SerializedJob` struct.
|
49
|
+
#
|
50
|
+
# @param [Job] job The job to serialize
|
51
|
+
#
|
52
|
+
# @return [SerializedJob] The job converted into struct
|
53
|
+
#
|
54
|
+
def serialize_job(job)
|
55
|
+
SerializedJob.new(job.job_id, job.status, job.error)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ductr
|
4
|
+
module Store
|
5
|
+
#
|
6
|
+
# Job's level store interactions.
|
7
|
+
#
|
8
|
+
module JobStore
|
9
|
+
include JobSerializer
|
10
|
+
|
11
|
+
# @return [String] The job key prefix
|
12
|
+
JOB_KEY_PREFIX = "ductr:job"
|
13
|
+
# @return [String] The job registry key
|
14
|
+
JOB_REGISTRY_KEY = "ductr:job_registry"
|
15
|
+
|
16
|
+
#
|
17
|
+
# Get all known job instances.
|
18
|
+
#
|
19
|
+
# @return [Array<Job>] The job instances
|
20
|
+
#
|
21
|
+
def all_jobs
|
22
|
+
all(JOB_REGISTRY_KEY, JOB_KEY_PREFIX)
|
23
|
+
end
|
24
|
+
|
25
|
+
#
|
26
|
+
# Read all given jobs.
|
27
|
+
#
|
28
|
+
# @param [Array<Job>] *jobs The jobs to read
|
29
|
+
#
|
30
|
+
# @return [Array<Job>] The read jobs
|
31
|
+
#
|
32
|
+
def read_jobs(*jobs)
|
33
|
+
read(JOB_KEY_PREFIX, *jobs)
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# Update the given job.
|
38
|
+
#
|
39
|
+
# @param [Job] job The job to update in the store
|
40
|
+
#
|
41
|
+
# @return [void]
|
42
|
+
#
|
43
|
+
def write_job(job)
|
44
|
+
write(JOB_KEY_PREFIX, serialize_job(job))
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# Add the given job to the store's job registry. This method is NOT thread-safe.
|
49
|
+
#
|
50
|
+
# @param [Job] job The job to register
|
51
|
+
#
|
52
|
+
# @return [void]
|
53
|
+
#
|
54
|
+
def register_job(job)
|
55
|
+
register(JOB_REGISTRY_KEY, serialize_job(job))
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ductr
|
4
|
+
module Store
|
5
|
+
#
|
6
|
+
# Convert pipelines and steps into active job serializable structs.
|
7
|
+
#
|
8
|
+
module PipelineSerializer
|
9
|
+
include JobSerializer
|
10
|
+
|
11
|
+
#
|
12
|
+
# @!parse
|
13
|
+
# #
|
14
|
+
# # The pipeline representation as a struct.
|
15
|
+
# #
|
16
|
+
# # @!attribute [r] job_id
|
17
|
+
# # @return [String] The active job's job id
|
18
|
+
# #
|
19
|
+
# # @!attribute [r] status
|
20
|
+
# # @return [Symbol] The pipeline job status
|
21
|
+
# #
|
22
|
+
# # @!attribute [r] error
|
23
|
+
# # @return [Exception, nil] The pipeline job error if any
|
24
|
+
# #
|
25
|
+
# # @!attribute [r] steps
|
26
|
+
# # @return [Array<SerializedPipelineStep>] The pipeline steps as struct
|
27
|
+
# #
|
28
|
+
# class SerializedPipeline < Struct
|
29
|
+
# #
|
30
|
+
# # @param [String] job_id Pipeline job id
|
31
|
+
# # @param [Symbol] status Pipeline status
|
32
|
+
# # @param [Exception, nil] error Pipeline error
|
33
|
+
# # @param [Array<SerializedPipelineStep>] steps Pipeline steps as struct
|
34
|
+
# #
|
35
|
+
# def initialize(job_id, status, error, steps)
|
36
|
+
# @job_id = job_id
|
37
|
+
# @status = status
|
38
|
+
# @error = error
|
39
|
+
# @steps = steps
|
40
|
+
# end
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
SerializedPipeline = Struct.new(:job_id, :status, :error, :steps) do
|
44
|
+
#
|
45
|
+
# Determines whether the pipeline has a `completed` or `failed` status.
|
46
|
+
#
|
47
|
+
# @return [Boolean] True when the status is `completed` or `failed`
|
48
|
+
#
|
49
|
+
def stopped?
|
50
|
+
%i[completed failed].include? status
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# @!parse
|
56
|
+
# #
|
57
|
+
# # The pipeline step representation as a struct.
|
58
|
+
# #
|
59
|
+
# # @!attribute [r] jobs
|
60
|
+
# # @return [Array<Job>] The step's jobs
|
61
|
+
# #
|
62
|
+
# # @!attribute [r] done
|
63
|
+
# # @return [Boolean] The step's fiber state
|
64
|
+
# #
|
65
|
+
# class SerializedPipelineStep < Struct
|
66
|
+
# #
|
67
|
+
# # @param [Array<Job>] jobs The step's jobs
|
68
|
+
# # @param [Boolean] done The step's fiber state
|
69
|
+
# #
|
70
|
+
# def initialize(jobs, done)
|
71
|
+
# @jobs = jobs
|
72
|
+
# @done = done
|
73
|
+
# end
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
SerializedPipelineStep = Struct.new(:jobs, :done) do
|
77
|
+
#
|
78
|
+
# Check if the step is done.
|
79
|
+
#
|
80
|
+
# @return [Boolean] True if the step is done
|
81
|
+
#
|
82
|
+
def done?
|
83
|
+
done
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
#
|
88
|
+
# Convert the given pipeline and its steps into
|
89
|
+
# `SerializedPipeline` and `SerializedPipelineStep` structs.
|
90
|
+
#
|
91
|
+
# @param [Pipeline] pipeline The pipeline to serialize
|
92
|
+
#
|
93
|
+
# @return [SerializedPipeline] The pipeline converted into struct
|
94
|
+
#
|
95
|
+
def serialize_pipeline(pipeline)
|
96
|
+
serialized_steps = pipeline.runner.steps.map do |step|
|
97
|
+
jobs = step.jobs.map { |j| serialize_job(j) }
|
98
|
+
|
99
|
+
SerializedPipelineStep.new(jobs, step.done?)
|
100
|
+
end
|
101
|
+
|
102
|
+
SerializedPipeline.new(pipeline.job_id, pipeline.status, pipeline.error, serialized_steps)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ductr
|
4
|
+
module Store
|
5
|
+
#
|
6
|
+
# Pipeline's level store interactions.
|
7
|
+
#
|
8
|
+
module PipelineStore
|
9
|
+
include PipelineSerializer
|
10
|
+
|
11
|
+
# @return [String] The pipeline key prefix
|
12
|
+
PIPELINE_KEY_PREFIX = "ductr:pipeline"
|
13
|
+
# @return [String] The pipeline registry key
|
14
|
+
PIPELINE_REGISTRY_KEY = "ductr:pipeline_registry"
|
15
|
+
|
16
|
+
#
|
17
|
+
# Get all known pipeline instances.
|
18
|
+
#
|
19
|
+
# @return [Array<SerializedPipeline>] The pipeline instances
|
20
|
+
#
|
21
|
+
def all_pipelines
|
22
|
+
all(PIPELINE_REGISTRY_KEY, PIPELINE_KEY_PREFIX)
|
23
|
+
end
|
24
|
+
|
25
|
+
#
|
26
|
+
# Update the given pipeline.
|
27
|
+
#
|
28
|
+
# @param [Pipeline] pipeline The pipeline to update in the store
|
29
|
+
#
|
30
|
+
# @return [void]
|
31
|
+
#
|
32
|
+
def write_pipeline(pipeline)
|
33
|
+
write(PIPELINE_KEY_PREFIX, serialize_pipeline(pipeline))
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# Add the given pipeline to the store's pipeline registry. This method is NOT thread-safe.
|
38
|
+
#
|
39
|
+
# @param [Pipeline] pipeline The job to register
|
40
|
+
#
|
41
|
+
# @return [void]
|
42
|
+
#
|
43
|
+
def register_pipeline(pipeline)
|
44
|
+
register(PIPELINE_REGISTRY_KEY, pipeline)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|