linux_container 0.1
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 +1 -0
- data/Manifest +6 -0
- data/README.md +62 -0
- data/Rakefile +8 -0
- data/lib/linux_container.rb +113 -0
- data/linux_container.gemspec +30 -0
- data/test/test_all.rb +48 -0
- metadata +61 -0
data/CHANGELOG
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
v0.1. first version
|
data/Manifest
ADDED
data/README.md
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
## LinuxContainer gem
|
2
|
+
|
3
|
+
This gem is an easy ruby interface to ubuntu linux containers (LXC). It
|
4
|
+
provides a convenient wrapper for starting Ubuntu Cloud image-based
|
5
|
+
ephemeral containers.
|
6
|
+
|
7
|
+
lxc-start-ephemeral uses AUFS overlay filesystem to clone an container using
|
8
|
+
very little disk space, which then disappears when shut down.
|
9
|
+
|
10
|
+
$ gem install linux_container
|
11
|
+
|
12
|
+
### Create a new container
|
13
|
+
|
14
|
+
> c = LinuxContainer.new(
|
15
|
+
name: 'mycontainer',
|
16
|
+
username: 'ubuntu', # optional for SSH, defaults to ubuntu
|
17
|
+
ssh_key_path: '~/.ssh/id_rsa' # optional
|
18
|
+
)
|
19
|
+
|
20
|
+
> c.create(
|
21
|
+
template: 'ubuntu-cloud', # optional, defaults to ubuntu-cloud
|
22
|
+
hostid: 'i12345', # optional, autogenerated
|
23
|
+
userdata: 'userdata.txt', # optional cloud-init user-data script
|
24
|
+
auth-key: '~/.ssh/id_rsa', # optional, defaults to ssh_key_path
|
25
|
+
arch: 'i686', # optional, defaults to host arch
|
26
|
+
release: 'quantal' # optional, defaults to host release
|
27
|
+
)
|
28
|
+
|
29
|
+
> c.dir('/tmp')
|
30
|
+
=> "/var/lib/lxc/mycontainer/rootfs/tmp"
|
31
|
+
|
32
|
+
### Start container and mess around with it
|
33
|
+
|
34
|
+
> c.start
|
35
|
+
> c.state
|
36
|
+
> c.running?
|
37
|
+
> c.ip
|
38
|
+
> c.wait_for { ip && sshable? }
|
39
|
+
> c.ssh 'uptime'
|
40
|
+
> c.stop
|
41
|
+
|
42
|
+
### Start an ephemeral container cloning an existing container
|
43
|
+
|
44
|
+
> e = c.start_ephemeral
|
45
|
+
|
46
|
+
|
47
|
+
### Get existing containers
|
48
|
+
|
49
|
+
> LinuxContainer.all
|
50
|
+
=> [container, container, container, ... ]
|
51
|
+
|
52
|
+
### Force shutdown
|
53
|
+
|
54
|
+
c.destroy '-f'
|
55
|
+
|
56
|
+
### other commands
|
57
|
+
|
58
|
+
execute, kill, wait, cgroup, ps, info, freeze, unfreeze, netstat
|
59
|
+
|
60
|
+
|
61
|
+
|
62
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
|
2
|
+
require 'timeout'
|
3
|
+
require 'shellwords'
|
4
|
+
require 'tempfile'
|
5
|
+
|
6
|
+
class LinuxContainer
|
7
|
+
attr_accessor :name, :username, :ssh_key_path
|
8
|
+
attr_writer :ip
|
9
|
+
|
10
|
+
def self.all
|
11
|
+
`sudo lxc-ls`.lines.map(&:strip).uniq.map {|cname| new(name: cname) }
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(params={})
|
15
|
+
params.each {|k,v| instance_variable_set "@#{k}", v }
|
16
|
+
@username ||= 'ubuntu'
|
17
|
+
end
|
18
|
+
|
19
|
+
def state
|
20
|
+
info('-s').chomp.split(':').last.strip
|
21
|
+
end
|
22
|
+
|
23
|
+
def ip
|
24
|
+
@ip ||= self.class.get_ip_for(name)
|
25
|
+
end
|
26
|
+
|
27
|
+
def create(config={}) # template, hostid, userdata, auth-key, arch, release
|
28
|
+
args = (ssh_key_path ? {'auth-key' => "#{ssh_key_path}.pub"} : {}).merge(config).map {|k, v| "--#{k}=#{v}" }
|
29
|
+
lxc_execute('create', '-t', config.delete(:template) || 'ubuntu-cloud', '--', *args)
|
30
|
+
end
|
31
|
+
|
32
|
+
[:start, :stop, :destroy, :execute, :kill, :wait, :cgroup, :ps, :info, :freeze, :unfreeze, :netstat].each do |cmd|
|
33
|
+
define_method(cmd) {|*args| lxc_execute(cmd, *args) }
|
34
|
+
end
|
35
|
+
|
36
|
+
def start_ephemeral
|
37
|
+
args = ['lxc-start-ephemeral','-U','aufs','-u',username,'-o',name]
|
38
|
+
logfile = bg_execute(*args)
|
39
|
+
newname = nil
|
40
|
+
while newname.nil?
|
41
|
+
sleep 1
|
42
|
+
logfile.rewind
|
43
|
+
newname = $1 if logfile.read =~ /^(.*) is running/
|
44
|
+
end
|
45
|
+
self.class.new(name: newname, ssh_key_path: ssh_key_path, username: username)
|
46
|
+
end
|
47
|
+
|
48
|
+
def ssh(cmd)
|
49
|
+
raise "cannot ssh without ip" unless ip
|
50
|
+
args = ['-o','StrictHostKeyChecking=no','-o','UserKnownHostsFile=/dev/null']
|
51
|
+
args.push('-i', ssh_key_path) if ssh_key_path
|
52
|
+
args.push("#{username}@#{ip}", cmd)
|
53
|
+
execute('ssh', *args)
|
54
|
+
end
|
55
|
+
|
56
|
+
def sshable?
|
57
|
+
ssh('true') rescue false
|
58
|
+
end
|
59
|
+
|
60
|
+
def running?
|
61
|
+
state == 'RUNNING'
|
62
|
+
end
|
63
|
+
|
64
|
+
def dir(subdir=nil)
|
65
|
+
"/var/lib/lxc/#{name}/rootfs/#{subdir}"
|
66
|
+
end
|
67
|
+
|
68
|
+
def wait_for(timeout=300, interval=1, &block)
|
69
|
+
Timeout.timeout(timeout) do
|
70
|
+
duration = 0
|
71
|
+
start = Time.now
|
72
|
+
until instance_eval(&block) || duration > timeout
|
73
|
+
sleep(interval.to_f)
|
74
|
+
duration = Time.now - start
|
75
|
+
end
|
76
|
+
if duration > timeout
|
77
|
+
false
|
78
|
+
else
|
79
|
+
duration
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
#############################################################################
|
85
|
+
|
86
|
+
def lxc_execute(cmd, *args)
|
87
|
+
execute("lxc-#{cmd}", '-n', name, *args)
|
88
|
+
end
|
89
|
+
|
90
|
+
def execute(*cmd)
|
91
|
+
cmdstring = "sudo #{cmd.shift} #{Shellwords.join(cmd)} "
|
92
|
+
result = `#{cmdstring} 2>&1`
|
93
|
+
raise "command failed: #{cmdstring.inspect}\n#{result}" unless $? == 0
|
94
|
+
result
|
95
|
+
end
|
96
|
+
|
97
|
+
def bg_execute(*cmd)
|
98
|
+
logfile = Tempfile.new(self.class.to_s)
|
99
|
+
cmdstring = "sudo #{cmd.shift} #{Shellwords.join(cmd)} >>#{logfile.path} 2>>#{logfile.path} &"
|
100
|
+
system(cmdstring)
|
101
|
+
raise "command failed: #{cmdstring.inspect}\n" unless $? == 0
|
102
|
+
logfile
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.get_ip_for(name)
|
106
|
+
File.read('/var/lib/misc/dnsmasq.leases').each_line do |line|
|
107
|
+
(timestamp,macaddr,ip,hostname,misc) = line.split(' ')
|
108
|
+
return ip if hostname == name
|
109
|
+
end
|
110
|
+
nil
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "linux_container"
|
5
|
+
s.version = "0.1"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Andrew Snow"]
|
9
|
+
s.date = "2013-02-08"
|
10
|
+
s.description = "Ruby gem for ephemeral LXC linux containers"
|
11
|
+
s.email = "andrew@modulus.org"
|
12
|
+
s.extra_rdoc_files = ["CHANGELOG", "README.md", "lib/linux_container.rb"]
|
13
|
+
s.files = ["CHANGELOG", "README.md", "Rakefile", "lib/linux_container.rb", "test/test_all.rb", "Manifest", "linux_container.gemspec"]
|
14
|
+
s.homepage = "https://github.com/andys/linux_container"
|
15
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Linux_container", "--main", "README.md"]
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
s.rubyforge_project = "linux_container"
|
18
|
+
s.rubygems_version = "1.8.25"
|
19
|
+
s.summary = "Ruby gem for ephemeral LXC linux containers"
|
20
|
+
s.test_files = ["test/test_all.rb"]
|
21
|
+
|
22
|
+
if s.respond_to? :specification_version then
|
23
|
+
s.specification_version = 3
|
24
|
+
|
25
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
26
|
+
else
|
27
|
+
end
|
28
|
+
else
|
29
|
+
end
|
30
|
+
end
|
data/test/test_all.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/../lib/linux_container"
|
2
|
+
|
3
|
+
require 'minitest/autorun'
|
4
|
+
|
5
|
+
class TestLinuxContainer < MiniTest::Unit::TestCase
|
6
|
+
def self.startup
|
7
|
+
$sshkey = "/tmp/linuxcontainergemtestssh#{$$}"
|
8
|
+
`ssh-keygen -q -t rsa -f #{$sshkey} -N ''` unless File.exists?($sshkey)
|
9
|
+
$c = LinuxContainer.new(name: 'linuxcontainergemtest', ssh_key_path: $sshkey)
|
10
|
+
$c.create
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_state
|
14
|
+
assert_equal 'STOPPED', $c.state
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_running
|
18
|
+
assert_equal false, $c.running?
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_dir
|
22
|
+
assert_equal '/var/lib/lxc/linuxcontainergemtest/rootfs/flibble', $c.dir('flibble')
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_list
|
26
|
+
assert_includes LinuxContainer.all.map(&:name), 'linuxcontainergemtest'
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_ephemeral
|
30
|
+
assert($ec = $c.start_ephemeral)
|
31
|
+
assert_match /^linuxcontainergemtest.+/, $ec.name
|
32
|
+
assert($ec.wait_for { running? }, 'wait_for running?')
|
33
|
+
assert($ec.wait_for { ip }, 'wait_for ip')
|
34
|
+
assert($ec.wait_for { sshable? }, 'wait_for sshable?')
|
35
|
+
assert_equal "hi\n", $ec.execute('echo hi')
|
36
|
+
$ec.stop
|
37
|
+
assert($ec.wait_for { !running? }, 'wait_for !running?')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
|
43
|
+
MiniTest::Unit.after_tests do
|
44
|
+
File.unlink($sshkey) rescue nil
|
45
|
+
($ec.destroy '-f' if $ec) rescue nil
|
46
|
+
$c.destroy '-f'
|
47
|
+
end
|
48
|
+
TestLinuxContainer.startup
|
metadata
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: linux_container
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Andrew Snow
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-02-08 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Ruby gem for ephemeral LXC linux containers
|
15
|
+
email: andrew@modulus.org
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files:
|
19
|
+
- CHANGELOG
|
20
|
+
- README.md
|
21
|
+
- lib/linux_container.rb
|
22
|
+
files:
|
23
|
+
- CHANGELOG
|
24
|
+
- README.md
|
25
|
+
- Rakefile
|
26
|
+
- lib/linux_container.rb
|
27
|
+
- test/test_all.rb
|
28
|
+
- Manifest
|
29
|
+
- linux_container.gemspec
|
30
|
+
homepage: https://github.com/andys/linux_container
|
31
|
+
licenses: []
|
32
|
+
post_install_message:
|
33
|
+
rdoc_options:
|
34
|
+
- --line-numbers
|
35
|
+
- --inline-source
|
36
|
+
- --title
|
37
|
+
- Linux_container
|
38
|
+
- --main
|
39
|
+
- README.md
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.2'
|
54
|
+
requirements: []
|
55
|
+
rubyforge_project: linux_container
|
56
|
+
rubygems_version: 1.8.25
|
57
|
+
signing_key:
|
58
|
+
specification_version: 3
|
59
|
+
summary: Ruby gem for ephemeral LXC linux containers
|
60
|
+
test_files:
|
61
|
+
- test/test_all.rb
|