parent
98f1df47a1
commit
d62a053674
11 changed files with 182 additions and 90 deletions
|
@ -10,6 +10,7 @@ BACKWARDS INCOMPATIBILITIES:
|
|||
FEATURES:
|
||||
|
||||
- Add support for salt-minion and add latest dev release for ubuntu codenamed saucy [#116](https://github.com/fgrehm/vagrant-lxc/pull/116)
|
||||
- Add support for using a sudo wrapper script [#90](https://github.com/fgrehm/vagrant-lxc/issues/90)
|
||||
|
||||
IMPROVEMENTS:
|
||||
|
||||
|
|
37
README.md
37
README.md
|
@ -97,6 +97,41 @@ set from container's configuration file that is usually kept under
|
|||
For other configuration options, please check [lxc.conf manpages](http://manpages.ubuntu.com/manpages/quantal/man5/lxc.conf.5.html).
|
||||
|
||||
|
||||
### Avoiding `sudo` passwords
|
||||
|
||||
This plugin requires **a lot** of `sudo`ing since [user namespaces](https://wiki.ubuntu.com/LxcSecurity)
|
||||
are not supported on mainstream kernels. In order to work around that we can use
|
||||
a really dumb Ruby wrapper script like the one below and add a `NOPASSWD` entry
|
||||
to our `/etc/sudoers` file:
|
||||
|
||||
```ruby
|
||||
#!/usr/bin/env ruby
|
||||
exec ARGV.join(' ')
|
||||
```
|
||||
|
||||
For example, you can save the code above under your `/usr/bin/lxc-vagrant-wrapper`,
|
||||
turn it into an executable script by running `chmod +x /usr/bin/lxc-vagrant-wrapper`
|
||||
and add the line below to your `/etc/sudoers` file:
|
||||
|
||||
```
|
||||
USERNAME ALL=NOPASSWD:/usr/bin/lxc-vagrant-wrapper
|
||||
```
|
||||
|
||||
In order to tell vagrant-lxc to use that script when `sudo` is needed, you can
|
||||
pass in the path to the script as a configuration for the provider:
|
||||
|
||||
```ruby
|
||||
Vagrant.configure("2") do |config|
|
||||
config.vm.provider :lxc do |lxc|
|
||||
lxc.sudo_wrapper = '/usr/bin/lxc-vagrant-wrapper'
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
If you want to set the `sudo_wrapper` globally, just add the code above to your
|
||||
`~/.vagrant.d/Vagrantfile`.
|
||||
|
||||
|
||||
### Base boxes
|
||||
|
||||
Please check [the wiki](https://github.com/fgrehm/vagrant-lxc/wiki/Base-boxes)
|
||||
|
@ -108,8 +143,6 @@ base boxes and information on [how to build your own](https://github.com/fgrehm/
|
|||
|
||||
* The plugin does not detect forwarded ports collision, right now you are
|
||||
responsible for taking care of that.
|
||||
* There is a hell lot of `sudo`s involved and this will probably be around until
|
||||
[user namespaces](https://wiki.ubuntu.com/LxcSecurity) are supported or I'm able to handle [#90](https://github.com/fgrehm/vagrant-lxc/issues/90)
|
||||
* [Does not tell you if dependencies are not met](https://github.com/fgrehm/vagrant-lxc/issues/11)
|
||||
(will probably just throw up some random error)
|
||||
* + bunch of other [core features](https://github.com/fgrehm/vagrant-lxc/issues?labels=core&milestone=&page=1&state=open)
|
||||
|
|
|
@ -14,7 +14,7 @@ module Vagrant
|
|||
if env[:machine].state.id == :stopped
|
||||
@logger.debug 'Removing temporary files'
|
||||
tmp_path = env[:machine].provider.driver.rootfs_path.join('tmp')
|
||||
system "sudo rm -rf #{tmp_path}/*"
|
||||
env[:machine].provider.sudo_wrapper.run('rm', '-rf', "#{tmp_path}/*")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,8 +6,15 @@ module Vagrant
|
|||
# @return [Array]
|
||||
attr_reader :customizations
|
||||
|
||||
# A String that points to a file that acts as a wrapper for sudo commands.
|
||||
#
|
||||
# This allows us to have a single entry when whitelisting NOPASSWD commands
|
||||
# on /etc/sudoers
|
||||
attr_accessor :sudo_wrapper
|
||||
|
||||
def initialize
|
||||
@customizations = []
|
||||
@sudo_wrapper = UNSET_VALUE
|
||||
end
|
||||
|
||||
# Customize the container by calling `lxc-start` with the given
|
||||
|
@ -25,7 +32,24 @@ module Vagrant
|
|||
@customizations << [key, value]
|
||||
end
|
||||
|
||||
# TODO: At some point in the future it would be nice to validate these options
|
||||
def finalize!
|
||||
@sudo_wrapper = nil if @sudo_wrapper == UNSET_VALUE
|
||||
end
|
||||
|
||||
def validate(machine)
|
||||
errors = []
|
||||
|
||||
if @sudo_wrapper
|
||||
hostpath = Pathname.new(@sudo_wrapper).expand_path(machine.env.root_path)
|
||||
if ! hostpath.file?
|
||||
errors << I18n.t('vagrant_lxc.sudo_wrapper_not_found', path: hostpath.to_s)
|
||||
elsif ! hostpath.executable?
|
||||
errors << I18n.t('vagrant_lxc.sudo_wrapper_not_executable', path: hostpath.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
{ "lxc provider" => errors }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,9 +14,10 @@ module Vagrant
|
|||
attr_reader :container_name,
|
||||
:customizations
|
||||
|
||||
def initialize(container_name, cli = CLI.new(container_name))
|
||||
def initialize(container_name, sudo_wrapper, cli = nil)
|
||||
@container_name = container_name
|
||||
@cli = cli
|
||||
@sudo_wrapper = sudo_wrapper
|
||||
@cli = cli || CLI.new(sudo_wrapper, container_name)
|
||||
@logger = Log4r::Logger.new("vagrant::provider::lxc::driver")
|
||||
@customizations = []
|
||||
end
|
||||
|
@ -48,7 +49,7 @@ module Vagrant
|
|||
unless guestpath.directory?
|
||||
begin
|
||||
@logger.debug("Guest path doesn't exist, creating: #{guestpath}")
|
||||
system "sudo mkdir -p #{guestpath.to_s}"
|
||||
@sudo_wrapper.run('mkdir', '-p', guestpath.to_s)
|
||||
rescue Errno::EACCES
|
||||
raise Vagrant::Errors::SharedFolderCreateFailed, :path => guestpath.to_s
|
||||
end
|
||||
|
@ -89,10 +90,11 @@ module Vagrant
|
|||
|
||||
Dir.chdir base_path do
|
||||
@logger.info "Compressing '#{rootfs_path}' rootfs to #{target_path}"
|
||||
system "sudo rm -f rootfs.tar.gz && sudo tar --numeric-owner -czf #{target_path} #{basename}/*"
|
||||
@sudo_wrapper.run('rm', '-f', 'rootfs.tar.gz')
|
||||
@sudo_wrapper.run('tar', '--numeric-owner', '-czf', target_path, "#{basename}/*")
|
||||
|
||||
@logger.info "Changing rootfs tarbal owner"
|
||||
system "sudo chown #{ENV['USER']}:#{ENV['USER']} #{target_path}"
|
||||
@sudo_wrapper.run('chown', "#{ENV['USER']}:#{ENV['USER']}", target_path)
|
||||
end
|
||||
|
||||
target_path
|
||||
|
@ -121,11 +123,11 @@ module Vagrant
|
|||
tmp_template_path = templates_path.join("lxc-#{template_name}").to_s
|
||||
|
||||
@logger.debug 'Copying LXC template into place'
|
||||
system(%Q[sudo su root -c "cp #{path} #{tmp_template_path}"])
|
||||
@sudo_wrapper.run('cp', path, tmp_template_path)
|
||||
|
||||
yield template_name
|
||||
ensure
|
||||
system(%Q[sudo su root -c "rm #{tmp_template_path}"])
|
||||
@sudo_wrapper.run('rm', tmp_template_path)
|
||||
end
|
||||
|
||||
TEMPLATES_PATH_LOOKUP = %w(
|
||||
|
|
|
@ -5,9 +5,9 @@ module Vagrant
|
|||
module LXC
|
||||
class Driver
|
||||
class Builder
|
||||
def self.build(id)
|
||||
version = CLI.new.version.match(/^(\d+\.\d+)\./)[1].to_f
|
||||
Driver.new(id).tap do |driver|
|
||||
def self.build(id, shell)
|
||||
version = CLI.new(shell).version.match(/^(\d+\.\d+)\./)[1].to_f
|
||||
Driver.new(id, shell).tap do |driver|
|
||||
mod = version >= 0.8 ?
|
||||
Driver::FetchIpWithAttach :
|
||||
Driver::FetchIpFromDsnmasq
|
||||
|
|
|
@ -17,12 +17,10 @@ module Vagrant
|
|||
end
|
||||
end
|
||||
|
||||
# Include this so we can use `Subprocess` more easily.
|
||||
include Vagrant::Util::Retryable
|
||||
|
||||
def initialize(name = nil)
|
||||
@name = name
|
||||
@logger = Log4r::Logger.new("vagrant::provider::lxc::container::cli")
|
||||
def initialize(sudo_wrapper, name = nil)
|
||||
@sudo_wrapper = sudo_wrapper
|
||||
@name = name
|
||||
@logger = Log4r::Logger.new("vagrant::provider::lxc::container::cli")
|
||||
end
|
||||
|
||||
def list
|
||||
|
@ -66,7 +64,7 @@ module Vagrant
|
|||
end
|
||||
|
||||
def start(overrides = [], extra_opts = [])
|
||||
options = overrides.map { |key, value| ["-s", "lxc.#{key}=#{value}"] }.flatten
|
||||
options = overrides.map { |key, value| ["-s", "lxc.#{key}='#{value}'"] }.flatten
|
||||
options += extra_opts if extra_opts
|
||||
run :start, '-d', '--name', @name, *options
|
||||
end
|
||||
|
@ -110,56 +108,7 @@ module Vagrant
|
|||
private
|
||||
|
||||
def run(command, *args)
|
||||
execute('sudo', "lxc-#{command}", *args)
|
||||
end
|
||||
|
||||
# TODO: Review code below this line, it was pretty much a copy and
|
||||
# paste from VirtualBox base driver and has no tests
|
||||
def execute(*command, &block)
|
||||
# Get the options hash if it exists
|
||||
opts = {}
|
||||
opts = command.pop if command.last.is_a?(Hash)
|
||||
|
||||
tries = 0
|
||||
tries = 3 if opts[:retryable]
|
||||
|
||||
sleep = opts.fetch(:sleep, 1)
|
||||
|
||||
# Variable to store our execution result
|
||||
r = nil
|
||||
|
||||
retryable(:on => LXC::Errors::ExecuteError, :tries => tries, :sleep => sleep) do
|
||||
# Execute the command
|
||||
r = raw(*command, &block)
|
||||
|
||||
# If the command was a failure, then raise an exception that is
|
||||
# nicely handled by Vagrant.
|
||||
if r.exit_code != 0
|
||||
if @interrupted
|
||||
@logger.info("Exit code != 0, but interrupted. Ignoring.")
|
||||
else
|
||||
raise LXC::Errors::ExecuteError, :command => command.inspect
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Return the output, making sure to replace any Windows-style
|
||||
# newlines with Unix-style.
|
||||
r.stdout.gsub("\r\n", "\n")
|
||||
end
|
||||
|
||||
def raw(*command, &block)
|
||||
int_callback = lambda do
|
||||
@interrupted = true
|
||||
@logger.info("Interrupted.")
|
||||
end
|
||||
|
||||
# Append in the options for subprocess
|
||||
command << { :notify => [:stdout, :stderr] }
|
||||
|
||||
Vagrant::Util::Busy.busy(int_callback) do
|
||||
Vagrant::Util::Subprocess.execute(*command, &block)
|
||||
end
|
||||
@sudo_wrapper.run("lxc-#{command}", *args)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,6 +3,7 @@ require "log4r"
|
|||
require "vagrant-lxc/action"
|
||||
require "vagrant-lxc/driver"
|
||||
require "vagrant-lxc/driver/builder"
|
||||
require "vagrant-lxc/sudo_wrapper"
|
||||
|
||||
module Vagrant
|
||||
module LXC
|
||||
|
@ -16,6 +17,14 @@ module Vagrant
|
|||
machine_id_changed
|
||||
end
|
||||
|
||||
def sudo_wrapper
|
||||
@shell ||= begin
|
||||
wrapper = @machine.provider_config.sudo_wrapper
|
||||
wrapper = Pathname(wrapper).expand_path(@machine.env.root_path).to_s if wrapper
|
||||
SudoWrapper.new(wrapper)
|
||||
end
|
||||
end
|
||||
|
||||
# If the machine ID changed, then we need to rebuild our underlying
|
||||
# container.
|
||||
def machine_id_changed
|
||||
|
@ -23,7 +32,7 @@ module Vagrant
|
|||
|
||||
begin
|
||||
@logger.debug("Instantiating the container for: #{id.inspect}")
|
||||
@driver = Driver::Builder.build(id)
|
||||
@driver = Driver::Builder.build(id, self.sudo_wrapper)
|
||||
@driver.validate!
|
||||
rescue Driver::ContainerNotFound
|
||||
# The container doesn't exist, so we probably have a stale
|
||||
|
|
69
lib/vagrant-lxc/sudo_wrapper.rb
Normal file
69
lib/vagrant-lxc/sudo_wrapper.rb
Normal file
|
@ -0,0 +1,69 @@
|
|||
module Vagrant
|
||||
module LXC
|
||||
class SudoWrapper
|
||||
# Include this so we can use `Subprocess` more easily.
|
||||
include Vagrant::Util::Retryable
|
||||
|
||||
def initialize(wrapper_path = nil)
|
||||
@wrapper_path = wrapper_path
|
||||
@logger = Log4r::Logger.new("vagrant::lxc::shell")
|
||||
end
|
||||
|
||||
def run(*command)
|
||||
command.unshift @wrapper_path if @wrapper_path
|
||||
execute *(['sudo'] + command)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# TODO: Review code below this line, it was pretty much a copy and
|
||||
# paste from VirtualBox base driver and has no tests
|
||||
def execute(*command, &block)
|
||||
# Get the options hash if it exists
|
||||
opts = {}
|
||||
opts = command.pop if command.last.is_a?(Hash)
|
||||
|
||||
tries = 0
|
||||
tries = 3 if opts[:retryable]
|
||||
|
||||
sleep = opts.fetch(:sleep, 1)
|
||||
|
||||
# Variable to store our execution result
|
||||
r = nil
|
||||
|
||||
retryable(:on => LXC::Errors::ExecuteError, :tries => tries, :sleep => sleep) do
|
||||
# Execute the command
|
||||
r = raw(*command, &block)
|
||||
|
||||
# If the command was a failure, then raise an exception that is
|
||||
# nicely handled by Vagrant.
|
||||
if r.exit_code != 0
|
||||
if @interrupted
|
||||
@logger.info("Exit code != 0, but interrupted. Ignoring.")
|
||||
else
|
||||
raise LXC::Errors::ExecuteError, :command => command.inspect
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Return the output, making sure to replace any Windows-style
|
||||
# newlines with Unix-style.
|
||||
r.stdout.gsub("\r\n", "\n")
|
||||
end
|
||||
|
||||
def raw(*command, &block)
|
||||
int_callback = lambda do
|
||||
@interrupted = true
|
||||
@logger.info("Interrupted.")
|
||||
end
|
||||
|
||||
# Append in the options for subprocess
|
||||
command << { :notify => [:stdout, :stderr] }
|
||||
|
||||
Vagrant::Util::Busy.busy(int_callback) do
|
||||
Vagrant::Util::Subprocess.execute(*command, &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -3,6 +3,10 @@ require 'unit_helper'
|
|||
require 'vagrant-lxc/driver/cli'
|
||||
|
||||
describe Vagrant::LXC::Driver::CLI do
|
||||
let(:sudo_wrapper) { instance_double('Vagrant::LXC::SudoWrapper', run: true) }
|
||||
|
||||
subject { described_class.new(sudo_wrapper) }
|
||||
|
||||
describe 'list' do
|
||||
let(:lxc_ls_out) { "dup-container\na-container dup-container" }
|
||||
let(:result) { @result }
|
||||
|
@ -41,7 +45,7 @@ describe Vagrant::LXC::Driver::CLI do
|
|||
let(:config_file) { 'config' }
|
||||
let(:template_args) { { '--extra-param' => 'param', '--other' => 'value' } }
|
||||
|
||||
subject { described_class.new(name) }
|
||||
subject { described_class.new(sudo_wrapper, name) }
|
||||
|
||||
before do
|
||||
subject.stub(:run) { |*args| @run_args = args }
|
||||
|
@ -64,7 +68,7 @@ describe Vagrant::LXC::Driver::CLI do
|
|||
describe 'destroy' do
|
||||
let(:name) { 'a-container-for-destruction' }
|
||||
|
||||
subject { described_class.new(name) }
|
||||
subject { described_class.new(sudo_wrapper, name) }
|
||||
|
||||
before do
|
||||
subject.stub(:run)
|
||||
|
@ -78,7 +82,7 @@ describe Vagrant::LXC::Driver::CLI do
|
|||
|
||||
describe 'start' do
|
||||
let(:name) { 'a-container' }
|
||||
subject { described_class.new(name) }
|
||||
subject { described_class.new(sudo_wrapper, name) }
|
||||
|
||||
before do
|
||||
subject.stub(:run)
|
||||
|
@ -96,15 +100,15 @@ describe Vagrant::LXC::Driver::CLI do
|
|||
it 'uses provided array to override container configs' do
|
||||
subject.start([['config', 'value'], ['other', 'value']])
|
||||
subject.should have_received(:run).with(:start, '-d', '--name', name,
|
||||
'-s', 'lxc.config=value',
|
||||
'-s', 'lxc.other=value'
|
||||
'-s', "lxc.config='value'",
|
||||
'-s', "lxc.other='value'"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'shutdown' do
|
||||
let(:name) { 'a-running-container' }
|
||||
subject { described_class.new(name) }
|
||||
subject { described_class.new(sudo_wrapper, name) }
|
||||
|
||||
before do
|
||||
subject.stub(:run)
|
||||
|
@ -118,7 +122,7 @@ describe Vagrant::LXC::Driver::CLI do
|
|||
|
||||
describe 'state' do
|
||||
let(:name) { 'a-container' }
|
||||
subject { described_class.new(name) }
|
||||
subject { described_class.new(sudo_wrapper, name) }
|
||||
|
||||
before do
|
||||
subject.stub(:run).and_return("state: STOPPED\npid: 2")
|
||||
|
@ -138,7 +142,7 @@ describe Vagrant::LXC::Driver::CLI do
|
|||
let(:name) { 'a-running-container' }
|
||||
let(:command) { ['ls', 'cat /tmp/file'] }
|
||||
let(:command_output) { 'folders list' }
|
||||
subject { described_class.new(name) }
|
||||
subject { described_class.new(sudo_wrapper, name) }
|
||||
|
||||
before do
|
||||
subject.stub(run: command_output)
|
||||
|
|
|
@ -6,9 +6,9 @@ require 'vagrant-lxc/driver/cli'
|
|||
|
||||
describe Vagrant::LXC::Driver do
|
||||
describe 'container name validation' do
|
||||
let(:unknown_container) { described_class.new('unknown', cli) }
|
||||
let(:valid_container) { described_class.new('valid', cli) }
|
||||
let(:new_container) { described_class.new(nil) }
|
||||
let(:unknown_container) { described_class.new('unknown', nil, cli) }
|
||||
let(:valid_container) { described_class.new('valid', nil, cli) }
|
||||
let(:new_container) { described_class.new(nil, nil) }
|
||||
let(:cli) { instance_double('Vagrant::LXC::Driver::CLI', list: ['valid']) }
|
||||
|
||||
it 'raises a ContainerNotFound error if an unknown container name gets provided' do
|
||||
|
@ -39,7 +39,7 @@ describe Vagrant::LXC::Driver do
|
|||
let(:rootfs_tarball) { '/path/to/cache/rootfs.tar.gz' }
|
||||
let(:cli) { instance_double('Vagrant::LXC::Driver::CLI', :create => true, :name= => true) }
|
||||
|
||||
subject { described_class.new(nil, cli) }
|
||||
subject { described_class.new(nil, nil, cli) }
|
||||
|
||||
before do
|
||||
subject.stub(:import_template).and_yield(template_name)
|
||||
|
@ -62,7 +62,7 @@ describe Vagrant::LXC::Driver do
|
|||
describe 'destruction' do
|
||||
let(:cli) { instance_double('Vagrant::LXC::Driver::CLI', destroy: true) }
|
||||
|
||||
subject { described_class.new('name', cli) }
|
||||
subject { described_class.new('name', nil, cli) }
|
||||
|
||||
before { subject.destroy }
|
||||
|
||||
|
@ -76,7 +76,7 @@ describe Vagrant::LXC::Driver do
|
|||
let(:internal_customization) { ['internal', 'customization'] }
|
||||
let(:cli) { instance_double('Vagrant::LXC::Driver::CLI', start: true) }
|
||||
|
||||
subject { described_class.new('name', cli) }
|
||||
subject { described_class.new('name', nil, cli) }
|
||||
|
||||
before do
|
||||
cli.stub(:transition_to).and_yield(cli)
|
||||
|
@ -96,7 +96,7 @@ describe Vagrant::LXC::Driver do
|
|||
describe 'halt' do
|
||||
let(:cli) { instance_double('Vagrant::LXC::Driver::CLI', shutdown: true) }
|
||||
|
||||
subject { described_class.new('name', cli) }
|
||||
subject { described_class.new('name', nil, cli) }
|
||||
|
||||
before do
|
||||
cli.stub(:transition_to).and_yield(cli)
|
||||
|
@ -124,7 +124,7 @@ describe Vagrant::LXC::Driver do
|
|||
let(:cli_state) { :something }
|
||||
let(:cli) { instance_double('Vagrant::LXC::Driver::CLI', state: cli_state) }
|
||||
|
||||
subject { described_class.new('name', cli) }
|
||||
subject { described_class.new('name', nil, cli) }
|
||||
|
||||
it 'delegates to cli' do
|
||||
subject.state.should == cli_state
|
||||
|
@ -161,8 +161,9 @@ describe Vagrant::LXC::Driver do
|
|||
let(:folders) { [shared_folder] }
|
||||
let(:rootfs_path) { Pathname('/path/to/rootfs') }
|
||||
let(:expected_guest_path) { "#{rootfs_path}/vagrant" }
|
||||
let(:sudo_wrapper) { instance_double('Vagrant::LXC::SudoWrapper', run: true) }
|
||||
|
||||
subject { described_class.new('name') }
|
||||
subject { described_class.new('name', sudo_wrapper) }
|
||||
|
||||
before do
|
||||
subject.stub(rootfs_path: rootfs_path, system: true)
|
||||
|
@ -170,7 +171,7 @@ describe Vagrant::LXC::Driver do
|
|||
end
|
||||
|
||||
it "creates guest folder under container's rootfs" do
|
||||
subject.should have_received(:system).with("sudo mkdir -p #{expected_guest_path}")
|
||||
sudo_wrapper.should have_received(:run).with("mkdir", "-p", expected_guest_path)
|
||||
end
|
||||
|
||||
it 'adds a mount.entry to its local customizations' do
|
||||
|
|
Loading…
Reference in a new issue