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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c3d89dd64aa0c3a0ef232692735c75c415d0a994
4
+ data.tar.gz: 9b769a6d71ff3efa3320004e31f7bc18453ae66b
5
+ SHA512:
6
+ metadata.gz: 0eb4a7dc26256da9e8ea791721391cfb89fe0edd041831a798654e6385ea71290070f8a18f8b6ca442cf025ebfba758375d61293a10f49dc5e69e4a3a841e896
7
+ data.tar.gz: e22ebacc0cabf3108d3ab04365b01c683caf36162155530cf2bfeddcb12a190ff4e2e32b63bf788e767ef6bb8df24c5438385bb11f1193febe881ccbcebc5191
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## v1.1.0
2
+ * Update all documentation to yardoc
3
+ * Group classes into logical namespaces
4
+ * Define expected returns for methods
5
+ * Remove calls to lxc-shutdown (not always available)
6
+ * Always attempt in container halt prior to lxc-stop
7
+ * Use lxc-ls for container listing to prevent permission issues
8
+ * Use the Rye library under the hood for container connects
9
+ * Use ChildProcess for shelling out
10
+
1
11
  ## v1.0.22
2
12
  * Update underlying implementation for execute
3
13
  * Provide better info interpretation
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,25 @@
1
+ # Contributing
2
+
3
+ ## Branches
4
+
5
+ ### `master` branch
6
+
7
+ The master branch is the current stable released version.
8
+
9
+ ### `develop` branch
10
+
11
+ The develop branch is the current edge of development.
12
+
13
+ ## Pull requests
14
+
15
+ * https://github.com/chrisroberts/elecksee/pulls
16
+
17
+ Please base all pull requests of the `develop` branch. Merges to
18
+ `master` only occur through the `develop` branch. Pull requests
19
+ based on `master` will likely be cherry picked.
20
+
21
+ ## Issues
22
+
23
+ Need to report an issue? Use the github issues:
24
+
25
+ * https://github.com/chrisroberts/elecksee/issues
data/README.md CHANGED
@@ -2,20 +2,57 @@
2
2
 
3
3
  An LXC library for Ruby
4
4
 
5
- ## Usage
5
+ ## Basic usage
6
6
 
7
7
  ```ruby
8
- require 'elecksee/lxc'
8
+ require 'elecksee'
9
9
 
10
10
  lxc = Lxc.new('container')
11
11
  lxc.start unless lxc.running?
12
12
  ```
13
13
 
14
- ## Included
14
+ ## Permissions
15
15
 
16
- * Container inspect and interaction (`Lxc`)
17
- * Container cloning (`Lxc::Clone`)
18
- * Ephemeral containers (`Lxc::Ephemeral`)
16
+ Root access is required for most operations. This library can
17
+ be utilized from a root user but is built to use sudo as required.
18
+ To enable sudo, use:
19
+
20
+ ```ruby
21
+ Lxc.use_sudo = true
22
+ ```
23
+
24
+ If you require a custom sudo for things like rvm, use:
25
+
26
+ ```ruby
27
+ Lxc.use_sudo = 'rvmsudo'
28
+ ```
29
+
30
+ ## What's in the box
31
+
32
+ ### Lxc
33
+
34
+ Container inspection and interaction. Will provide state
35
+ information about the container as well as providing an
36
+ interaction interface for running commands within the
37
+ container and changing its state (stop, start, freeze, thaw).
38
+
39
+ ```ruby
40
+ my_lxc = Lxc.new('my_container')
41
+ puts "Address: #{my_lxc.container_ip}"
42
+ puts "State: #{my_lxc.state}"
43
+ ```
44
+
45
+ ### Lxc::Ephemeral
46
+
47
+ Create ephemeral containers from existing stopped containers. Utilizes
48
+ an overlay filesystem to leave original container untouched. All ephemeral
49
+ resources are removed once the container is halted.
50
+
51
+ ### Lxc::Clone
52
+
53
+ Make clones of existing stopped containers. Allows for utilizing optional
54
+ storage backends like full copies, overlay directories, virtual block
55
+ device or fs specific like btrfs snapshots.
19
56
 
