inprovise 0.2.7 → 0.2.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- YWVkNzEwYzZlNTI3ZGM0ZjE5NTY2ZjY5MzY5NWNhZTRiMDM2MjFiNw==
4
+ ZWIzZmMxNTQzOTY1MjAxNTZiYjE2ZjQwNzZjZDg1ZDQwMDA3MWMxMg==
5
5
  data.tar.gz: !binary |-
6
- ODJlMDRmMTBjMmYxNTEwMTMzZTRlMDY5ZjdlZDI0MGQ1NzZjOGQ3OQ==
6
+ YzYzYjJlYzc2MmYxOTFkYTJjY2Q0ZmY3Njg5MWFlYzc0NDU0MGUzNw==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- NzEwM2RiMjdkMDMyYTBjYTgzYjFiOWZlYzU2ZjBiODM3ZjA1ZGNiMzY4MzA4
10
- MTAwODUyNTY3ZDZiYzQwYjAzMGU1YzdjN2RlYzljZmI0YmVhM2Q1OGFjOGRi
11
- YWIzMjYxYWM0YmM4ODQ3MjIwNzBlN2I5NDkzMzNlOTMyMzJhZWU=
9
+ MDljNzdlYTgwNTEyM2I4YmNkMzE0Y2NlYTJhMWRhMjJiYzUyNGRiMjQ4YmEx
10
+ NmViZmRmMTUzNGJiMWRkNDg4YmEzYjNjMDAyMzI1OWVhMmNiMGU1MjBiYmJl
11
+ ZjU1YTZkYzUzZTk0ZjdlNDMzNzExMGM1MTYwNWI4MjZiZTI1ZTA=
12
12
  data.tar.gz: !binary |-
13
- MDkzNjdhYzc0ZGNiMWVkOTIzMzAzNzhjOTVkM2UwNzNjNzRmMmEyOThhZDEz
14
- Y2JiZTQ0ZWFjMTFhMmIyMmUwNmM5MTA3OTBmYzJhOGFlYWUyY2ZhMWIxZjEx
15
- NzBmYWQzMjRhZjhmOGIyNjNmNzk1ODQzNDcxNzhlZGMzOGVmM2M=
13
+ YTc2YjdjYzc3MGFkMGM3NDgwNGRjOWI2OGE0MTA4NjVkMDNjOTA0NDgyMDZi
14
+ ZWQ0NWMyMjcyMWYyNzliM2JlMzk0ZmFlZDhkMjMzNDQzMWFiMzk0MjI5YzVj
15
+ YmI4MGRiYWJmMDY2ZGJkNmE3ZTA1OGZjMWRiNjc2MWNhYWI2MWU=
@@ -191,11 +191,15 @@ Inprovise::CmdChannel.define('ssh') do
191
191
  def execute(cmd, forcelog=false)
192
192
  @node.log.remote("SSH: #{cmd}") if Inprovise.verbosity > 1 || forcelog
193
193
  output = ''
194
- connection.exec! cmd do |_channel, stream, data|
195
- output << data if stream == :stdout
196
- data.split("\n").each do |line|
197
- @node.log.send(stream, line, forcelog)
198
- end if Inprovise.verbosity > 1 || forcelog
194
+ begin
195
+ connection.exec! cmd do |_channel, stream, data|
196
+ output << data if stream == :stdout
197
+ @node.log.send(stream, data, forcelog) if Inprovise.verbosity > 1 || forcelog
198
+ end
199
+ rescue Net::SSH::Exception => ex
200
+ raise Inprovise::CmdChannel::Exception, "#{ex.message}"
201
+ ensure
202
+ @node.log.flush_all if Inprovise.verbosity > 1 || forcelog
199
203
  end
200
204
  output
201
205
  end
@@ -5,6 +5,8 @@
5
5
 
6
6
  module Inprovise::CmdChannel
7
7
 
8
+ class Exception < ::RuntimeError; end
9
+
8
10
  class << self
9
11
 
10
12
  def implementations
