task-orchestrator 0.0.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.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2013 Alexander Piavlo
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Task Orchestrator
2
+
3
+ Simple task orchestration framework driven by Yaml config files
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'task-orchestrator'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install task-orchestrator
18
+
19
+ ## Usage
20
+
21
+ $ orchestrator -h
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ unless $:.include?(File.dirname(__FILE__) + '/../lib/')
4
+ $: << File.dirname(__FILE__) + '/../lib'
5
+ end
6
+
7
+ require 'orchestrator'
8
+
9
+ options = Orchestrator::Cli.parse(ARGV)
10
+ task = Orchestrator::Task.new(options)
11
+ task.run
@@ -0,0 +1,21 @@
1
+ orchestrator:
2
+ statedir: states
3
+ dependencies:
4
+ description: lalalalal
5
+ save: true
6
+ steps:
7
+ - type: parallel
8
+ parallel: 2
9
+ scripts:
10
+ - command: sleep 5
11
+ timeout: 10
12
+ tags: [ zopa, popa ]
13
+ - command: sleep 5
14
+ timeout: 2
15
+ tags: [ kaka, popa ]
16
+ - command: sleep 5
17
+ depends: zopa
18
+ - command: sleep 5
19
+ depends: kaka
20
+ - command: sleep 5
21
+ depends: [ zopa, kaka ]
@@ -0,0 +1,15 @@
1
+ orchestrator:
2
+ statedir: states
3
+ interpolation:
4
+ description: command interpolation test
5
+ save: true
6
+ steps:
7
+ - type: sequential
8
+ scripts:
9
+ - command: "echo :::ARG.name:::"
10
+ - command: "echo :::ARG.arg1:::"
11
+ - command: "echo :::ARG.arg2:::"
12
+ - command: "echo :::ENV.DNS:::"
13
+ - command: "echo :::EXEC.date:::"
14
+ - command: "echo :::EXEC./bin/date +%s:::"
15
+ # - command: "echo :::EXEC./dasdfsd:::"
@@ -0,0 +1,23 @@
1
+ orchestrator:
2
+ statedir: states
3
+ multistep:
4
+ description: multiple steps test
5
+ save: true
6
+ steps:
7
+ - type: parallel
8
+ parallel: 2
9
+ sleep: 0.1
10
+ scripts:
11
+ - command: sleep 5
12
+ - command: echo parallel 1
13
+ - command: echo parallel 2
14
+ - command: echo parallel 3
15
+ - command: echo parallel 4
16
+ - command: echo parallel 5
17
+ - type: sequential
18
+ scripts:
19
+ - command: echo sequential 1
20
+ - command: echo sequential 2
21
+ - command: echo sequential 3
22
+ - command: echo sequential 4
23
+ - command: echo sequential 5
data/examples/parallel ADDED
@@ -0,0 +1,16 @@
1
+ orchestrator:
2
+ statedir: states
3
+ parallel:
4
+ description: parallel step test
5
+ save: true
6
+ steps:
7
+ - type: parallel
8
+ parallel: 2
9
+ sleep: 0.1
10
+ scripts:
11
+ - command: sleep 5
12
+ - command: echo 1
13
+ - command: echo 2
14
+ - command: echo 3
15
+ - command: echo 4
16
+ - command: echo 5
@@ -0,0 +1,13 @@
1
+ orchestrator:
2
+ statedir: states
3
+ sequential:
4
+ description: lalalalal
5
+ save: true
6
+ steps:
7
+ - type: sequential
8
+ scripts:
9
+ - command: echo 1
10
+ - command: echo 2
11
+ - command: echo 3
12
+ - command: echo 4
13
+ - command: echo 5
data/examples/timeouts ADDED
@@ -0,0 +1,40 @@
1
+ orchestrator:
2
+ statedir: states
3
+ timeouts1:
4
+ description: timeouts test 1
5
+ save: true
6
+ steps:
7
+ - type: sequential
8
+ # on_failure: die
9
+ on_failure: ignore
10
+ scripts:
11
+ - command: sleep 1
12
+ timeout: 2
13
+ - command: sleep 5
14
+ timeout: 2
15
+ timeouts2:
16
+ description: timeouts test 2
17
+ save: true
18
+ steps:
19
+ - type: parallel
20
+ parallel: 2
21
+ sleep: 0.1
22
+ # on_failure: die
23
+ # on_failure: wait
24
+ # on_failure: finish
25
+ on_failure: ignore
26
+ scripts:
27
+ - command: sleep 1
28
+ timeout: 2
29
+ - command: sleep 1
30
+ timeout: 2
31
+ - command: sleep 1
32
+ timeout: 2
33
+ - command: sleep 5
34
+ timeout: 2
35
+ - command: sleep 1
36
+ timeout: 2
37
+ - command: sleep 1
38
+ timeout: 2
39
+ - command: sleep 1
40
+ timeout: 2
@@ -0,0 +1,7 @@
1
+ module Orchestrator
2
+ Settings = File.expand_path('.settings')
3
+
4
+ require 'orchestrator/version.rb'
5
+ require 'orchestrator/cli.rb'
6
+ require 'orchestrator/task.rb'
7
+ end
@@ -0,0 +1,67 @@
1
+ require 'formatador'
2
+ require 'optparse'
3
+ require 'ostruct'
4
+
5
+ module Orchestrator
6
+
7
+ class Cli
8
+
9
+ def self.parse(args)
10
+ options = OpenStruct.new
11
+ options.args = Object.new
12
+
13
+ parser = OptionParser.new
14
+ parser.banner = "Usage: #{$0} [options]"
15
+
16
+ options.config = Orchestrator::Settings
17
+ parser.on( '--config [PATH]', 'Path to settings yaml' ) do |config|
18
+ options.config = config
19
+ options.args.instance_variable_set(:@config,config)
20
+ end
21
+
22
+ options.statefile = nil
23
+ options.name = nil
24
+ parser.on( '--name NAME', 'Name of the cron job to run' ) do |name|
25
+ options.name = name
26
+ options.args.instance_variable_set(:@name,name)
27
+ end
28
+
29
+ parser.on( '--statefile PATH', 'Path to state file yaml' ) do |statefile|
30
+ options.statefile = statefile
31
+ options.args.instance_variable_set(:@statefile,statefile)
32
+ end
33
+
34
+ options.reset = false
35
+ parser.on( '--reset', 'Do not use state file if it exists' ) { |reset| options.reset = true }
36
+
37
+ parser.on( '--args ARGS,', 'extra args for interpolation as arg1=val1[,arg2=val2[]]' ) do |a|
38
+ a.split(',').each do |x|
39
+ arg,val = x.split('=')
40
+ options.args.instance_variable_set("@#{arg}".to_sym,val)
41
+ end
42
+ end
43
+
44
+ options.verbose = false
45
+ parser.on( '--verbose') { options.verbose = true }
46
+
47
+ options.email = true
48
+ parser.on( '--no-email') { options.email = false }
49
+
50
+ options.sms = true
51
+ parser.on( '--no-sms') { options.sms = false }
52
+
53
+ parser.on( '-h', '--help', 'Display this screen' ) { puts parser; exit }
54
+
55
+ parser.parse!(args)
56
+
57
+ unless options.name
58
+ puts opts
59
+ exit 1
60
+ end
61
+
62
+ options
63
+ end
64
+
65
+ end
66
+
67
+ end
@@ -0,0 +1,301 @@
1
+ require 'popen4'
2
+ require 'pony'
3
+ require 'yaml'
4
+ require 'timeout'
5
+ require 'formatador'
6
+ require 'fileutils'
7
+
8
+ module Orchestrator
9
+
10
+ class Task
11
+
12
+ def initialize(options)
13
+ @mutex = Mutex.new
14
+ Thread.abort_on_exception = true
15
+ @log = ''
16
+
17
+ @options = options
18
+
19
+ invalid("config file #{config} does not exists") unless File.exist?(@options.config)
20
+
21
+ @settings = YAML.load_file(@options.config)
22
+ invalid("no task job #{@options.name} is defined in settings file") unless @settings['orchestrator'].has_key?(@options.name)
23
+
24
+ invalid("no statedir is defined in settings file") if @settings['orchestrator'][@options.name]['save'] && !@settings['orchestrator'].has_key?('statedir')
25
+ unless @options.statefile
26
+ if @settings['orchestrator'][@options.name]['save']
27
+ @options.statefile = @settings['orchestrator']['statedir'] + "/" + @options.name
28
+ FileUtils.mkdir_p(@settings['orchestrator']['statedir'])
29
+ end
30
+ end
31
+ @state = (@options.statefile && File.exist?(@options.statefile) && !@options.reset) ? YAML.load_file(@options.statefile) : @settings['orchestrator'][@options.name]
32
+
33
+ @options.email = false unless @state.has_key?('email')
34
+ @options.email_on_success = false if @options.email and @state['email'].has_key?('on_success') and not @state['email']['on_success']
35
+
36
+ @options.sms = false unless @state.has_key?('sms')
37
+ @options.sms_on_success = true if @options.sms and @state['sms'].has_key?('on_success') and @state['sms']['on_success']
38
+ end
39
+
40
+ def invalid(reason)
41
+ Formatador.display_line("[red]ERROR[/]: #{reason}")
42
+ exit 1
43
+ end
44
+
45
+ def save_state
46
+ if @options.statefile
47
+ @mutex.synchronize do
48
+ File.open(@options.statefile, "w") {|f| YAML.dump(@state, f)}
49
+ end
50
+ end
51
+ end
52
+
53
+ def interpolate_command(command)
54
+ command.gsub(/:::([^:]*):::/) do
55
+ match = $1
56
+ case match
57
+ when /^ENV\./
58
+ env = match["ENV.".length..-1]
59
+ invalid("command interpolation failed no such env variable - #{env}") unless ENV[env]
60
+ ENV[env]
61
+ when /^ARG\./
62
+ arg = match["ARG.".length..-1]
63
+ invalid("command interpolation failed no such arg - #{arg}") unless @options.args.instance_variable_defined?("@#{arg}".to_sym)
64
+ @options.args.instance_variable_get("@#{arg}".to_sym)
65
+ when /^EXEC\./
66
+ exec = match["EXEC.".length..-1]
67
+ result = nil
68
+ begin
69
+ result = IO.popen(exec)
70
+ rescue
71
+ invalid("command interpolation failed to exec - #{exec}")
72
+ end
73
+ invalid("command interpolation exec exit with non zero status - #{exec}") unless $?.to_i == 0
74
+ result.readline.delete("\n")
75
+ else
76
+ invalid("command interpolation failed not valid parameter - :::#{match}:::")
77
+ end
78
+ end
79
+ end
80
+
81
+ def validate_config
82
+ if @state.has_key?('email')
83
+ invalid("config email recipients is missing or invalid") unless @state['email'].has_key?('recipients') && @state['email']['recipients'].is_a?(String) || @state['email']['recipients'].is_a?(Array)
84
+ invalid("config email from is missing or invalid") unless @state['email'].has_key?('from') && @state['email']['from'].is_a?(String)
85
+ end
86
+ if @state.has_key?('sms')
87
+ invalid("task sms recipients is missing") unless @state['sms'].has_key?('recipients') && @state['sms']['recipients'].is_a?(String) || @state['sms']['recipients'].is_a?(Array)
88
+ invalid("task sms from is missing") unless @state['sms'].has_key?('from') && @state['sms']['from'].is_a?(String)
89
+ end
90
+ invalid("task description is missing or invalid") unless @state.has_key?('description') && @state['description'].is_a?(String)
91
+ invalid("task save must be boolean") if @state.has_key?('save') && !!@state['save'] != @state['save']
92
+ @state['save'] = false unless @state.has_key?('save')
93
+ invalid("task steps is missing") unless @state.has_key?('steps')
94
+ invalid("task steps must be array") unless @state['steps'].is_a?(Array)
95
+ @state['steps'].each do |step|
96
+ invalid("task step is not hash") unless step.is_a?(Hash)
97
+ invalid("task step has no type") unless step.has_key?('type') && step['type'].is_a?(String)
98
+ invalid("task step type #{step['type']} is invalid") unless [:parallel,:sequential].find_index(step['type'].to_sym)
99
+ invalid("task step scripts is missing or invalid") unless step.has_key?('scripts') && step['scripts'].is_a?(Array)
100
+ step['scripts'].each_index do |index|
101
+ if step['scripts'][index].is_a?(String)
102
+ step['scripts'][index] = { 'command' => interpolate_command(step['scripts'][index]) }
103
+ elsif step['scripts'][index].is_a?(Hash)
104
+ invalid("task step script command is invalid") unless step['scripts'][index].has_key?('command') && step['scripts'][index]['command'].is_a?(String)
105
+ step['scripts'][index]['command'] = interpolate_command(step['scripts'][index]['command'])
106
+ else
107
+ invalid("task script is invalid")
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ def fail
114
+ system "#{@state['failure_handler']} #{@state['failure_handler_args']}" if @state.has_key?('failure_handler')
115
+
116
+ Pony.mail(
117
+ :to => @state['email']['recipients'],
118
+ :from => @state['email']['from'],
119
+ :subject => "#{@state['description']} - [FAILED]",
120
+ :body => @log
121
+ ) if @options.email
122
+
123
+ Pony.mail(
124
+ :to => @state['sms']['recipients'],
125
+ :from => @state['sms']['from'],
126
+ :subject => "#{@state['description']} - [FAILED]",
127
+ :body => @state['sms']['auth']
128
+ ) if @options.sms
129
+
130
+ exit 1
131
+ end
132
+
133
+ def notify
134
+ Pony.mail(
135
+ :to => @state['email']['recipients'],
136
+ :from => @state['email']['from'],
137
+ :subject => "#{@state['description']} - [OK]",
138
+ :body => @log
139
+ ) if @options.email and @options.email_on_success
140
+
141
+ Pony.mail(
142
+ :to => @state['sms']['recipients'],
143
+ :from => @state['sms']['from'],
144
+ :subject => "#{@state['description']} - [OK]",
145
+ :body => @state['sms']['auth']
146
+ ) if @options.sms and @options.sms_on_success
147
+ end
148
+
149
+ def run_script(script)
150
+ result = ""
151
+ error = ""
152
+
153
+ timeout = script.has_key?('timeout') ? script['timeout'].to_i : @timeout
154
+
155
+ script['status'] = 'STARTED'
156
+ save_state
157
+
158
+ # start = Time.now
159
+
160
+ begin
161
+ Timeout::timeout(timeout) do
162
+ status = POpen4::popen4(script['command']) do |stdout, stderr, stdin, pid|
163
+ result = stdout.read.strip
164
+ error = stderr.read.strip
165
+ end
166
+ script['status'] = (status.nil? or status.exitstatus != 0) ? 'FAILED' : 'OK'
167
+ end
168
+ rescue Timeout::Error
169
+ script['status'] = 'TIMEOUT'
170
+ end
171
+
172
+ save_state
173
+
174
+ # runtime = Time.now - start
175
+ # runtime = runtime > 60 ? runtime/60 : runtime
176
+
177
+ @mutex.synchronize do
178
+ output = <<-EOF
179
+
180
+ Running: #{script['command']} - #{script['status']}
181
+ ============ STDOUT ============
182
+ #{result}
183
+ ============ STDERR ============
184
+ #{error}
185
+ ================================
186
+ EOF
187
+
188
+ @log += output
189
+ puts output if @options.verbose
190
+ end
191
+
192
+ script['status'] == 'OK'
193
+ end
194
+
195
+ def thread_wrapper(i,script)
196
+ failures = 0
197
+
198
+ loop do
199
+ begin
200
+ @statuses[i] = run_script(script)
201
+ rescue Exception => e
202
+ script['status'] = 'EXCEPTION'
203
+ save_state
204
+ @statuses[i] = false
205
+ @mutex.synchronize do
206
+ output = <<-EOF
207
+
208
+ Thread - (#{script['command']})
209
+ Died due to following exception:
210
+ #{e.inspect}
211
+ #{e.backtrace}
212
+ EOF
213
+ @log += output
214
+ puts output if @options.verbose
215
+ end
216
+ end
217
+
218
+ break if @statuses[i]
219
+
220
+ failures += 1
221
+ break if @retries < failures
222
+ sleep @retry_delay
223
+ end
224
+
225
+ @threads.delete(i)
226
+ fail if @on_failure == :die and not @statuses[i]
227
+ end
228
+
229
+ def run
230
+ validate_config
231
+ save_state
232
+
233
+ @state['steps'].each do |step|
234
+ @statuses = Array.new
235
+
236
+ @timeout = step.has_key?('timeout') ? step['timeout'].to_i : 0
237
+ @retries = step.has_key?('retries') ? step['retries'].to_i : 0
238
+ @retry_delay = step.has_key?('retry_delay') ? step['retry_delay'] : 0
239
+ @on_week_days = step.has_key?('on_week_days') ? step['on_week_days'].map{|d| "#{d}?".downcase.to_sym} : [ :sunday?, :monday?, :tuesday?, :wednesday?, :thursday?, :friday?, :saturday? ]
240
+ @on_month_days = step.has_key?('on_month_days') ? step['on_month_days'] : (1..31).to_a
241
+
242
+ if step['type'].to_sym == :parallel and @on_week_days.map {|d| Time.now.send(d) }.find_index(true) and @on_month_days.find_index(Time.now.mday)
243
+ #Parallel
244
+ interval = step.has_key?('sleep') ? step['sleep'] : 1
245
+ @on_failure = step.has_key?('on_failure') ? step['on_failure'].to_sym : :finish
246
+ parallel_factor = step.has_key?('parallel') ? step['parallel'] : 1
247
+
248
+ @threads = Hash.new
249
+ index = 0
250
+ running_threads = 0
251
+
252
+ step['scripts'].each_index do |index|
253
+ next if step['scripts'][index].has_key?('status') and step['scripts'][index]['status'] == 'OK'
254
+ loop do
255
+ @mutex.synchronize do
256
+ running_threads = @threads.length
257
+ end
258
+ break if @on_failure == :wait and @statuses.find_index(false)
259
+ if parallel_factor > running_threads
260
+ @threads[index] = Thread.new { thread_wrapper(index, step['scripts'][index]) }
261
+ break
262
+ end
263
+ sleep interval
264
+ end
265
+ end
266
+ loop do
267
+ @mutex.synchronize do
268
+ running_threads = @threads.length
269
+ end
270
+ break if running_threads == 0
271
+ sleep interval
272
+ end
273
+ fail if @on_failure != :ignore and @statuses.find_index(false)
274
+
275
+ elsif step['type'].to_sym == :sequential and @on_week_days.map {|d| Time.now.send(d) }.find_index(true) and @on_month_days.find_index(Time.now.mday)
276
+ #Sequential
277
+ @on_failure = step.has_key?('on_failure') ? step['on_failure'].to_sym : :die
278
+
279
+ step['scripts'].each_index do |index|
280
+ failures = 0
281
+ next if step['scripts'][index].has_key?('status') and step['scripts'][index]['status'] == 'OK'
282
+ loop do
283
+ @statuses[index] = run_script(step['scripts'][index])
284
+ break if @statuses[index]
285
+ failures += 1
286
+ break if failures > @retries
287
+ sleep @retry_delay
288
+ end
289
+ fail if not @statuses[index] and @on_failure == :die
290
+ end
291
+ fail if @on_failure != :ignore and @statuses.find_index(false)
292
+ end
293
+ end
294
+
295
+ FileUtils.rm_f(@options.statefile) if @options.statefile
296
+ notify
297
+ end
298
+
299
+ end
300
+
301
+ end
@@ -0,0 +1,3 @@
1
+ module Orchestrator
2
+ VERSION ||= '0.0.1'
3
+ end
@@ -0,0 +1,29 @@
1
+ lib = File.expand_path('../lib/', __FILE__)
2
+ $:.unshift lib unless $:.include?(lib)
3
+
4
+ require "orchestrator/version"
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "task-orchestrator"
8
+ s.version = Orchestrator::VERSION
9
+ s.platform = Gem::Platform::RUBY
10
+ s.authors = ["Alexander Piavlo"]
11
+ s.email = ["lolitushka@gmail.com"]
12
+ s.homepage = "http://github.com/piavlo/task-orchestrator"
13
+ s.summary = "Simple task orchestration framework"
14
+ s.description = "Simple task orchestration framework driven by Yaml config files"
15
+ s.license = 'MIT'
16
+ s.has_rdoc = false
17
+
18
+
19
+ s.add_dependency('pony')
20
+ s.add_dependency('popen4')
21
+ s.add_dependency('formatador')
22
+
23
+ s.add_development_dependency('rake')
24
+
25
+ s.files = Dir.glob("{bin,lib,examples}/**/*") + %w(task-orchestrator.gemspec LICENSE README.md)
26
+ s.executables = Dir.glob('bin/**/*').map { |file| File.basename(file) }
27
+ s.test_files = nil
28
+ s.require_paths = ['lib']
29
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: task-orchestrator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Alexander Piavlo
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-01-07 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: pony
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: popen4
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: formatador
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rake
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ description: Simple task orchestration framework driven by Yaml config files
79
+ email:
80
+ - lolitushka@gmail.com
81
+ executables:
82
+ - orchestrator.rb
83
+ extensions: []
84
+ extra_rdoc_files: []
85
+ files:
86
+ - bin/orchestrator.rb
87
+ - lib/orchestrator.rb
88
+ - lib/orchestrator/task.rb
89
+ - lib/orchestrator/version.rb
90
+ - lib/orchestrator/cli.rb
91
+ - examples/sequential
92
+ - examples/multistep
93
+ - examples/parallel
94
+ - examples/interpolation
95
+ - examples/timeouts
96
+ - examples/dependencies
97
+ - task-orchestrator.gemspec
98
+ - LICENSE
99
+ - README.md
100
+ homepage: http://github.com/piavlo/task-orchestrator
101
+ licenses:
102
+ - MIT
103
+ post_install_message:
104
+ rdoc_options: []
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ none: false
109
+ requirements:
110
+ - - ! '>='
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ segments:
114
+ - 0
115
+ hash: -2708732489028219220
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ none: false
118
+ requirements:
119
+ - - ! '>='
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ segments:
123
+ - 0
124
+ hash: -2708732489028219220
125
+ requirements: []
126
+ rubyforge_project:
127
+ rubygems_version: 1.8.24
128
+ signing_key:
129
+ specification_version: 3
130
+ summary: Simple task orchestration framework
131
+ test_files: []