20
57
  ### Notes
21
58
 
data/elecksee.gemspec CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |s|
10
10
  s.description = 'LXC helpers'
11
11
  s.require_path = 'lib'
12
12
  s.executables = %w(lxc-awesome-ephemeral)
13
- s.add_dependency 'mixlib-shellout'
14
- s.add_dependency 'net-ssh'
15
- s.files = Dir['**/*']
13
+ s.add_dependency 'childprocess'
14
+ s.add_dependency 'rye'
15
+ s.files = Dir['{bin,lib}/**/**/*'] + %w(elecksee.gemspec README.md CHANGELOG.md LICENSE CONTRIBUTING.md)
16
16
  end
@@ -1,12 +1,7 @@
1
- %w(
2
- helpers/base helpers/options helpers/copies lxc
3
- storage/overlay_directory storage/overlay_mount
4
- storage/virtual_device
5
- ).each do |path|
6
- require "elecksee/#{path}"
7
- end
1
+ require 'elecksee'
8
2
 
9
3
  class Lxc
4
+ # Clone existing containers
10
5
  class Clone
11
6
 
12
7
  include Helpers
@@ -25,9 +20,14 @@ class Lxc
25
20
  option :gateway, '-G', :string, :desc => 'Custom gateway'
26
21
  option :netmask, '-N', :string, :default => '255.255.255.0', :desc => 'Custom netmask'
27
22
 
28
- # Hash containing original and new container instances
23
+ # @return [Hash] original and new container instances
29
24
  attr_reader :lxcs
30
25
 
26
+ # Create new instance
27
+ #
28
+ # @param args [Hash]
29
+ # @option args [String] :original existing container name
30
+ # @option args [String] :new new container name
31
31
  def initialize(args={})
32
32
  configure!(args)
33
33
  @lxcs = {}
@@ -37,31 +37,40 @@ class Lxc
37
37
  validate!
38
38
  end
39
39
 
40
+ # Create the clone
41
+ #
42
+ # @return [Lxc] new clone
40
43
  def clone!
41
44
  begin
42
45
  copy_original
43
46
  update_naming(:no_config)
44
47
  apply_custom_addressing if ipaddress
45
- lxcs[:new]
48
+ lxc
46
49
  rescue Exception
47
50
  @created.map(&:destroy)
48
51
  raise
49
52
  end
50
53
  end
51
-
54
+
52
55
  private
53
56
 
54
57
  alias_method :name, :new_name
55
-
56
- # Returns new lxc instance
58
+
59
+ # @return [Lxc] new lxc instance
57
60
  def lxc
58
61
  lxcs[:new]
59
62
  end
60
63
 
64
+ # Add to list of created items
65
+ #
66
+ # @param thing [Object] item created
61
67
  def created(thing)
62
68
  @created << thing
63
69
  end
64
70
 
71
+ # Validate current state
72
+ #
73
+ # @return [TrueClass]
65
74
  def validate!
66
75
  unless(lxcs[:original].exists?)
67
76
  raise "Requested `original` container does not exist (#{original})"
@@ -72,8 +81,12 @@ class Lxc
72
81
  if(lxcs[:original].running?)
73
82
  raise "Requested `original` container is current running (#{original})"
74
83
  end
84
+ true
75
85
  end
76
86
 
87
+ # Create copy of original container
88
+ #
89
+ # @return [TrueClass]
77
90
  def copy_original
78
91
  copy_init
79
92
  if(device)
@@ -86,38 +99,56 @@ class Lxc
86
99
  rootfs_dir = copy_fs
87
100
  end
88
101
  update_rootfs(rootfs_dir)
102
+ true
89
103
  end
90
104
 
105
+ # Initialize container copy (base file copy)
106
+ #
107
+ # @return [TrueClass]
91
108
  def copy_init
92
- directory = CloneDirectory.new(lxcs[:new].name, :dir => File.dirname(lxcs[:original].path.to_s))
109
+ directory = Storage::CloneDirectory.new(lxcs[:new].name, :dir => File.dirname(lxcs[:original].path.to_s))
93
110
  created(directory)