@@ -0,0 +1,94 @@
1
+ # Config class for Inprovise
2
+ #
3
+ # Author:: Martin Corino
4
+ # License:: Distributes under the same license as Ruby
5
+
6
+ class Inprovise::Config
7
+
8
+ def initialize(other=nil)
9
+ @table = {}
10
+ copy!(other.to_h) if other
11
+ end
12
+
13
+ def _k_(key)
14
+ Symbol === key ? key : key.to_s.to_sym
15
+ end
16
+ private :_k_
17
+
18
+ def _v_(val)
19
+ Hash === val ? self.class.new.merge!(val) : val
20
+ end
21
+ private :_v_
22
+
23
+ def [](key)
24
+ @table[_k_(key)]
25
+ end
26
+
27
+ def []=(key, val)
28
+ @table[_k_(key)] = _v_(val)
29
+ end
30
+
31
+ def has_key?(key)
32
+ @table.has_key?(_k_(key))
33
+ end
34
+
35
+ def empty?
36
+ @table.empty?
37
+ end
38
+
39
+ def merge!(other)
40
+ other.to_h.each do |k,v|
41
+ case self[k]
42
+ when self.class
43
+ self[k].merge!(v)
44
+ else
45
+ self[k] = v
46
+ end
47
+ end
48
+ self
49
+ end
50
+
51
+ def copy!(other)
52
+ other.to_h.each do |k,v|
53
+ case self[k]
54
+ when self.class
55
+ self[k].copy!(v)
56
+ else
57
+ self[k] = v.is_a?(Hash) ? v : (v.dup rescue v)
58
+ end
59
+ end
60
+ self
61
+ end
62
+
63
+ def update!(other)
64
+ other.to_h.each do |k,v|
65
+ if self.has_key?(k)
66
+ self[k].update!(v) if self.class === self[k]
67
+ else
68
+ self[k] = v.is_a?(Hash) ? v : (v.dup rescue v)
69
+ end
70
+ end
71
+ self
72
+ end
73
+
74
+ def each(&block)
75
+ @table.each { |k,v| block.call(k,v) }
76
+ end
77
+
78
+ def dup
79
+ self.class.new(@table)
80
+ end
81
+
82
+ def to_h
83
+ @table
84
+ end
85
+
86
+ def method_missing(method, *args)
87
+ if /(.*)=$/ =~ method.to_s
88
+ self[$1] = (args.size > 1 ? args : args.shift)
89
+ else
90
+ self[method]
91
+ end
92
+ end
93
+
94
+ end
@@ -87,11 +87,13 @@ class Inprovise::Controller
87
87
  begin
88
88
  case command
89
89
  when :add, :remove, :update
90
- case args.shift
91
- when :node
92
- run_node_command(command, options, *args)
93
- when :group
94
- run_group_command(command, options, *args)
90
+ target = args.shift
91
+ if command == :remove
92
+ run_infra_command(command, target, *args)
93
+ else
94
+ tgtcfg = parse_config(options[:config])
95
+ tgtcfg[:credentials] = parse_config(options[:credential]) if target == :node && options.has_key?(:credential)
96
+ run_infra_command(command, target, options, tgtcfg, *args)
95
97
  end
96
98
  else # :apply, :revert, :validate or :trigger
97
99
  load_schemes(options)
@@ -129,18 +131,13 @@ class Inprovise::Controller
129
131
  end
130
132
  end
131
133
 
132
- def run_node_command(cmd, options, *names)
133
- add(Inprovise::Controller.new).send(:"#{cmd}_node", options, *names)
134
+ def run_infra_command(cmd, target, *args)
135
+ add(Inprovise::Controller.new).send(:"#{cmd}_#{target}", *args)
134
136
  Inprovise::Infrastructure.save
135
137
  end
136
138
 
137
- def run_group_command(cmd, options, *names)
138
- add(Inprovise::Controller.new).send(:"#{cmd}_group", options, *names)
139
- Inprovise::Infrastructure.save
140
- end
141
-
142
- def run_provisioning_command(command, script, opts, *targets)
143
- add(Inprovise::Controller.new).run_provisioning_command(command, script, opts, *targets)
139
+ def run_provisioning_command(command, script, cfg, *targets)
140
+ add(Inprovise::Controller.new).run_provisioning_command(command, script, cfg, *targets)
144
141
  end
145
142
 
146
143
  end
@@ -163,11 +160,11 @@ class Inprovise::Controller
163
160
  Inprovise.log.local('Done!') if Inprovise.verbosity > 0
164
161
  end
165
162
 
166
- def run_provisioning_command(command, cmdtgt, opts, *names)
163
+ def run_provisioning_command(command, cmdtgt, cmdcfg, *names)
167
164
  # get intended infrastructure targets/config tuples
168
165
  targets = get_targets(*names)
169
166
  # create runner/config for each target/config
170
- runners = targets.map do |tgt, cfg|
167
+ runners = targets.map do |tgt, tgtcfg|
171
168
  @targets << tgt
172
169
  [
173
170
  if command == :trigger
@@ -175,21 +172,20 @@ class Inprovise::Controller
175
172
  else
176
173
  Inprovise::ScriptRunner.new(tgt, Inprovise::ScriptIndex.default.get(cmdtgt), Inprovise.skip_dependencies)
177
174
  end,
178
- cfg
175
+ tgtcfg
179
176
  ]
180
177
  end
181
178
  # execute runners
182
179
  if Inprovise.sequential
183
- runners.each {|runner, cfg| exec(runner, command, cfg.merge(opts)) }
180
+ runners.each {|runner, tgtcfg| exec(runner, command, tgtcfg.merge(cmdcfg)) }
184
181
  else
185
- @threads = runners.map {|runner, cfg| Thread.new { exec(runner, command, cfg.merge(opts)) } }
182
+ @threads = runners.map {|runner, tgtcfg| Thread.new { exec(runner, command, tgtcfg.merge(cmdcfg)) } }
186
183
  end
187
184
  end
188
185
 
