elecksee 1.0.22 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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