vagrant-lxc 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -1
- data/.vimrc +1 -1
- data/CHANGELOG.md +22 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +10 -9
- data/README.md +43 -29
- data/boxes/quantal64/download-ubuntu +21 -20
- data/boxes/quantal64/lxc-template +11 -11
- data/boxes/quantal64/metadata.json +1 -1
- data/development/Vagrantfile +8 -4
- data/example/Vagrantfile +3 -15
- data/lib/vagrant-lxc.rb +0 -2
- data/lib/vagrant-lxc/action.rb +1 -14
- data/lib/vagrant-lxc/action/boot.rb +8 -9
- data/lib/vagrant-lxc/action/check_created.rb +6 -2
- data/lib/vagrant-lxc/action/check_running.rb +6 -2
- data/lib/vagrant-lxc/action/compress_rootfs.rb +1 -1
- data/lib/vagrant-lxc/action/create.rb +15 -7
- data/lib/vagrant-lxc/action/created.rb +6 -2
- data/lib/vagrant-lxc/action/destroy.rb +6 -2
- data/lib/vagrant-lxc/action/disconnect.rb +5 -1
- data/lib/vagrant-lxc/action/forced_halt.rb +3 -3
- data/lib/vagrant-lxc/action/forward_ports.rb +2 -2
- data/lib/vagrant-lxc/action/handle_box_metadata.rb +38 -27
- data/lib/vagrant-lxc/action/is_running.rb +6 -2
- data/lib/vagrant-lxc/action/share_folders.rb +8 -8
- data/lib/vagrant-lxc/config.rb +20 -10
- data/lib/vagrant-lxc/driver.rb +162 -0
- data/lib/vagrant-lxc/driver/builder.rb +21 -0
- data/lib/vagrant-lxc/{container → driver}/cli.rb +16 -11
- data/lib/vagrant-lxc/driver/fetch_ip_from_dnsmasq.rb +41 -0
- data/lib/vagrant-lxc/driver/fetch_ip_with_attach.rb +29 -0
- data/lib/vagrant-lxc/errors.rb +10 -0
- data/lib/vagrant-lxc/plugin.rb +4 -0
- data/lib/vagrant-lxc/provider.rb +14 -11
- data/lib/vagrant-lxc/version.rb +1 -1
- data/spec/fixtures/sample-ip-addr-output +2 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/unit/action/compress_rootfs_spec.rb +4 -4
- data/spec/unit/action/forward_ports_spec.rb +3 -3
- data/spec/unit/action/handle_box_metadata_spec.rb +52 -26
- data/spec/unit/{container → driver}/cli_spec.rb +17 -19
- data/spec/unit/driver_spec.rb +173 -0
- data/tasks/boxes.rake +3 -3
- metadata +13 -15
- data/lib/vagrant-lxc/action/base_action.rb +0 -11
- data/lib/vagrant-lxc/action/network.rb +0 -21
- data/lib/vagrant-lxc/container.rb +0 -141
- data/lib/vagrant-lxc/machine_state.rb +0 -25
- data/spec/fixtures/sample-ifconfig-output +0 -18
- data/spec/unit/container_spec.rb +0 -147
- data/spec/unit/machine_state_spec.rb +0 -39
@@ -0,0 +1,162 @@
|
|
1
|
+
require "vagrant/util/retryable"
|
2
|
+
require "vagrant/util/subprocess"
|
3
|
+
|
4
|
+
require "vagrant-lxc/errors"
|
5
|
+
require "vagrant-lxc/driver/cli"
|
6
|
+
|
7
|
+
module Vagrant
|
8
|
+
module LXC
|
9
|
+
class Driver
|
10
|
+
# This is raised if the container can't be found when initializing it with
|
11
|
+
# a name.
|
12
|
+
class ContainerNotFound < StandardError; end
|
13
|
+
|
14
|
+
attr_reader :container_name,
|
15
|
+
:customizations
|
16
|
+
|
17
|
+
def initialize(container_name, cli = CLI.new(container_name))
|
18
|
+
@container_name = container_name
|
19
|
+
@cli = cli
|
20
|
+
@logger = Log4r::Logger.new("vagrant::provider::lxc::driver")
|
21
|
+
@customizations = []
|
22
|
+
end
|
23
|
+
|
24
|
+
def validate!
|
25
|
+
raise ContainerNotFound if @container_name && ! @cli.list.include?(@container_name)
|
26
|
+
end
|
27
|
+
|
28
|
+
def base_path
|
29
|
+
Pathname.new("#{CONTAINERS_PATH}/#{@container_name}")
|
30
|
+
end
|
31
|
+
|
32
|
+
def rootfs_path
|
33
|
+
Pathname.new(base_path.join('config').read.match(/^lxc\.rootfs\s+=\s+(.+)$/)[1])
|
34
|
+
end
|
35
|
+
|
36
|
+
def create(name, template_path, template_options = {})
|
37
|
+
@cli.name = @container_name = name
|
38
|
+
|
39
|
+
import_template(template_path) do |template_name|
|
40
|
+
@logger.debug "Creating container..."
|
41
|
+
@cli.create template_name, template_options
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def share_folders(folders)
|
46
|
+
folders.each do |folder|
|
47
|
+
guestpath = rootfs_path.join(folder[:guestpath].gsub(/^\//, ''))
|
48
|
+
unless guestpath.directory?
|
49
|
+
begin
|
50
|
+
@logger.debug("Guest path doesn't exist, creating: #{guestpath}")
|
51
|
+
system "sudo mkdir -p #{guestpath.to_s}"
|
52
|
+
rescue Errno::EACCES
|
53
|
+
raise Vagrant::Errors::SharedFolderCreateFailed, :path => guestpath.to_s
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
@customizations << ['mount.entry', "#{folder[:hostpath]} #{guestpath} none bind 0 0"]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def start(customizations)
|
62
|
+
@logger.info('Starting container...')
|
63
|
+
|
64
|
+
if ENV['LXC_START_LOG_FILE']
|
65
|
+
extra = ['-o', ENV['LXC_START_LOG_FILE'], '-l', 'DEBUG']
|
66
|
+
end
|
67
|
+
customizations = customizations + @customizations
|
68
|
+
|
69
|
+
@cli.transition_to(:running) { |c| c.start(customizations, (extra || nil)) }
|
70
|
+
end
|
71
|
+
|
72
|
+
def halt
|
73
|
+
@logger.info('Shutting down container...')
|
74
|
+
|
75
|
+
# TODO: issue an lxc-stop if a timeout gets reached
|
76
|
+
@cli.transition_to(:stopped) { |c| c.shutdown }
|
77
|
+
end
|
78
|
+
|
79
|
+
def destroy
|
80
|
+
@cli.destroy
|
81
|
+
end
|
82
|
+
|
83
|
+
# TODO: This needs to be reviewed and specs needs to be written
|
84
|
+
def compress_rootfs
|
85
|
+
rootfs_dirname = File.dirname rootfs_path
|
86
|
+
basename = rootfs_path.to_s.gsub(/^#{Regexp.escape rootfs_dirname}\//, '')
|
87
|
+
# TODO: Pass in tmpdir so we can clean up from outside
|
88
|
+
target_path = "#{Dir.mktmpdir}/rootfs.tar.gz"
|
89
|
+
|
90
|
+
Dir.chdir base_path do
|
91
|
+
@logger.info "Compressing '#{rootfs_path}' rootfs to #{target_path}"
|
92
|
+
system "sudo rm -f rootfs.tar.gz && sudo tar --numeric-owner -czf #{target_path} #{basename}/*"
|
93
|
+
|
94
|
+
@logger.info "Changing rootfs tarbal owner"
|
95
|
+
system "sudo chown #{ENV['USER']}:#{ENV['USER']} #{target_path}"
|
96
|
+
end
|
97
|
+
|
98
|
+
target_path
|
99
|
+
end
|
100
|
+
|
101
|
+
def state
|
102
|
+
if @container_name
|
103
|
+
@cli.state
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def assigned_ip
|
108
|
+
ip = ''
|
109
|
+
retryable(:on => LXC::Errors::ExecuteError, :tries => 10, :sleep => 3) do
|
110
|
+
unless ip = get_container_ip_from_ip_addr
|
111
|
+
# retry
|
112
|
+
raise LXC::Errors::ExecuteError, :command => "lxc-attach"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
ip
|
116
|
+
end
|
117
|
+
|
118
|
+
# From: https://github.com/lxc/lxc/blob/staging/src/python-lxc/lxc/__init__.py#L371-L385
|
119
|
+
def get_container_ip_from_ip_addr
|
120
|
+
output = @cli.attach '/sbin/ip', '-4', 'addr', 'show', 'scope', 'global', 'eth0', namespaces: 'network'
|
121
|
+
if output =~ /^\s+inet ([0-9.]+)\/[0-9]+\s+/
|
122
|
+
return $1.to_s
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
protected
|
127
|
+
|
128
|
+
# Root folder where container configs are stored
|
129
|
+
CONTAINERS_PATH = '/var/lib/lxc'
|
130
|
+
|
131
|
+
def base_path
|
132
|
+
Pathname.new("#{CONTAINERS_PATH}/#{@container_name}")
|
133
|
+
end
|
134
|
+
|
135
|
+
def import_template(path)
|
136
|
+
template_name = "vagrant-tmp-#{@container_name}"
|
137
|
+
tmp_template_path = templates_path.join("lxc-#{template_name}").to_s
|
138
|
+
|
139
|
+
@logger.debug 'Copying LXC template into place'
|
140
|
+
system(%Q[sudo su root -c "cp #{path} #{tmp_template_path}"])
|
141
|
+
|
142
|
+
yield template_name
|
143
|
+
ensure
|
144
|
+
system(%Q[sudo su root -c "rm #{tmp_template_path}"])
|
145
|
+
end
|
146
|
+
|
147
|
+
TEMPLATES_PATH_LOOKUP = %w(
|
148
|
+
/usr/share/lxc/templates
|
149
|
+
/usr/lib/lxc/templates
|
150
|
+
)
|
151
|
+
def templates_path
|
152
|
+
return @templates_path if @templates_path
|
153
|
+
|
154
|
+
path = TEMPLATES_PATH_LOOKUP.find { |candidate| File.directory?(candidate) }
|
155
|
+
# TODO: Raise an user friendly error
|
156
|
+
raise 'Unable to identify lxc templates path!' unless path
|
157
|
+
|
158
|
+
@templates_path = Pathname(path)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative 'fetch_ip_with_attach'
|
2
|
+
require_relative 'fetch_ip_from_dnsmasq'
|
3
|
+
|
4
|
+
module Vagrant
|
5
|
+
module LXC
|
6
|
+
class Driver
|
7
|
+
class Builder
|
8
|
+
def self.build(id)
|
9
|
+
version = CLI.new.version.match(/^(\d+\.\d+)\./)[1].to_f
|
10
|
+
Driver.new(id).tap do |driver|
|
11
|
+
mod = version >= 0.8 ?
|
12
|
+
Driver::FetchIpWithAttach :
|
13
|
+
Driver::FetchIpFromDsnmasq
|
14
|
+
|
15
|
+
driver.extend(mod)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -5,7 +5,7 @@ require "vagrant-lxc/errors"
|
|
5
5
|
|
6
6
|
module Vagrant
|
7
7
|
module LXC
|
8
|
-
class
|
8
|
+
class Driver
|
9
9
|
class CLI
|
10
10
|
attr_accessor :name
|
11
11
|
|
@@ -23,6 +23,15 @@ module Vagrant
|
|
23
23
|
run(:ls).split(/\s+/).uniq
|
24
24
|
end
|
25
25
|
|
26
|
+
def version
|
27
|
+
if run(:version) =~ /lxc version:\s+(.+)\s*$/
|
28
|
+
$1.downcase
|
29
|
+
else
|
30
|
+
# TODO: Raise an user friendly error
|
31
|
+
raise 'Unable to parse lxc version!'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
26
35
|
def state
|
27
36
|
if @name && run(:info, '--name', @name) =~ /^state:[^A-Z]+([A-Z]+)$/
|
28
37
|
$1.downcase.to_sym
|
@@ -31,28 +40,24 @@ module Vagrant
|
|
31
40
|
end
|
32
41
|
end
|
33
42
|
|
34
|
-
def create(template,
|
43
|
+
def create(template, template_opts = {})
|
35
44
|
extra = template_opts.to_a.flatten
|
36
45
|
extra.unshift '--' unless extra.empty?
|
37
46
|
|
38
|
-
rootfs_args = target_rootfs_path ?
|
39
|
-
['-B', 'dir', '--dir', target_rootfs_path] :
|
40
|
-
[]
|
41
|
-
|
42
47
|
run :create,
|
43
48
|
'--template', template,
|
44
49
|
'--name', @name,
|
45
|
-
*
|
50
|
+
*extra
|
46
51
|
end
|
47
52
|
|
48
53
|
def destroy
|
49
54
|
run :destroy, '--name', @name
|
50
55
|
end
|
51
56
|
|
52
|
-
def start(
|
53
|
-
|
54
|
-
|
55
|
-
run :start, '-d', '--name', @name, *
|
57
|
+
def start(overrides = [], extra_opts = [])
|
58
|
+
options = overrides.map { |key, value| ["-s", "lxc.#{key}=#{value}"] }.flatten
|
59
|
+
options += extra_opts if extra_opts
|
60
|
+
run :start, '-d', '--name', @name, *options
|
56
61
|
end
|
57
62
|
|
58
63
|
def shutdown
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Vagrant
|
2
|
+
module LXC
|
3
|
+
class Driver
|
4
|
+
module FetchIpFromDsnmasq
|
5
|
+
def assigned_ip
|
6
|
+
@logger.debug 'Loading ip from dnsmasq leases'
|
7
|
+
ip = nil
|
8
|
+
# TODO: Use Vagrant::Util::Retryable
|
9
|
+
10.times do
|
10
|
+
if dnsmasq_leases =~ /#{Regexp.escape mac_address}\s+([0-9.]+)\s+/
|
11
|
+
ip = $1.to_s
|
12
|
+
break
|
13
|
+
else
|
14
|
+
@logger.debug 'Ip could not be parsed from dnsmasq leases file'
|
15
|
+
sleep 2
|
16
|
+
end
|
17
|
+
end
|
18
|
+
# TODO: Raise an user friendly error
|
19
|
+
raise 'Unable to identify container IP!' unless ip
|
20
|
+
ip
|
21
|
+
end
|
22
|
+
|
23
|
+
def mac_address
|
24
|
+
@mac_address ||= base_path.join('config').read.match(/^lxc\.network\.hwaddr\s+=\s+(.+)$/)[1]
|
25
|
+
end
|
26
|
+
|
27
|
+
LEASES_PATHS = %w(
|
28
|
+
/var/lib/misc/dnsmasq.leases
|
29
|
+
/var/lib/dnsmasq/dnsmasq.leases
|
30
|
+
/var/db/dnsmasq.leases
|
31
|
+
)
|
32
|
+
|
33
|
+
def dnsmasq_leases
|
34
|
+
LEASES_PATHS.map do |path|
|
35
|
+
File.read(path) if File.exists?(path)
|
36
|
+
end.join("\n")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Vagrant
|
2
|
+
module LXC
|
3
|
+
class Driver
|
4
|
+
module FetchIpWithAttach
|
5
|
+
# Include this so we can use `Subprocess` more easily.
|
6
|
+
include Vagrant::Util::Retryable
|
7
|
+
|
8
|
+
def assigned_ip
|
9
|
+
ip = ''
|
10
|
+
retryable(:on => LXC::Errors::ExecuteError, :tries => 10, :sleep => 3) do
|
11
|
+
unless ip = get_container_ip_from_ip_addr
|
12
|
+
# retry
|
13
|
+
raise LXC::Errors::ExecuteError, :command => "lxc-attach"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
ip
|
17
|
+
end
|
18
|
+
|
19
|
+
# From: https://github.com/lxc/lxc/blob/staging/src/python-lxc/lxc/__init__.py#L371-L385
|
20
|
+
def get_container_ip_from_ip_addr
|
21
|
+
output = @cli.attach '/sbin/ip', '-4', 'addr', 'show', 'scope', 'global', 'eth0', namespaces: 'network'
|
22
|
+
if output =~ /^\s+inet ([0-9.]+)\/[0-9]+\s+/
|
23
|
+
return $1.to_s
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/vagrant-lxc/errors.rb
CHANGED
@@ -1,12 +1,22 @@
|
|
1
|
+
require 'vagrant/errors'
|
2
|
+
|
1
3
|
module Vagrant
|
2
4
|
module LXC
|
3
5
|
module Errors
|
4
6
|
class ExecuteError < Vagrant::Errors::VagrantError
|
5
7
|
error_key(:lxc_execute_error)
|
6
8
|
end
|
9
|
+
|
10
|
+
# Box related errors
|
7
11
|
class TemplateFileMissing < Vagrant::Errors::VagrantError
|
8
12
|
error_key(:lxc_template_file_missing)
|
9
13
|
end
|
14
|
+
class RootFSTarballMissing < Vagrant::Errors::VagrantError
|
15
|
+
error_key(:lxc_invalid_box_version)
|
16
|
+
end
|
17
|
+
class InvalidBoxVersion < Vagrant::Errors::VagrantError
|
18
|
+
error_key(:lxc_invalid_box_version)
|
19
|
+
end
|
10
20
|
end
|
11
21
|
end
|
12
22
|
end
|
data/lib/vagrant-lxc/plugin.rb
CHANGED
data/lib/vagrant-lxc/provider.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
require "log4r"
|
2
2
|
|
3
3
|
require "vagrant-lxc/action"
|
4
|
-
require "vagrant-lxc/
|
5
|
-
require "vagrant-lxc/
|
4
|
+
require "vagrant-lxc/driver"
|
5
|
+
require "vagrant-lxc/driver/builder"
|
6
6
|
|
7
7
|
module Vagrant
|
8
8
|
module LXC
|
9
9
|
class Provider < Vagrant.plugin("2", :provider)
|
10
|
-
attr_reader :
|
10
|
+
attr_reader :driver
|
11
11
|
|
12
12
|
def initialize(machine)
|
13
13
|
@logger = Log4r::Logger.new("vagrant::provider::lxc")
|
@@ -23,9 +23,9 @@ module Vagrant
|
|
23
23
|
|
24
24
|
begin
|
25
25
|
@logger.debug("Instantiating the container for: #{id.inspect}")
|
26
|
-
@
|
27
|
-
@
|
28
|
-
rescue
|
26
|
+
@driver = Driver::Builder.build(id)
|
27
|
+
@driver.validate!
|
28
|
+
rescue Driver::ContainerNotFound
|
29
29
|
# The container doesn't exist, so we probably have a stale
|
30
30
|
# ID. Just clear the id out of the machine and reload it.
|
31
31
|
@logger.debug("Container not found! Clearing saved machine ID and reloading.")
|
@@ -40,7 +40,6 @@ module Vagrant
|
|
40
40
|
# exists, otherwise return nil to show that we don't support the
|
41
41
|
# given action.
|
42
42
|
action_method = "action_#{name}"
|
43
|
-
# TODO: Rename to singular
|
44
43
|
return LXC::Action.send(action_method) if LXC::Action.respond_to?(action_method)
|
45
44
|
nil
|
46
45
|
end
|
@@ -52,17 +51,21 @@ module Vagrant
|
|
52
51
|
return nil if state == :not_created
|
53
52
|
|
54
53
|
{
|
55
|
-
:host => @
|
54
|
+
:host => @driver.assigned_ip,
|
56
55
|
:port => @machine.config.ssh.guest_port
|
57
56
|
}
|
58
57
|
end
|
59
58
|
|
60
59
|
def state
|
61
60
|
state_id = nil
|
62
|
-
state_id = :not_created if !@
|
63
|
-
state_id = @
|
61
|
+
state_id = :not_created if !@driver.container_name
|
62
|
+
state_id = @driver.state if !state_id
|
64
63
|
state_id = :unknown if !state_id
|
65
|
-
|
64
|
+
|
65
|
+
short = state_id.to_s.gsub("_", " ")
|
66
|
+
long = I18n.t("vagrant.commands.status.#{state_id}")
|
67
|
+
|
68
|
+
Vagrant::MachineState.new(state_id, short, long)
|
66
69
|
end
|
67
70
|
|
68
71
|
def to_s
|
data/lib/vagrant-lxc/version.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -6,8 +6,8 @@ describe Vagrant::LXC::Action::CompressRootFS do
|
|
6
6
|
let(:app) { mock(:app, call: true) }
|
7
7
|
let(:env) { {machine: machine, ui: stub(info: true)} }
|
8
8
|
let(:machine) { fire_double('Vagrant::Machine', provider: provider) }
|
9
|
-
let(:provider) { fire_double('Vagrant::LXC::Provider',
|
10
|
-
let(:
|
9
|
+
let(:provider) { fire_double('Vagrant::LXC::Provider', driver: driver) }
|
10
|
+
let(:driver) { fire_double('Vagrant::LXC::Driver', compress_rootfs: compressed_rootfs_path) }
|
11
11
|
let(:compressed_rootfs_path) { '/path/to/rootfs.tar.gz' }
|
12
12
|
|
13
13
|
subject { described_class.new(app, env) }
|
@@ -17,8 +17,8 @@ describe Vagrant::LXC::Action::CompressRootFS do
|
|
17
17
|
subject.call(env)
|
18
18
|
end
|
19
19
|
|
20
|
-
it
|
21
|
-
|
20
|
+
it "asks the driver to compress container's rootfs" do
|
21
|
+
driver.should have_received(:compress_rootfs)
|
22
22
|
end
|
23
23
|
|
24
24
|
it 'sets export.temp_dir on action env' do
|