189
- def add_node(options, *names)
190
- opts = self.class.parse_config(options[:config], { host: options[:address] })
191
- opts[:credentials] = self.class.parse_config(options[:credential])
192
- @targets << (node = Inprovise::Infrastructure::Node.new(names.first, opts))
186
+ def add_node(options, nodecfg, name)
187
+ nodecfg.merge!({ host: options[:address] })
188
+ @targets << (node = Inprovise::Infrastructure::Node.new(name, nodecfg))
193
189
 
194
190
  Inprovise.log.local("Adding #{node}")
195
191
 
@@ -202,7 +198,7 @@ class Inprovise::Controller
202
198
  end
203
199
  end
204
200
 
205
- def remove_node(_options, *names)
201
+ def remove_node(*names)
206
202
  names.each do |name|
207
203
  node = Inprovise::Infrastructure.find(name)
208
204
  raise ArgumentError, "Invalid node #{name}" unless node && node.is_a?(Inprovise::Infrastructure::Node)
@@ -213,26 +209,23 @@ class Inprovise::Controller
213
209
  end
214
210
  end
215
211
 
216
- def update_node(options, *names)
212
+ def update_node(options, nodecfg, *names)
217
213
  @targets = names.collect do |name|
218
214
  tgt = Inprovise::Infrastructure.find(name)
219
215
  raise ArgumentError, "Unknown target [#{name}]" unless tgt
220
216
  tgt.targets
221
217
  end.flatten.uniq
222
- opts = self.class.parse_config(options[:config])
223
- opts[:credentials] = self.class.parse_config(options[:credential])
224
218
  if Inprovise.sequential || (!options[:sniff]) || @targets.size == 1
225
- @targets.each {|tgt| run_target_update(tgt, opts.dup, options) }
219
+ @targets.each {|tgt| run_node_update(tgt, nodecfg.dup, options) }
226
220
  else
227
- threads = @targets.map {|tgt| Thread.new { run_target_update(tgt, opts.dup, options) } }
221
+ threads = @targets.map {|tgt| Thread.new { run_node_update(tgt, nodecfg.dup, options) } }
228
222
  threads.each {|t| t.join }
229
223
  end
230
224
  end
231
225
 
232
- def add_group(options, *names)
226
+ def add_group(options, grpcfg, name)
233
227
  options[:target].each {|t| raise ArgumentError, "Unknown target [#{t}]" unless Inprovise::Infrastructure.find(t) }
234
- opts = self.class.parse_config(options[:config])
235
- grp = Inprovise::Infrastructure::Group.new(names.first, opts, options[:target])
228
+ grp = Inprovise::Infrastructure::Group.new(name, grpcfg, options[:target])
236
229
 
237
230
  Inprovise.log.local("Adding #{grp}")
238
231
 
@@ -243,7 +236,7 @@ class Inprovise::Controller
243
236
  end
244
237
  end
245
238
 
246
- def remove_group(_options, *names)
239
+ def remove_group(*names)
247
240
  names.each do |name|
248
241
  grp = Inprovise::Infrastructure.find(name)
249
242
  raise ArgumentError, "Invalid group #{name}" unless grp && grp.is_a?(Inprovise::Infrastructure::Group)
@@ -254,13 +247,12 @@ class Inprovise::Controller
254
247
  end
255
248
  end
256
249
 
257
- def update_group(options, *names)
250
+ def update_group(options, grpcfg, *names)
258
251
  groups = names.collect do |name|
259
252
  tgt = Inprovise::Infrastructure.find(name)
260
253
  raise ArgumentError, "Invalid group #{name}" unless tgt && tgt.is_a?(Inprovise::Infrastructure::Group)
261
254
  tgt
262
255
  end
263
- opts = self.class.parse_config(options[:config])
264
256
  grp_tgts = options[:target].collect do |tnm|
265
257
  tgt = Inprovise::Infrastructure.find(tnm)
266
258
  raise ArgumentError, "Unknown target #{tnm}" unless tgt
@@ -270,7 +262,7 @@ class Inprovise::Controller
270
262
  Inprovise.log.local("Updating #{grp}")
271
263
 
272
264
  grp.config.clear if options[:reset]
273
- grp.config.merge!(opts)
265
+ grp.config.merge!(grpcfg)
274
266
  grp_tgts.each {|gt| gt.add_to(grp) }
275
267
  end
276
268
  end
@@ -292,37 +284,37 @@ class Inprovise::Controller
292
284
  end
293
285
  end
294
286
 
295
- def exec(runner, command, opts)
287
+ def exec(runner, command, cfg)
296
288
  if Inprovise.demonstrate
297
- runner.demonstrate(command, opts)
289
+ runner.demonstrate(command, cfg)
298
290
  else
299
- runner.execute(command, opts)
291
+ runner.execute(command, cfg)
300
292
  end
301
293
  end
302
294
 
303
- def run_target_update(tgt, tgt_opts, options)
304
- Inprovise.log.local("Updating #{tgt}")
295
+ def run_node_update(node, nodecfg, options)
296
+ Inprovise.log.local("Updating #{node}")
305
297
 
