inprovise 0.2.7 → 0.2.8

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 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