94
111
  %w(config fstab).each do |file|
95
112
  command("cp '#{lxcs[:original].path.join(file)}' '#{directory.target_path}'", :sudo => true)
96
113
  end
114
+ true
97
115
  end
98
116
 
117
+ # Copy into file system directory
118
+ #
119
+ # @return [String] path to new rootfs
99
120
  def copy_fs
100
- directory = CloneDirectory.new(lxcs[:new].name, :dir => File.dirname(lxcs[:original].path.to_s))
121
+ directory = Storage::CloneDirectory.new(lxcs[:new].name, :dir => File.dirname(lxcs[:original].path.to_s))
101
122
  created(directory)
102
123
  command("rsync -ax '#{lxcs[:original].rootfs}/' '#{File.join(directory.target_path, 'rootfs')}/'", :sudo => true)
103
124
  File.join(directory.target_path, 'rootfs')
104
125
  end
105
126
 
127
+ # Copy into new virtual block device
128
+ #
129
+ # @return [String] path to new rootfs
106
130
  def copy_vbd
107
- storage = VirtualDevice.new(lxcs[:new].name, :tmp_dir => '/opt/lxc-vbd')
131
+ storage = Storage::VirtualDevice.new(lxcs[:new].name, :tmp_dir => '/opt/lxc-vbd')
108
132
  created(storage)
109
133
  command("rsync -ax '#{lxcs[:original].rootfs}/' '#{storage.target_path}/'", :sudo => true)
110
134
  storage.target_path
111
135
  end
112
136
 
137
+ # Copy into new LVM partition
138
+ #
139
+ # @note not implemented
140
+ # @todo implement
113
141
  def copy_lvm
114
142
  raise 'Not implemented'
115
143
  end
116
144
 
145
+ # Copy into new btrfs subvolume snapshot
146
+ #
147
+ # @return [String] path to new rootfs
148
+ # @todo remove on failure
117
149
  def copy_btrfs
118
150
  rootfs_path = lxcs[:new].path.join('rootfs')
119
151
  command("btrfs subvolume snapshot '#{lxcs[:original].rootfs}' '#{rootfs_path}'", :sudo => true)
120
- # TODO: Remove on failure
121
152
  rootfs_path
122
153
  end
123
154
  end
@@ -1,18 +1,11 @@
1
+ require 'elecksee'
1
2
  require 'securerandom'
2
3
  require 'fileutils'
3
4
  require 'tmpdir'
4
5
  require 'etc'
5
6
 
6
- %w(
7
- helpers/base helpers/options helpers/copies lxc
8
- storage/overlay_directory storage/overlay_mount
9
- storage/virtual_device
10
- ).each do |path|
11
- require "elecksee/#{path}"
12
- end
13
-
14
7
  class Lxc
15
-
8
+ # Create ephemeral containers
16
9
  class Ephemeral
17
10
 
18
11
  include Helpers
@@ -34,15 +27,27 @@ class Lxc
34
27
  option :tmp_dir, '-T', :string, :default => '/tmp/lxc/ephemerals', :aliases => 'tmp-dir', :desc => 'Directory of ephemeral temp files'
35
28
  option :ephemeral_command, '-C', :string, :aliases => 'command'
36
29
 
30
+ # @return [String] name of container
37
31
  attr_reader :name
32
+ # @return [TrueClass, FalseClass] enable CLI output
38
33
  attr_reader :cli
34
+ # @return [String] hostname of container
39
35
  attr_reader :hostname
36
+ # @return [String] path to container
40
37
  attr_reader :path
38
+ # @return [Lxc] instance of ephemeral
41
39
  attr_reader :lxc
40
+ # @return [Storage::OverlayDirectory, Storage::VirtualDevice]
42
41
  attr_reader :ephemeral_device
42
+ # @return [Storage::OverlayMount]
43
43
  attr_reader :ephemeral_overlay
44
+ # @return [Array<Storage::VirtualDevice]
44
45
  attr_reader :ephemeral_binds