306
298
  if options[:reset]
307
299
  # preserve :host
308
- tgt_opts[:host] = tgt.get(:host) if tgt.get(:host)
300
+ nodecfg[:host] = node.get(:host) if node.get(:host)
309
301
  # preserve :user if no new user specified
310
- tgt_opts[:user] = tgt.get(:user) if tgt.get(:user) && !tgt_opts.has_key?(:user)
302
+ nodecfg[:user] = node.get(:user) if node.get(:user) && !nodecfg.has_key?(:user)
311
303
  # preserve sniffed attributes when not running sniffers now
312
304
  unless options[:sniff]
313
- tgt_opts[:attributes] = tgt.get(:attributes)
305
+ nodecfg[:attributes] = node.get(:attributes)
314
306
  end
315
- # clear the target config
316
- tgt.config.clear
307
+ # clear the node config
308
+ node.config.clear
317
309
  end
318
- tgt.config.merge!(tgt_opts) # merge new + preserved config
310
+ node.config.merge!(nodecfg) # merge new + preserved config
319
311
  # force update of user if specified
320
- tgt.prepare_connection_for_user!(tgt_opts[:user]) if tgt_opts[:user]
321
- Inprovise::Sniffer.run_sniffers_for(tgt) if options[:sniff]
312
+ node.prepare_connection_for_user!(nodecfg[:user]) if nodecfg[:user]
313
+ Inprovise::Sniffer.run_sniffers_for(node) if options[:sniff]
322
314
  options[:group].each do |g|
323
315
  grp = Inprovise::Infrastructure.find(g)
324
316
  raise ArgumentError, "Unknown group #{g}" unless grp
325
- tgt.add_to(grp)
317
+ node.add_to(grp)
326
318
  end
327
319
  end
328
320
 
@@ -4,7 +4,6 @@
4
4
  # License:: Distributes under the same license as Ruby
5
5
 
6
6
  require 'open3'
7
- require 'ostruct'
8
7
 
9
8
  class Inprovise::ExecutionContext
10
9
 
@@ -49,7 +48,7 @@ class Inprovise::ExecutionContext
49
48
  @context.env(var)
50
49
  end
51
50
 
52
- def log(msg=nil)
51
+ def log(msg=nil, color=nil)
53
52
  @context.log(msg)
54
53
  end
55
54
 
@@ -97,18 +96,11 @@ class Inprovise::ExecutionContext
97
96
  @node = node
98
97
  @log = log
99
98
  @node.log_to(@log)
100
- @config = init_config(config || @node.config)
99
+ @config = Inprovise::Config.new(config || @node.config)
101
100
  @index = index
102
101
  @script = nil
103
102
  end
104
103
 
105
- def init_config(hash)
106
- hash.to_h.reduce(OpenStruct.new(hash)) do |os,(k,v)|
107
- os[k] = init_config(v) if Hash === v
108
- os
109
- end
110
- end
111
-
112
104
  def exec(blk, *args)
113
105
  if args.empty?
114
106
  DSL.new(self).instance_eval(&blk)
@@ -136,7 +128,13 @@ class Inprovise::ExecutionContext
136
128
  return self if user.nil? || user == node.user
137
129
  new_node = @node.for_user(user)
138
130
  new_log = @log.clone_for_node(new_node)
139
- self.class.new(new_node, new_log, @index, @config)
131
+ self.dup.setup_for_node!(new_node, new_log)
132
+ end
133
+
134
+ def setup_for_node!(node, log)
135
+ @node = node
136
+ @log = log
137
+ self
140
138
  end
141
139
 
142
140
  def run_local(cmd)
@@ -158,8 +156,8 @@ class Inprovise::ExecutionContext
158
156
  @node.env(var)
159
157
  end
160
158
 
161
- def log(msg=nil)
162
- @log.log(msg) if msg
159
+ def log(msg=nil, color=nil)
160
+ @log.log(msg, color) if msg
163
161
  @log
164
162
  end
165
163
 
@@ -216,7 +214,7 @@ class Inprovise::ExecutionContext
216
214
  curtask = @node.log.set_task(action_ref)
217
215
  curscript = @script
218
216
  @script = pkg
219
- @script.merge_configuration(self.config)
217
+ @script.update_configuration(self)
220
218
  begin
221
219
  exec(action, *args)
222
220
  ensure
@@ -12,13 +12,13 @@ module Inprovise::Infrastructure
12
12
  JSON.create_id = 'json_class'
13
13
 
14
14
  def self.symbolize_keys(hsh)
15
- return hsh unless Hash === hsh
15
+ return hsh unless ::Hash === hsh
16
16
  hsh.reduce({}) {|h, (k,v)| h[k.to_sym] = symbolize_keys(v); h }
17
17
  end
18
18
 
19
19
  class << self
20
20
  def targets
21
- @targets ||= Hash.new.extend(MonitorMixin)
21
+ @targets ||= ::Hash.new.extend(::MonitorMixin)
22
22
  end
23
23
  private :targets
24
24
 
