elecksee 1.0.22 → 1.1.0

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/lib/elecksee/lxc.rb CHANGED
@@ -1,8 +1,9 @@
1
- require 'elecksee/helpers/base'
1
+ require 'elecksee'
2
2
  require 'securerandom'
3
3
  require 'shellwords'
4
4
  require 'pathname'
5
5
  require 'tmpdir'
6
+ require 'rye'
6
7
 
7
8
  class Lxc
8
9
 
@@ -10,6 +11,10 @@ class Lxc
10
11
  # begin with '/', and that's dumb. So we'll make our own Pathname,
11
12
  # with a #join that uses File
12
13
  class Pathname < ::Pathname
14
+ # Join arguments using ::File.join
15
+ #
16
+ # @param args [String] argument list
17
+ # @return [String]
13
18
  def join(*args)
14
19
  self.class.new(::File.join(self.to_path, *args))
15
20
  end
@@ -17,16 +22,39 @@ class Lxc
17
22
 
18
23
  include Helpers
19
24
 
20
- attr_reader :name, :base_path, :lease_file, :preferred_device
25
+ # @return [String] name of container
26
+ attr_reader :name
27
+ # @return [String] base path of container
28
+ attr_reader :base_path
29
+ # @return [String] path to dnsmasq lease file
30
+ attr_reader :lease_file
31
+ # @return [String] network device to use for ssh connection
32
+ attr_reader :preferred_device
33
+ # @return [String, NilClass] path to default ssh key
34
+ attr_accessor :ssh_key
35
+ # @return [String, NilClass] ssh password
36
+ attr_accessor :ssh_password
37
+ # @return [String, NilClass] ssh user
38
+ attr_accessor :ssh_user
21
39
 
22
40
  class << self
23
41
 
24
42
  include Helpers
25
43
 
44
+ # @return [Truthy, String] use sudo when required (set to string for custom sudo command)
26
45
  attr_accessor :use_sudo
46
+ # @return [String] base path for containers
27
47
  attr_accessor :base_path
48
+ # @return [Symbol] :mixlib_shellout or :childprocess
28
49
  attr_accessor :shellout_helper
29
-
50
+ # @return [String, NilClass] path to default ssh key
51
+ attr_accessor :default_ssh_key
52
+ # @return [String, NilClass] default ssh password
53
+ attr_accessor :default_ssh_password
54
+ # @return [String, NilClass] default ssh user
55
+ attr_accessor :default_ssh_user
56
+
57
+ # @return [String] sudo command
30
58
  def sudo
31
59
  case use_sudo
32
60
  when TrueClass
@@ -36,44 +64,56 @@ class Lxc
36
64
  end
37
65
  end
38
66
 
67
+ # @return [String] base path for containers
39
68
  def base_path
40
69
  @base_path || '/var/lib/lxc'
41
70
  end
42
71
 
43
- # List running containers
72
+ # Currently running container names
73
+ #
74
+ # @return [Array<String>]
44
75
  def running
45
76
  full_list[:running]
46
77
  end
47
78
 
48
- # List stopped containers
79
+ # Currently stopped container names
80
+ #
81
+ # @return [Array<String>]
49
82
  def stopped
50
83
  full_list[:stopped]
51
84
  end
52
85
 
53
- # List frozen containers
86
+ # Currently frozen container names
87
+ #
88
+ # @return [Array<String>]
54
89
  def frozen
55
90
  full_list[:frozen]
56
91
  end
57
92
 
58
- # name:: name of container
59
- # Returns if container exists
93
+ # Container currently exists
94
+ #
95
+ # @param name [String] name of container
96
+ # @return [TrueClass, FalseClass]
60
97
  def exists?(name)
61
98
  list.include?(name)
62
99
  end
63
100
 
64
- # List of containers
101
+ # List of all containers
102
+ #
103
+ # @return [Array<String>] container names
65
104
  def list
66
- Dir.glob(File.join(base_path, '*')).map do |item|
67
- if(File.directory?(item) && File.exists?(File.join(item, 'config')))
68
- File.basename(item)
69
- end
70
- end.compact
105
+ run_command('lxc-ls', :sudo => true).
106
+ stdout.split(/\s/).map(&:strip).compact
71
107
  end
72
108
 
73
- # name:: Name of container
74
- # Returns information about given container
109
+ # Information available for given container
110
+ #
111
+ # @param name [String] name of container
112
+ # @return [Hash]
75
113
  def info(name)