45
46
 
47
+ # Create new instance
48
+ #
49
+ # @param args [Hash]
50
+ # @option args [TrueClass, FalseClass] :cli enable CLI output
46
51
  def initialize(args={})
47
52
  configure!(args)
48
53
  @cli = args[:cli]
@@ -54,19 +59,34 @@ class Lxc
54
59
  @lxc = nil
55
60
  end
56
61
 
62
+ # Trap signals to force cleanup
63
+ #
64
+ # @return [TrueClass]
57
65
  def register_traps
58
66
  %w(TERM INT QUIT).each do |sig|
59
67
  Signal.trap(sig){ cleanup && raise }
60
68
  end
69
+ true
61
70
  end
62
71
 
72
+ # Write output to CLI
73
+ #
74
+ # @return [TrueClass, FalseClass]
63
75
  def cli_output
64
76
  if(cli)
65
77
  puts "New ephemeral container started. (#{name})"
66
78
  puts " - Connect using: sudo ssh -i #{ssh_key} root@#{lxc.container_ip(10)}"
79
+ true
80
+ else
81
+ false
67
82
  end
68
83
  end
69
84
 
85
+ # Start the ephemeral container
86
+ #
87
+ # @return [TrueClass]
88
+ # @note generally should not be called directly
89
+ # @see start!
70
90
  def start_action
71
91
  begin
72
92
  lxc.start
@@ -83,10 +103,19 @@ class Lxc
83
103
  true
84
104
  end
85
105
 
106
+ # Create the ephemeral container
107
+ #
108
+ # @return [TrueClass]
86
109
  def create!
87
110
  setup
111
+ true
88
112
  end
89
113
 
114
+ # Start the ephemeral container
115
+ #
116
+ # @param args [Symbol] argument list
117
+ # @return [TrueClass]
118
+ # @note use :fork to fork startup
90
119
  def start!(*args)
91
120
  register_traps
92
121
  setup
@@ -102,8 +131,12 @@ class Lxc
102
131
  else
103
132
  start_action
104
133
  end
134
+ true
105
135
  end
106
136
 
137
+ # Stop container and cleanup ephemeral items
138
+ #
139
+ # @return [TrueClass, FalseClass]
107
140
  def cleanup
108
141
  lxc.stop
109
142
  @ephemeral_overlay.unmount
@@ -120,22 +153,29 @@ class Lxc
120
153
 
121
154
  private
122
155
 
156
+ # Setup the ephemeral container resources
157
+ #
158
+ # @return [TrueClass]
123
159
  def setup
124
160
  create
125
161
  build_overlay
126
162
  update_naming
127
163
  discover_binds
128
164
  apply_custom_networking if ipaddress
165
+ true
129
166
  end
130
167
 
168
+ # Create the overlay
169
+ #
170
+ # @return [TrueClass, FalseClass]
131
171
  def build_overlay
132
172
  if(directory)
133
- @ephemeral_device = OverlayDirectory.new(name, :tmp_dir => directory.is_a?(String) ? directory : tmp_dir)
173
+ @ephemeral_device = Storage::OverlayDirectory.new(name, :tmp_dir => directory.is_a?(String) ? directory : tmp_dir)
134
174
  else
135
- @ephemeral_device = VirtualDevice.new(name, :size => device, :tmp_fs => !device, :tmp_dir => tmp_dir)
175
+ @ephemeral_device = Storage::VirtualDevice.new(name, :size => device, :tmp_fs => !device, :tmp_dir => tmp_dir)
136
176
  @ephemeral_device.mount
137
177
  end