@@ -3,13 +3,27 @@
3
3
  # Author:: Martin Corino
4
4
  # License:: Distributes under the same license as Ruby
5
5
 
6
+ require 'monitor'
7
+
6
8
  class Inprovise::Logger
7
9
  attr_accessor :node
8
10
  attr_reader :task
9
11
 
12
+ class << self
13
+ def streams
14
+ @streams ||= ::Hash.new.extend(::MonitorMixin).merge!({
15
+ :stdout => { :ios => $stdout, :buffer => [{col: nil, ln: '', cr: false}] },
16
+ :stderr => { :ios => $stderr, :buffer => [{col: nil, ln: '', cr: false}] }
17
+ })
18
+ end
19
+ end
20
+
10
21
  def initialize(node, task)
22
+ @streams = {
23
+ :stdout => { :ios => $stdout, :buffer => [{col: nil, ln: '', cr: false}] },
24
+ :stderr => { :ios => $stderr, :buffer => [{col: nil, ln: '', cr: false}] }
25
+ }
11
26
  @node = node
12
- @nl = true
13
27
  set_task(task)
14
28
  end
15
29
 
@@ -49,35 +63,145 @@ class Inprovise::Logger
49
63
  say(cmd, :blue)
50
64
  end
51
65
 
52
- def log(msg)
53
- say(msg)
66
+ def log(msg, color=nil)
67
+ say(msg, color)
68
+ end
69
+
70
+ def synchronize(&block)
71
+ self.class.streams.synchronize do
72
+ block.call
73
+ end if block_given?
74
+ end
75
+ private :synchronize
76
+
77
+ def ios(stream=:stdout)
78
+ self.class.streams[stream][:ios]
79
+ end
80
+ private :ios
81
+
82
+ def buffer(stream=:stdout)
83
+ self.class.streams[stream][:buffer]
84
+ end
85
+ private :buffer
86
+
87
+ def put(msg, color=nil, stream=:stdout)
88
+ streambuf = buffer(stream)
89
+ streambuf.last[:col] ||= color
90
+ streambuf.last[:ln] << msg
91
+ streambuf
92
+ end
93
+ private :put
94
+
95
+ def puts(msg, color=nil, stream=:stdout)
96
+ put(msg, color, stream) << {col:nil, ln:'',cr:false}
97
+ end
98
+ private :puts
99
+
100
+ def do_print(stream=:stdout)
101
+ streambuf = buffer(stream)
102
+ while lnbuf = streambuf.shift
103
+ clear_to_eol = lnbuf[:cr]
104
+ lnbuf[:ln].scan(/([^\r]*)(\r)?/) do |txt, cr|
105
+ # do we have a (full) line to print?
106
+ if cr || !streambuf.empty?
107
+ out = lnbuf[:col] ? txt.to_s.send(lnbuf[:col]) : txt
108
+ unless txt.empty?
109
+ ios(stream).print "\r".to_eol if clear_to_eol
110
+ ios(stream).print "#{@node.to_s} [#{@task.bold}] #{out}"
111
+ end
112
+ ios(stream).flush if cr
113
+ ios(stream).puts unless cr || (txt.empty? && !clear_to_eol)
114
+ clear_to_eol = cr ? true : false
115
+ break unless cr # next line or cr?
116
+ else
117
+ streambuf << if txt.empty?
118
+ # restart with empty line
119
+ {col:nil,ln:'',cr:clear_to_eol}
120
+ else
121
+ # stuff the remaining text back for a next round
122
+ {col:lnbuf[:col],ln:txt,cr:clear_to_eol}
123
+ end
124
+ return
125
+ end
126
+ end
127
+ end
128
+ end
129
+ private :do_print
130
+
131
+ def do_flush(stream)
132
+ lnbuf = buffer(stream).last
133
+ unless lnbuf[:ln].empty? && !lnbuf[:cr]
134
+ # add an empty line buffer to force output of current buffered contents
135
+ buffer(stream) << {col:nil, ln:'',cr:false}
136
+ do_print(stream)
137
+ end
138
+ end
139
+ private :do_flush
140
+
141
+ def flush(stream=:stdout)
142
+ synchronize do
143
+ do_flush(stream)
144
+ end
145
+ self
54
146
  end
55
147
 
56
- def print(msg)
57
- Thread.exclusive do
58
- $stdout.print "#{@node.to_s} [#{@task.bold}] " if @nl
59
- $stdout.print msg.sub("\r", "\r".to_eol << "#{@node.to_s} [#{@task.bold}] ")
60
- @nl = false
148
+ def flush_all
149
+ synchronize do
150
+ [:stderr, :stdout].each { |stream| do_flush(stream) }
61
151
  end
152
+ self
62
153
  end
63
154
 
