vagrant-lxc 0.4.0 → 0.5.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.
@@ -0,0 +1,46 @@
1
+ module Vagrant
2
+ module LXC
3
+ module Action
4
+ class FetchIpWithLxcAttach
5
+ # Include this so we can use `Subprocess` more easily.
6
+ include Vagrant::Util::Retryable
7
+
8
+ def initialize(app, env)
9
+ @app = app
10
+ @logger = Log4r::Logger.new("vagrant::lxc::action::fetch_ip_with_lxc_attach")
11
+ end
12
+
13
+ def call(env)
14
+ env[:machine_ip] ||= assigned_ip(env)
15
+ @app.call(env)
16
+ end
17
+
18
+ def assigned_ip(env)
19
+ driver = env[:machine].provider.driver
20
+ version = driver.version.match(/^(\d+\.\d+)\./)[1].to_f
21
+ unless version >= 0.8
22
+ @logger.debug "lxc version does not support the --namespaces argument to lxc-attach"
23
+ return nil
24
+ end
25
+
26
+ ip = ''
27
+ retryable(:on => LXC::Errors::ExecuteError, :tries => 10, :sleep => 3) do
28
+ unless ip = get_container_ip_from_ip_addr(driver)
29
+ # retry
30
+ raise LXC::Errors::ExecuteError, :command => "lxc-attach"
31
+ end
32
+ end
33
+ ip
34
+ end
35
+
36
+ # From: https://github.com/lxc/lxc/blob/staging/src/python-lxc/lxc/__init__.py#L371-L385
37
+ def get_container_ip_from_ip_addr(driver)
38
+ output = driver.attach '/sbin/ip', '-4', 'addr', 'show', 'scope', 'global', 'eth0', namespaces: 'network'
39
+ if output =~ /^\s+inet ([0-9.]+)\/[0-9]+\s+/
40
+ return $1.to_s
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -10,12 +10,13 @@ module Vagrant
10
10
  def call(env)
11
11
  @env = env
12
12
 
13
- # Continue, we need the VM to be booted in order to grab its IP
14
- @app.call env
15
-
16
13
  # Get the ports we're forwarding
17
14
  env[:forwarded_ports] = compile_forwarded_ports(env[:machine].config)
18
15
 
16
+ if @env[:forwarded_ports].any? and not redir_installed?
17
+ raise Errors::RedirNotInstalled
18
+ end
19
+
19
20
  # Warn if we're port forwarding to any privileged ports
20
21
  env[:forwarded_ports].each do |fp|
21
22
  if fp[:host] <= 1024
@@ -24,6 +25,9 @@ module Vagrant
24
25
  end
25
26
  end
26
27
 
28
+ # Continue, we need the VM to be booted in order to grab its IP
29
+ @app.call env
30
+
27
31
  if @env[:forwarded_ports].any?
28
32
  env[:ui].info I18n.t("vagrant.actions.vm.forward_ports.forwarding")
29
33
  forward_ports
@@ -31,8 +35,6 @@ module Vagrant
31
35
  end
32
36
 
33
37
  def forward_ports
34
- @container_ip = @env[:machine].provider.driver.assigned_ip
35
-
36
38
  @env[:forwarded_ports].each do |fp|
