task-orchestrator.gemspec 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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.gemspec"
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,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: task-orchestrator.gemspec
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
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
+ none: false
115
+ requirements:
116
+ - - ! '>='
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ requirements: []
120
+ rubyforge_project:
121
+ rubygems_version: 1.8.24
122
+ signing_key:
123
+ specification_version: 3
124
+ summary: Simple task orchestration framework
125
+ test_files: []