64
- def println(msg)
65
- print(msg)
66
- Thread.exclusive { $stdout.puts; @nl = true }
155
+ def print(msg, color=nil, stream=:stdout)
156
+ synchronize do
157
+ put(msg, color, stream)
158
+ do_print(stream)
159
+ end
160
+ self
161
+ end
162
+
163
+ def println(msg, color=nil, stream=:stdout)
164
+ synchronize do
165
+ puts(msg, color, stream)
166
+ do_print(stream)
167
+ end
168
+ self
169
+ end
170
+
171
+ def redirect(msg, color, stream)
172
+ synchronize do
173
+ msg.to_s.scan(/([^\n]*)(\n\r|\n)?/) do |txt,sep|
174
+ if sep
175
+ puts(txt, color, stream)
176
+ else
177
+ put(txt, color, stream)
178
+ end
179
+ break unless sep
180
+ end
181
+ do_print(stream)
182
+ end
183
+ self
67
184
  end
185
+ private :redirect
68
186
 
69
187
  def stdout(msg, force=false)
70
- say(msg, :green) if force || Inprovise.verbosity>0
188
+ redirect(msg, :green, :stdout) if force || Inprovise.verbosity>0
71
189
  end
72
190
 
73
191
  def stderr(msg, force=false)
74
- say(msg, :red, $stderr) if force || Inprovise.verbosity>0
192
+ redirect(msg, :red, :stderr) if force || Inprovise.verbosity>0
75
193
  end
76
194
 
77
- def say(msg, color=nil, stream=$stdout)
78
- msg.to_s.split("\n").each do |line|
79
- out = color ? line.send(color) : line
80
- Thread.exclusive { stream.puts unless @nl; stream.puts "#{@node.to_s} [#{@task.bold}] #{out}"; @nl = true }
195
+ def say(msg, color=nil, stream=:stdout)
196
+ synchronize do
197
+ [:stderr, :stdout].each { |stream| do_flush(stream) }
198
+ streambuf = buffer(stream)
199
+ msg.to_s.scan(/([^\n]*)(\n\r|\n)?/) do |txt,sep|
200
+ puts(txt)
201
+ break unless sep
202
+ end
203
+ do_print(stream)
81
204
  end
205
+ self
82
206
  end
83
207
  end
@@ -252,8 +252,8 @@ class Inprovise::Infrastructure::Node < Inprovise::Infrastructure::Target
252
252
  output = exec.run(cmd, opts[:log])
253
253
  @history << {cmd:cmd, output:output}
254
254
  output
255
- rescue Exception
256
- raise RuntimeError, "Failed to communicate with [#{self}]"
255
+ rescue Inprovise::CmdChannel::Exception => ex
256
+ raise RuntimeError, "Failed to communicate with [#{self}] : #{ex.message}"
257
257
  end
258
258
  end
259
259
 
@@ -3,10 +3,8 @@
3
3
  # Author:: Martin Corino
4
4
  # License:: Distributes under the same license as Ruby
5
5
 
6
- require 'ostruct'
7
-
8
6
  class Inprovise::Script
9
- attr_reader :name, :dependencies, :actions, :children, :user
7
+ attr_reader :name, :dependencies, :actions, :children, :user, :configuration
10
8
 
11
9
  class DSL
12
10
  def initialize(script)
@@ -16,10 +14,12 @@ class Inprovise::Script
16
14
  def description(desc)
17
15
  @script.description(desc)
18
16
  end
17
+ alias :describe :description
19
18
 
20
- def configuration(cfg)
21
- @script.configuration(cfg)
19
+ def configure(cfg=nil, &block)
20
+ @script.configure(cfg, &block)
22
21
  end
22
+ alias :configuration :configure
23
23
 
24
24
  def depends_on(*scr_names)
25
25
  @script.depends_on(*scr_names)
@@ -73,48 +73,18 @@ class Inprovise::Script
73
73
  self.description.split("\n").collect {|ld| "#{"%-25s" % nm.shift.to_s}\t#{ld.strip}"}
74
74
  end
75
75
 
76
- def configuration(cfg=nil)
77
- @configuration = cfg if cfg
76
+ def configure(cfg=nil, &definition)
77
+ @configuration = Inprovise::Config.new.merge!(cfg) if cfg
78
+ command(:configure, &definition)
78
79
  @configuration
79
80
  end
80
81
 
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
82
+ def update_configuration(context)
83
+ if @configuration
84
+ context.config[self.name.to_sym] ||= Inprovise::Config.new
85
+ context.config[self.name.to_sym].update!(@configuration)
89
86
  end
90
87
  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
88
 
119
89
  def depends_on(*scr_names)
120
90
  scr_names.each do |scr_name|
@@ -33,11 +33,13 @@ class Inprovise::ScriptRunner
33
33
  def execute(command_name, config=nil)
34
34
  Inprovise.log.local("#{COMMANDS[command_name].first} #{script.name} #{COMMANDS[command_name].last} #{@node.to_s}")
35
35
  scrs = scripts
36
- scrs.reverse! if command_name.to_sym == :revert
37
- @log.say scrs.map(&:name).join(', ').yellow if Inprovise.verbosity > 0
38
36
  context = @perform ? Inprovise::ExecutionContext.new(@node, @log, @index, config) : Inprovise::MockExecutionContext.new(@node, @log, @index, config)
