elecksee 1.0.8 → 1.0.10
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/CHANGELOG.md +6 -0
- data/Gemfile.lock +18 -0
- data/README.md +9 -31
- data/lib/elecksee/clone.rb +115 -6
- data/lib/elecksee/ephemeral.rb +50 -178
- data/lib/elecksee/{helpers.rb → helpers/base.rb} +4 -4
- data/lib/elecksee/helpers/copies.rb +106 -0
- data/lib/elecksee/helpers/options.rb +58 -0
- data/lib/elecksee/lxc.rb +7 -2
- data/lib/elecksee/storage/overlay_directory.rb +16 -1
- data/lib/elecksee/storage/overlay_mount.rb +1 -1
- data/lib/elecksee/storage/virtual_device.rb +1 -1
- data/lib/elecksee/version.rb +1 -1
- metadata +6 -4
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
ADDED
data/README.md
CHANGED
@@ -1,47 +1,25 @@
|
|
1
1
|
# Elecksee
|
2
2
|
|
3
|
-
|
4
|
-
LXC tools being installed on the system to properly function.
|
5
|
-
It is extracted from the Chef LXC cookbook, so poke around there
|
3
|
+
An LXC library for Ruby
|
6
4
|
|
7
5
|
## Usage
|
8
6
|
|
9
7
|
```ruby
|
10
|
-
|
11
8
|
require 'elecksee/lxc'
|
12
9
|
|
13
|
-
lxc = Lxc.new('
|
14
|
-
|
15
|
-
```
|
16
|
-
|
17
|
-
## Awesome
|
18
|
-
|
19
|
-
Awesome ephemerals lets you create ephemeral nodes with different
|
20
|
-
overlays for the rootfs. Since tmpfs will place a size restriction
|
21
|
-
on ephemerals based on current memory available, this provides a
|
22
|
-
simple workaround. Currently supported is a raw temporary directory
|
23
|
-
on the host, or a VBD that defaults to 2GB.
|
24
|
-
|
25
|
-
### Using temporary directory
|
26
|
-
|
27
|
-
```
|
28
|
-
$ lxc-awesome-ephemeral -o ubuntu -d -z /tmp/lxc-roots
|
10
|
+
lxc = Lxc.new('container')
|
11
|
+
lxc.start unless lxc.running?
|
29
12
|
```
|
30
13
|
|
31
|
-
|
32
|
-
|
33
|
-
```
|
34
|
-
$ lxc-awesome-ephemeral -o ubuntu -d -D 2000
|
35
|
-
```
|
14
|
+
## Included
|
36
15
|
|
37
|
-
|
38
|
-
|
16
|
+
* Container inspect and interaction (`Lxc`)
|
17
|
+
* Container cloning (`Lxc::Clone`)
|
18
|
+
* Ephemeral containers (`Lxc::Ephemeral`)
|
39
19
|
|
40
|
-
|
41
|
-
----
|
20
|
+
### Notes
|
42
21
|
|
43
|
-
|
44
|
-
cleaned up when the container has reached a stop state.
|
22
|
+
This library is currently tested on ubuntu platforms >= 12.04
|
45
23
|
|
46
24
|
# Info
|
47
25
|
|
data/lib/elecksee/clone.rb
CHANGED
@@ -1,15 +1,124 @@
|
|
1
|
-
|
2
|
-
|
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
|
3
8
|
|
4
9
|
class Lxc
|
5
10
|
class Clone
|
6
11
|
|
7
|
-
|
12
|
+
include Helpers
|
13
|
+
include Helpers::Options
|
14
|
+
include Helpers::Copies
|
15
|
+
|
16
|
+
option :original, '-o', :string, :required => true, :desc => 'Original container name', :aliases => 'orig'
|
17
|
+
option :new_name, '-n', :string, :required => true, :desc => 'New container name', :aliases => 'new'
|
18
|
+
option :snapshot, '-s', :boolean, :desc => 'Make new rootfs a snapshot of original'
|
19
|
+
option :fssize, '-L', :string, :desc => 'Size of new file system', :default => '2G'
|
20
|
+
option :vgname, '-v', :string, :desc => 'LVM volume group name', :default => 'lxc'
|
21
|
+
option :lvprefix, '-p', :string, :desc => 'LVM volume name prefix'
|
22
|
+
option :fstype, '-t', :string, :desc => 'New container file system', :default => 'ext4'
|
23
|
+
option :device, '-D', :integer, :desc => 'Make copy in VBD of {SIZE}M'
|
24
|
+
option :ipaddress, '-I', :string, :desc => 'Custom IP address'
|
25
|
+
option :gateway, '-G', :string, :desc => 'Custom gateway'
|
26
|
+
option :netmask, '-N', :string, :default => '255.255.255.0', :desc => 'Custom netmask'
|
27
|
+
|
28
|
+
# Hash containing original and new container instances
|
29
|
+
attr_reader :lxcs
|
30
|
+
|
8
31
|
def initialize(args={})
|
9
|
-
|
10
|
-
|
11
|
-
|
32
|
+
configure!(args)
|
33
|
+
@lxcs = {}
|
34
|
+
@lxcs[:original] = Lxc.new(original)
|
35
|
+
@lxcs[:new] = Lxc.new(new_name)
|
36
|
+
@created = []
|
37
|
+
validate!
|
38
|
+
end
|
39
|
+
|
40
|
+
def clone!
|
41
|
+
begin
|
42
|
+
copy_original
|
43
|
+
update_naming(:no_config)
|
44
|
+
apply_custom_addressing if ipaddress
|
45
|
+
lxcs[:new]
|
46
|
+
rescue Exception
|
47
|
+
@created.map(&:destroy)
|
48
|
+
raise
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
alias_method :name, :new_name
|
55
|
+
|
56
|
+
# Returns new lxc instance
|
57
|
+
def lxc
|
58
|
+
lxcs[:new]
|
59
|
+
end
|
60
|
+
|
61
|
+
def created(thing)
|
62
|
+
@created << thing
|
63
|
+
end
|
64
|
+
|
65
|
+
def validate!
|
66
|
+
unless(lxcs[:original].exists?)
|
67
|
+
raise "Requested `original` container does not exist (#{original})"
|
12
68
|
end
|
69
|
+
if(lxcs[:new].exists?)
|
70
|
+
raise "Requested `new` container already exists (#{new_name})"
|
71
|
+
end
|
72
|
+
if(lxcs[:original].running?)
|
73
|
+
raise "Requested `original` container is current running (#{original})"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def copy_original
|
78
|
+
copy_init
|
79
|
+
if(device)
|
80
|
+
rootfs_dir = copy_vbd
|
81
|
+
elsif(File.stat(lxcs[:original].path.to_s).blockdev?)
|
82
|
+
rootfs_dir = copy_lvm
|
83
|
+
elsif(command("btrfs subvolume list '#{lxcs[:original].path}'", :allow_failure => true))
|
84
|
+
rootfs_dir = copy_btrfs
|
85
|
+
else
|
86
|
+
rootfs_dir = copy_fs
|
87
|
+
end
|
88
|
+
update_rootfs(rootfs_dir)
|
89
|
+
end
|
90
|
+
|
91
|
+
def copy_init
|
92
|
+
directory = CloneDirectory.new(lxcs[:new].name, :dir => File.dirname(lxcs[:original].path.to_s))
|
93
|
+
created(directory)
|
94
|
+
%w(config fstab).each do |file|
|
95
|
+
command("cp '#{lxcs[:original].path.join(file)}' '#{directory.target_path}'", :sudo => true)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def copy_fs
|
100
|
+
directory = CloneDirectory.new(lxcs[:new].name, :dir => File.dirname(lxcs[:original].path.to_s))
|
101
|
+
created(directory)
|
102
|
+
command("rsync -ax '#{lxcs[:original].rootfs}/' '#{File.join(directory.target_path, 'rootfs')}/'", :sudo => true)
|
103
|
+
File.join(directory.target_path, 'rootfs')
|
104
|
+
end
|
105
|
+
|
106
|
+
def copy_vbd
|
107
|
+
storage = VirtualDevice.new(lxcs[:new].name, :tmp_dir => '/opt/lxc-vbd')
|
108
|
+
created(storage)
|
109
|
+
command("rsync -ax '#{lxcs[:original].rootfs}/' '#{storage.target_path}/'", :sudo => true)
|
110
|
+
storage.target_path
|
111
|
+
end
|
112
|
+
|
113
|
+
def copy_lvm
|
114
|
+
raise 'Not implemented'
|
115
|
+
end
|
116
|
+
|
117
|
+
def copy_btrfs
|
118
|
+
rootfs_path = lxcs[:new].path.join('rootfs')
|
119
|
+
command("btrfs subvolume snapshot '#{lxcs[:original].rootfs}' '#{rootfs_path}'", :sudo => true)
|
120
|
+
# TODO: Remove on failure
|
121
|
+
rootfs_path
|
13
122
|
end
|
14
123
|
end
|
15
124
|
end
|
data/lib/elecksee/ephemeral.rb
CHANGED
@@ -4,8 +4,9 @@ require 'tmpdir'
|
|
4
4
|
require 'etc'
|
5
5
|
|
6
6
|
%w(
|
7
|
-
helpers
|
8
|
-
storage/
|
7
|
+
helpers/base helpers/options helpers/copies lxc
|
8
|
+
storage/overlay_directory storage/overlay_mount
|
9
|
+
storage/virtual_device
|
9
10
|
).each do |path|
|
10
11
|
require "elecksee/#{path}"
|
11
12
|
end
|
@@ -15,26 +16,8 @@ class Lxc
|
|
15
16
|
class Ephemeral
|
16
17
|
|
17
18
|
include Helpers
|
18
|
-
|
19
|
-
|
20
|
-
HOSTNAME_FILES = %w(
|
21
|
-
rootfs/etc/hostname
|
22
|
-
rootfs/etc/hosts
|
23
|
-
rootfs/etc/sysconfig/network
|
24
|
-
rootfs/etc/sysconfig/network-scripts/ifcfg-eth0
|
25
|
-
)
|
26
|
-
|
27
|
-
class << self
|
28
|
-
attr_reader :options
|
29
|
-
|
30
|
-
def option(name, short, type, args={})
|
31
|
-
@options ||= {}
|
32
|
-
@options[name] = args.merge(:short => short, :type => type)
|
33
|
-
instance_eval do
|
34
|
-
attr_accessor name.to_sym
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
19
|
+
include Helpers::Options
|
20
|
+
include Helpers::Copies
|
38
21
|
|
39
22
|
option :original, '-o', :string, :required => true, :desc => 'Original container name'
|
40
23
|
option :ipaddress, '-I', :string, :desc => 'Custom IP address'
|
@@ -49,6 +32,7 @@ class Lxc
|
|
49
32
|
option :ssh_key, '-S', :string, :default => '/opt/hw-lxc-config/id_rsa', :aliases => 'ssh-key', :desc => 'Deprecated: Provided for compatibility'
|
50
33
|
option :lxc_dir, '-L', :string, :default => '/var/lib/lxc', :aliases => 'lxc-dir', :desc => 'Directory of LXC store'
|
51
34
|
option :tmp_dir, '-T', :string, :default => '/tmp/lxc/ephemerals', :aliases => 'tmp-dir', :desc => 'Directory of ephemeral temp files'
|
35
|
+
option :ephemeral_command, '-C', :string, :aliases => 'command'
|
52
36
|
|
53
37
|
attr_reader :name
|
54
38
|
attr_reader :cli
|
@@ -63,6 +47,7 @@ class Lxc
|
|
63
47
|
configure!(args)
|
64
48
|
@cli = args[:cli]
|
65
49
|
@path = command("mktemp -d -p #{lxc_dir} #{original}-XXXXXXXXXXXX", :sudo => true).stdout.strip
|
50
|
+
command("chmod 0755 #{@path}", :sudo => true)
|
66
51
|
@name = File.basename(@path)
|
67
52
|
@hostname = @name.gsub(%r{[^A-Za-z0-9\-]}, '')
|
68
53
|
@ephemeral_binds = []
|
@@ -81,6 +66,21 @@ class Lxc
|
|
81
66
|
puts " - Connect using: sudo ssh -i #{ssh_key} root@#{lxc.container_ip(10)}"
|
82
67
|
end
|
83
68
|
end
|
69
|
+
|
70
|
+
def start_action
|
71
|
+
begin
|
72
|
+
lxc.start
|
73
|
+
if(ephemeral_command)
|
74
|
+
lxc.container_command(ephemeral_command)
|
75
|
+
else
|
76
|
+
cli_output
|
77
|
+
lxc.wait_for_state(:stopped)
|
78
|
+
end
|
79
|
+
ensure
|
80
|
+
cleanup
|
81
|
+
end
|
82
|
+
true
|
83
|
+
end
|
84
84
|
|
85
85
|
def start!(*args)
|
86
86
|
register_traps
|
@@ -88,23 +88,14 @@ class Lxc
|
|
88
88
|
if(daemon)
|
89
89
|
if(args.include?(:fork))
|
90
90
|
fork do
|
91
|
-
|
92
|
-
cli_output
|
93
|
-
lxc.wait_for_state(:stopped)
|
94
|
-
cleanup
|
91
|
+
start_action
|
95
92
|
end
|
96
93
|
else
|
97
94
|
Process.daemon
|
98
|
-
|
99
|
-
cli_output
|
100
|
-
lxc.wait_for_state(:stopped)
|
101
|
-
cleanup
|
95
|
+
start_action
|
102
96
|
end
|
103
97
|
else
|
104
|
-
|
105
|
-
cli_output
|
106
|
-
lxc.wait_for_state(:stopped)
|
107
|
-
cleanup
|
98
|
+
start_action
|
108
99
|
end
|
109
100
|
end
|
110
101
|
|
@@ -123,39 +114,7 @@ class Lxc
|
|
123
114
|
end
|
124
115
|
|
125
116
|
private
|
126
|
-
|
127
|
-
def configure!(args)
|
128
|
-
self.class.options.each do |name, opts|
|
129
|
-
argv = args.detect{|k,v| (Array(opts[:aliases]) + Array(opts[:short]) + [name]).include?(k.to_sym)}
|
130
|
-
argv = argv.last if argv
|
131
|
-
argv ||= opts[:default]
|
132
|
-
if(argv)
|
133
|
-
check_type!(name, argv, opts[:type])
|
134
|
-
self.send("#{name}=", argv)
|
135
|
-
else
|
136
|
-
if(opts[:required])
|
137
|
-
raise ArgumentError.new "Missing required argument: #{name}"
|
138
|
-
end
|
139
|
-
end
|
140
|
-
end
|
141
|
-
if(ipaddress && gateway.nil?)
|
142
|
-
self.gateway = ipaddress.sub(%r{\d+$}, '1')
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
def check_type!(arg_name, val, type)
|
147
|
-
valid = false
|
148
|
-
case type
|
149
|
-
when :string
|
150
|
-
valid = val.is_a?(String)
|
151
|
-
when :boolean
|
152
|
-
valid = val.is_a?(TrueClass) || val.is_a?(FalseClass)
|
153
|
-
when :integer
|
154
|
-
valid = val.is_a?(Numeric)
|
155
|
-
end
|
156
|
-
raise ArgumentError.new "Invalid type provided for #{arg_name}. Expecting value type of: #{type.inspect} Got: #{val.class} - #{val}" unless valid
|
157
|
-
end
|
158
|
-
|
117
|
+
|
159
118
|
def setup
|
160
119
|
create
|
161
120
|
build_overlay
|
@@ -174,137 +133,50 @@ class Lxc
|
|
174
133
|
@ephemeral_overlay = OverlayMount.new(
|
175
134
|
:base => Lxc.new(original).rootfs.to_path,
|
176
135
|
:overlay => ephemeral_device.target_path,
|
177
|
-
:target => lxc.rootfs.to_path,
|
136
|
+
:target => lxc.path.join('rootfs').to_path,
|
178
137
|
:overlay_type => union
|
179
138
|
)
|
180
139
|
@ephemeral_overlay.mount
|
181
140
|
end
|
182
|
-
|
183
|
-
def writable_path!(path)
|
184
|
-
unless(File.directory?(File.dirname(path)))
|
185
|
-
command("mkdir -p #{File.dirname(path)}", :sudo => true)
|
186
|
-
end
|
187
|
-
unless(File.exists?(path))
|
188
|
-
command("touch #{path}", :sudo => true)
|
189
|
-
end
|
190
|
-
command("chown #{Etc.getlogin} #{path}", :sudo => true)
|
191
|
-
end
|
192
141
|
|
193
142
|
def create
|
194
143
|
Dir.glob(File.join(lxc_dir, original, '*')).each do |o_path|
|
195
144
|
next unless File.file?(o_path)
|
196
145
|
command("cp #{o_path} #{File.join(path, File.basename(o_path))}", :sudo => true)
|
197
146
|
end
|
198
|
-
command("chown -R #{Etc.getlogin} #{path}", :sudo => true)
|
199
147
|
@lxc = Lxc.new(name)
|
200
|
-
|
201
|
-
|
202
|
-
File.open(lxc.config, 'w') do |file|
|
203
|
-
contents.each do |line|
|
204
|
-
if(line.strip.start_with?('lxc.network.hwaddr'))
|
205
|
-
file.write "00:16:3e#{SecureRandom.hex(3).gsub(/(..)/, ':\1')}"
|
206
|
-
else
|
207
|
-
file.write line
|
208
|
-
end
|
209
|
-
end
|
210
|
-
end
|
148
|
+
command("mkdir -p '#{lxc.path.join('rootfs')}'", :sudo => true)
|
149
|
+
update_net_hwaddr
|
211
150
|
end
|
212
151
|
|
213
152
|
# TODO: Discovered binds for ephemeral are all tmpfs for now.
|
153
|
+
# TODO: We should default to overlay mount, make virt dev optional
|
214
154
|
def discover_binds
|
215
|
-
contents = File.readlines(lxc.path.join('fstab'))
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
if(union == 'overlayfs')
|
228
|
-
file.write "none #{container_target} overlayfs upperdir=#{device.mount_path},lowerdir=#{source} 0 0"
|
229
|
-
else
|
230
|
-
file.write "none #{container_target} aufs br=#{device.mount_path}=rw:#{source}=ro,noplink 0 0"
|
231
|
-
end
|
155
|
+
contents = File.readlines(lxc.path.join('fstab')).each do |line|
|
156
|
+
parts = line.split(' ')
|
157
|
+
if(parts[3] == 'bind')
|
158
|
+
source = parts.first
|
159
|
+
target = parts[1].sub(%r{^.+rootfs/}, '')
|
160
|
+
container_target = lxc.rootfs.join(target).to_path
|
161
|
+
device = VirtualDevice.new(target.gsub('/', '_'), :tmp_fs => true)
|
162
|
+
device.mount
|
163
|
+
FileUtils.mkdir_p(container_target)
|
164
|
+
ephemeral_binds << device
|
165
|
+
if(union == 'overlayfs')
|
166
|
+
"none #{container_target} overlayfs upperdir=#{device.mount_path},lowerdir=#{source} 0 0"
|
232
167
|
else
|
233
|
-
|
168
|
+
"none #{container_target} aufs br=#{device.mount_path}=rw:#{source}=ro,noplink 0 0"
|
234
169
|
end
|
235
|
-
|
236
|
-
|
237
|
-
if(bind)
|
238
|
-
command("mkdir -p #{lxc.rootfs.join(bind).to_path}", :sudo => true)
|
239
|
-
file.puts "#{bind} #{lxc.rootfs.join(bind)} none bind 0 0"
|
240
|
-
end
|
241
|
-
end
|
242
|
-
end
|
243
|
-
|
244
|
-
def update_naming
|
245
|
-
NAME_FILES.each do |file|
|
246
|
-
next unless File.exists?(lxc.path.join(file))
|
247
|
-
writable_path!(lxc.path.join(file).to_path)
|
248
|
-
contents = File.read(lxc.path.join(file))
|
249
|
-
File.open(lxc.path.join(file), 'w') do |new_file|
|
250
|
-
new_file.write contents.gsub(original, name)
|
251
|
-
end
|
252
|
-
end
|
253
|
-
HOSTNAME_FILES.each do |file|
|
254
|
-
next unless File.exists?(lxc.path.join(file))
|
255
|
-
writable_path!(lxc.path.join(file).to_path)
|
256
|
-
contents = File.read(lxc.path.join(file))
|
257
|
-
File.open(lxc.path.join(file), 'w') do |new_file|
|
258
|
-
new_file.write contents.gsub(original, hostname)
|
170
|
+
else
|
171
|
+
line
|
259
172
|
end
|
260
173
|
end
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
end
|
266
|
-
|
267
|
-
def apply_custom_networking
|
268
|
-
if(el_platform?)
|
269
|
-
writable_path!(path = lxc.rootfs.join('etc/sysconfig/network-scripts/ifcfg-eth0'))
|
270
|
-
File.open(path, 'w') do |file|
|
271
|
-
file.write <<-EOF
|
272
|
-
DEVICE=eth0
|
273
|
-
BOOTPROTO=static
|
274
|
-
NETMASK=#{netmask}
|
275
|
-
IPADDR=#{ipaddress}
|
276
|
-
ONBOOT=yes
|
277
|
-
TYPE=Ethernet
|
278
|
-
USERCTL=yes
|
279
|
-
PEERDNS=yes
|
280
|
-
IPV6INIT=no
|
281
|
-
GATEWAY=#{gateway}
|
282
|
-
EOF
|
283
|
-
end
|
284
|
-
writable_path!(path = lxc.rootfs.join('etc/sysconfig/network'))
|
285
|
-
File.open(path, 'w') do |file|
|
286
|
-
file.write <<-EOF
|
287
|
-
NETWORKING=yes
|
288
|
-
HOSTNAME=#{hostname}
|
289
|
-
EOF
|
290
|
-
end
|
291
|
-
File.open(@lxc.rootfs.join('etc/rc.local'), 'w') do |file|
|
292
|
-
file.puts "hostname #{hostname}"
|
293
|
-
end
|
294
|
-
else
|
295
|
-
writable_path!(path = lxc.rootfs.join('etc/network/interfaces'))
|
296
|
-
File.open(path, 'w') do |file|
|
297
|
-
file.write <<-EOF
|
298
|
-
auto lo
|
299
|
-
iface lo inet loopback
|
300
|
-
auto eth0
|
301
|
-
iface eth0 inet static
|
302
|
-
address #{ipaddress}
|
303
|
-
netmask #{netmask}
|
304
|
-
gateway #{gateway}
|
305
|
-
EOF
|
306
|
-
end
|
174
|
+
# If bind option used, bind in for rw
|
175
|
+
if(bind)
|
176
|
+
command("mkdir -p #{lxc.rootfs.join(bind).to_path}", :sudo => true)
|
177
|
+
contents << "#{bind} #{lxc.rootfs.join(bind)} none bind 0 0\n"
|
307
178
|
end
|
179
|
+
write_file(lxc.path.join('fstab'), contents.join)
|
308
180
|
end
|
309
181
|
end
|
310
182
|
end
|
@@ -1,9 +1,9 @@
|
|
1
1
|
class Lxc
|
2
2
|
class CommandFailed < StandardError
|
3
3
|
end
|
4
|
-
|
5
|
-
module Helpers
|
6
4
|
|
5
|
+
module Helpers
|
6
|
+
|
7
7
|
def sudo
|
8
8
|
Lxc.sudo
|
9
9
|
end
|
@@ -14,7 +14,7 @@ class Lxc
|
|
14
14
|
cmd = [sudo, cmd].join(' ') if args[:sudo]
|
15
15
|
begin
|
16
16
|
shlout = Mixlib::ShellOut.new(cmd,
|
17
|
-
:logger => defined?(Chef) ? Chef::Log.logger : log,
|
17
|
+
:logger => defined?(Chef) && defined?(Chef::Log) ? Chef::Log.logger : log,
|
18
18
|
:live_stream => args[:livestream] ? STDOUT : nil,
|
19
19
|
:timeout => args[:timeout] || 1200,
|
20
20
|
:environment => {'HOME' => detect_home}
|
@@ -30,7 +30,7 @@ class Lxc
|
|
30
30
|
retries -= 1
|
31
31
|
retry
|
32
32
|
elsif(args[:allow_failure])
|
33
|
-
|
33
|
+
false
|
34
34
|
else
|
35
35
|
raise
|
36
36
|
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
class Lxc
|
4
|
+
module Helpers
|
5
|
+
module Copies
|
6
|
+
|
7
|
+
NAME_FILES = %w(fstab config)
|
8
|
+
HOSTNAME_FILES = %w(
|
9
|
+
rootfs/etc/hostname
|
10
|
+
rootfs/etc/hosts
|
11
|
+
rootfs/etc/sysconfig/network
|
12
|
+
rootfs/etc/sysconfig/network-scripts/ifcfg-eth0
|
13
|
+
)
|
14
|
+
|
15
|
+
def update_rootfs(rootfs_path)
|
16
|
+
contents = File.readlines(lxc.config.to_s).map do |line|
|
17
|
+
if(line.start_with?('lxc.rootfs'))
|
18
|
+
"lxc.rootfs = #{rootfs_path}\n"
|
19
|
+
else
|
20
|
+
line
|
21
|
+
end
|
22
|
+
end.join
|
23
|
+
write_file(lxc.config, contents)
|
24
|
+
end
|
25
|
+
|
26
|
+
def update_net_hwaddr
|
27
|
+
contents = File.readlines(lxc.config).map do |line|
|
28
|
+
if(line.start_with?('lxc.network.hwaddr'))
|
29
|
+
parts = line.split('=')
|
30
|
+
"#{parts.first.strip} = 00:16:3e#{SecureRandom.hex(3).gsub(/(..)/, ':\1')}\n"
|
31
|
+
else
|
32
|
+
line
|
33
|
+
end
|
34
|
+
end.join
|
35
|
+
write_file(lxc.config, contents)
|
36
|
+
end
|
37
|
+
|
38
|
+
def write_file(path, contents)
|
39
|
+
contents = contents.join if contents.is_a?(Array)
|
40
|
+
tmp = Tempfile.new('lxc-copy')
|
41
|
+
tmp.write(contents)
|
42
|
+
tmp.close
|
43
|
+
command("cp #{tmp.path} #{path}", :sudo => true)
|
44
|
+
tmp.unlink
|
45
|
+
command("chmod 0644 #{path}", :sudo => true)
|
46
|
+
end
|
47
|
+
|
48
|
+
def update_naming(*args)
|
49
|
+
NAME_FILES.each do |file|
|
50
|
+
next unless File.exists?(lxc.path.join(file))
|
51
|
+
next if args.include?("no_#{file}".to_sym)
|
52
|
+
contents = File.read(lxc.path.join(file)).gsub(original, name)
|
53
|
+
write_file(lxc.path.join(file), contents)
|
54
|
+
end
|
55
|
+
HOSTNAME_FILES.each do |file|
|
56
|
+
next unless File.exists?(lxc.path.join(file))
|
57
|
+
next if args.include?("no_#{file}".to_sym)
|
58
|
+
contents = File.read(lxc.path.join(file)).gsub(original, name)
|
59
|
+
write_file(lxc.path.join(file), contents)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def el_platform?
|
64
|
+
lxc.rootfs.join('etc/redhat-release').exist?
|
65
|
+
end
|
66
|
+
|
67
|
+
def apply_custom_networking
|
68
|
+
if(el_platform?)
|
69
|
+
path = lxc.rootfs.join('etc/sysconfig/network-scripts/ifcfg-eth0')
|
70
|
+
content = <<-EOF
|
71
|
+
DEVICE=eth0
|
72
|
+
BOOTPROTO=static
|
73
|
+
NETMASK=#{netmask}
|
74
|
+
IPADDR=#{ipaddress}
|
75
|
+
ONBOOT=yes
|
76
|
+
TYPE=Ethernet
|
77
|
+
USERCTL=yes
|
78
|
+
PEERDNS=yes
|
79
|
+
IPV6INIT=no
|
80
|
+
GATEWAY=#{gateway}
|
81
|
+
EOF
|
82
|
+
write_file(path, content)
|
83
|
+
path = lxc.rootfs.join('etc/sysconfig/network')
|
84
|
+
content = <<-EOF
|
85
|
+
NETWORKING=yes
|
86
|
+
HOSTNAME=#{hostname}
|
87
|
+
EOF
|
88
|
+
write_file(path, content)
|
89
|
+
write_file(lxc.rootfs.join('etc/rc.local'), "hostname #{hostname}\n")
|
90
|
+
else
|
91
|
+
path = lxc.rootfs.join('etc/network/interfaces')
|
92
|
+
content = <<-EOF
|
93
|
+
auto lo
|
94
|
+
iface lo inet loopback
|
95
|
+
auto eth0
|
96
|
+
iface eth0 inet static
|
97
|
+
address #{ipaddress}
|
98
|
+
netmask #{netmask}
|
99
|
+
gateway #{gateway}
|
100
|
+
EOF
|
101
|
+
write_file(path, content)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
class Lxc
|
2
|
+
module Helpers
|
3
|
+
|
4
|
+
module Options
|
5
|
+
class << self
|
6
|
+
def included(klass)
|
7
|
+
klass.class_eval do
|
8
|
+
class << self
|
9
|
+
attr_reader :options
|
10
|
+
|
11
|
+
def option(name, short, type, args={})
|
12
|
+
@options ||= {}
|
13
|
+
@options[name] = args.merge(:short => short, :type => type)
|
14
|
+
instance_eval do
|
15
|
+
attr_accessor name.to_sym
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def configure!(args)
|
26
|
+
self.class.options.each do |name, opts|
|
27
|
+
argv = args.detect{|k,v| (Array(opts[:aliases]) + Array(opts[:short]) + [name]).include?(k.to_sym)}
|
28
|
+
argv = argv.last if argv
|
29
|
+
argv ||= opts[:default]
|
30
|
+
if(argv)
|
31
|
+
check_type!(name, argv, opts[:type])
|
32
|
+
self.send("#{name}=", argv)
|
33
|
+
else
|
34
|
+
if(opts[:required])
|
35
|
+
raise ArgumentError.new "Missing required argument: #{name}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
if(ipaddress && gateway.nil?)
|
40
|
+
self.gateway = ipaddress.sub(%r{\d+$}, '1')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def check_type!(arg_name, val, type)
|
45
|
+
valid = false
|
46
|
+
case type
|
47
|
+
when :string
|
48
|
+
valid = val.is_a?(String)
|
49
|
+
when :boolean
|
50
|
+
valid = val.is_a?(TrueClass) || val.is_a?(FalseClass)
|
51
|
+
when :integer
|
52
|
+
valid = val.is_a?(Numeric)
|
53
|
+
end
|
54
|
+
raise ArgumentError.new "Invalid type provided for #{arg_name}. Expecting value type of: #{type.inspect} Got: #{val.class} - #{val}" unless valid
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/elecksee/lxc.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'elecksee/helpers'
|
1
|
+
require 'elecksee/helpers/base'
|
2
2
|
require 'mixlib/shellout'
|
3
3
|
require 'pathname'
|
4
4
|
require 'tmpdir'
|
@@ -245,7 +245,12 @@ class Lxc
|
|
245
245
|
alias_method :config, :container_config
|
246
246
|
|
247
247
|
def container_rootfs
|
248
|
-
|
248
|
+
if(File.exists?(config))
|
249
|
+
r_path = File.readlines(config).detect do |line|
|
250
|
+
line.start_with?('lxc.rootfs')
|
251
|
+
end.to_s.split('=').last.to_s.strip
|
252
|
+
end
|
253
|
+
r_path.to_s.empty? ? container_path.join('rootfs') : Pathname.new(r_path)
|
249
254
|
end
|
250
255
|
alias_method :rootfs, :container_rootfs
|
251
256
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'elecksee/helpers'
|
1
|
+
require 'elecksee/helpers/base'
|
2
2
|
|
3
3
|
class Lxc
|
4
4
|
class OverlayDirectory
|
@@ -32,4 +32,19 @@ class Lxc
|
|
32
32
|
end
|
33
33
|
|
34
34
|
end
|
35
|
+
|
36
|
+
# Clone directory does the same as the overlay, just in
|
37
|
+
# a persistent place
|
38
|
+
class CloneDirectory < OverlayDirectory
|
39
|
+
def initialize(name, args={})
|
40
|
+
args[:tmp_dir] = args[:dir] if args[:dir]
|
41
|
+
args[:tmp_dir] || '/var/lib/lxc'
|
42
|
+
super
|
43
|
+
end
|
44
|
+
|
45
|
+
def overlay_path
|
46
|
+
File.join(tmp_dir, name)
|
47
|
+
end
|
48
|
+
alias_method :target_path, :overlay_path
|
49
|
+
end
|
35
50
|
end
|
data/lib/elecksee/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: elecksee
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.10
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-08-06 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: mixlib-shellout
|
@@ -54,7 +54,9 @@ files:
|
|
54
54
|
- lib/elecksee/clone.rb
|
55
55
|
- lib/elecksee/version.rb
|
56
56
|
- lib/elecksee/lxc_file_config.rb
|
57
|
-
- lib/elecksee/helpers.rb
|
57
|
+
- lib/elecksee/helpers/copies.rb
|
58
|
+
- lib/elecksee/helpers/options.rb
|
59
|
+
- lib/elecksee/helpers/base.rb
|
58
60
|
- lib/elecksee/storage/overlay_mount.rb
|
59
61
|
- lib/elecksee/storage/virtual_device.rb
|
60
62
|
- lib/elecksee/storage/overlay_directory.rb
|
@@ -65,8 +67,8 @@ files:
|
|
65
67
|
- README.md
|
66
68
|
- LICENSE
|
67
69
|
- bin/lxc-awesome-ephemeral
|
68
|
-
- elecksee-1.0.8.gem
|
69
70
|
- CHANGELOG.md
|
71
|
+
- Gemfile.lock
|
70
72
|
homepage: http://github.com/chrisroberts/elecksee
|
71
73
|
licenses: []
|
72
74
|
post_install_message:
|