inprovise 0.2.2
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 +15 -0
- data/.gitignore +4 -0
- data/.travis.yml +28 -0
- data/Gemfile +9 -0
- data/LICENSE +8 -0
- data/README.md +197 -0
- data/Rakefile.rb +9 -0
- data/bin/rig +5 -0
- data/inprovise.gemspec +22 -0
- data/lib/inprovise/channel/ssh.rb +202 -0
- data/lib/inprovise/cli/group.rb +86 -0
- data/lib/inprovise/cli/node.rb +95 -0
- data/lib/inprovise/cli/provision.rb +84 -0
- data/lib/inprovise/cli.rb +105 -0
- data/lib/inprovise/cmd_channel.rb +100 -0
- data/lib/inprovise/cmd_helper.rb +150 -0
- data/lib/inprovise/control.rb +326 -0
- data/lib/inprovise/execution_context.rb +277 -0
- data/lib/inprovise/group.rb +67 -0
- data/lib/inprovise/helper/cygwin.rb +43 -0
- data/lib/inprovise/helper/linux.rb +181 -0
- data/lib/inprovise/helper/windows.rb +123 -0
- data/lib/inprovise/infra.rb +122 -0
- data/lib/inprovise/local_file.rb +120 -0
- data/lib/inprovise/logger.rb +79 -0
- data/lib/inprovise/node.rb +271 -0
- data/lib/inprovise/remote_file.rb +128 -0
- data/lib/inprovise/resolver.rb +36 -0
- data/lib/inprovise/script.rb +175 -0
- data/lib/inprovise/script_index.rb +46 -0
- data/lib/inprovise/script_runner.rb +110 -0
- data/lib/inprovise/sniff.rb +46 -0
- data/lib/inprovise/sniffer/linux.rb +64 -0
- data/lib/inprovise/sniffer/platform.rb +46 -0
- data/lib/inprovise/sniffer/unknown.rb +11 -0
- data/lib/inprovise/sniffer/windows.rb +32 -0
- data/lib/inprovise/template/inprovise.rb.erb +92 -0
- data/lib/inprovise/template.rb +38 -0
- data/lib/inprovise/trigger_runner.rb +36 -0
- data/lib/inprovise/version.rb +10 -0
- data/lib/inprovise.rb +145 -0
- data/test/cli_test.rb +314 -0
- data/test/cli_test_helper.rb +19 -0
- data/test/dsl_test.rb +43 -0
- data/test/fixtures/example.txt +1 -0
- data/test/fixtures/include.rb +4 -0
- data/test/fixtures/inprovise.rb +1 -0
- data/test/fixtures/myscheme.rb +1 -0
- data/test/infra_test.rb +189 -0
- data/test/local_file_test.rb +64 -0
- data/test/remote_file_test.rb +106 -0
- data/test/resolver_test.rb +66 -0
- data/test/script_index_test.rb +53 -0
- data/test/script_test.rb +56 -0
- data/test/test_helper.rb +237 -0
- metadata +182 -0
@@ -0,0 +1,271 @@
|
|
1
|
+
# Infrastructure node for Inprovise
|
2
|
+
#
|
3
|
+
# Author:: Martin Corino
|
4
|
+
# License:: Distributes under the same license as Ruby
|
5
|
+
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
class Inprovise::Infrastructure::Node < Inprovise::Infrastructure::Target
|
9
|
+
attr_reader :host, :user
|
10
|
+
|
11
|
+
def initialize(name, config={})
|
12
|
+
@host = config[:host] || name
|
13
|
+
@user = config[:user] || 'root'
|
14
|
+
@channel = nil
|
15
|
+
@helper = nil
|
16
|
+
@history = []
|
17
|
+
@user_nodes = {}
|
18
|
+
super(name, config)
|
19
|
+
end
|
20
|
+
|
21
|
+
def channel
|
22
|
+
@channel ||= Inprovise::CmdChannel.open(self, config[:channel])
|
23
|
+
end
|
24
|
+
|
25
|
+
def helper
|
26
|
+
@helper ||= Inprovise::CmdHelper.get(self, config[:helper])
|
27
|
+
end
|
28
|
+
|
29
|
+
def disconnect!
|
30
|
+
@user_nodes.each_value {|n| n.disconnect! }
|
31
|
+
@channel.close if @channel
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
# generic command execution
|
36
|
+
|
37
|
+
def run(cmd, opts={})
|
38
|
+
log.execute("RUN: #{cmd}") if Inprovise.verbosity > 0
|
39
|
+
if should_run?(cmd, opts)
|
40
|
+
really_run(cmd, opts)
|
41
|
+
else
|
42
|
+
cached_run(cmd, opts)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def sudo(cmd, opts={})
|
47
|
+
log.execute("SUDO: #{cmd}") if Inprovise.verbosity > 0
|
48
|
+
opts = opts.merge({:sudo => true})
|
49
|
+
if should_run?(cmd, opts)
|
50
|
+
really_run(cmd, opts)
|
51
|
+
else
|
52
|
+
cached_run(cmd, opts)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# file management
|
57
|
+
|
58
|
+
def upload(from, to)
|
59
|
+
log.execute("UPLOAD: #{from} => #{to}") if Inprovise.verbosity > 0
|
60
|
+
helper.upload(from, to)
|
61
|
+
end
|
62
|
+
|
63
|
+
def download(from, to)
|
64
|
+
log.execute("DOWLOAD: #{to} <= #{from}") if Inprovise.verbosity > 0
|
65
|
+
helper.download(from, to)
|
66
|
+
end
|
67
|
+
|
68
|
+
# basic commands
|
69
|
+
|
70
|
+
def echo(arg)
|
71
|
+
log.execute("ECHO: #{arg}") if Inprovise.verbosity > 0
|
72
|
+
out = helper.echo(arg)
|
73
|
+
log.execute("ECHO: #{out}") if Inprovise.verbosity > 0
|
74
|
+
out
|
75
|
+
end
|
76
|
+
|
77
|
+
def env(var)
|
78
|
+
log.execute("ENV: #{var}") if Inprovise.verbosity > 0
|
79
|
+
val = helper.env(var)
|
80
|
+
log.execute("ENV: #{val}") if Inprovise.verbosity > 0
|
81
|
+
val
|
82
|
+
end
|
83
|
+
|
84
|
+
def cat(path)
|
85
|
+
log.execute("CAT: #{path}") if Inprovise.verbosity > 0
|
86
|
+
out = helper.cat(path)
|
87
|
+
log.execute("CAT: #{out}") if Inprovise.verbosity > 0
|
88
|
+
out
|
89
|
+
end
|
90
|
+
|
91
|
+
def hash_for(path)
|
92
|
+
log.execute("HASH_FOR: #{path}") if Inprovise.verbosity > 0
|
93
|
+
hsh = helper.hash_for(path)
|
94
|
+
log.execute("HASH_FOR: #{hsh}") if Inprovise.verbosity > 0
|
95
|
+
hsh
|
96
|
+
end
|
97
|
+
|
98
|
+
def mkdir(path)
|
99
|
+
log.execute("MKDIR: #{path}") if Inprovise.verbosity > 0
|
100
|
+
helper.mkdir(path)
|
101
|
+
end
|
102
|
+
|
103
|
+
def exists?(path)
|
104
|
+
log.execute("EXISTS?: #{path}") if Inprovise.verbosity > 0
|
105
|
+
rc = helper.exists?(path)
|
106
|
+
log.execute("EXISTS?: #{rc}") if Inprovise.verbosity > 0
|
107
|
+
rc
|
108
|
+
end
|
109
|
+
|
110
|
+
def file?(path)
|
111
|
+
log.execute("FILE?: #{path}") if Inprovise.verbosity > 0
|
112
|
+
rc = helper.file?(path)
|
113
|
+
log.execute("FILE?: #{rc}") if Inprovise.verbosity > 0
|
114
|
+
rc
|
115
|
+
end
|
116
|
+
|
117
|
+
def directory?(path)
|
118
|
+
log.execute("DIRECTORY?: #{path}") if Inprovise.verbosity > 0
|
119
|
+
rc = helper.directory?(path)
|
120
|
+
log.execute("DIRECTORY?: #{rc}") if Inprovise.verbosity > 0
|
121
|
+
rc
|
122
|
+
end
|
123
|
+
|
124
|
+
def copy(from, to)
|
125
|
+
log.execute("COPY: #{from} #{to}") if Inprovise.verbosity > 0
|
126
|
+
helper.copy(from, to)
|
127
|
+
end
|
128
|
+
|
129
|
+
def delete(path)
|
130
|
+
log.execute("DELETE: #{path}") if Inprovise.verbosity > 0
|
131
|
+
helper.delete(path)
|
132
|
+
end
|
133
|
+
|
134
|
+
def permissions(path)
|
135
|
+
log.execute("PERMISSIONS: #{path}") if Inprovise.verbosity > 0
|
136
|
+
perm = helper.permissions(path)
|
137
|
+
log.execute("PERMISSIONS: #{'%o' % perm}") if Inprovise.verbosity > 0
|
138
|
+
perm
|
139
|
+
end
|
140
|
+
|
141
|
+
def set_permissions(path, perm)
|
142
|
+
log.execute("SET_PERMISSIONS: #{path} #{'%o' % perm}") if Inprovise.verbosity > 0
|
143
|
+
helper.set_permissions(path, perm)
|
144
|
+
end
|
145
|
+
|
146
|
+
def owner(path)
|
147
|
+
log.execute("OWNER: #{path}") if Inprovise.verbosity > 0
|
148
|
+
owner = helper.owner(path)
|
149
|
+
log.execute("OWNER: #{owner}") if Inprovise.verbosity > 0
|
150
|
+
owner
|
151
|
+
end
|
152
|
+
|
153
|
+
def group(path)
|
154
|
+
log.execute("GROUP: #{path}") if Inprovise.verbosity > 0
|
155
|
+
group = helper.group(path)
|
156
|
+
log.execute("OWNER: #{group}") if Inprovise.verbosity > 0
|
157
|
+
group
|
158
|
+
end
|
159
|
+
|
160
|
+
def set_owner(path, user, group=nil)
|
161
|
+
log.execute("SET_OWNER: #{path} #{user}#{group ? " #{group}" : ''}") if Inprovise.verbosity > 0
|
162
|
+
helper.set_owner(path, user, group)
|
163
|
+
end
|
164
|
+
|
165
|
+
def binary_exists?(bin)
|
166
|
+
log.execute("BINARY_EXISTS?: #{bin}") if Inprovise.verbosity > 0
|
167
|
+
rc = helper.binary_exists?(bin)
|
168
|
+
log.execute("BINARY_EXISTS?: #{rc}") if Inprovise.verbosity > 0
|
169
|
+
rc
|
170
|
+
end
|
171
|
+
|
172
|
+
def log
|
173
|
+
@log ||= Inprovise::Logger.new(self, nil)
|
174
|
+
end
|
175
|
+
|
176
|
+
def log_to(log)
|
177
|
+
@log = log
|
178
|
+
end
|
179
|
+
|
180
|
+
def for_user(new_user, user_key=nil)
|
181
|
+
new_user = new_user.to_s
|
182
|
+
return self if self.user == new_user
|
183
|
+
user_key ||= new_user
|
184
|
+
return @user_nodes[user_key] if @user_nodes[user_key]
|
185
|
+
new_node = self.dup
|
186
|
+
new_node.prepare_connection_for_user!(new_user)
|
187
|
+
@user_nodes[user_key] = new_node
|
188
|
+
new_node
|
189
|
+
end
|
190
|
+
|
191
|
+
def for_dir(path)
|
192
|
+
user_key = "#{self.user}:#{path}"
|
193
|
+
return @user_nodes[user_key] if @user_nodes[user_key]
|
194
|
+
new_node = self.dup
|
195
|
+
new_node.prepare_connection_for_user!(self.user)
|
196
|
+
end
|
197
|
+
|
198
|
+
def prepare_connection_for_user!(new_user)
|
199
|
+
@user = new_user
|
200
|
+
@channel = nil
|
201
|
+
@helper = nil
|
202
|
+
@user_nodes = {}
|
203
|
+
@history = []
|
204
|
+
@log = Inprovise::Logger.new(self, @log.task) if @log
|
205
|
+
end
|
206
|
+
|
207
|
+
def to_s
|
208
|
+
"#{name}(#{user}@#{host})"
|
209
|
+
end
|
210
|
+
|
211
|
+
def safe_config
|
212
|
+
scfg = config.dup
|
213
|
+
scfg.delete :passphrase
|
214
|
+
scfg.delete :password
|
215
|
+
scfg.delete :credentials
|
216
|
+
scfg
|
217
|
+
end
|
218
|
+
protected :safe_config
|
219
|
+
|
220
|
+
def to_json(*a)
|
221
|
+
{
|
222
|
+
JSON.create_id => self.class.name,
|
223
|
+
:data => {
|
224
|
+
:name => name,
|
225
|
+
:config => safe_config
|
226
|
+
}
|
227
|
+
}.to_json(*a)
|
228
|
+
end
|
229
|
+
|
230
|
+
def self.json_create(o)
|
231
|
+
data = o[:data]
|
232
|
+
new(data[:name], data[:config])
|
233
|
+
end
|
234
|
+
|
235
|
+
private
|
236
|
+
|
237
|
+
def cached_run(cmd, opts={})
|
238
|
+
cmd = "sudo #{cmd}" if opts[:sudo]
|
239
|
+
log.cached(cmd)
|
240
|
+
last_output(cmd)
|
241
|
+
end
|
242
|
+
|
243
|
+
def really_run(cmd, opts={})
|
244
|
+
exec = opts[:sudo] ? helper.sudo : helper
|
245
|
+
cmd = prefixed_command(cmd)
|
246
|
+
begin
|
247
|
+
output = exec.run(cmd, opts[:log])
|
248
|
+
@history << {cmd:cmd, output:output}
|
249
|
+
output
|
250
|
+
rescue Exception
|
251
|
+
raise RuntimeError, "Failed to communicate with [#{self.to_s}]"
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def should_run?(cmd, opts)
|
256
|
+
return true unless opts[:once]
|
257
|
+
cmd = "sudo #{cmd}" if opts[:sudo]
|
258
|
+
last_output(cmd).nil?
|
259
|
+
end
|
260
|
+
|
261
|
+
def last_output(cmd)
|
262
|
+
results = @history.select {|h| h[:cmd] == cmd }
|
263
|
+
return nil unless results && results.size > 0
|
264
|
+
results.last[:output]
|
265
|
+
end
|
266
|
+
|
267
|
+
def prefixed_command(cmd)
|
268
|
+
return cmd unless config[:prefix]
|
269
|
+
config[:prefix] + cmd
|
270
|
+
end
|
271
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# RemoteFile support for Inprovise
|
2
|
+
#
|
3
|
+
# Author:: Martin Corino
|
4
|
+
# License:: Distributes under the same license as Ruby
|
5
|
+
|
6
|
+
require 'digest/sha1'
|
7
|
+
require 'fileutils'
|
8
|
+
|
9
|
+
class Inprovise::RemoteFile
|
10
|
+
attr_reader :path
|
11
|
+
|
12
|
+
def initialize(context, path)
|
13
|
+
@context = context
|
14
|
+
@path = path
|
15
|
+
@exists = nil
|
16
|
+
@permissions = nil
|
17
|
+
@owner = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def hash
|
21
|
+
return nil unless exists?
|
22
|
+
@hash ||= @context.node.hash_for(path)
|
23
|
+
end
|
24
|
+
|
25
|
+
def exists?
|
26
|
+
return @exists unless @exists.nil?
|
27
|
+
@exists = @context.node.exists?(path)
|
28
|
+
end
|
29
|
+
|
30
|
+
def directory?
|
31
|
+
@context.node.directory?(path)
|
32
|
+
end
|
33
|
+
|
34
|
+
def file?
|
35
|
+
@context.node.file?(path)
|
36
|
+
end
|
37
|
+
|
38
|
+
def content
|
39
|
+
@context.node.cat(path)
|
40
|
+
end
|
41
|
+
|
42
|
+
# doesnt check permissions or user. should it?
|
43
|
+
def matches?(other)
|
44
|
+
self.exists? && other.exists? && self.hash == other.hash
|
45
|
+
end
|
46
|
+
|
47
|
+
def copy_to(destination)
|
48
|
+
if destination.is_local?
|
49
|
+
download(destination)
|
50
|
+
else
|
51
|
+
duplicate(destination)
|
52
|
+
end
|
53
|
+
destination
|
54
|
+
end
|
55
|
+
|
56
|
+
def copy_from(destination)
|
57
|
+
destination.copy_to(self)
|
58
|
+
end
|
59
|
+
|
60
|
+
def duplicate(destination)
|
61
|
+
@context.copy(path, destination.path)
|
62
|
+
destination
|
63
|
+
end
|
64
|
+
|
65
|
+
def download(destination)
|
66
|
+
if String === destination || destination.is_local?
|
67
|
+
@context.download(path, String === destination ? destination : destination.path)
|
68
|
+
else
|
69
|
+
@context.copy(path, destination.path)
|
70
|
+
end
|
71
|
+
String === destination ? @context.local(destination) : destination
|
72
|
+
end
|
73
|
+
|
74
|
+
def upload(source)
|
75
|
+
if String === source || source.is_local?
|
76
|
+
@context.upload(String === source ? source : source.path, path)
|
77
|
+
else
|
78
|
+
@context.copy(source.path, path)
|
79
|
+
end
|
80
|
+
self
|
81
|
+
end
|
82
|
+
|
83
|
+
def delete!
|
84
|
+
@context.remove(path) if exists?
|
85
|
+
invalidate!
|
86
|
+
self
|
87
|
+
end
|
88
|
+
|
89
|
+
def set_permissions(mask)
|
90
|
+
@context.set_permissions(path, mask)
|
91
|
+
invalidate!
|
92
|
+
self
|
93
|
+
end
|
94
|
+
|
95
|
+
def permissions
|
96
|
+
@permissions ||= @context.node.permissions(path)
|
97
|
+
end
|
98
|
+
|
99
|
+
def set_owner(user, group=nil)
|
100
|
+
user ||= owner[:user]
|
101
|
+
@context.set_owner(path, user, group)
|
102
|
+
invalidate!
|
103
|
+
self
|
104
|
+
end
|
105
|
+
|
106
|
+
def owner
|
107
|
+
@owner ||= @context.node.owner(path)
|
108
|
+
end
|
109
|
+
|
110
|
+
def user
|
111
|
+
owner[:user]
|
112
|
+
end
|
113
|
+
|
114
|
+
def group
|
115
|
+
owner[:group]
|
116
|
+
end
|
117
|
+
|
118
|
+
def is_local?
|
119
|
+
false
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
def invalidate!
|
125
|
+
@permissions = nil
|
126
|
+
@owner = nil
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# Script dependency Resolver for Inprovise
|
2
|
+
#
|
3
|
+
# Author:: Martin Corino
|
4
|
+
# License:: Distributes under the same license as Ruby
|
5
|
+
|
6
|
+
class Inprovise::Resolver
|
7
|
+
attr_reader :scripts
|
8
|
+
def initialize(script,index=nil)
|
9
|
+
@script = script
|
10
|
+
@index = index || Inprovise::ScriptIndex.default
|
11
|
+
@last_seen = script
|
12
|
+
@scripts = [@script]
|
13
|
+
end
|
14
|
+
|
15
|
+
def resolve
|
16
|
+
begin
|
17
|
+
@script.dependencies.reverse.each do |d|
|
18
|
+
@scripts.insert(0, *Inprovise::Resolver.new(@index.get(d), @index).resolve.scripts)
|
19
|
+
end
|
20
|
+
@script.children.each do |c|
|
21
|
+
child = @index.get(c)
|
22
|
+
@scripts.concat(Inprovise::Resolver.new(child, @index).resolve.scripts) unless @scripts.include?(child)
|
23
|
+
end
|
24
|
+
rescue SystemStackError
|
25
|
+
raise CircularDependencyError.new
|
26
|
+
end
|
27
|
+
@scripts.uniq!
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
class CircularDependencyError < StandardError
|
32
|
+
def initialize
|
33
|
+
super('Circular dependecy detected')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
# Script base class for Inprovise
|
2
|
+
#
|
3
|
+
# Author:: Martin Corino
|
4
|
+
# License:: Distributes under the same license as Ruby
|
5
|
+
|
6
|
+
require 'ostruct'
|
7
|
+
|
8
|
+
class Inprovise::Script
|
9
|
+
attr_reader :name, :dependencies, :actions, :children, :user
|
10
|
+
|
11
|
+
class DSL
|
12
|
+
def initialize(script)
|
13
|
+
@script = script
|
14
|
+
end
|
15
|
+
|
16
|
+
def description(desc)
|
17
|
+
@script.description(desc)
|
18
|
+
end
|
19
|
+
|
20
|
+
def configuration(cfg)
|
21
|
+
@script.configuration(cfg)
|
22
|
+
end
|
23
|
+
|
24
|
+
def depends_on(*scr_names)
|
25
|
+
@script.depends_on(*scr_names)
|
26
|
+
end
|
27
|
+
|
28
|
+
def triggers(*scr_names)
|
29
|
+
@script.triggers(*scr_names)
|
30
|
+
end
|
31
|
+
|
32
|
+
def validate(&definition)
|
33
|
+
@script.validate(&definition)
|
34
|
+
end
|
35
|
+
|
36
|
+
def apply(&definition)
|
37
|
+
@script.apply(&definition)
|
38
|
+
end
|
39
|
+
|
40
|
+
def revert(&definition)
|
41
|
+
@script.revert(&definition)
|
42
|
+
end
|
43
|
+
|
44
|
+
def as(user)
|
45
|
+
@script.as(user)
|
46
|
+
end
|
47
|
+
|
48
|
+
def action(name, &definition)
|
49
|
+
@script.action(name, &definition)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def initialize(name)
|
54
|
+
@name = name
|
55
|
+
@description = nil
|
56
|
+
@configuration = nil
|
57
|
+
@user = nil
|
58
|
+
@dependencies = []
|
59
|
+
@children = []
|
60
|
+
@actions = {}
|
61
|
+
@commands = {}
|
62
|
+
@remove = nil
|
63
|
+
end
|
64
|
+
|
65
|
+
def description(desc=nil)
|
66
|
+
@description = desc if desc
|
67
|
+
@description
|
68
|
+
end
|
69
|
+
|
70
|
+
def describe
|
71
|
+
return [self.name] unless self.description
|
72
|
+
nm = [self.name]
|
73
|
+
self.description.split("\n").collect {|ld| "#{"%-25s" % nm.shift.to_s}\t#{ld.strip}"}
|
74
|
+
end
|
75
|
+
|
76
|
+
def configuration(cfg=nil)
|
77
|
+
@configuration = cfg if cfg
|
78
|
+
@configuration
|
79
|
+
end
|
80
|
+
|
81
|
+
def copy_config(cfg)
|
82
|
+
case cfg
|
83
|
+
when Hash, OpenStruct
|
84
|
+
cfg.to_h.reduce(OpenStruct.new) { |os, (k,v)| os[k] = copy_config(v); os }
|
85
|
+
when Array
|
86
|
+
cfg.collect { |e| copy_config(e) }
|
87
|
+
else
|
88
|
+
cfg.dup rescue cfg
|
89
|
+
end
|
90
|
+
end
|
91
|
+
private :copy_config
|
92
|
+
|
93
|
+
def merge_config(runcfg, scrcfg)
|
94
|
+
return scrcfg unless runcfg
|
95
|
+
case runcfg
|
96
|
+
when Hash, OpenStruct
|
97
|
+
return runcfg unless scrcfg.respond_to?(:to_h)
|
98
|
+
return scrcfg.to_h.reduce(runcfg) do |rc, (k,v)|
|
99
|
+
case rc[k]
|
100
|
+
when Hash,OpenStruct
|
101
|
+
rc[k] = merge_config(rc[k], v)
|
102
|
+
else
|
103
|
+
rc[k] = v unless rc[k]
|
104
|
+
end
|
105
|
+
rc
|
106
|
+
end
|
107
|
+
else
|
108
|
+
return runcfg
|
109
|
+
end
|
110
|
+
end
|
111
|
+
private :merge_config
|
112
|
+
|
113
|
+
def merge_configuration(config)
|
114
|
+
return unless self.configuration
|
115
|
+
script_cfg = copy_config(self.configuration)
|
116
|
+
config[self.name.to_sym] = merge_config(config[self.name.to_sym], script_cfg)
|
117
|
+
end
|
118
|
+
|
119
|
+
def depends_on(*scr_names)
|
120
|
+
scr_names.each do |scr_name|
|
121
|
+
@dependencies << scr_name
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def triggers(*scr_names)
|
126
|
+
scr_names.each do |scr_name|
|
127
|
+
@children << scr_name
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def validate(&definition)
|
132
|
+
command(:validate, &definition)
|
133
|
+
end
|
134
|
+
|
135
|
+
def apply(&definition)
|
136
|
+
command(:apply, &definition)
|
137
|
+
end
|
138
|
+
|
139
|
+
def revert(&definition)
|
140
|
+
command(:revert, &definition)
|
141
|
+
end
|
142
|
+
|
143
|
+
def as(user)
|
144
|
+
@user = user
|
145
|
+
end
|
146
|
+
|
147
|
+
def action(name, &definition)
|
148
|
+
@actions[name] = definition
|
149
|
+
end
|
150
|
+
|
151
|
+
def command(name, &definition)
|
152
|
+
if block_given?
|
153
|
+
(@commands[name.to_sym] ||= []) << definition
|
154
|
+
else
|
155
|
+
@commands[name.to_sym] ||= []
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def provides_command?(name)
|
160
|
+
@commands.has_key?(name.to_sym)
|
161
|
+
end
|
162
|
+
|
163
|
+
def to_s
|
164
|
+
self.name
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
Inprovise::DSL.dsl_define do
|
169
|
+
def script(name, &definition)
|
170
|
+
Inprovise.log.local("Adding provisioning script #{name}") if Inprovise.verbosity > 1
|
171
|
+
Inprovise.add_script(Inprovise::Script.new(name)) do |script|
|
172
|
+
Inprovise::Script::DSL.new(script).instance_eval(&definition)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# Script Index for Inprovise
|
2
|
+
#
|
3
|
+
# Author:: Martin Corino
|
4
|
+
# License:: Distributes under the same license as Ruby
|
5
|
+
|
6
|
+
class Inprovise::ScriptIndex
|
7
|
+
attr_reader :index_name
|
8
|
+
|
9
|
+
def initialize(index_name)
|
10
|
+
@index_name = index_name
|
11
|
+
@scripts = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.default
|
15
|
+
@default ||= new('default')
|
16
|
+
end
|
17
|
+
|
18
|
+
def add(scr)
|
19
|
+
@scripts[scr.name] = scr
|
20
|
+
end
|
21
|
+
|
22
|
+
def get(scr_name)
|
23
|
+
scr = @scripts[scr_name]
|
24
|
+
raise MissingScriptError.new(index_name, scr_name) if scr.nil?
|
25
|
+
scr
|
26
|
+
end
|
27
|
+
|
28
|
+
def scripts
|
29
|
+
@scripts.keys
|
30
|
+
end
|
31
|
+
|
32
|
+
def clear!
|
33
|
+
@scripts = {}
|
34
|
+
end
|
35
|
+
|
36
|
+
class MissingScriptError < StandardError
|
37
|
+
def initialize(index_name, script_name)
|
38
|
+
@index_name = index_name
|
39
|
+
@script_name = script_name
|
40
|
+
end
|
41
|
+
|
42
|
+
def message
|
43
|
+
"script #{@script_name} could not be found in the index #{@index_name}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|