138
- @ephemeral_overlay = OverlayMount.new(
178
+ @ephemeral_overlay = Storage::OverlayMount.new(
139
179
  :base => Lxc.new(original).rootfs.to_path,
140
180
  :overlay => ephemeral_device.target_path,
141
181
  :target => lxc.path.join('rootfs').to_path,
@@ -144,6 +184,9 @@ class Lxc
144
184
  @ephemeral_overlay.mount
145
185
  end
146
186
 
187
+ # Create the container
188
+ #
189
+ # @return [TrueClass]
147
190
  def create
148
191
  Dir.glob(File.join(lxc_dir, original, '*')).each do |o_path|
149
192
  next unless File.file?(o_path)
@@ -152,10 +195,15 @@ class Lxc
152
195
  @lxc = Lxc.new(name)
153
196
  command("mkdir -p #{lxc.path.join('rootfs')}", :sudo => true)
154
197
  update_net_hwaddr
198
+ true
155
199
  end
156
200
 
157
- # TODO: Discovered binds for ephemeral are all tmpfs for now.
158
- # TODO: We should default to overlay mount, make virt dev optional
201
+ # Discover any bind mounts defined
202
+ #
203
+ # @return [TrueClass]
204
+ # @todo discovered binds for ephemeral are all tmpfs for
205
+ # now. should default to overlay mount, make virtual
206
+ # device and tmpfs optional
159
207
  def discover_binds
160
208
  contents = File.readlines(lxc.path.join('fstab')).each do |line|
161
209
  parts = line.split(' ')
@@ -163,7 +211,7 @@ class Lxc
163
211
  source = parts.first
164
212
  target = parts[1].sub(%r{^.+rootfs/}, '')
165
213
  container_target = lxc.rootfs.join(target).to_path
166
- device = VirtualDevice.new(target.gsub('/', '_'), :tmp_fs => true)
214
+ device = Storage::VirtualDevice.new(target.gsub('/', '_'), :tmp_fs => true)
167
215
  device.mount
168
216
  FileUtils.mkdir_p(container_target)
169
217
  ephemeral_binds << device
@@ -182,6 +230,8 @@ class Lxc
182
230
  contents << "#{bind} #{lxc.rootfs.join(bind)} none bind 0 0\n"
183
231
  end
184
232
  write_file(lxc.path.join('fstab'), contents.join)
233
+ true
185
234
  end
235
+
186
236
  end
187
237
  end
@@ -1,17 +1,24 @@
1
+ require 'elecksee'
1
2
  require 'tempfile'
2
3
 
3
4
  class Lxc
4
5
  module Helpers
6
+ # Container related file copy helpers
5
7
  module Copies
6
-
8
+ # Files requiring name updates
7
9
  NAME_FILES = %w(fstab config)
10
+ # Files requiring hostname updates
8
11
  HOSTNAME_FILES = %w(
9
12
  rootfs/etc/hostname
10
13
  rootfs/etc/hosts
11
14
  rootfs/etc/sysconfig/network
12
15
  rootfs/etc/sysconfig/network-scripts/ifcfg-eth0
13
16
  )
14
-
17
+
18
+ # Update the rootfs
19
+ #
20
+ # @param rootfs_path [String] new rootfs path
21
+ # @return [TrueClass]
15
22
  def update_rootfs(rootfs_path)
16
23
  contents = File.readlines(lxc.config.to_s).map do |line|
17
24
  if(line.start_with?('lxc.rootfs'))
@@ -21,8 +28,12 @@ class Lxc
21
28
  end
22
29
  end.join
23
30
  write_file(lxc.config, contents)
31
+ true
24
32
  end
25
33
 
34
+ # Update network hardware address
35
+ #
36
+ # @return [TrueClass]
26
37
  def update_net_hwaddr
27
38
  contents = File.readlines(lxc.config).map do |line|
28
39
  if(line.start_with?('lxc.network.hwaddr'))
@@ -33,8 +44,14 @@ class Lxc
33
44
  end
34
45
  end.join
35
46
  write_file(lxc.config, contents)
47
+ true
36
48
  end
37
49
 
50
+ # Write file
51
+ #
52
+ # @param path [String]
53
+ # @param contents [String, Array<String>]
54
+ # @return [TrueClass]
38
55
  def write_file(path, contents)
39
56
  contents = contents.join if contents.is_a?(Array)
40
57
  tmp = Tempfile.new('lxc-copy')
@@ -43,8 +60,14 @@ class Lxc
43
60
  command("cp #{tmp.path} #{path}", :sudo => true)
44
61
  tmp.unlink
45
62
  command("chmod 0644 #{path}", :sudo => true)
63
+ true
46
64
  end
47
-
65
+
66
+ # Update container names and host names
67
+ #
68
+ # @param args [Symbol] argument list
69
+ # @return [TrueClass]
70
+ # @note use :no_$FILE where $FILE is the basename to skip
48
71
  def update_naming(*args)
49
72
  NAME_FILES.each do |file|
50
73
  next unless File.exists?(lxc.path.join(file))
@@ -54,16 +77,23 @@ class Lxc
54
77
  end
55
78
  HOSTNAME_FILES.each do |file|
56
79
  next unless File.exists?(lxc.path.join(file))
57
- next if args.include?("no_#{file}".to_sym)
80
+ next if args.include?("no_#{file.split('/').last}".to_sym)
58
81
  contents = File.read(lxc.path.join(file)).gsub(original, name)
59
82
  write_file(lxc.path.join(file), contents)
60
83
  end
84
+ true
61
85
  end
62
86
 
87
+ # Container is Enterprise linux (redhat)
88
+ #
89
+ # @return [TrueClass, FalseClass]
63
90
  def el_platform?
64
91
  lxc.rootfs.join('etc/redhat-release').exist?
65
92
  end
66
-
93
+
94
+ # Apply custom networking files depending on platform
95
+ #
96
+ # @return [TrueClass]
67
97
  def apply_custom_networking
68
98
  if(el_platform?)
69
99
  path = lxc.rootfs.join('etc/sysconfig/network-scripts/ifcfg-eth0')
@@ -100,6 +130,7 @@ gateway #{gateway}
100
130
  EOF
101
131
  write_file(path, content)
102
132
  end
133
+ true
103
134
  end
104
135
  end
105
136
  end
@@ -1,13 +1,27 @@
1
+ require 'elecksee'
2
+
1
3
  class Lxc
2
4
  module Helpers
3
-
5
+ # Helper methods for processing CLI options
4
6
  module Options
5
7
  class << self
8
+ # Load option helper into included class
9
+ #
10
+ # @param klass [Class]
11
+ # @return [TrueClass]
6
12
  def included(klass)
7
13
  klass.class_eval do
8
14
  class << self
15
+
16
+ # @return [Hash] options
9
17
  attr_reader :options
10
-
18
+
19
+ # Define option
20
+ #
21
+ # @param name [String] name of option
22
+ # @param short [String] short flag
23
+ # @param long [String] long flag
24
+ # @param args [Hash]
11
25
  def option(name, short, type, args={})
12
26
  @options ||= {}
13
27
  @options[name] = args.merge(:short => short, :type => type)
@@ -22,6 +36,9 @@ class Lxc
22
36
 
23
37
  private
24
38
 
39
+ # Configure instance and validate options
40
+ #
41
+ # @param args [Array]
25
42
  def configure!(args)
26
43
  self.class.options.each do |name, opts|
27
44
  argv = args.detect{|k,v| (Array(opts[:aliases]) + Array(opts[:short]) + [name]).include?(k.to_sym)}
@@ -41,6 +58,12 @@ class Lxc
41
58
  end
42
59
  end
43
60
 
61
+ # Validate option type
62
+ #
63
+ # @param arg_name [String]
64
+ # @param val [Object]
65
+ # @param type [Symbol] expected type
66
+ # @return [TrueClass]
44
67
  def check_type!(arg_name, val, type)
45
68
  valid = false
46
69
  case type
@@ -51,7 +74,10 @@ class Lxc
51
74
  when :integer
52
75
  valid = val.is_a?(Numeric)
53
76
  end
54
- raise ArgumentError.new "Invalid type provided for #{arg_name}. Expecting value type of: #{type.inspect} Got: #{val.class} - #{val}" unless valid
77
+ unless(valid)
78
+ raise ArgumentError.new "Invalid type provided for #{arg_name}. Expecting value type of: #{type.inspect} Got: #{val.class} - #{val}"
79
+ end
80
+ true
55
81
  end
56
82
  end
57
83
  end