76
- info = run_command("#{sudo}lxc-info -n #{name}", :allow_failure_retry => 3, :allow_failure => true)
114
+ if(exists?(name))
115
+ info = run_command("#{sudo}lxc-info -n #{name}", :allow_failure_retry => 3, :allow_failure => true)
116
+ end
77
117
  if(info)
78
118
  Hash[
79
119
  info.stdout.split("\n").map do |string|
@@ -93,7 +133,9 @@ class Lxc
93
133
  end
94
134
  end
95
135
 
96
- # Return full container information list
136
+ # Full list of containers grouped by state
137
+ #
138
+ # @return [Hash]
97
139
  def full_list
98
140
  res = {}
99
141
  list.each do |item|
@@ -104,51 +146,69 @@ class Lxc
104
146
  res
105
147
  end
106
148
 
107
- # ip:: IP address
108
- # Returns if IP address is alive
149
+ # IP address is currently active
150
+ #
151
+ # @param ip [String]
152
+ # @return [TrueClass, FalseClass]
109
153
  def connection_alive?(ip)
110
154
  %x{ping -c 1 -W 1 #{ip}}
111
155
  $?.exitstatus == 0
112
156
  end
113
157
  end
114
158
 
115
- # name:: name of container
116
- # args:: Argument hash
117
- # - :base_path -> path to container directory
118
- # - :dnsmasq_lease_file -> path to lease file
119
- # - :net_device -> network device to use within container for ssh connection
159
+ # Create new instance
160
+ #
161
+ # @param name [String] container name
162
+ # @param args [Hash]
163
+ # @option args [String] :base_path path to container
164
+ # @option args [String] :dnsmasq_lease_file path to lease file
165
+ # @option args [String] :net_device network device within container for ssh connection
166
+ # @option args [String] :ssh_key path to ssh key
167
+ # @option args [String] :ssh_password ssh password
168
+ # @option args [String] :ssh_user ssh user
120
169
  def initialize(name, args={})
121
170
  @name = name
122
171
  @base_path = args[:base_path] || self.class.base_path
123
172
  @lease_file = args[:dnsmasq_lease_file] || '/var/lib/misc/dnsmasq.leases'
124
173
  @preferred_device = args[:net_device]
174
+ @ssh_key = args.fetch(:ssh_key, self.class.default_ssh_key)
175
+ @ssh_password = args.fetch(:ssh_password, self.class.default_ssh_password)
176
+ @ssh_user = args.fetch(:ssh_user, self.class.default_ssh_user)
125
177
  end
126
178
 
127
- # Returns if container exists
179
+ # @return [TrueClass, FalseClass] container exists
128
180
  def exists?
129
181
  self.class.exists?(name)
130
182
  end
131
183
 
132
- # Returns if container is running
184
+ # @return [TrueClass, FalseClass] container is currently running
133
185
  def running?
134
186
  self.class.info(name)[:state] == :running
135
187
  end
136
188
 
137
- # Returns if container is stopped
189
+ # @return [TrueClass, FalseClass] container is currently stopped
138
190
  def stopped?
139
191
  self.class.info(name)[:state] == :stopped
140
192
  end
141
193
 
142
- # Returns if container is frozen
194
+ # @return [TrueClass, FalseClass] container is currently frozen
143
195
  def frozen?
144
196
  self.class.info(name)[:state] == :frozen
145
197
  end
146
198
 
147
- # retries:: Number of discovery attempt (3 second sleep intervals)
148
- # Returns container IP
199
+ # Current IP address of container
200
+ #
201
+ # @param retries [Integer] number of times to retry discovery
202
+ # @param raise_on_fail [TrueClass, FalseClass] raise exception on failure
203
+ # @return [String, NilClass] IP address
204
+ # @note retries are executed on 3 second sleep intervals
149
205
  def container_ip(retries=0, raise_on_fail=false)
150
206
  (retries.to_i + 1).times do
151
- ip = proc_detected_address || hw_detected_address || leased_address || lxc_stored_address
207
+ ip = info_detected_address ||
208
+ proc_detected_address ||
209
+ hw_detected_address ||
210
+ leased_address ||
211
+ lxc_stored_address
152
212
  if(ip.is_a?(Array))
153
213
  # Filter any found loopbacks
154
214
  ip.delete_if{|info| info[:device].start_with?('lo') }
@@ -168,7 +228,9 @@ class Lxc
168
228
  raise "Failed to detect live IP address for container: #{name}" if raise_on_fail
169
229
  end
170
230
 
171
- # Container address via lxc config file
231
+ # Container address defined within the container's config file
232
+ #
233
+ # @return [String, NilClass] IP address
172
234
  def lxc_stored_address
173
235
  if(File.exists?(container_config))
174
236
  ip = File.readlines(container_config).detect{|line|
@@ -183,7 +245,9 @@ class Lxc
183
245
  end
184
246
  end
185
247
 
186
- # Container address via dnsmasq lease
248
+ # Container address discovered via dnsmasq lease
249
+ #
250
+ # @return [String, NilClass] IP address
187
251
  def leased_address
188
252
  ip = nil
189
253
  if(File.exists?(@lease_file))
@@ -202,6 +266,16 @@ class Lxc
202
266
  end
203
267
  end
204
268
 
269
+ # Container address discovered via info
270
+ #
271
+ # @return [String, NilClass] IP address
272
+ def info_detected_address
273
+ self.class.info(name)[:ip]
274
+ end
275
+
276
+ # Container address discovered via device
277
+ #
278
+ # @return [String, NilClass] IP address
205
279
  def hw_detected_address
206
280
  if(container_config.readable?)
207
281
  hw = File.readlines(container_config).detect{|line|
@@ -222,6 +296,10 @@ class Lxc
222
296
  end
223
297
  end
224
298
 
299
+ # Container address discovered via process
300
+ #
301
+ # @param base [String] path to netns
302
+ # @return [String, NilClass] IP address
225
303
  def proc_detected_address(base='/run/netns')
226
304
  if(pid != -1)
227
305
  Dir.mktmpdir do |t_dir|
@@ -240,18 +318,19 @@ class Lxc
240
318
  end
241
319
  end
242
320
 
243
- # Full path to container
321
+ # @return [Pathname] path to container
244
322
  def container_path
245
323
  Pathname.new(@base_path).join(name)
246
324
  end
247
325
  alias_method :path, :container_path
248
326
 
249
- # Full path to container configuration file
327
+ # @return [Pathname] path to configuration file
250
328
  def container_config
251
329
  container_path.join('config')
252
330
  end
253
331
  alias_method :config, :container_config
254
332
 
333
+ # @return [Pathname] path to rootfs
255
334
  def container_rootfs
256
335
  if(File.exists?(config))
257
336
  r_path = File.readlines(config).detect do |line|
@@ -262,19 +341,28 @@ class Lxc
262
341
  end
263
342
  alias_method :rootfs, :container_rootfs
264
343
 
344
+ # Expand path within containers rootfs
345
+ #
346
+ # @param path [String] relative path
347
+ # @return [Pathname] full path within container
265
348
  def expand_path(path)
266
349
  container_rootfs.join(path)
267
350
  end
268
351
 
352
+ # @return [Symbol] current state
269
353
  def state
270
354
  self.class.info(name)[:state]
271
355
  end
272
356
 
357
+ # @return [Integer, Symbol] process ID or :unknown
273
358
  def pid
274
359
  self.class.info(name)[:pid]
275
360
  end
276
361
 
277
362
  # Start the container
363
+ #
364
+ # @param args [Symbol] argument list (:no_daemon to foreground)
365
+ # @return [self]
278
366
  def start(*args)
279
367
  if(args.include?(:no_daemon))
280
368
  run_command("lxc-start -n #{name}", :sudo => true)
@@ -282,52 +370,73 @@ class Lxc
282
370
  run_command("lxc-start -n #{name} -d", :sudo => true)
283
371
  wait_for_state(:running)
284
372
  end
373
+ self
285
374
  end
286
375
 
287
376
  # Stop the container
377
+ #
378
+ # @return [self]
288
379
  def stop
289
380
  run_command("lxc-stop -n #{name}", :allow_failure_retry => 3, :sudo => true)
290
381
  wait_for_state(:stopped)
382
+ self
291
383
  end
292
384
 
293
385
  # Freeze the container
386
+ #
387
+ # @return [self]
294
388
  def freeze
295
389
  run_command("lxc-freeze -n #{name}", :sudo => true)
296
390
  wait_for_state(:frozen)
391
+ self
297
392
  end
298
393
 
299
394
  # Unfreeze the container
395
+ #
396
+ # @return [self]
300
397
  def unfreeze
301
398
  run_command("lxc-unfreeze -n #{name}", :sudo => true)
302
399
  wait_for_state(:running)
400
+ self
303
401
  end
304
402
 
305
403
  # Shutdown the container
404
+ #
405
+ # @return [self]
306
406
  def shutdown
307
- run_command("lxc-shutdown -n #{name}", :sudo => true)
308
- wait_for_state(:stopped, :timeout => 120)
309
407
  # This block is for fedora/centos/anyone else that does not like lxc-shutdown
310
408
  if(running?)
311
409
  container_command('shutdown -h now')
312
410
  wait_for_state(:stopped, :timeout => 120)
313
411
  # If still running here, something is wrong
314
412
  if(running?)
315
- raise "Failed to shutdown container: #{name}"
413
+ run_command("lxc-stop -n #{name}", :sudo => true)
414
+ wait_for_state(:stopped, :timeout => 120)
415
+ if(running?)
416
+ raise "Failed to shutdown container: #{name}"
417
+ end
316
418
  end
317
419
  end
420
+ self
318
421
  end
319
422
 
320
423
  # Destroy the container
424
+ #
425
+ # @return [self]
321
426
  def destroy
322
427
  unless stopped?
323
428
  stop
324
429
  end
325
430
  run_command("lxc-destroy -n #{name}", :sudo => true)
431
+ self
326
432
  end
327
433
 
328
- # command:: command string
329
- # opts:: option hash (:networking)
330
- # Execute command string within container
434
+ # Execute command within the container
435
+ #
436
+ # @param command [String] command to execute
437
+ # @param opts [Hash] options passed to #tmp_execute_script
438
+ # @return [CommandResult]
439
+ # @note container must be stopped
331
440
  def execute(command, opts={})
332
441
  if(stopped?)
333
442
  cmd = Shellwords.split(command)
@@ -347,22 +456,47 @@ class Lxc
347
456
  end
348
457
  end
349
458
 
459
+ # Execute command within running container
460
+ #
461
+ # @param command [String]
462
+ # @param args [Hash]
463
+ # @option args [Integer] :timeout
464
+ # @option args [TrueClass, FalseClass] :live_stream
465
+ # @option args [TrueClass, FalseClass] :raise_on_failure
466
+ # @return [CommandResult]
350
467
  def direct_container_command(command, args={})
351
468
  begin
352
- run_command(
353
- "ssh root@#{args[:ip] || container_ip} -i /opt/hw-lxc-config/id_rsa -oStrictHostKeyChecking=no '#{command}'",
354
- :sudo => true,
355
- :timeout => args[:timeout],
356
- :live_stream => args[:live_stream]
469
+ box = Rye::Box.new(
470
+ args.fetch(:ip, container_ip(3)),
471
+ :user => ssh_user,
472
+ :password => ssh_password,
473
+ :password_prompt => false,
474
+ :keys => [ssh_key],
475
+ :safe => false,
476
+ :paranoid => false
357
477
  )
358
- true
359
- rescue
360
- raise if args[:raise_on_failure]
361
- false
478
+ result = box.execute command
479
+ CommandResult.new(result)
480
+ rescue Rye::Err => e
481
+ if(args[:raise_on_failure])
482
+ raise CommandFailed.new(
483
+ "Command failed: #{command}",
484
+ CommandResult.new(e)
485
+ )
486
+ else
487
+ false
488
+ end
362
489
  end
363
490
  end
364
491
  alias_method :knife_container, :direct_container_command
365
492
 
493
+ # Wait for container to reach given state
494
+ #
495
+ # @param desired_state [Symbol]
496
+ # @param args [Hash]
497
+ # @option args [Integer] :timeout
498
+ # @option args [Numeric] :sleep_interval
499
+ # @return [self]
366
500
  def wait_for_state(desired_state, args={})
367
501
  args[:sleep_interval] ||= 1.0
368
502
  wait_total = 0.0
@@ -370,10 +504,18 @@ class Lxc
370
504
  sleep(args[:sleep_interval])
371
505
  wait_total += args[:sleep_interval]
372
506
  end
507
+ self
373
508
  end
374
509
 
375
- # command:: command string
376
- # Write command to temporary script with networking support wrap
510
+ # Write command to temporary file with networking enablement wrapper
511
+ # and yield relative path
512
+ #
513
+ # @param command [String]
514
+ # @param opts [Hash]
515
+ # @options opts [TrueClass, FalseClass] enable networking
516
+ # @yield block to execute
517
+ # @yieldparam command_script [String] path to file
518
+ # @return [Object] result of block
377
519
  def tmp_execute_script(command, opts)
378
520
  script_path = "tmp/#{SecureRandom.uuid}"
379
521
  File.open(rootfs.join(script_path), 'w') do |file|
@@ -400,23 +542,12 @@ EOS
400
542
  end
401
543
  end
402
544
 
403
- # Detect HOME environment variable. If not an acceptable
404
- # value, set to /root or /tmp
405
- def detect_home(set_if_missing=false)
406
- if(ENV['HOME'] && Pathname.new(ENV['HOME']).absolute?)
407
- ENV['HOME']
408
- else
409
- home = File.directory?('/root') && File.writable?('/root') ? '/root' : '/tmp'
410
- if(set_if_missing)
411
- ENV['HOME'] = home
412
- end
413
- home
414
- end
415
- end
416
-
417
- # cmd:: Shell command string
418
- # retries:: Number of retry attempts (1 second sleep interval)
419
- # Runs command in container via ssh
545
+ # Run command within container
546
+ #
547
+ # @param cmd [String] command to run
548
+ # @param retries [Integer] number of retry attempts
549
+ # @return [CommandResult]
550
+ # @note retries are over 1 second intervals
420
551
  def container_command(cmd, retries=1)
421
552
  begin
422
553
  detect_home(true)
@@ -438,18 +569,9 @@ EOS
438
569
  end
439
570
  end
440
571
 
441
- def log
442
- if(defined?(Chef))
443
- Chef::Log
444
- else
445
- unless(@logger)
446
- require 'logger'
447
- @logger = Logger.new('/dev/null')
448
- end
449
- @logger
450
- end
451
- end
452
-
453
572
  end
454
573
 
574
+ # Make default settings
455
575
  Lxc.shellout_helper = :mixlib_shellout
576
+ Lxc.default_ssh_key = '/opt/hw-lxc-config/id_rsa'
577
+ Lxc.default_ssh_user = 'root'
@@ -1,11 +1,21 @@
1
+ require 'elecksee'
2
+
1
3
  class Lxc
4
+ # Configuration file interface
2
5
  class FileConfig
3
6
 
7
+ # @return [Array]
4
8
  attr_reader :network
9
+ # @return [String] path to configuration file
5
10
  attr_reader :base
6
11
 
7
12
  class << self
8
13
 
14
+ # Convert object to Hash if possible
15
+ #
16
+ # @param thing [Object]
17
+ # @return [Hash]
18
+ # @note used mostly for the lxc resource within chef
9
19
  def convert_to_hash(thing)
10
20
  if(defined?(Chef) && thing.is_a?(Chef::Resource))
11
21
  result = Hash[*(
@@ -26,6 +36,10 @@ class Lxc
26
36
  result || thing
27
37
  end
28
38
 
39
+ # Symbolize keys within hash
40
+ #
41
+ # @param thing [Hashish]
42
+ # @return [Hash]
29
43
  def symbolize_hash(thing)
30
44
  if(defined?(Mash))
31
45
  Mash.new(thing)
@@ -38,6 +52,10 @@ class Lxc
38
52
  end
39
53
  end
40
54
 
55
+ # Generate configuration file contents
56
+ #
57
+ # @param resource [Hashish]
58
+ # @return [String]
41
59
  def generate_config(resource)
42
60
  resource = symbolize_hash(convert_to_hash(resource))
43
61
  config = []
@@ -79,6 +97,9 @@ class Lxc
79
97
 
80
98
  end
81
99
 
100
+ # Create new instance
101
+ #
102
+ # @param path [String]
82
103
  def initialize(path)
83
104
  raise 'LXC config file not found' unless File.exists?(path)
84
105
  @path = path
@@ -89,6 +110,9 @@ class Lxc
89
110
 
90
111
  private
91
112
 
113
+ # Parse the configuration file
114
+ #
115
+ # @return [TrueClass]
92
116
  def parse!
93
117
  cur_net = nil
94
118
  File.readlines(@path).each do |line|
@@ -115,6 +139,8 @@ class Lxc
115
139
  end
116
140
  end
117
141
  @network << cur_net if cur_net
142
+ true
118
143
  end
144
+
119
145
  end
120
146
  end