libis-workflow-activerecord 0.9.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/.gitignore +57 -0
- data/.travis.yml +50 -0
- data/Gemfile +3 -0
- data/README.md +13 -0
- data/Rakefile +85 -0
- data/db/config.yml +71 -0
- data/db/migrate/001_create_work_items_table.rb +18 -0
- data/db/migrate/002_create_jobs_table.rb +27 -0
- data/db/schema.rb +72 -0
- data/db/setup_db.rb +14 -0
- data/db/travis.config.yml +4 -0
- data/lib/libis-workflow-activerecord.rb +1 -0
- data/lib/libis/workflow/activerecord.rb +25 -0
- data/lib/libis/workflow/activerecord/base.rb +79 -0
- data/lib/libis/workflow/activerecord/config.rb +30 -0
- data/lib/libis/workflow/activerecord/helpers/hash_serializer.rb +32 -0
- data/lib/libis/workflow/activerecord/helpers/property_helper.rb +40 -0
- data/lib/libis/workflow/activerecord/helpers/status_serializer.rb +30 -0
- data/lib/libis/workflow/activerecord/job.rb +73 -0
- data/lib/libis/workflow/activerecord/run.rb +99 -0
- data/lib/libis/workflow/activerecord/version.rb +7 -0
- data/lib/libis/workflow/activerecord/work_item.rb +90 -0
- data/lib/libis/workflow/activerecord/worker.rb +20 -0
- data/lib/libis/workflow/activerecord/workflow.rb +40 -0
- data/libis-workflow-activerecord.gemspec +42 -0
- data/spec/db_env.sh +5 -0
- data/spec/db_start.sh +5 -0
- data/spec/items.rb +3 -0
- data/spec/items/test_dir_item.rb +18 -0
- data/spec/items/test_file_item.rb +29 -0
- data/spec/items/test_item.rb +6 -0
- data/spec/items/test_run.rb +8 -0
- data/spec/job_spec.rb +10 -0
- data/spec/run_spec.rb +4 -0
- data/spec/spec_helper.rb +37 -0
- data/spec/tasks/camelize_name.rb +13 -0
- data/spec/tasks/checksum_tester.rb +34 -0
- data/spec/tasks/collect_files.rb +52 -0
- data/spec/test_job.rb +6 -0
- data/spec/test_workflow.rb +6 -0
- data/spec/test_workitem.rb +7 -0
- data/spec/workflow_spec.rb +208 -0
- data/spec/workitem_spec.rb +366 -0
- metadata +245 -0
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'libis/tools/extend/hash'
|
3
|
+
|
4
|
+
module Libis
|
5
|
+
module Workflow
|
6
|
+
module ActiveRecord
|
7
|
+
|
8
|
+
module Base
|
9
|
+
|
10
|
+
def self.included(klass)
|
11
|
+
klass.extend(ClassMethods)
|
12
|
+
|
13
|
+
# def [](key)
|
14
|
+
# self.send(key.to_sym)
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# def []=(key,value)
|
18
|
+
# self.send(key.to_s + '=', value)
|
19
|
+
# end
|
20
|
+
end
|
21
|
+
|
22
|
+
module ClassMethods
|
23
|
+
def from_hash(hash)
|
24
|
+
self.create_from_hash(hash.cleanup, [:name])
|
25
|
+
end
|
26
|
+
|
27
|
+
def create_from_hash(hash, id_tags, &block)
|
28
|
+
hash = hash.key_symbols_to_strings(recursive: true)
|
29
|
+
id_tags = id_tags.map(&:to_s)
|
30
|
+
return nil unless id_tags.empty? || id_tags.any? { |k| hash.include?(k) }
|
31
|
+
tags = id_tags.inject({}) do |h, k|
|
32
|
+
v = hash.delete(k)
|
33
|
+
h[k] = v if v
|
34
|
+
h
|
35
|
+
end
|
36
|
+
item = tags.empty? ? self.new : self.find_or_initialize_by(tags)
|
37
|
+
block.call(item, hash) if block unless hash.empty?
|
38
|
+
item.assign_attributes(hash)
|
39
|
+
item.save!
|
40
|
+
item
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_hash
|
46
|
+
result = self.attributes.reject { |k, v| v.blank? || volatile_attributes.include?(k) }
|
47
|
+
result = result.to_yaml
|
48
|
+
# noinspection RubyResolve
|
49
|
+
YAML.load(result)
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_s
|
53
|
+
self.name || "#{self.class.name}_#{self.id}"
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
|
58
|
+
def volatile_attributes
|
59
|
+
%w'id created_at updated_at'
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def copy_attributes(other)
|
65
|
+
self.set(
|
66
|
+
other.attributes.reject do |k, _|
|
67
|
+
volatile_attributes.include? k.to_s
|
68
|
+
end.each_with_object({}) do |(k, v), h|
|
69
|
+
h[k] = v.duplicable? ? v.dup : v
|
70
|
+
end
|
71
|
+
)
|
72
|
+
self
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# noinspection RubyResolve
|
2
|
+
require 'singleton'
|
3
|
+
require 'active_record'
|
4
|
+
|
5
|
+
require 'libis/workflow/base/logging'
|
6
|
+
|
7
|
+
require 'libis/workflow/config'
|
8
|
+
module Libis
|
9
|
+
module Workflow
|
10
|
+
module ActiveRecord
|
11
|
+
|
12
|
+
# noinspection RubyConstantNamingConvention
|
13
|
+
Config = ::Libis::Workflow::Config
|
14
|
+
|
15
|
+
Config.define_singleton_method(:database_connect) do |config_file = 'db/config.yml', environment = :production|
|
16
|
+
# noinspection RubyResolve
|
17
|
+
instance.database_connect(config_file, environment)
|
18
|
+
end
|
19
|
+
|
20
|
+
Config.send(:define_method, :database_connect) do |config_file = 'db/config.yml', environment = :production|
|
21
|
+
db_config = YAML.load_file(config_file)
|
22
|
+
# ::ActiveRecord::Base.logger = Libis::Workflow::ActiveRecord::Config.logger
|
23
|
+
::ActiveRecord::Base.establish_connection(db_config[environment.to_s])
|
24
|
+
end
|
25
|
+
|
26
|
+
Config[:log_dir] = '.'
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
2
|
+
require 'libis/tools/extend/hash'
|
3
|
+
|
4
|
+
module Libis
|
5
|
+
module Workflow
|
6
|
+
module ActiveRecord
|
7
|
+
module Helpers
|
8
|
+
|
9
|
+
class HashSerializer
|
10
|
+
|
11
|
+
def self.dump(hash)
|
12
|
+
delete_proc = Proc.new do |_, v|
|
13
|
+
# Cleanup the hash recursively and remove entries with value == nil
|
14
|
+
# noinspection RubyScope
|
15
|
+
v.delete_if(&delete_proc) if v.kind_of?(Hash)
|
16
|
+
v.nil?
|
17
|
+
end
|
18
|
+
hash.delete_if &delete_proc if hash.is_a?(Hash)
|
19
|
+
# Store a nil value if the hash is empty
|
20
|
+
(hash.is_a?(Hash) && !hash.empty?) ? hash : nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.load(hash)
|
24
|
+
(hash || {}).with_indifferent_access
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Libis
|
4
|
+
module Workflow
|
5
|
+
module ActiveRecord
|
6
|
+
module Helpers
|
7
|
+
|
8
|
+
module PropertyHelper
|
9
|
+
def self.included(klass)
|
10
|
+
klass.extend(ClassMethods)
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
|
15
|
+
def property_field(name, options = {})
|
16
|
+
default = options[:default]
|
17
|
+
default = default.call if default.is_a? Proc
|
18
|
+
if options[:type]
|
19
|
+
# noinspection RubyResolve
|
20
|
+
options.reverse_merge! reader: lambda {|value| value.blank? ? nil : YAML.load(value)},
|
21
|
+
writer: lambda {|value| value ? YAML.dump(value) : nil}
|
22
|
+
end
|
23
|
+
self.send(:define_method, name, lambda {
|
24
|
+
value = properties[name]
|
25
|
+
(options[:reader] ? options[:reader].call(value) : value) || default
|
26
|
+
})
|
27
|
+
self.send(:define_method, "#{name}=", lambda {|value|
|
28
|
+
value ||= default
|
29
|
+
properties[name] = options[:writer] ? options[:writer].call(value) : value
|
30
|
+
})
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
2
|
+
|
3
|
+
module Libis
|
4
|
+
module Workflow
|
5
|
+
module ActiveRecord
|
6
|
+
module Helpers
|
7
|
+
|
8
|
+
class StatusSerializer
|
9
|
+
|
10
|
+
def self.dump(array)
|
11
|
+
return nil unless array.is_a?(Array) && !array.empty?
|
12
|
+
array || []
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.load(array)
|
16
|
+
(array || []).map do |status|
|
17
|
+
status = status.with_indifferent_access
|
18
|
+
status[:status] = status[:status].to_sym if status.has_key? :status
|
19
|
+
status[:created] = DateTime.parse(status[:created]) if status.has_key? :created
|
20
|
+
status[:updated] = DateTime.parse(status[:updated]) if status.has_key? :updated
|
21
|
+
status
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'libis/workflow/base/job'
|
2
|
+
require 'libis/workflow/activerecord/base'
|
3
|
+
|
4
|
+
module Libis
|
5
|
+
module Workflow
|
6
|
+
module ActiveRecord
|
7
|
+
|
8
|
+
class Job < ::ActiveRecord::Base
|
9
|
+
include ::Libis::Workflow::Base::Job
|
10
|
+
include ::Libis::Workflow::ActiveRecord::Base
|
11
|
+
|
12
|
+
# noinspection RailsParamDefResolve
|
13
|
+
has_many :runs, -> {order 'created_at asc'},
|
14
|
+
class_name: Libis::Workflow::ActiveRecord::Run.to_s,
|
15
|
+
foreign_key: :job_id,
|
16
|
+
autosave: true
|
17
|
+
# noinspection RailsParamDefResolve
|
18
|
+
belongs_to :workflow, class_name: Libis::Workflow::ActiveRecord::Workflow.to_s,
|
19
|
+
autosave: true
|
20
|
+
|
21
|
+
# index({workflow_id: 1, workflow_type: 1, name: 1}, {name: 'by_workflow'})
|
22
|
+
|
23
|
+
def self.from_hash(hash)
|
24
|
+
self.create_from_hash(hash, [:name]) do |item, cfg|
|
25
|
+
item.workflow = Libis::Workflow::ActiveRecord::Workflow.from_hash(name: cfg.delete('workflow'))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def logger
|
30
|
+
# noinspection RubyResolve
|
31
|
+
return ::Libis::Workflow::ActiveRecord::Config.logger unless self.log_to_file
|
32
|
+
logger = ::Logging::Repository.instance[self.name]
|
33
|
+
return logger if logger
|
34
|
+
unless ::Logging::Appenders[self.name]
|
35
|
+
::Logging::Appenders::RollingFile.new(
|
36
|
+
self.name,
|
37
|
+
filename: File.join(::Libis::Workflow::ActiveRecord::Config[:log_dir], "#{self.name}.{{%Y%m%d}}.log"),
|
38
|
+
layout: ::Libis::Workflow::ActiveRecord::Config.get_log_formatter,
|
39
|
+
truncate: true,
|
40
|
+
age: self.log_age,
|
41
|
+
keep: self.log_keep,
|
42
|
+
roll_by: 'date',
|
43
|
+
level: self.log_level
|
44
|
+
)
|
45
|
+
end
|
46
|
+
logger = ::Libis::Workflow::ActiveRecord::Config.logger(self.name, self.name)
|
47
|
+
logger.additive = false
|
48
|
+
logger
|
49
|
+
end
|
50
|
+
|
51
|
+
# noinspection RubyStringKeysInHashInspection
|
52
|
+
def execute(opts = {})
|
53
|
+
opts['run_config'] ||= {}
|
54
|
+
if self.log_each_run
|
55
|
+
opts['run_config']['log_to_file'] = true
|
56
|
+
opts['run_config']['log_level'] = self.log_level
|
57
|
+
end
|
58
|
+
if (run_name = opts.delete('run_name'))
|
59
|
+
opts['run_config']['run_name'] = run_name
|
60
|
+
end
|
61
|
+
super opts
|
62
|
+
end
|
63
|
+
|
64
|
+
# def create_run_object
|
65
|
+
# # noinspection RubyResolve
|
66
|
+
# self.runs.build
|
67
|
+
# end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'libis/workflow/base/run'
|
3
|
+
|
4
|
+
module Libis
|
5
|
+
module Workflow
|
6
|
+
module ActiveRecord
|
7
|
+
|
8
|
+
class Run < Libis::Workflow::ActiveRecord::WorkItem
|
9
|
+
|
10
|
+
include ::Libis::Workflow::Base::Run
|
11
|
+
|
12
|
+
property_field :start_date, type: Time, default: lambda {Time.now}
|
13
|
+
property_field :log_to_file, type: TrueClass, default: false
|
14
|
+
property_field :log_level, default: 'INFO'
|
15
|
+
property_field :log_filename
|
16
|
+
property_field :run_name
|
17
|
+
|
18
|
+
# noinspection RailsParamDefResolve
|
19
|
+
belongs_to :job, class_name: Libis::Workflow::ActiveRecord::Job.to_s,
|
20
|
+
autosave: true
|
21
|
+
|
22
|
+
set_callback(:destroy, :before) do |run|
|
23
|
+
run.rm_workdir
|
24
|
+
run.rm_log
|
25
|
+
end
|
26
|
+
|
27
|
+
def rm_log
|
28
|
+
# noinspection RubyResolve
|
29
|
+
log_file = self.log_filename
|
30
|
+
FileUtils.rm(log_file) if log_file && !log_file.blank? && File.exist?(log_file)
|
31
|
+
end
|
32
|
+
|
33
|
+
def rm_workdir
|
34
|
+
workdir = self.work_dir
|
35
|
+
FileUtils.rmtree workdir if workdir && !workdir.blank? && Dir.exist?(workdir)
|
36
|
+
end
|
37
|
+
|
38
|
+
def work_dir
|
39
|
+
# noinspection RubyResolve
|
40
|
+
dir = File.join(Libis::Workflow::Config.workdir, self.id.to_s)
|
41
|
+
FileUtils.mkpath dir unless Dir.exist?(dir)
|
42
|
+
dir
|
43
|
+
end
|
44
|
+
|
45
|
+
def run(action = :run)
|
46
|
+
self.start_date = Time.now
|
47
|
+
self.tasks = []
|
48
|
+
super action
|
49
|
+
close_logger
|
50
|
+
end
|
51
|
+
|
52
|
+
def logger
|
53
|
+
# noinspection RubyResolve
|
54
|
+
unless self.log_to_file
|
55
|
+
return self.job.logger
|
56
|
+
end
|
57
|
+
logger = ::Logging::Repository.instance[self.name]
|
58
|
+
return logger if logger
|
59
|
+
unless ::Logging::Appenders[self.name]
|
60
|
+
# noinspection RubyResolve
|
61
|
+
self.log_filename ||= File.join(::Libis::Workflow::ActiveRecord::Config[:log_dir], "#{self.name}-#{self.id}.log")
|
62
|
+
# noinspection RubyResolve
|
63
|
+
::Logging::Appenders::File.new(
|
64
|
+
self.name,
|
65
|
+
filename: self.log_filename,
|
66
|
+
layout: ::Libis::Workflow::ActiveRecord::Config.get_log_formatter,
|
67
|
+
level: self.log_level
|
68
|
+
)
|
69
|
+
end
|
70
|
+
logger = ::Libis::Workflow::ActiveRecord::Config.logger(self.name, self.name)
|
71
|
+
logger.additive = false
|
72
|
+
# noinspection RubyResolve
|
73
|
+
logger.level = self.log_level
|
74
|
+
logger
|
75
|
+
end
|
76
|
+
|
77
|
+
def close_logger
|
78
|
+
# noinspection RubyResolve
|
79
|
+
return unless self.log_to_file
|
80
|
+
::Logging::Appenders[self.name].close
|
81
|
+
::Logging::Appenders.remove(self.name)
|
82
|
+
::Logging::Repository.instance.delete(self.name)
|
83
|
+
end
|
84
|
+
|
85
|
+
def name
|
86
|
+
parts = [self.job.name]
|
87
|
+
parts << self.run_name unless self.run_name.blank?
|
88
|
+
parts << self.created_at.strftime('%Y%m%d-%H%M%S')
|
89
|
+
parts << self.id.to_s if self.run_name.blank?
|
90
|
+
parts.join('-')
|
91
|
+
rescue
|
92
|
+
self.id.to_s
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
2
|
+
require 'libis-workflow-activerecord'
|
3
|
+
|
4
|
+
require_relative 'helpers/hash_serializer'
|
5
|
+
require_relative 'helpers/status_serializer'
|
6
|
+
require_relative 'helpers/property_helper'
|
7
|
+
|
8
|
+
module Libis
|
9
|
+
module Workflow
|
10
|
+
module ActiveRecord
|
11
|
+
|
12
|
+
class WorkItem < ::ActiveRecord::Base
|
13
|
+
include Libis::Workflow::Base::WorkItem
|
14
|
+
include Libis::Workflow::ActiveRecord::Base
|
15
|
+
include Libis::Workflow::ActiveRecord::Helpers::PropertyHelper
|
16
|
+
|
17
|
+
self.table_name = 'work_items'
|
18
|
+
# noinspection RubyArgCount
|
19
|
+
serialize :options, Libis::Workflow::ActiveRecord::Helpers::HashSerializer
|
20
|
+
# noinspection RubyArgCount
|
21
|
+
serialize :properties, Libis::Workflow::ActiveRecord::Helpers::HashSerializer
|
22
|
+
# noinspection RubyArgCount
|
23
|
+
serialize :status_log, Libis::Workflow::ActiveRecord::Helpers::StatusSerializer
|
24
|
+
|
25
|
+
# noinspection RailsParamDefResolve
|
26
|
+
has_many :items,
|
27
|
+
-> {order('id')},
|
28
|
+
class_name: Libis::Workflow::ActiveRecord::WorkItem.to_s,
|
29
|
+
foreign_key: :parent_id,
|
30
|
+
autosave: true
|
31
|
+
|
32
|
+
# noinspection RailsParamDefResolve
|
33
|
+
belongs_to :parent, class_name: Libis::Workflow::ActiveRecord::WorkItem.to_s,
|
34
|
+
inverse_of: :items,
|
35
|
+
autosave: true
|
36
|
+
|
37
|
+
def add_item(item)
|
38
|
+
raise Libis::WorkflowError, 'Trying to add item already linked to another item' unless item.parent.nil?
|
39
|
+
super(item)
|
40
|
+
end
|
41
|
+
|
42
|
+
def duplicate
|
43
|
+
new_item = self.class.new
|
44
|
+
new_item.properties = {}.with_indifferent_access
|
45
|
+
self.properties.each {|k, v| new_item.properties[k.to_sym] = v.dup}
|
46
|
+
new_item.options = {}.with_indifferent_access
|
47
|
+
self.options.each {|k, v| new_item.options[k.to_sym] = v.dup}
|
48
|
+
new_item.status_log = []
|
49
|
+
yield new_item if block_given?
|
50
|
+
new_item
|
51
|
+
end
|
52
|
+
|
53
|
+
def copy_item(item, &block)
|
54
|
+
new_item = item.duplicate &block
|
55
|
+
new_item.parent = nil
|
56
|
+
add_item new_item
|
57
|
+
item.items.each {|i| new_item.copy_item(i)}
|
58
|
+
new_item
|
59
|
+
end
|
60
|
+
|
61
|
+
def move_item(item)
|
62
|
+
old_parent = item.parent
|
63
|
+
item.parent = self
|
64
|
+
item.save
|
65
|
+
old_parent.items.reset
|
66
|
+
self.items.reset
|
67
|
+
item
|
68
|
+
end
|
69
|
+
|
70
|
+
def get_items
|
71
|
+
self.items
|
72
|
+
end
|
73
|
+
|
74
|
+
def get_item_list
|
75
|
+
self.items.to_a
|
76
|
+
end
|
77
|
+
|
78
|
+
protected
|
79
|
+
|
80
|
+
def add_status_log(info)
|
81
|
+
# noinspection RubyResolve
|
82
|
+
self.status_log << info.with_indifferent_access
|
83
|
+
self.status_log.last
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|