diff --git a/boxes/quantal64/lxc-template b/boxes/quantal64/lxc-template index 0fdef44..24c25e1 100755 --- a/boxes/quantal64/lxc-template +++ b/boxes/quantal64/lxc-template @@ -78,8 +78,10 @@ EOF rm -f $rootfs/etc/init/tty{5,6}.conf fi - chroot $rootfs useradd --create-home -s /bin/bash vagrant - echo "vagrant:vagrant" | chroot $rootfs chpasswd + if ! (grep -q vagrant $rootfs/etc/passwd); then + chroot $rootfs useradd --create-home -s /bin/bash vagrant + echo "vagrant:vagrant" | chroot $rootfs chpasswd + fi return 0 } diff --git a/boxes/quantal64/metadata.json b/boxes/quantal64/metadata.json index ea24c7b..5b63afa 100644 --- a/boxes/quantal64/metadata.json +++ b/boxes/quantal64/metadata.json @@ -1,6 +1,6 @@ { - "provider": "lxc", - "vagrant-lxc-version": "0.0.1", + "provider": "lxc", + "version": "1", "template-opts": { "--arch": "amd64", diff --git a/lib/vagrant-lxc/action.rb b/lib/vagrant-lxc/action.rb index 85d42c7..c5f97d3 100644 --- a/lib/vagrant-lxc/action.rb +++ b/lib/vagrant-lxc/action.rb @@ -10,11 +10,13 @@ require 'vagrant-lxc/action/create' require 'vagrant-lxc/action/created' require 'vagrant-lxc/action/destroy' require 'vagrant-lxc/action/disconnect' +require 'vagrant-lxc/action/compress_rootfs' require 'vagrant-lxc/action/forced_halt' require 'vagrant-lxc/action/forward_ports' require 'vagrant-lxc/action/handle_box_metadata' require 'vagrant-lxc/action/is_running' require 'vagrant-lxc/action/network' +require 'vagrant-lxc/action/setup_package_files' require 'vagrant-lxc/action/share_folders' module Vagrant @@ -173,6 +175,25 @@ module Vagrant end end + # This action packages the virtual machine into a single box file. + def self.action_package + Vagrant::Action::Builder.new.tap do |b| + # b.use CheckDependencies + b.use Vagrant::Action::Builtin::Call, Created do |env1, b2| + if !env1[:result] + # TODO: Implement our own MessageNotCreated + b2.use VagrantPlugins::ProviderVirtualBox::Action::MessageNotCreated + next + end + + b2.use action_halt + b2.use CompressRootFS + b2.use SetupPackageFiles + b2.use Vagrant::Action::General::Package + end + end + end + # This is the action that will exec into an SSH shell. def self.action_ssh Vagrant::Action::Builder.new.tap do |b| diff --git a/lib/vagrant-lxc/action/compress_rootfs.rb b/lib/vagrant-lxc/action/compress_rootfs.rb new file mode 100644 index 0000000..63b7186 --- /dev/null +++ b/lib/vagrant-lxc/action/compress_rootfs.rb @@ -0,0 +1,30 @@ +require "fileutils" + +module Vagrant + module LXC + module Action + class CompressRootFS + def initialize(app, env) + @app = app + end + + def call(env) + raise Vagrant::Errors::VMPowerOffToPackage if env[:machine].provider.state.id != :stopped + + env[:ui].info I18n.t("vagrant.actions.lxc.compressing_rootfs") + @rootfs = env['package.rootfs'] = env[:machine].provider.container.compress_rootfs + + @app.call env + + recover # called to remove the rootfs tarball + end + + def recover(*) + if @rootfs && File.exist?(@rootfs) + FileUtils.rm_rf(File.dirname @rootfs) + end + end + end + end + end +end diff --git a/lib/vagrant-lxc/action/handle_box_metadata.rb b/lib/vagrant-lxc/action/handle_box_metadata.rb index a6198c3..92f1dad 100644 --- a/lib/vagrant-lxc/action/handle_box_metadata.rb +++ b/lib/vagrant-lxc/action/handle_box_metadata.rb @@ -29,7 +29,8 @@ module Vagrant system(%Q[sudo su root -c "cp #{lxc_template_src} #{dest}"]) @logger.debug('Extracting rootfs') - system(%Q[sudo su root -c "cd #{box.directory} && tar xfz rootfs.tar.gz -C #{rootfs_cache}"]) + # TODO: Ideally the compressed rootfs should not output errors... + system(%Q[sudo su root -c "cd #{box.directory} && tar xfz rootfs.tar.gz -C #{rootfs_cache} 2>/dev/null"]) box.metadata.merge!( 'template-name' => template_name, diff --git a/lib/vagrant-lxc/action/setup_package_files.rb b/lib/vagrant-lxc/action/setup_package_files.rb new file mode 100644 index 0000000..a1a8a73 --- /dev/null +++ b/lib/vagrant-lxc/action/setup_package_files.rb @@ -0,0 +1,52 @@ +require 'fileutils' + +module Vagrant + module LXC + module Action + class SetupPackageFiles + def initialize(app, env) + @app = app + + env["package.include"] ||= [] + env["package.vagrantfile"] ||= nil + end + + def call(env) + @env = env + + create_package_temp_dir + move_rootfs_to_pkg_dir + copy_box_files_to_pkg_dir + + @app.call env + + recover # called to cleanup temp directory + end + + def recover(*) + if @temp_dir && File.exist?(@temp_dir) + FileUtils.rm_rf(@temp_dir) + end + end + + private + + def create_package_temp_dir + @env[:ui].info I18n.t("vagrant.actions.vm.export.create_dir") + @temp_dir = @env["package.directory"] = @env[:tmp_path].join("container-export-#{Time.now.to_i.to_s}") + FileUtils.mkpath(@temp_dir) + end + + def move_rootfs_to_pkg_dir + FileUtils.mv @env['package.rootfs'].to_s, @env['package.directory'].to_s + end + + def copy_box_files_to_pkg_dir + box_dir = @env[:machine].box.directory + FileUtils.cp box_dir.join('lxc-template').to_s, @env['package.directory'].to_s + FileUtils.cp box_dir.join('metadata.json').to_s, @env['package.directory'].to_s + end + end + end + end +end diff --git a/lib/vagrant-lxc/container.rb b/lib/vagrant-lxc/container.rb index 31df834..4d9d347 100644 --- a/lib/vagrant-lxc/container.rb +++ b/lib/vagrant-lxc/container.rb @@ -93,6 +93,26 @@ module Vagrant @cli.destroy end + # TODO: This needs to be reviewed and specs needs to be written + def compress_rootfs + # TODO: Our template should not depend on container's arch + arch = base_path.join('config').read.match(/^lxc\.arch\s+=\s+(.+)$/)[1] + rootfs_dirname = File.dirname rootfs_path + basename = rootfs_path.to_s.gsub(/^#{Regexp.escape rootfs_dirname}\//, '') + # TODO: Pass in tmpdir so we can clean up from outside + target_path = "#{Dir.mktmpdir}/rootfs.tar.gz" + + Dir.chdir base_path do + @logger.info "Compressing '#{rootfs_path}' rootfs to #{target_path}" + system "sudo rm -f rootfs.tar.gz && sudo bsdtar -s /#{basename}/rootfs-#{arch}/ --numeric-owner -czf #{target_path} #{basename}/* 2>/dev/null" + + @logger.info "Changing rootfs tarbal owner" + system "sudo chown #{ENV['USER']}:#{ENV['USER']} #{target_path}" + end + + target_path + end + def state if @name @cli.state diff --git a/locales/en.yml b/locales/en.yml index 4224875..132d2c0 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -2,6 +2,8 @@ en: vagrant: actions: lxc: + compressing_rootfs: Compressing container's rootfs... + share_folders: preparing: Setting up mount entries for shared folders... diff --git a/spec/unit/action/compress_rootfs_spec.rb b/spec/unit/action/compress_rootfs_spec.rb new file mode 100644 index 0000000..17d854e --- /dev/null +++ b/spec/unit/action/compress_rootfs_spec.rb @@ -0,0 +1,27 @@ +require 'unit_helper' + +require 'vagrant-lxc/action/compress_rootfs' + +describe Vagrant::LXC::Action::CompressRootFS do + let(:app) { mock(:app, call: true) } + let(:env) { {machine: machine, ui: stub(info: true)} } + let(:machine) { fire_double('Vagrant::Machine', provider: provider) } + let(:provider) { fire_double('Vagrant::LXC::Provider', container: container) } + let(:container) { fire_double('Vagrant::LXC::Container', compress_rootfs: compressed_rootfs_path) } + let(:compressed_rootfs_path) { '/path/to/rootfs.tar.gz' } + + subject { described_class.new(app, env) } + + before do + provider.stub_chain(:state, :id).and_return(:stopped) + subject.call(env) + end + + it 'asks the container to compress its rootfs' do + container.should have_received(:compress_rootfs) + end + + it 'sets export.temp_dir on action env' do + env['package.rootfs'].should == compressed_rootfs_path + end +end diff --git a/spec/unit/action/handle_box_metadata_spec.rb b/spec/unit/action/handle_box_metadata_spec.rb index 7a76015..18c81aa 100644 --- a/spec/unit/action/handle_box_metadata_spec.rb +++ b/spec/unit/action/handle_box_metadata_spec.rb @@ -39,6 +39,6 @@ describe Vagrant::LXC::Action::HandleBoxMetadata do it 'extracts rootfs into a tmp folder' do subject.should have_received(:system). - with(%Q[sudo su root -c "cd #{box_directory} && tar xfz rootfs.tar.gz -C #{tmpdir}"]) + with(%Q[sudo su root -c "cd #{box_directory} && tar xfz rootfs.tar.gz -C #{tmpdir} 2>/dev/null"]) end end diff --git a/spec/unit/action/setup_package_files_spec.rb b/spec/unit/action/setup_package_files_spec.rb new file mode 100644 index 0000000..4e50189 --- /dev/null +++ b/spec/unit/action/setup_package_files_spec.rb @@ -0,0 +1,41 @@ +require 'unit_helper' + +require 'vagrant-lxc/action/setup_package_files' + +describe Vagrant::LXC::Action::SetupPackageFiles do + let(:app) { mock(:app, call: true) } + let(:env) { {machine: machine, tmp_path: tmp_path, ui: stub(info: true), 'package.rootfs' => rootfs_path} } + let(:machine) { fire_double('Vagrant::Machine', box: box) } + let!(:tmp_path) { Pathname.new(Dir.mktmpdir) } + let(:box) { fire_double('Vagrant::Box', directory: tmp_path.join('box')) } + let(:rootfs_path) { tmp_path.join('rootfs-amd64.tar.gz') } + + subject { described_class.new(app, env) } + + before do + box.directory.mkdir + [box.directory.join('lxc-template'), box.directory.join('metadata.json'), rootfs_path].each do |file| + file.open('w') { |f| f.puts file.to_s } + end + + subject.stub(recover: true) # Prevents files from being removed on specs + subject.call(env) + end + + after do + FileUtils.rm_rf(tmp_path.to_s) + end + + it 'copies box lxc-template to package directory' do + env['package.directory'].join('lxc-template').should be_file + end + + it 'copies metadata.json to package directory' do + env['package.directory'].join('metadata.json').should be_file + end + + it 'moves the compressed rootfs to package directory' do + env['package.directory'].join(rootfs_path.basename).should be_file + env['package.rootfs'].should_not be_file + end +end