coral_plan 0.1.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/.document +5 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +37 -0
- data/LICENSE.txt +674 -0
- data/README.rdoc +48 -0
- data/Rakefile +68 -0
- data/VERSION +1 -0
- data/lib/coral_plan/action.rb +167 -0
- data/lib/coral_plan/base.rb +263 -0
- data/lib/coral_plan/command.rb +188 -0
- data/lib/coral_plan/event.rb +69 -0
- data/lib/coral_plan.rb +75 -0
- metadata +206 -0
data/README.rdoc
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
= coral_plan
|
2
|
+
|
3
|
+
This library provides the ability to create, load, execute, and save
|
4
|
+
execution plans.
|
5
|
+
|
6
|
+
The Coral Plan library contains a system that defines execution plans of
|
7
|
+
CLI commands. The goal is to make it easy to create, edit, load, execute,
|
8
|
+
and save these plans in a machine readable format (ie; JSON). The system
|
9
|
+
is composed of Events, Commands, Actions, and of course Plans. There are a
|
10
|
+
few simple rules:
|
11
|
+
|
12
|
+
1. All plans begin with a Command but may have more Commands defined
|
13
|
+
that act in sequence unless there are errors with previous commands.
|
14
|
+
|
15
|
+
2. Commands can trigger Events of various types, where Events are reading
|
16
|
+
and checking (ie; Regular expressions) Command output line by line.
|
17
|
+
|
18
|
+
3. Events have different criteria for measuring success and will have
|
19
|
+
different fields. For example, a Regexp event contains a 'pattern'
|
20
|
+
field.
|
21
|
+
|
22
|
+
4. Actions listen for Events and run Commands, with the ability to override
|
23
|
+
default Command parameters. Actions may also generate Events on either
|
24
|
+
successful execution or failure which can trigger other Actions.
|
25
|
+
|
26
|
+
Note: This library is still very early in development!
|
27
|
+
|
28
|
+
== Contributing to coral_plan
|
29
|
+
|
30
|
+
* Check out the latest master to make sure the feature hasn't been implemented
|
31
|
+
or the bug hasn't been fixed yet.
|
32
|
+
* Check out the issue tracker to make sure someone already hasn't requested
|
33
|
+
it and/or contributed it.
|
34
|
+
* Fork the project.
|
35
|
+
* Start a feature/bugfix branch.
|
36
|
+
* Commit and push until you are happy with your contribution.
|
37
|
+
* Make sure to add tests for it. This is important so I don't break it in a
|
38
|
+
future version unintentionally.
|
39
|
+
* Please try not to mess with the Rakefile, version, or history. If you want
|
40
|
+
to have your own version, or is otherwise necessary, that is fine, but
|
41
|
+
please isolate to its own commit so I can cherry-pick around it.
|
42
|
+
|
43
|
+
== Copyright
|
44
|
+
|
45
|
+
Licensed under GPLv3. See LICENSE.txt for further details.
|
46
|
+
|
47
|
+
Copyright (c) 2013 Adrian Webb <adrian.webb@coraltech.net>
|
48
|
+
Coral Technology Group LLC
|
data/Rakefile
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
require 'rake'
|
6
|
+
require 'rake/testtask'
|
7
|
+
require 'rdoc/task'
|
8
|
+
require 'jeweler'
|
9
|
+
|
10
|
+
require './lib/coral_plan.rb'
|
11
|
+
|
12
|
+
#-------------------------------------------------------------------------------
|
13
|
+
# Dependencies
|
14
|
+
|
15
|
+
begin
|
16
|
+
Bundler.setup(:default, :development)
|
17
|
+
rescue Bundler::BundlerError => e
|
18
|
+
$stderr.puts e.message
|
19
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
20
|
+
exit e.status_code
|
21
|
+
end
|
22
|
+
|
23
|
+
#-------------------------------------------------------------------------------
|
24
|
+
# Gem specification
|
25
|
+
|
26
|
+
Jeweler::Tasks.new do |gem|
|
27
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
28
|
+
gem.name = "coral_plan"
|
29
|
+
gem.homepage = "http://github.com/coraltech/ruby-coral_plan"
|
30
|
+
gem.rubyforge_project = 'coral_plan'
|
31
|
+
gem.license = "GPLv3"
|
32
|
+
gem.email = "adrian.webb@coraltech.net"
|
33
|
+
gem.authors = ["Adrian Webb"]
|
34
|
+
gem.summary = %Q{Provides the ability to create, load, execute, and save execution plans}
|
35
|
+
gem.description = File.read('README.rdoc')
|
36
|
+
gem.required_ruby_version = '>= 1.8.1'
|
37
|
+
gem.has_rdoc = true
|
38
|
+
gem.rdoc_options << '--title' << 'Coral Execution Plan library' <<
|
39
|
+
'--main' << 'README.rdoc' <<
|
40
|
+
'--line-numbers'
|
41
|
+
|
42
|
+
# Dependencies defined in Gemfile
|
43
|
+
end
|
44
|
+
|
45
|
+
Jeweler::RubygemsDotOrgTasks.new
|
46
|
+
|
47
|
+
#-------------------------------------------------------------------------------
|
48
|
+
# Testing
|
49
|
+
|
50
|
+
Rake::TestTask.new(:test) do |test|
|
51
|
+
test.libs << 'lib' << 'test'
|
52
|
+
test.pattern = 'test/**/test_*.rb'
|
53
|
+
test.verbose = true
|
54
|
+
end
|
55
|
+
|
56
|
+
task :default => :test
|
57
|
+
|
58
|
+
#-------------------------------------------------------------------------------
|
59
|
+
# Documentation
|
60
|
+
|
61
|
+
Rake::RDocTask.new do |rdoc|
|
62
|
+
version = Coral::Plan::VERSION
|
63
|
+
|
64
|
+
rdoc.rdoc_dir = 'rdoc'
|
65
|
+
rdoc.title = "coral_plan #{version}"
|
66
|
+
rdoc.rdoc_files.include('README*')
|
67
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
68
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.1
|
@@ -0,0 +1,167 @@
|
|
1
|
+
|
2
|
+
module Coral
|
3
|
+
module Plan
|
4
|
+
class Action < Core
|
5
|
+
|
6
|
+
#-----------------------------------------------------------------------------
|
7
|
+
# Constructor / Destructor
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
super(options)
|
11
|
+
@plan = ( options.has_key?(:plan) ? options[:plan] : nil )
|
12
|
+
@name = ( options.has_key?(:name) ? string(options[:name]) : '' )
|
13
|
+
end
|
14
|
+
|
15
|
+
#-----------------------------------------------------------------------------
|
16
|
+
# Property accessors / modifiers
|
17
|
+
|
18
|
+
attr_accessor :name
|
19
|
+
|
20
|
+
#---
|
21
|
+
|
22
|
+
def events
|
23
|
+
events = {}
|
24
|
+
@plan.action(@name, 'events', [], :array).each do |name|
|
25
|
+
events[name] = @plan.events[name]
|
26
|
+
end
|
27
|
+
return events
|
28
|
+
end
|
29
|
+
|
30
|
+
#---
|
31
|
+
|
32
|
+
def events=events
|
33
|
+
@plan.set_action(@name, "events", events)
|
34
|
+
end
|
35
|
+
|
36
|
+
#---
|
37
|
+
|
38
|
+
def commands
|
39
|
+
commands = []
|
40
|
+
@plan.action(@name, 'commands', [], :array).each do |name|
|
41
|
+
commands << @plan.commands[name]
|
42
|
+
end
|
43
|
+
return commands
|
44
|
+
end
|
45
|
+
|
46
|
+
#---
|
47
|
+
|
48
|
+
def commands=commands
|
49
|
+
@plan.set_action(@name, "commands", commands)
|
50
|
+
end
|
51
|
+
|
52
|
+
#---
|
53
|
+
|
54
|
+
def args
|
55
|
+
return search('args', :array)
|
56
|
+
end
|
57
|
+
|
58
|
+
#---
|
59
|
+
|
60
|
+
def args=args
|
61
|
+
@plan.set_action(@name, "args", args)
|
62
|
+
end
|
63
|
+
|
64
|
+
#---
|
65
|
+
|
66
|
+
def flags
|
67
|
+
return search('flags', :array)
|
68
|
+
end
|
69
|
+
|
70
|
+
#---
|
71
|
+
|
72
|
+
def flags=flags
|
73
|
+
@plan.set_action(@name, "flags", flags)
|
74
|
+
end
|
75
|
+
|
76
|
+
#---
|
77
|
+
|
78
|
+
def data
|
79
|
+
return search('data', :hash)
|
80
|
+
end
|
81
|
+
|
82
|
+
#---
|
83
|
+
|
84
|
+
def data=data
|
85
|
+
@plan.set_action(@name, "data", data)
|
86
|
+
end
|
87
|
+
|
88
|
+
#---
|
89
|
+
|
90
|
+
def next
|
91
|
+
commands = []
|
92
|
+
@plan.action(@name, 'next', [], :array).each do |name|
|
93
|
+
commands << @plan.commands[name]
|
94
|
+
end
|
95
|
+
return commands
|
96
|
+
end
|
97
|
+
|
98
|
+
#---
|
99
|
+
|
100
|
+
def next=commands
|
101
|
+
@plan.set_action(@name, "next", commands)
|
102
|
+
end
|
103
|
+
|
104
|
+
#---
|
105
|
+
|
106
|
+
def trigger_success
|
107
|
+
return @plan.action('trigger_success', :array)
|
108
|
+
end
|
109
|
+
|
110
|
+
#---
|
111
|
+
|
112
|
+
def trigger_success=event_names
|
113
|
+
@plan.set_action(@name, "trigger_success", array(event_names))
|
114
|
+
end
|
115
|
+
|
116
|
+
#---
|
117
|
+
|
118
|
+
def trigger_failure
|
119
|
+
return @plan.action('trigger_failure', :array)
|
120
|
+
end
|
121
|
+
|
122
|
+
#---
|
123
|
+
|
124
|
+
def trigger_failure=event_names
|
125
|
+
@plan.set_action(@name, "trigger_failure", array(event_names))
|
126
|
+
end
|
127
|
+
|
128
|
+
#-----------------------------------------------------------------------------
|
129
|
+
# Import / Export
|
130
|
+
|
131
|
+
def export
|
132
|
+
return symbol_map(@plan.action(@name))
|
133
|
+
end
|
134
|
+
|
135
|
+
#-----------------------------------------------------------------------------
|
136
|
+
# Utilities
|
137
|
+
|
138
|
+
def search(key, format = :array)
|
139
|
+
action_info = @plan.action(@name)
|
140
|
+
action_value = map(action_info[key])
|
141
|
+
|
142
|
+
merged_value = {}
|
143
|
+
|
144
|
+
commands.each do |command|
|
145
|
+
command_info = command.export
|
146
|
+
command_value = ( command_info[key] ? { command.name => filter(command_info[key], format) } : {} )
|
147
|
+
|
148
|
+
merged_value[name] = Coral::Util::Data.merge([ action_value, command_value ], true)
|
149
|
+
end
|
150
|
+
return merged_value
|
151
|
+
end
|
152
|
+
|
153
|
+
#---
|
154
|
+
|
155
|
+
def map(data)
|
156
|
+
if data && ! data.is_a?(Hash)
|
157
|
+
data_map = {}
|
158
|
+
commands.each do |command|
|
159
|
+
data_map[command.name] = array(data)
|
160
|
+
end
|
161
|
+
data = data_map
|
162
|
+
end
|
163
|
+
return data
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,263 @@
|
|
1
|
+
|
2
|
+
module Coral
|
3
|
+
module Plan
|
4
|
+
|
5
|
+
#*******************************************************************************
|
6
|
+
# Errors
|
7
|
+
|
8
|
+
class ExecuteError < StandardError
|
9
|
+
end
|
10
|
+
|
11
|
+
#*******************************************************************************
|
12
|
+
# Base Plan definition
|
13
|
+
|
14
|
+
class Base < Memory
|
15
|
+
|
16
|
+
#-----------------------------------------------------------------------------
|
17
|
+
# Properties
|
18
|
+
|
19
|
+
@@instances = {}
|
20
|
+
|
21
|
+
#-----------------------------------------------------------------------------
|
22
|
+
# Constructor / Destructor
|
23
|
+
|
24
|
+
def self.create(name, options = {})
|
25
|
+
options[:name] = name unless options.has_key?(:name)
|
26
|
+
@@instances[name] = new(options)
|
27
|
+
return @@instances[name]
|
28
|
+
end
|
29
|
+
|
30
|
+
#---
|
31
|
+
|
32
|
+
def self.delete(name)
|
33
|
+
if @@instances.has_key?(name) && @@instances[name]
|
34
|
+
@@instances.delete(name)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
#-----------------------------------------------------------------------------
|
39
|
+
|
40
|
+
def self.[](name)
|
41
|
+
if ! @@instances.has_key?(name) || ! @@instances[name]
|
42
|
+
@@instances[name] = new({ :name => name })
|
43
|
+
end
|
44
|
+
return @@instances[name]
|
45
|
+
end
|
46
|
+
|
47
|
+
#-----------------------------------------------------------------------------
|
48
|
+
|
49
|
+
def initialize(options = {})
|
50
|
+
super(options)
|
51
|
+
|
52
|
+
@home = ( options.has_key?(:home) && options[:home].is_a?(Coral::Repository) ? options[:home] : self )
|
53
|
+
|
54
|
+
@start_commands = []
|
55
|
+
@commands = {}
|
56
|
+
@events = {}
|
57
|
+
@actions = {}
|
58
|
+
end
|
59
|
+
|
60
|
+
#-----------------------------------------------------------------------------
|
61
|
+
# Property accessors / modifiers
|
62
|
+
|
63
|
+
attr_reader :home
|
64
|
+
|
65
|
+
#---
|
66
|
+
|
67
|
+
def home=home
|
68
|
+
if home && home.is_a?(Coral::Repository)
|
69
|
+
@home = home
|
70
|
+
set_repository(@home.directory, @submodule)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
#---
|
75
|
+
|
76
|
+
def submodule=submodule
|
77
|
+
set_repository(@home.directory, submodule)
|
78
|
+
end
|
79
|
+
|
80
|
+
#-----------------------------------------------------------------------------
|
81
|
+
|
82
|
+
def start(reset = false)
|
83
|
+
if reset || @start_commands.empty?
|
84
|
+
@start_commands = []
|
85
|
+
get('start', [], :array).each do |name|
|
86
|
+
@start_commands << commands[name]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
return @start_commands
|
90
|
+
end
|
91
|
+
|
92
|
+
#-----------------------------------------------------------------------------
|
93
|
+
|
94
|
+
def commands(reset = false)
|
95
|
+
if reset || @commands.empty?
|
96
|
+
@commands = {}
|
97
|
+
get('commands', {}, :hash).each do |name, command_info|
|
98
|
+
command_info = Coral::Util::Data.merge([ {
|
99
|
+
:plan => self,
|
100
|
+
:name => name,
|
101
|
+
}, symbol_map(command_info) ])
|
102
|
+
|
103
|
+
@commands[name] = Coral::Plan::Command.new(command_info)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
return @commands
|
107
|
+
end
|
108
|
+
|
109
|
+
#---
|
110
|
+
|
111
|
+
def set_commands(commands = {})
|
112
|
+
return set('commands', events)
|
113
|
+
end
|
114
|
+
|
115
|
+
#---
|
116
|
+
|
117
|
+
def command(name, key = nil, default = {}, format = false)
|
118
|
+
return get_group('commands', name, key, default, format)
|
119
|
+
end
|
120
|
+
|
121
|
+
#---
|
122
|
+
|
123
|
+
def set_command(name, key = nil, value = {})
|
124
|
+
return set_group('commands', name, key, value)
|
125
|
+
end
|
126
|
+
|
127
|
+
#---
|
128
|
+
|
129
|
+
def delete_command(name, key = nil)
|
130
|
+
return delete_group('commands', name, key)
|
131
|
+
end
|
132
|
+
|
133
|
+
#-----------------------------------------------------------------------------
|
134
|
+
|
135
|
+
def events(reset = false)
|
136
|
+
if reset || @events.empty?
|
137
|
+
@events = {}
|
138
|
+
get('events', {}, :hash).each do |name, event_info|
|
139
|
+
event_info = Coral::Util::Data.merge([ {
|
140
|
+
:plan => self,
|
141
|
+
:name => name,
|
142
|
+
}, symbol_map(event_info) ])
|
143
|
+
|
144
|
+
@events[name] = Coral::Plan::Event.instance(event_info)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
return @events
|
148
|
+
end
|
149
|
+
|
150
|
+
#---
|
151
|
+
|
152
|
+
def set_events(events = {})
|
153
|
+
return set('events', events)
|
154
|
+
end
|
155
|
+
|
156
|
+
#---
|
157
|
+
|
158
|
+
def event(name, key = nil, default = {}, format = false)
|
159
|
+
return get_group('events', name, key, default, format)
|
160
|
+
end
|
161
|
+
|
162
|
+
#---
|
163
|
+
|
164
|
+
def set_event(name, key = nil, value = {})
|
165
|
+
return set_group('events', name, key, value)
|
166
|
+
end
|
167
|
+
|
168
|
+
#---
|
169
|
+
|
170
|
+
def delete_event(name, key = nil)
|
171
|
+
return delete_group('events', name, key)
|
172
|
+
end
|
173
|
+
|
174
|
+
#-----------------------------------------------------------------------------
|
175
|
+
|
176
|
+
def actions(reset = false)
|
177
|
+
if reset || @actions.empty?
|
178
|
+
@actions = {}
|
179
|
+
get('actions', {}, :hash).each do |name, action_info|
|
180
|
+
action_info = Coral::Util::Data.merge([ {
|
181
|
+
:plan => self,
|
182
|
+
:name => name,
|
183
|
+
}, symbol_map(action_info) ])
|
184
|
+
|
185
|
+
@actions[name] = Coral::Plan::Action.new(action_info)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
return @actions
|
189
|
+
end
|
190
|
+
|
191
|
+
#---
|
192
|
+
|
193
|
+
def set_actions(actions = {})
|
194
|
+
return set('actions', actions)
|
195
|
+
end
|
196
|
+
|
197
|
+
#---
|
198
|
+
|
199
|
+
def action(name, key = nil, default = {}, format = false)
|
200
|
+
return get_group('actions', name, key, default, format)
|
201
|
+
end
|
202
|
+
|
203
|
+
#---
|
204
|
+
|
205
|
+
def set_action(name, key = nil, value = {})
|
206
|
+
return set_group('actions', name, key, value)
|
207
|
+
end
|
208
|
+
|
209
|
+
#---
|
210
|
+
|
211
|
+
def delete_action(name, key = nil)
|
212
|
+
return delete_group('actions', name, key)
|
213
|
+
end
|
214
|
+
|
215
|
+
#-----------------------------------------------------------------------------
|
216
|
+
# Execution
|
217
|
+
|
218
|
+
def run(options)
|
219
|
+
success = true
|
220
|
+
start.each do |command|
|
221
|
+
success = recursive_exec(command, options)
|
222
|
+
break unless success
|
223
|
+
end
|
224
|
+
return success
|
225
|
+
end
|
226
|
+
|
227
|
+
#---
|
228
|
+
|
229
|
+
def recursive_exec(command, options = {}, parent_action = nil)
|
230
|
+
success = false
|
231
|
+
|
232
|
+
options[:info_prefix] = 'info' unless options.has_key?(:info_prefix)
|
233
|
+
options[:error_prefix] = 'error' unless options.has_key?(:error_prefix)
|
234
|
+
|
235
|
+
begin
|
236
|
+
triggered_events = command.exec(options, parent_action)
|
237
|
+
rescue
|
238
|
+
raise Coral::Plan::ExecuteError.new(command.name)
|
239
|
+
end
|
240
|
+
|
241
|
+
if triggered_events
|
242
|
+
success = true
|
243
|
+
|
244
|
+
unless triggered_events.empty?
|
245
|
+
triggered_events.each do |event_name, event|
|
246
|
+
actions.each do |action_name, action|
|
247
|
+
if action.events.has_key?(event_name)
|
248
|
+
action.commands.each do |subcommand|
|
249
|
+
success = recursive_exec(subcommand, options, action)
|
250
|
+
break unless success
|
251
|
+
end
|
252
|
+
end
|
253
|
+
break unless success
|
254
|
+
end
|
255
|
+
break unless success
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
return success
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|