vagrant-lxc 0.4.0 → 0.5.0

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