37
39
  message_attributes = {
38
40
  # TODO: Add support for multiple adapters
@@ -45,7 +47,12 @@ module Vagrant
45
47
  @env[:ui].info(I18n.t("vagrant.actions.vm.forward_ports.forwarding_entry",
46
48
  message_attributes))
47
49
 
48
- redir_pid = redirect_port(fp[:host], fp[:guest])
50
+ redir_pid = redirect_port(
51
+ fp[:host_ip],
52
+ fp[:host],
53
+ fp[:guest_ip] || @env[:machine].provider.ssh_info[:host],
54
+ fp[:guest]
55
+ )
49
56
  store_redir_pid(fp[:host], redir_pid)
50
57
  end
51
58
  end
@@ -64,8 +71,11 @@ module Vagrant
64
71
  mappings.values
65
72
  end
66
73
 
67
- def redirect_port(host, guest)
68
- redir_cmd = "sudo redir --laddr=127.0.0.1 --lport=#{host} --cport=#{guest} --caddr=#{@container_ip} 2>/dev/null"
74
+ def redirect_port(host_ip, host_port, guest_ip, guest_port)
75
+ params = %W( --lport=#{host_port} --caddr=#{guest_ip} --cport=#{guest_port} )
76
+ params.unshift "--laddr=#{host_ip}" if host_ip
77
+ params << '--syslog' if ENV['REDIR_LOG']
78
+ redir_cmd = "redir #{params.join(' ')} 2>/dev/null"
69
79
 
70
80
  @logger.debug "Forwarding port with `#{redir_cmd}`"
71
81
  spawn redir_cmd
@@ -79,6 +89,10 @@ module Vagrant
79
89
  pid_file.write(redir_pid)
80
90
  end
81
91
  end
92
+
93
+ def redir_installed?
94
+ system "which redir > /dev/null"
95
+ end
82
96
  end
83
97
  end
84
98
  end
@@ -14,7 +14,7 @@ module Vagrant
14
14
  if env[:machine].state.id == :stopped
15
15
  @logger.debug 'Removing temporary files'
16
16
  tmp_path = env[:machine].provider.driver.rootfs_path.join('tmp')
17
- system "sudo rm -rf #{tmp_path}/*"
17
+ env[:machine].provider.sudo_wrapper.run('rm', '-rf', "#{tmp_path}/*")
18
18
  end
19
19
  end
20
20
  end
@@ -6,8 +6,15 @@ module Vagrant
6
6
  # @return [Array]
7
7
  attr_reader :customizations
8
8
 
9
+ # A String that points to a file that acts as a wrapper for sudo commands.
10
+ #
11
+ # This allows us to have a single entry when whitelisting NOPASSWD commands
12
+ # on /etc/sudoers
13
+ attr_accessor :sudo_wrapper
14
+
9
15
  def initialize
10
16
  @customizations = []
17
+ @sudo_wrapper = UNSET_VALUE
11
18
  end
12
19
 
13
20
  # Customize the container by calling `lxc-start` with the given
@@ -25,7 +32,24 @@ module Vagrant
25
32
  @customizations << [key, value]
26
33
  end
27
34
 
28
- # TODO: At some point in the future it would be nice to validate these options
35
+ def finalize!
36
+ @sudo_wrapper = nil if @sudo_wrapper == UNSET_VALUE
37
+ end
38
+
39
+ def validate(machine)
40
+ errors = []
41
+
42
+ if @sudo_wrapper
43
+ hostpath = Pathname.new(@sudo_wrapper).expand_path(machine.env.root_path)
44
+ if ! hostpath.file?
45
+ errors << I18n.t('vagrant_lxc.sudo_wrapper_not_found', path: hostpath.to_s)
46
+ elsif ! hostpath.executable?
47
+ errors << I18n.t('vagrant_lxc.sudo_wrapper_not_executable', path: hostpath.to_s)
48
+ end
49
+ end
50
+
51
+ { "lxc provider" => errors }
52
+ end
29
53
  end
30
54
  end
31
55
  end
@@ -11,12 +11,16 @@ module Vagrant
11
11
  # a name.
12
12
  class ContainerNotFound < StandardError; end
13
13
 
14
+ # Root folder where container configs are stored
15
+ CONTAINERS_PATH = '/var/lib/lxc'
16
+
14
17
  attr_reader :container_name,
15
18
  :customizations
16
19
 
17
- def initialize(container_name, cli = CLI.new(container_name))
20
+ def initialize(container_name, sudo_wrapper, cli = nil)
18
21
  @container_name = container_name
19
- @cli = cli
22
+ @sudo_wrapper = sudo_wrapper
23
+ @cli = cli || CLI.new(sudo_wrapper, container_name)
20
24
  @logger = Log4r::Logger.new("vagrant::provider::lxc::driver")
21
25
  @customizations = []
22
26
  end
@@ -33,6 +37,10 @@ module Vagrant
33
37
  Pathname.new(base_path.join('config').read.match(/^lxc\.rootfs\s+=\s+(.+)$/)[1])
34
38
  end
35
39
 
40
+ def mac_address
41
+ @mac_address ||= base_path.join('config').read.match(/^lxc\.network\.hwaddr\s+=\s+(.+)$/)[1]
42
+ end
43
+
36
44
  def create(name, template_path, config_file, template_options = {})
37
45
  @cli.name = @container_name = name
38
46
 
@@ -48,7 +56,7 @@ module Vagrant
48
56
  unless guestpath.directory?
49
57
  begin
50
58
  @logger.debug("Guest path doesn't exist, creating: #{guestpath}")
51
- system "sudo mkdir -p #{guestpath.to_s}"
59
+ @sudo_wrapper.run('mkdir', '-p', guestpath.to_s)
52
60
  rescue Errno::EACCES
53
61
  raise Vagrant::Errors::SharedFolderCreateFailed, :path => guestpath.to_s
54
62
  end
@@ -64,9 +72,11 @@ module Vagrant
64
72
  if ENV['LXC_START_LOG_FILE']
65
73
  extra = ['-o', ENV['LXC_START_LOG_FILE'], '-l', 'DEBUG']
66
74
  end
67
- customizations = customizations + @customizations
68
75
 
69
- @cli.transition_to(:running) { |c| c.start(customizations, (extra || nil)) }
76
+ prune_customizations
77
+ write_customizations(customizations + @customizations)
78
+
79
+ @cli.transition_to(:running) { |c| c.start(extra) }
70
80
  end
71
81
 
72
82
  def forced_halt
@@ -80,6 +90,14 @@ module Vagrant
80
90
  @cli.destroy
81
91
  end
82
92
 
93
+ def attach(*command)
94
+ @cli.attach(*command)
95
+ end
96
+
97
+ def version
98
+ @version ||= @cli.version
99
+ end
100
+
83
101
  # TODO: This needs to be reviewed and specs needs to be written
84
102
  def compress_rootfs
85
103
  rootfs_dirname = File.dirname rootfs_path
@@ -89,10 +107,11 @@ module Vagrant
89
107
 
90
108
  Dir.chdir base_path do
91
109
  @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}/*"
110
+ @sudo_wrapper.run('rm', '-f', 'rootfs.tar.gz')
111
+ @sudo_wrapper.run('tar', '--numeric-owner', '-czf', target_path, "#{basename}/*")
93
112
 
94
113
  @logger.info "Changing rootfs tarbal owner"
95
- system "sudo chown #{ENV['USER']}:#{ENV['USER']} #{target_path}"
114
+ @sudo_wrapper.run('chown', "#{ENV['USER']}:#{ENV['USER']}", target_path)
96
115
  end
97
116
 
98
117
  target_path
@@ -104,16 +123,25 @@ module Vagrant
104
123
  end
105
124
  end
106
125
 
107
- def assigned_ip
126
+ def prune_customizations
127
+ # Use sed to just strip out the block of code which was inserted by Vagrant
128
+ @logger.debug 'Prunning vagrant-lxc customizations'
129
+ @sudo_wrapper.su_c("sed -e '/^# VAGRANT-BEGIN/,/^# VAGRANT-END/ d' -ibak #{base_path.join('config')}")
108
130
  end
109
131
 
110
132
  protected
111
133
 
112
- # Root folder where container configs are stored
113
- CONTAINERS_PATH = '/var/lib/lxc'
134
+ def write_customizations(customizations)
135
+ customizations = customizations.map do |key, value|
136
+ "lxc.#{key}=#{value}"
137
+ end
138
+ customizations.unshift '# VAGRANT-BEGIN'
139
+ customizations << '# VAGRANT-END'
114
140
 
115
- def base_path
116
- Pathname.new("#{CONTAINERS_PATH}/#{@container_name}")
141
+ config_file = base_path.join('config').to_s
142
+ customizations.each do |line|
143
+ @sudo_wrapper.su_c("echo '#{line}' >> #{config_file}")
144
+ end
117
145
  end
118
146
 
119
147
  def import_template(path)
@@ -121,11 +149,11 @@ module Vagrant
121
149
  tmp_template_path = templates_path.join("lxc-#{template_name}").to_s
122
150
 
123
151
  @logger.debug 'Copying LXC template into place'
124
- system(%Q[sudo su root -c "cp #{path} #{tmp_template_path}"])
152
+ @sudo_wrapper.run('cp', path, tmp_template_path)
125
153
 
126
154
  yield template_name
127
155
  ensure
128
- system(%Q[sudo su root -c "rm #{tmp_template_path}"])
156
+ @sudo_wrapper.run('rm', tmp_template_path)
129
157
  end
130
158
 
131
159
  TEMPLATES_PATH_LOOKUP = %w(
@@ -17,12 +17,10 @@ module Vagrant
17
17
  end
18
18
  end
19
19
 
20
- # Include this so we can use `Subprocess` more easily.
21
- include Vagrant::Util::Retryable
22
-
23
- def initialize(name = nil)
24
- @name = name
25
- @logger = Log4r::Logger.new("vagrant::provider::lxc::container::cli")
20
+ def initialize(sudo_wrapper, name = nil)
21
+ @sudo_wrapper = sudo_wrapper
22
+ @name = name
23
+ @logger = Log4r::Logger.new("vagrant::provider::lxc::container::cli")
26
24
  end
27
25
 
28
26
  def list
@@ -65,10 +63,8 @@ module Vagrant
65
63
  run :destroy, '--name', @name
66
64
  end
67
65
 
68
- def start(overrides = [], extra_opts = [])
69
- options = overrides.map { |key, value| ["-s", "lxc.#{key}=#{value}"] }.flatten
70
- options += extra_opts if extra_opts
71
- run :start, '-d', '--name', @name, *options
66
+ def start(options = [])
67
+ run :start, '-d', '--name', @name, *Array(options)
72
68
  end
73
69
 
74
70
  def stop
@@ -110,56 +106,7 @@ module Vagrant
110
106
  private
111
107
 
112
108
  def run(command, *args)
113
- execute('sudo', "lxc-#{command}", *args)
114
- end
115
-
116
- # TODO: Review code below this line, it was pretty much a copy and
117
- # paste from VirtualBox base driver and has no tests
118
- def execute(*command, &block)
119
- # Get the options hash if it exists
120
- opts = {}
121
- opts = command.pop if command.last.is_a?(Hash)
122
-
123
- tries = 0
124
- tries = 3 if opts[:retryable]
125
-
126
- sleep = opts.fetch(:sleep, 1)
127
-
128
- # Variable to store our execution result
129
- r = nil
130
-
131
- retryable(:on => LXC::Errors::ExecuteError, :tries => tries, :sleep => sleep) do
132
- # Execute the command
133
- r = raw(*command, &block)
134
-
135
- # If the command was a failure, then raise an exception that is
136
- # nicely handled by Vagrant.
137
- if r.exit_code != 0
138
- if @interrupted
139
- @logger.info("Exit code != 0, but interrupted. Ignoring.")
140
- else
141
- raise LXC::Errors::ExecuteError, :command => command.inspect
142
- end
143
- end
144
- end
145
-
146
- # Return the output, making sure to replace any Windows-style
147
- # newlines with Unix-style.
148
- r.stdout.gsub("\r\n", "\n")
149
- end
150
-
151
- def raw(*command, &block)
152
- int_callback = lambda do
153
- @interrupted = true
154
- @logger.info("Interrupted.")
155
- end
156
-
157
- # Append in the options for subprocess
158
- command << { :notify => [:stdout, :stderr] }
159
-
160
- Vagrant::Util::Busy.busy(int_callback) do
161
- Vagrant::Util::Subprocess.execute(*command, &block)
162
- end
109
+ @sudo_wrapper.run("lxc-#{command}", *args)
163
110
  end
164
111
  end
165
112
  end
@@ -7,6 +7,10 @@ module Vagrant
7
7
  error_key(:lxc_execute_error)
8
8
  end
9
9
 
10
+ class LxcNotInstalled < Vagrant::Errors::VagrantError
11
+ error_key(:lxc_not_installed)
12
+ end
13
+
10
14
  # Box related errors
11
15
  class TemplateFileMissing < Vagrant::Errors::VagrantError
12
16
  error_key(:lxc_template_file_missing)
@@ -17,6 +21,9 @@ module Vagrant
17
21
  class IncompatibleBox < Vagrant::Errors::VagrantError
18
22
  error_key(:lxc_incompatible_box)
19
23
  end
24
+ class RedirNotInstalled < Vagrant::Errors::VagrantError
25
+ error_key(:lxc_redir_not_installed)
26
+ end
20
27
  end
21
28
  end
22
29
  end
@@ -2,7 +2,7 @@ require "log4r"
2
2
 
3
3
  require "vagrant-lxc/action"
4
4
  require "vagrant-lxc/driver"
5
- require "vagrant-lxc/driver/builder"
5
+ require "vagrant-lxc/sudo_wrapper"
6
6
 
7
7
  module Vagrant
8
8
  module LXC
@@ -13,9 +13,24 @@ module Vagrant
13
13
  @logger = Log4r::Logger.new("vagrant::provider::lxc")
14
14
  @machine = machine
15
15
 
16
+ ensure_lxc_installed!
16
17
  machine_id_changed
17
18
  end
18
19
 
20
+ def sudo_wrapper
21
+ @shell ||= begin
22
+ wrapper = @machine.provider_config.sudo_wrapper
23
+ wrapper = Pathname(wrapper).expand_path(@machine.env.root_path).to_s if wrapper
24
+ SudoWrapper.new(wrapper)
25
+ end
26
+ end
27
+
28
+ def ensure_lxc_installed!
29
+ unless system("which lxc-version > /dev/null")
30
+ raise Errors::LxcNotInstalled
31
+ end
32
+ end
33
+
19
34
  # If the machine ID changed, then we need to rebuild our underlying
20
35
  # container.
21
36
  def machine_id_changed
@@ -23,7 +38,7 @@ module Vagrant
23
38
 
24
39
  begin
25
40
  @logger.debug("Instantiating the container for: #{id.inspect}")
26
- @driver = Driver::Builder.build(id)
41
+ @driver = Driver.new(id, self.sudo_wrapper)
27
42
  @driver.validate!
28
43
  rescue Driver::ContainerNotFound
29
44
  # The container doesn't exist, so we probably have a stale
@@ -50,8 +65,16 @@ module Vagrant
50
65
  # we return nil.
51
66
  return nil if state == :not_created
52
67
 
68
+ # Run a custom action called "fetch_ip" which does what it says and puts
69
+ # the IP found into the `:machine_ip` key in the environment.
70
+ env = @machine.action("fetch_ip")
71
+
72
+ # If we were not able to identify the container's IP, we return nil
73
+ # here and we let Vagrant core deal with it ;)
74
+ return nil unless env[:machine_ip]
75
+
53
76
  {
54
- :host => @driver.assigned_ip,
77
+ :host => env[:machine_ip],
55
78
  :port => @machine.config.ssh.guest_port
56
79
  }
57
80
  end