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.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +14 -0
  4. data/.vscode/settings.json +18 -0
  5. data/COPYING +674 -0
  6. data/COPYING.LESSER +165 -0
  7. data/Gemfile +6 -0
  8. data/Gemfile.lock +121 -0
  9. data/README.md +37 -0
  10. data/Rakefile +37 -0
  11. data/bin/console +15 -0
  12. data/bin/setup +8 -0
  13. data/ductr.gemspec +50 -0
  14. data/exe/ductr +24 -0
  15. data/lib/ductr/adapter.rb +94 -0
  16. data/lib/ductr/cli/default.rb +25 -0
  17. data/lib/ductr/cli/main.rb +60 -0
  18. data/lib/ductr/cli/new_project_generator.rb +72 -0
  19. data/lib/ductr/cli/templates/project/bin_ductr.rb +7 -0
  20. data/lib/ductr/cli/templates/project/config_app.rb +5 -0
  21. data/lib/ductr/cli/templates/project/config_development.yml +8 -0
  22. data/lib/ductr/cli/templates/project/config_environment_development.rb +18 -0
  23. data/lib/ductr/cli/templates/project/gemfile.rb +6 -0
  24. data/lib/ductr/cli/templates/project/rubocop.yml +14 -0
  25. data/lib/ductr/cli/templates/project/tool-versions +1 -0
  26. data/lib/ductr/configuration.rb +145 -0
  27. data/lib/ductr/etl/controls/buffered_destination.rb +65 -0
  28. data/lib/ductr/etl/controls/buffered_transform.rb +76 -0
  29. data/lib/ductr/etl/controls/control.rb +46 -0
  30. data/lib/ductr/etl/controls/destination.rb +28 -0
  31. data/lib/ductr/etl/controls/paginated_source.rb +47 -0
  32. data/lib/ductr/etl/controls/source.rb +21 -0
  33. data/lib/ductr/etl/controls/transform.rb +28 -0
  34. data/lib/ductr/etl/fiber_control.rb +136 -0
  35. data/lib/ductr/etl/fiber_runner.rb +68 -0
  36. data/lib/ductr/etl/kiba_runner.rb +26 -0
  37. data/lib/ductr/etl/parser.rb +115 -0
  38. data/lib/ductr/etl/runner.rb +37 -0
  39. data/lib/ductr/etl_job.rb +161 -0
  40. data/lib/ductr/job.rb +58 -0
  41. data/lib/ductr/job_etl_runner.rb +37 -0
  42. data/lib/ductr/job_status.rb +56 -0
  43. data/lib/ductr/kiba_job.rb +130 -0
  44. data/lib/ductr/log/formatters/color_formatter.rb +48 -0
  45. data/lib/ductr/log/logger.rb +169 -0
  46. data/lib/ductr/log/outputs/file_output.rb +30 -0
  47. data/lib/ductr/log/outputs/standard_output.rb +39 -0
  48. data/lib/ductr/pipeline.rb +133 -0
  49. data/lib/ductr/pipeline_runner.rb +95 -0
  50. data/lib/ductr/pipeline_step.rb +92 -0
  51. data/lib/ductr/registry.rb +55 -0
  52. data/lib/ductr/rufus_trigger.rb +106 -0
  53. data/lib/ductr/scheduler.rb +117 -0
  54. data/lib/ductr/store/job_serializer.rb +59 -0
  55. data/lib/ductr/store/job_store.rb +59 -0
  56. data/lib/ductr/store/pipeline_serializer.rb +106 -0
  57. data/lib/ductr/store/pipeline_store.rb +48 -0
  58. data/lib/ductr/store.rb +81 -0
  59. data/lib/ductr/trigger.rb +49 -0
  60. data/lib/ductr/version.rb +6 -0
  61. data/lib/ductr.rb +143 -0
  62. data/sig/ductr.rbs +1107 -0
  63. 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