libis-workflow 2.0.beta.3
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/.coveralls.yml +2 -0
- data/.gitignore +36 -0
- data/.travis.yml +33 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +296 -0
- data/Rakefile +7 -0
- data/lib/libis/exceptions.rb +8 -0
- data/lib/libis/workflow/base/logger.rb +30 -0
- data/lib/libis/workflow/base/run.rb +68 -0
- data/lib/libis/workflow/base/workflow.rb +123 -0
- data/lib/libis/workflow/config.rb +92 -0
- data/lib/libis/workflow/message_registry.rb +32 -0
- data/lib/libis/workflow/run.rb +27 -0
- data/lib/libis/workflow/task.rb +259 -0
- data/lib/libis/workflow/tasks/analyzer.rb +41 -0
- data/lib/libis/workflow/version.rb +7 -0
- data/lib/libis/workflow/worker.rb +42 -0
- data/lib/libis/workflow/workflow.rb +29 -0
- data/lib/libis/workflow/workitems/dir_item.rb +12 -0
- data/lib/libis/workflow/workitems/file_item.rb +78 -0
- data/lib/libis/workflow/workitems/work_item.rb +231 -0
- data/lib/libis/workflow/workitems.rb +5 -0
- data/lib/libis/workflow.rb +28 -0
- data/lib/libis-workflow.rb +2 -0
- data/libis-workflow.gemspec +36 -0
- data/spec/items/test_dir_item.rb +16 -0
- data/spec/items/test_file_item.rb +19 -0
- data/spec/items/test_run.rb +10 -0
- data/spec/items.rb +3 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/task_spec.rb +17 -0
- data/spec/tasks/camelize_name.rb +13 -0
- data/spec/tasks/checksum_tester.rb +33 -0
- data/spec/tasks/collect_files.rb +48 -0
- data/spec/workflow_spec.rb +231 -0
- metadata +187 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9790f9c81c7096dd8871ffd8d586a233543e68d9
|
4
|
+
data.tar.gz: 605daffb9699e2deb3da97b921be71182ef5d65a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4ea5a60fb96c162c8a9c138613bf057cfac85ecde4d4dc42a5ea28833b98480ed1113ecf121fb28c3c5fe63f47813e4e13185dee7c406c1fb08246a307cc5cb7
|
7
|
+
data.tar.gz: 2cfe51aaa9e05dde6d6b5cba4aee591e614121d04f1121710f369787baffae0c326903c6b308a57c2b46058a8ebd780546499cba4b5c5bcf93aecf457b537ea8
|
data/.coveralls.yml
ADDED
data/.gitignore
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/test/tmp/
|
9
|
+
/test/version_tmp/
|
10
|
+
/tmp/
|
11
|
+
|
12
|
+
## Specific to RubyMotion:
|
13
|
+
.dat*
|
14
|
+
.repl_history
|
15
|
+
build/
|
16
|
+
|
17
|
+
## Documentation cache and generated files:
|
18
|
+
/.yardoc/
|
19
|
+
/_yardoc/
|
20
|
+
/doc/
|
21
|
+
/rdoc/
|
22
|
+
|
23
|
+
## Environment normalisation:
|
24
|
+
/.bundle/
|
25
|
+
/lib/bundler/man/
|
26
|
+
|
27
|
+
# for a library or gem, you might want to ignore these files since the code is
|
28
|
+
# intended to run in multiple environments; otherwise, check them in:
|
29
|
+
Gemfile.lock
|
30
|
+
.ruby-version
|
31
|
+
.ruby-gemset
|
32
|
+
|
33
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
34
|
+
.rvmrc
|
35
|
+
|
36
|
+
.idea/
|
data/.travis.yml
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
language: ruby
|
2
|
+
cache: bundler
|
3
|
+
rvm:
|
4
|
+
- 1.9.3
|
5
|
+
- 2.1.0
|
6
|
+
- 2.2.0
|
7
|
+
- ruby-head
|
8
|
+
- jruby-19mode
|
9
|
+
jdk:
|
10
|
+
- openjdk7
|
11
|
+
- oraclejdk7
|
12
|
+
- oraclejdk8
|
13
|
+
matrix:
|
14
|
+
exclude:
|
15
|
+
- rvm: 1.9.3
|
16
|
+
jdk: oraclejdk7
|
17
|
+
- rvm: 1.9.3
|
18
|
+
jdk: oraclejdk8
|
19
|
+
- rvm: 2.1.0
|
20
|
+
jdk: oraclejdk7
|
21
|
+
- rvm: 2.1.0
|
22
|
+
jdk: oraclejdk8
|
23
|
+
- rvm: 2.2.0
|
24
|
+
jdk: oraclejdk7
|
25
|
+
- rvm: 2.2.0
|
26
|
+
jdk: oraclejdk8
|
27
|
+
- rvm: ruby-head
|
28
|
+
jdk: oraclejdk7
|
29
|
+
- rvm: ruby-head
|
30
|
+
jdk: oraclejdk8
|
31
|
+
branches:
|
32
|
+
only:
|
33
|
+
- master
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 LIBIS
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,296 @@
|
|
1
|
+
|
2
|
+
[](https://travis-ci.org/Kris-LIBIS/workflow)
|
3
|
+
[](https://coveralls.io/r/Kris-LIBIS/workflow)
|
4
|
+
|
5
|
+
# LIBIS Workflow
|
6
|
+
|
7
|
+
LIBIS Workflow framework
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem 'libis-workflow'
|
15
|
+
```
|
16
|
+
|
17
|
+
|
18
|
+
And then execute:
|
19
|
+
|
20
|
+
$ bundle
|
21
|
+
|
22
|
+
Or install it yourself as:
|
23
|
+
|
24
|
+
$ gem install 'libis-workflow'
|
25
|
+
|
26
|
+
## Architecture
|
27
|
+
|
28
|
+
This gem is essentially a simple, custom workflow system. The core of the workflow are the tasks. You can - and should -
|
29
|
+
create your own tasks by creating new classes and include ::Libis::Workflow::Task. The ::Libis::Workflow::Task module
|
30
|
+
and the included ::Libis::Workflow::Base::Logger module provide the necessary attributes and methods to make them work
|
31
|
+
in the workflow. See the detailed documentation for the modules for more information.
|
32
|
+
|
33
|
+
The objects that the tasks will be working on should include the ::Libis::Workflow::WorkItem module.
|
34
|
+
When working with file objects the module ::Libis::Workflow::FileItem and/or ::Libis::Workflow::DirItem modules should
|
35
|
+
be included for additional file-specific functionality.
|
36
|
+
Work items can be organized in different types and a hierarchical structure.
|
37
|
+
|
38
|
+
All the tasks will be organized into a ::Libis::Workflow::WorkflowDefinition which will be able to execute the tasks in
|
39
|
+
proper order on all the WorkItems supplied/collected. Each task can be implemented with code to run or simply contain a
|
40
|
+
list of child tasks.
|
41
|
+
|
42
|
+
Two tasks are predefined:
|
43
|
+
::Libis::Workflow::Tasks::VirusChecker - runs a virus check on each WorkItem that is also a FileItem.
|
44
|
+
::Libis::Workflow::Tasks::Analyzer - analyzes the workflow run and summarizes the results. It is always included as the
|
45
|
+
last task by the workflow unless you supply a closing task called 'Analyzer' yourself.
|
46
|
+
|
47
|
+
The whole ingester workflow is configured by a Singleton object ::Libis::Workflow::Config which contains settings for
|
48
|
+
logging, paths where tasks and workitems can be found and the path to the virus scanner program.
|
49
|
+
|
50
|
+
## Usage
|
51
|
+
|
52
|
+
You should start by including the following line in your source code:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
require 'libis-workflow'
|
56
|
+
```
|
57
|
+
|
58
|
+
This will load all of the Libis Workflow framework into your environment, but including only the required parts is OK as
|
59
|
+
well. This is shown in the examples below.
|
60
|
+
|
61
|
+
### Workflows
|
62
|
+
|
63
|
+
A ::Libis::Workflow::WorkflowDefinition instance contains the definition of a workflow. Once instantiated, it can be run
|
64
|
+
by calling the 'run' method. This will create a ::Libis::Workflow::WorkflowRun instance, configure it and call the 'run'
|
65
|
+
method on it. The Workflow constructor takes no arguments, but is should be configured by calling the 'set_config'
|
66
|
+
method with the workflow configuration as an argument. The 'run' method takes an option Hash as argument.
|
67
|
+
|
68
|
+
#### Workflow configuration
|
69
|
+
|
70
|
+
A workflow configuration is a Hash with:
|
71
|
+
* tasks: Array of task descriptions
|
72
|
+
* start_object: String with class name of the starting object to be created. An istance of this class will be created
|
73
|
+
for each run and serves as the root work item for that particular run.
|
74
|
+
* input: Hash with input variable definitions
|
75
|
+
|
76
|
+
##### Task description
|
77
|
+
|
78
|
+
is a Hash with:
|
79
|
+
* class: String with class name of the task
|
80
|
+
* name: String with the name of the task
|
81
|
+
* tasks: Array with task definitions of sub-tasks
|
82
|
+
* options: Hash with additional task configuration options (see 'Tasks - Configuration' for more info)
|
83
|
+
|
84
|
+
If 'class' is not present, the default '::Libis::Workflow::Task' with the given name will be instantiated, which simply
|
85
|
+
iterates over the child items of the given work item and performs each sub-task on each of the child items. If a 'class'
|
86
|
+
value is given, an instance of that class will be created and the task will be handed the work item to process on. See
|
87
|
+
the chapter on 'Tasks' below for more information on tasks.
|
88
|
+
|
89
|
+
##### Input variable definition
|
90
|
+
|
91
|
+
The key of the input Hash is the unique id of the variable. The value is a Hash with:
|
92
|
+
* name: String with the name of the input variable
|
93
|
+
This value is used for display only
|
94
|
+
* description: String with descriptive text explaining the use/meaning of the variable
|
95
|
+
* type: String with the type of the variable
|
96
|
+
Currently only 'String', 'Time' and 'Boolean' are supported. If the value is not present, 'String' is asumed.
|
97
|
+
* default: String with the default value
|
98
|
+
If the default value contains the string %s, it will be replaced with the current time in the format yymmddHHMMSS when
|
99
|
+
the workflow is started. For boolean values, 'true', 'yes', 't', 'y' and 1 are all interpreted as boolean true.
|
100
|
+
|
101
|
+
All of these Hash keys are optional. Each input variable key and value will be added to the root work item's option Hash.
|
102
|
+
|
103
|
+
#### Options
|
104
|
+
|
105
|
+
The option Hash contains special run-time configuration parameters for the workflow:
|
106
|
+
* action: String with the action that should be taken. Currently only 'start' is supported. In the future support for
|
107
|
+
'restart' and 'continue' will be added.
|
108
|
+
* interactive: Boolean that indicates if the user should be queried to input values for variables that have no value set.
|
109
|
+
This will pause the workflow run and is therefore not compatible with scheduling the workflow. For unattended runs the
|
110
|
+
options should be set to false, causing the run to throw an exception if an input variable is missing a value.
|
111
|
+
|
112
|
+
Remaining values are considered to be (default) values for the input variables.
|
113
|
+
|
114
|
+
#### Run-time configuration
|
115
|
+
|
116
|
+
The 'run' method takes an optional Hash as argument which will complement and override the options Hash described in the
|
117
|
+
previous chapter.
|
118
|
+
|
119
|
+
Once the workflow is configured and the root work item instantiated, the method will run each top-level task on the root
|
120
|
+
work item in sequence until all tasks have completed successfully or a task has failed.
|
121
|
+
|
122
|
+
### Work items
|
123
|
+
|
124
|
+
Creating your own work items is highly recommended and is fairly easy:
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
|
128
|
+
require 'libis/workflow/workitems'
|
129
|
+
|
130
|
+
class MyWorkItem < ::Libis::Workflow::WorkItem
|
131
|
+
attr_accesor :name
|
132
|
+
|
133
|
+
def initialize
|
134
|
+
@name = 'My work item'
|
135
|
+
super # Note: this is important as the base class requires some initialization
|
136
|
+
end
|
137
|
+
end
|
138
|
+
```
|
139
|
+
|
140
|
+
Work items that are file-based should also include the ::Libis::Workflow::FileItem module:
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
|
144
|
+
require 'libis/workflow/workitems'
|
145
|
+
|
146
|
+
class MyFileItem < ::Libis::Workflow::WorkItem
|
147
|
+
include ::Libis::Workflow::FileItem
|
148
|
+
|
149
|
+
def initialize(file)
|
150
|
+
filename = file
|
151
|
+
super
|
152
|
+
end
|
153
|
+
|
154
|
+
def filesize
|
155
|
+
properties[:size]
|
156
|
+
end
|
157
|
+
|
158
|
+
def fixity_check(checksum)
|
159
|
+
properties[:checksum] == checksum
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
```
|
164
|
+
|
165
|
+
## Tasks
|
166
|
+
|
167
|
+
Tasks should inherit from ::Libis::Workflow::Task and specify the actions it wants to
|
168
|
+
perform on each work item:
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
|
172
|
+
class MyTask < ::Libis::Workflow::Task
|
173
|
+
|
174
|
+
def process_item(item)
|
175
|
+
item.perform_my_action
|
176
|
+
rescue Exception => e
|
177
|
+
item.set_status(to_status(:failed))
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
181
|
+
```
|
182
|
+
|
183
|
+
You have some options to specify the actions:
|
184
|
+
|
185
|
+
### Performing an action on each child item of the provided work item
|
186
|
+
|
187
|
+
In that case the task should provide a 'process_item' method as above. Each child item will be passed as the argument
|
188
|
+
to the method and perform whatever needs to be done on the item.
|
189
|
+
|
190
|
+
If the action fails the method is expected to set the item status field to failed. This is also shown in the previous
|
191
|
+
example. If the error is so severe that no other child items should be processed, the action can decide to throw an
|
192
|
+
exception, preferably a ::Libis::Workflow::Exception or a child exception thereof.
|
193
|
+
|
194
|
+
### Performing an action on the provided work item
|
195
|
+
|
196
|
+
If the task wants to perform an action on the work item directly, it should define a 'process' method. The work item is
|
197
|
+
available to the method as class instance variable 'workitem'. Again the method is responsible to communicate errors
|
198
|
+
with a failed status or by throwing an exception.
|
199
|
+
|
200
|
+
### Combining both
|
201
|
+
|
202
|
+
It is possible to perform some action on the parent work item first and then process each child item. Processing the
|
203
|
+
child items should be done in process_item as usual, but processing the parent item can be done either by defining a
|
204
|
+
pre_process method or a process method that ends with a 'super' call. Using this should be an exception as it is
|
205
|
+
recommended to create a seperate task to process the child work items.
|
206
|
+
|
207
|
+
### Default behaviour
|
208
|
+
|
209
|
+
The default implementation of 'process' is to call 'pre_process' and then call 'process_item' on each child item.
|
210
|
+
|
211
|
+
The default implementation for 'process_item' is to run each child task for each given child item. This will raise an
|
212
|
+
exception unless the workflow has defined some sub-tasks for this task. This means that in the workflow definition tree
|
213
|
+
each leaf task should either implement it's own 'process_item' method or override the 'process' method. Only non-leaf
|
214
|
+
nodes in the workflow definition tree are allowed to use the default implementation (by defining only 'name' and 'tasks'
|
215
|
+
value). See above on 'Workflow configuration' for more info.
|
216
|
+
|
217
|
+
### Configuration
|
218
|
+
|
219
|
+
The task takes some options that determine how the task will be handling special cases. The options should be passed to
|
220
|
+
the Task constructor as part of the initialization. The workflow configuration will take care of that.
|
221
|
+
|
222
|
+
* quiet: Boolean - default: false
|
223
|
+
* always_run: Boolean - default: false
|
224
|
+
* items_first: Boolean - default: false
|
225
|
+
|
226
|
+
The quiet option surpresses all logging for this task.
|
227
|
+
|
228
|
+
When the option always_run is set, the task will run even when a previous task failed to run on the item before. Note
|
229
|
+
that successfully running such a task will unmark the item as failed. The status history of the item will show which
|
230
|
+
tasks failed. Only use this option if you are sure the task will fully recover if the previous tasks failed or did not
|
231
|
+
run due to a previous failure.
|
232
|
+
|
233
|
+
The items_fist option determines the processing order. If a task has multiple subtasks and the given workitem has
|
234
|
+
multiple subitems, setting the items_first option will cause it to take the first subitem, run the first subtask on it,
|
235
|
+
then the second subtask and so on. Next it will run the first, second, ... subtask on the second subitem and so on. If
|
236
|
+
the option is not set or set to false, the first subtask will run on each subitem, then the second subtask on each
|
237
|
+
subitem, and so on.
|
238
|
+
|
239
|
+
### Convenience functions
|
240
|
+
|
241
|
+
#### get_root_item()
|
242
|
+
|
243
|
+
Returns the work item that the workflow started with (and is the root/grand parent of all work items in the ingest run).
|
244
|
+
|
245
|
+
#### get_work_dir()
|
246
|
+
|
247
|
+
Returns the work directory as configured for the current ingest run. The work directory can be used as scrap directory
|
248
|
+
for creating derived files that can be added as work items to the current flow or for downloading files that will be
|
249
|
+
processed later. The work directory is not automaticaly cleaned up, which is considered a task for the workflow implementation.
|
250
|
+
|
251
|
+
#### capture_cmd(cmd, *args)
|
252
|
+
|
253
|
+
Allows the task to run an external command-line program and capture it's stdout and stderr output at the same time. The
|
254
|
+
first argument is mandatory and should be the command-line program that has to be executed. An arbitrary number of
|
255
|
+
command-line arguments may follow.
|
256
|
+
|
257
|
+
The return value is an array with three elements: the status code returned by the command, the stdout string and the
|
258
|
+
stderr string.
|
259
|
+
|
260
|
+
#### names()
|
261
|
+
|
262
|
+
An array of strings with the hierarchical path of tasks leading to the current task. Can be usefull for log messages.
|
263
|
+
|
264
|
+
#### (debug/info/warn/error/fatal)(message, *args)
|
265
|
+
|
266
|
+
Convenience function for creating log entries. The logger set in ::Libis::Workflow::Config is used to dump log messages.
|
267
|
+
|
268
|
+
The first argument is mandatory and can be:
|
269
|
+
* an integer. The integer is used to look up the message text in ::Libis::Workflow::MessageRegistry.
|
270
|
+
* a static string. The message text is used as-is.
|
271
|
+
* a string with placement holders as used in String#%. Args can either be an array or a hash. See also Kernel#sprintf.
|
272
|
+
|
273
|
+
The log message is logged to the general logging and attached to the current work item (workitem) unless another
|
274
|
+
work item is passed as first argument after the message.
|
275
|
+
|
276
|
+
#### check_item_type(klass, item = nil)
|
277
|
+
|
278
|
+
Checks if the work item is of the given class. 'workitem' is checked if the item argument is not present. If the check
|
279
|
+
fails a Runtime exception is thrown which will cause the task to abort if not catched.
|
280
|
+
|
281
|
+
#### item_type?(klass, item = nil)
|
282
|
+
|
283
|
+
A less severe variant version of check_item_type which returns a boolean (false if failed).
|
284
|
+
|
285
|
+
#### to_status(status)
|
286
|
+
|
287
|
+
Simply prepends the status text with the current task name. The output of this function is typically what the work item
|
288
|
+
status field should be set at.
|
289
|
+
|
290
|
+
## Contributing
|
291
|
+
|
292
|
+
1. Fork it ( https://github.com/libis/workflow/fork )
|
293
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
294
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
295
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
296
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'libis/tools/logger'
|
2
|
+
|
3
|
+
module Libis
|
4
|
+
module Workflow
|
5
|
+
module Base
|
6
|
+
module Logger
|
7
|
+
include ::Libis::Tools::Logger
|
8
|
+
|
9
|
+
def message(severity, msg, *args)
|
10
|
+
item = self.workitem
|
11
|
+
item = args.shift if args.size > 0 and args[0].is_a?(WorkItem)
|
12
|
+
|
13
|
+
item.log_message(severity, to_msg(msg), *args) if item
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_msg(msg)
|
17
|
+
case msg
|
18
|
+
when String
|
19
|
+
{text: msg}
|
20
|
+
when Integer
|
21
|
+
{id: msg}
|
22
|
+
else
|
23
|
+
{text: (msg.to_s rescue '')}
|
24
|
+
end.merge task: self.namepath
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
require 'libis/workflow/workitems/work_item'
|
6
|
+
|
7
|
+
module Libis
|
8
|
+
module Workflow
|
9
|
+
module Base
|
10
|
+
module Run
|
11
|
+
include ::Libis::Workflow::WorkItem
|
12
|
+
|
13
|
+
def start_date; raise RuntimeError.new "Method not implemented: #{caller[0]}"; end
|
14
|
+
def start_date=(_); raise RuntimeError.new "Method not implemented: #{caller[0]}"; end
|
15
|
+
|
16
|
+
def tasks; raise RuntimeError.new "Method not implemented: #{caller[0]}"; end
|
17
|
+
def tasks=(_); raise RuntimeError.new "Method not implemented: #{caller[0]}"; end
|
18
|
+
|
19
|
+
def workflow; raise RuntimeError.new "Method not implemented: #{caller[0]}"; end
|
20
|
+
|
21
|
+
def work_dir
|
22
|
+
dir = File.join(Config.workdir, self.name)
|
23
|
+
FileUtils.mkpath dir unless Dir.exist?(dir)
|
24
|
+
dir
|
25
|
+
end
|
26
|
+
|
27
|
+
def name
|
28
|
+
self.workflow.run_name(self.start_date)
|
29
|
+
end
|
30
|
+
|
31
|
+
def names
|
32
|
+
Array.new
|
33
|
+
end
|
34
|
+
|
35
|
+
def namepath
|
36
|
+
self.name
|
37
|
+
end
|
38
|
+
|
39
|
+
def run(opts = {})
|
40
|
+
|
41
|
+
self.start_date = Time.now
|
42
|
+
|
43
|
+
self.options = workflow.prepare_input(self.options.merge(opts))
|
44
|
+
|
45
|
+
self.tasks = self.workflow.tasks(self)
|
46
|
+
configure_tasks self.options
|
47
|
+
|
48
|
+
self.status = :STARTED
|
49
|
+
|
50
|
+
self.tasks.each do |task|
|
51
|
+
next if self.failed? and not task.options[:allways_run]
|
52
|
+
task.run self
|
53
|
+
end
|
54
|
+
|
55
|
+
self.status = :DONE unless self.failed?
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
protected
|
60
|
+
|
61
|
+
def configure_tasks(opts)
|
62
|
+
self.tasks.each { |task| task.apply_options opts }
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'libis/tools/parameter'
|
4
|
+
|
5
|
+
module Libis
|
6
|
+
module Workflow
|
7
|
+
module Base
|
8
|
+
module Workflow
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def require_all
|
12
|
+
Config.require_all(File.join(File.dirname(__FILE__), '..', 'tasks'))
|
13
|
+
Config.require_all(Config.taskdir)
|
14
|
+
Config.require_all(Config.itemdir)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.included(base)
|
19
|
+
base.extend ClassMethods
|
20
|
+
end
|
21
|
+
|
22
|
+
def name; raise RuntimeError.new "Method not implemented: #{caller[0]}"; end
|
23
|
+
def name=(_) ; raise RuntimeError.new "Method not implemented: #{caller[0]}"; end
|
24
|
+
|
25
|
+
def description; raise RuntimeError.new "Method not implemented: #{caller[0]}"; end
|
26
|
+
def description=(_); raise RuntimeError.new "Method not implemented: #{caller[0]}"; end
|
27
|
+
|
28
|
+
def config; raise RuntimeError.new "Method not implemented: #{caller[0]}"; end
|
29
|
+
def config=(_); raise RuntimeError.new "Method not implemented: #{caller[0]}"; end
|
30
|
+
|
31
|
+
def configure(cfg)
|
32
|
+
self.config.merge! input: {}, tasks: []
|
33
|
+
self.config.merge! cfg
|
34
|
+
self.name = self.config.delete(:name) || self.class.name
|
35
|
+
self.description = self.config.delete(:description) || ''
|
36
|
+
|
37
|
+
self.class.require_all
|
38
|
+
|
39
|
+
unless self.config[:tasks].last[:class] && self.config[:tasks].last[:class].split('::').last == 'Analyzer'
|
40
|
+
self.config[:tasks] << {class: '::Libis::Workflow::Tasks::Analyzer'}
|
41
|
+
end
|
42
|
+
|
43
|
+
self.config
|
44
|
+
end
|
45
|
+
|
46
|
+
def input
|
47
|
+
self.config[:input].inject({}) do |hash, input_def|
|
48
|
+
parameter = ::Libis::Tools::Parameter.new input_def.first.to_sym
|
49
|
+
input_def.last.each { |k, v| parameter[k] = v}
|
50
|
+
hash[input_def.first.to_sym] = parameter
|
51
|
+
hash
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def run_name(timestamp = Time.now)
|
56
|
+
"#{self.workflow.name}-#{timestamp.strftime('%Y%m%d%H%M%S')}"
|
57
|
+
end
|
58
|
+
|
59
|
+
def perform(opts = {})
|
60
|
+
self.run opts
|
61
|
+
end
|
62
|
+
|
63
|
+
def create_run_object
|
64
|
+
self.config[:run_object].constantize.new
|
65
|
+
end
|
66
|
+
|
67
|
+
# @param [Hash] opts
|
68
|
+
def run(opts = {})
|
69
|
+
|
70
|
+
run_object = self.create_run_object
|
71
|
+
raise RuntimeError.new "Could not create instance of run object '#{self.config[:run_object]}'" unless run_object
|
72
|
+
|
73
|
+
run_object.workflow = self
|
74
|
+
run_object.options = opts
|
75
|
+
run_object.save
|
76
|
+
|
77
|
+
run_object.run opts
|
78
|
+
|
79
|
+
run_object
|
80
|
+
end
|
81
|
+
|
82
|
+
# @param [Hash] opts
|
83
|
+
def prepare_input(opts)
|
84
|
+
options = opts.dup
|
85
|
+
self.input.each do |key, parameter|
|
86
|
+
key
|
87
|
+
# provided in opts
|
88
|
+
options[key] = parameter[:default] unless options.has_key? key
|
89
|
+
options[key] = parameter.parse(options[key])
|
90
|
+
propagate_to = []
|
91
|
+
propagate_to = parameter[:propagate_to] if parameter[:propagate_to].is_a? Array
|
92
|
+
propagate_to = [parameter[:propagate_to]] if parameter[:propagate_to].is_a? String
|
93
|
+
propagate_to.each do |target|
|
94
|
+
task_name, param_name = target.split('#')
|
95
|
+
param_name ||= key
|
96
|
+
options[task_name] ||= {}
|
97
|
+
options[task_name][param_name.to_sym] = options[key]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
options
|
101
|
+
end
|
102
|
+
|
103
|
+
def tasks(parent = nil)
|
104
|
+
self.config[:tasks].map do |cfg|
|
105
|
+
instantize_task(parent || self, cfg)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def instantize_task(parent, cfg)
|
110
|
+
task_class = Task
|
111
|
+
task_class = cfg[:class].constantize if cfg[:class]
|
112
|
+
# noinspection RubyArgCount
|
113
|
+
task_instance = task_class.new(parent, cfg)
|
114
|
+
cfg[:tasks].map do |task_cfg|
|
115
|
+
task_instance << instantize_task(task_instance, task_cfg)
|
116
|
+
end rescue nil
|
117
|
+
task_instance
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|