39
- context.config.command = command_name
40
- scrs.each { |script| script.merge_configuration(context.config) }
37
+ context.config.command = command_name.to_sym
38
+ scrs.each do |script|
39
+ execute_configuration(script, context)
40
+ end
41
+ scrs.reverse! if command_name.to_sym == :revert
42
+ @log.say(scrs.map(&:name).join(', '), :yellow) if Inprovise.verbosity > 0
41
43
  scrs.each do |script|
42
44
  send(:"execute_#{command_name}", script, context)
43
45
  end
@@ -49,6 +51,11 @@ class Inprovise::ScriptRunner
49
51
  @perform = true
50
52
  end
51
53
 
54
+ def execute_configuration(script, context)
55
+ script.update_configuration(context)
56
+ exec(script, :configure, context)
57
+ end
58
+
52
59
  def execute_apply(script, context)
53
60
  return unless should_run?(script, :apply, context)
54
61
  exec(script, :apply, context)
@@ -5,6 +5,6 @@
5
5
 
6
6
  module Inprovise
7
7
 
8
- VERSION = '0.2.7'
8
+ VERSION = '0.2.8'
9
9
 
10
10
  end
data/lib/inprovise.rb CHANGED
@@ -127,6 +127,7 @@ module Inprovise
127
127
  end
128
128
 
129
129
  require_relative './inprovise/version'
130
+ require_relative './inprovise/config'
130
131
  require_relative './inprovise/logger'
131
132
  require_relative './inprovise/cmd_channel'
132
133
  require_relative './inprovise/cmd_helper'
@@ -0,0 +1,124 @@
1
+ # Config tests for Inprovise
2
+ #
3
+ # Author:: Martin Corino
4
+ # License:: Distributes under the same license as Ruby
5
+
6
+ require_relative 'test_helper'
7
+
8
+ describe Inprovise::Config do
9
+
10
+ it 'creates an empty instance' do
11
+ @config = Inprovise::Config.new
12
+ @config.empty?.must_equal true
13
+ end
14
+
15
+ it 'creates an instance from Hash' do
16
+ @config = Inprovise::Config.new({ :key => 'value'})
17
+ @config.empty?.must_equal false
18
+ @config.has_key?(:key).must_equal true
19
+ @config[:key].must_equal 'value'
20
+ end
21
+
22
+ it 'creates an instance from Config' do
23
+ tmp = Inprovise::Config.new({ :key => 'value'})
24
+ @config = Inprovise::Config.new(tmp)
25
+ @config.empty?.must_equal false
26
+ @config.has_key?(:key).must_equal true
27
+ @config[:key].must_equal 'value'
28
+ end
29
+
30
+ it 'copies initialization values' do
31
+ tmp = Inprovise::Config.new({ :key => %w{value1 value2}, :key2 => { :another => 999} })
32
+ @config = Inprovise::Config.new(tmp)
33
+ @config.empty?.must_equal false
34
+ @config.has_key?(:key).must_equal true
35
+ tmp[:key] << 'value3'
36
+ tmp[:key].size.must_equal 3
37
+ @config[:key].size.must_equal 2
38
+ tmp[:key].first << 'X'
39
+ # but not recursively (except for hashes/config)
40
+ tmp[:key].first.must_equal 'value1X'
41
+ @config[:key].first.must_equal 'value1X'
42
+ tmp[:key2][:another] = 100
43
+ tmp[:key2][:another].must_equal 100
44
+ @config[:key2][:another].must_equal 999
45
+ end
46
+
47
+ it 'merges configurations' do
48
+ tmp = Inprovise::Config.new({ :key => 'value1', :key2 => 'value2'})
49
+ @config = Inprovise::Config.new({ :key => 'value'})
50
+ @config.merge!(tmp)
51
+ @config.empty?.must_equal false
52
+ @config.has_key?(:key).must_equal true
53
+ @config[:key].must_equal 'value1'
54
+ @config[:key2].must_equal 'value2'
55
+ end
56
+
57
+ it 'copies configurations' do
58
+ tmp = Inprovise::Config.new({ :key => 'value1', :key2 => 'value2'})
59
+ @config = Inprovise::Config.new({ :key => 'value'})
60
+ @config.copy!(tmp)
61
+ @config.empty?.must_equal false
62
+ @config.has_key?(:key).must_equal true
63
+ tmp[:key].tr!('1', '')
64
+ tmp[:key].must_equal 'value'
65
+ @config[:key].must_equal 'value1'
66
+ @config[:key2].must_equal 'value2'
67
+ end
68
+
69
+ it 'updates configurations' do
70
+ tmp = Inprovise::Config.new({ :key => 'value1', :key2 => 'value2'})
71
+ @config = Inprovise::Config.new({ :key => 'value'})
72
+ @config.update!(tmp)
73
+ @config.empty?.must_equal false
74
+ @config.has_key?(:key).must_equal true
75
+ tmp[:key].must_equal 'value1'
76
+ @config[:key].must_equal 'value'
77
+ @config[:key2].must_equal 'value2'
78
+ end
79
+
80
+ it 'iterates content' do
81
+ @config = Inprovise::Config.new({ :key => 'value1', :key2 => 'value2'})
82
+ @config.each do |k,v|
83
+ [:key, :key2].must_include k
84
+ v.must_equal 'value1' if k == :key
85
+ v.must_equal 'value2' if k == :key2
86
+ end
87
+ end
88
+
89
+ it 'returns Hash' do
90
+ @config = Inprovise::Config.new({ :key => 'value1', :key2 => 'value2'})
91
+ @config.to_h.must_be_kind_of Hash
92
+ @config.to_h[:key].must_equal 'value1'
93
+ end
94
+
95
+ it 'duplicates Config' do
96
+ tmp = Inprovise::Config.new({ :key => 'value1', :key2 => 'value2'})
97
+ @config = tmp.dup
98
+ @config.empty?.must_equal false
99
+ @config.has_key?(:key).must_equal true
100
+ tmp[:key].tr!('1', '')
101
+ tmp[:key].must_equal 'value'
102
+ @config[:key].must_equal 'value1'
103
+ @config[:key2].must_equal 'value2'
104
+ end
105
+
106
+ it 'supports method_missing access to members' do
107
+ @config = Inprovise::Config.new({ :key => 'value1', :key2 => 'value2'})
108
+ @config.key.must_equal 'value1'
109
+ @config[:key2].must_equal 'value2'
110
+ @config.key2 = 'value3'
111
+ @config.key2.must_equal 'value3'
112
+ @config[:key2].must_equal 'value3'
113
+ end
114
+
115
+ it 'converts hashes to Config' do
116
+ @config = Inprovise::Config.new({ :key => 'value1', :key2 => 'value2'})
117
+ @config[:key3] = {}
118
+ @config[:key3].must_be_kind_of Inprovise::Config
119
+ @config.key4 = {}
120
+ @config.key4.must_be_kind_of Inprovise::Config
121
+ @config.key5 ||= {}
122
+ @config.key5.must_be_kind_of Inprovise::Config
123
+ end
124
+ end
@@ -100,8 +100,8 @@ describe Inprovise::ScriptRunner do
100
100
 
101
101
  it 'validates before and after applying a script' do
102
102
  @runner = Inprovise::ScriptRunner.new(@node, 'validate')
103
- @runner.expects(:exec).once
104
- .with() { |script, cmd, context| script.name.must_equal('validate') && Inprovise::ExecutionContext === context && cmd == :apply }
103
+ @runner.expects(:exec).twice
104
+ .with() { |script, cmd, context| script.name.must_equal('validate') && Inprovise::ExecutionContext === context && (cmd == :configure || cmd == :apply) }
105
105
  @runner.expects(:is_valid?).twice
106
106
  .with() { |script, context| script.name.must_equal('validate') && Inprovise::ExecutionContext === context }
107
107
  .returns(false, true)
@@ -135,8 +135,8 @@ describe Inprovise::ScriptRunner do
135
135
 
136
136
  it 'validates before reverting a script' do
137
137
  @runner = Inprovise::ScriptRunner.new(@node, 'validate')
138
- @runner.expects(:exec).once
139
- .with() { |script, cmd, context| script.name.must_equal('validate') && Inprovise::ExecutionContext === context && cmd == :revert }
138
+ @runner.expects(:exec).twice
139
+ .with() { |script, cmd, context| script.name.must_equal('validate') && Inprovise::ExecutionContext === context && (cmd == :configure || cmd == :revert) }
140
140
  @runner.expects(:is_valid?).once
141
141
  .with() { |script, context| script.name.must_equal('validate') && Inprovise::ExecutionContext === context }
142
142
  .returns(true)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inprovise
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.7
4
+ version: 0.2.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Martin Corino
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-09-08 00:00:00.000000000 Z
11
+ date: 2016-09-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colored
@@ -108,6 +108,7 @@ files:
108
108
  - lib/inprovise/cli/provision.rb
109
109
  - lib/inprovise/cmd_channel.rb
110
110
  - lib/inprovise/cmd_helper.rb
111
+ - lib/inprovise/config.rb
111
112
  - lib/inprovise/control.rb
112
113
  - lib/inprovise/execution_context.rb
113
114
  - lib/inprovise/group.rb
@@ -134,6 +135,7 @@ files:
134
135
  - lib/inprovise/version.rb
135
136
  - test/cli_test.rb
136
137
  - test/cli_test_helper.rb
138
+ - test/config_test.rb
137
139
  - test/dsl_test.rb
138
140
  - test/fixtures/example.txt
139
141
  - test/fixtures/include.rb
@@ -175,6 +177,7 @@ summary: Simple, easy and intuitive infrastructure provisioning
175
177
  test_files:
176
178
  - test/cli_test.rb
177
179
  - test/cli_test_helper.rb
180
+ - test/config_test.rb
178
181
  - test/dsl_test.rb
179
182
  - test/fixtures/example.txt
180
183
  - test/fixtures/include.rb