# Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: BUSL-1.1 require File.expand_path("../../../../base", __FILE__) describe Vagrant::Action::General::Package do let(:app) { double("app", call: nil) } let(:env) { {env: environment, machine: machine, ui: ui} } let(:environment) { double("environment") } let(:machine) { double("machine") } let(:ui) { Vagrant::UI::Silent.new } let(:subject) { described_class.new(app, env) } before do allow_any_instance_of(Vagrant::Errors::VagrantError). to receive(:translate_error) end describe ".validate!" do let(:output) { double("output", to_s: "output-path") } let(:directory) { double("directory") } before do allow(described_class).to receive(:fullpath).and_return(output) allow(File).to receive(:directory?).with(output).and_return(false) allow(File).to receive(:directory?).with(directory).and_return(true) allow(File).to receive(:exist?).and_return(false) allow(Vagrant::Util::Presence).to receive(:present?).with(directory).and_return(true) end it "should not raise an error when options are valid" do expect { described_class.validate!(output, directory) }.not_to raise_error end it "should raise error when output directory exists" do expect(File).to receive(:directory?).with(output).and_return(true) expect { described_class.validate!(output, directory) }.to raise_error(Vagrant::Errors::PackageOutputDirectory) end it "should raise error if output path exists" do expect(File).to receive(:exist?).with(output).and_return(true) expect { described_class.validate!(output, directory) }.to raise_error(Vagrant::Errors::PackageOutputExists) end it "should raise error if directory value not provided" do expect(Vagrant::Util::Presence).to receive(:present?).and_call_original expect { described_class.validate!(output, nil) }.to raise_error(Vagrant::Errors::PackageRequiresDirectory) end it "should raise error if directory path is not a directory" do expect(File).to receive(:directory?).with(directory).and_return(false) expect { described_class.validate!(output, directory) }.to raise_error(Vagrant::Errors::PackageRequiresDirectory) end end describe "#package_with_folder_path" do let(:expanded_path) { double("expanded_path") } before do allow(File).to receive(:expand_path).and_return(expanded_path) end it "should create box folder if it does not exist" do expect(File).to receive(:directory?).with(expanded_path).and_return(false) expect(subject).to receive(:create_box_folder).with(expanded_path) subject.package_with_folder_path end it "should not create box folder if it already exists" do expect(File).to receive(:directory?).with(expanded_path).and_return(true) expect(subject).not_to receive(:create_box_folder) subject.package_with_folder_path end end describe "#create_box_folder" do let(:path) { double("path") } before do allow(FileUtils).to receive(:mkdir_p) subject.instance_variable_set(:@env, env) end it "should notify user of new directory creation" do expect(I18n).to receive(:t).with(an_instance_of(String), hash_including(folder_path: path)) subject.create_box_folder(path) end it "should create the directory" do expect(FileUtils).to receive(:mkdir_p).with(path) subject.create_box_folder(path) end end describe "#recover" do let(:env) { {"vagrant.error" => error} } let(:error) { nil } let(:fullpath) { double("fullpath") } before { allow(described_class).to receive(:fullpath).and_return(fullpath) } it "should delete packaged files if they exist" do expect(File).to receive(:exist?).with(fullpath).and_return(true) expect(File).to receive(:delete).with(fullpath) subject.recover(env) end it "should not delete anything if package files do not exist" do expect(File).to receive(:exist?).with(fullpath).and_return(false) expect(File).not_to receive(:delete).with(fullpath) subject.recover(env) end context "when vagrant error is PackageOutputDirectory" do let(:error) { Vagrant::Errors::PackageOutputDirectory.new } it "should not do anything" do expect(File).not_to receive(:exist?) expect(File).not_to receive(:delete) subject.recover(env) end end context "when vagrant error is PackageOutputExists" do let(:error) { Vagrant::Errors::PackageOutputExists.new } it "should not do anything" do expect(File).not_to receive(:exist?) expect(File).not_to receive(:delete) subject.recover(env) end end end describe "#copy_include_files" do let(:package_directory) { @package_directory } let(:package_files) { Dir.glob(File.join(@package_files_directory, "*")).map {|i| [i, File.basename(i)] } } before do @package_directory = Dir.mktmpdir @package_files_directory = Dir.mktmpdir 3.times { |i| FileUtils.touch(File.join(@package_files_directory, "file.#{i}")) } env["package.files"] = package_files env["package.directory"] = package_directory subject.instance_variable_set(:@env, env) end after do FileUtils.rm_rf(@package_directory) FileUtils.rm_rf(@package_files_directory) end it "should copy all files to package directory" do subject.copy_include_files expected_files = package_files.map(&:last).map { |f| File.join(package_directory, "include", f) }.sort expect( Dir.glob(File.join(package_directory, "**", "*.*")).sort ).to eq(expected_files) end it "should notify user of copy" do expect(ui).to receive(:info).at_least(1).and_call_original subject.copy_include_files end end describe "#copy_info" do let(:package_directory) { @package_directory } let(:package_info) { File.join(@package_info_directory, "info.json") } before do @package_directory = Dir.mktmpdir @package_info_directory = Dir.mktmpdir FileUtils.touch(File.join(@package_info_directory, "info.json")) env["package.info"] = package_info env["package.directory"] = package_directory subject.instance_variable_set(:@env, env) allow(ui).to receive(:info) end after do FileUtils.rm_rf(@package_directory) FileUtils.rm_rf(@package_info_directory) end it "should copy the specified info.json to package directory" do subject.copy_info expected_info = File.join(package_directory, "info.json") expect(File.file? expected_info).to be_truthy end end describe "#compress" do let(:package_directory) { @package_directory } let(:fullpath) { "PATH" } before do @package_directory = Dir.mktmpdir FileUtils.touch(File.join(@package_directory, "test-file1")) env["package.directory"] = package_directory subject.instance_variable_set(:@env, env) allow(subject).to receive(:fullpath).and_return(fullpath) end after do FileUtils.rm_rf(package_directory) end it "should change directory into package directory" do expect(Vagrant::Util::SafeChdir).to receive(:safe_chdir).with(package_directory) subject.compress end it "should compress files using bsdtar" do expect(Vagrant::Util::SafeChdir).to receive(:safe_chdir).with(package_directory).and_call_original expect(Vagrant::Util::Subprocess).to receive(:execute).with("bsdtar", any_args, "./test-file1") subject.compress end end describe "#write_metadata_json" do let(:metadata_path) { File.join(package_directory, "metadata.json") } let(:package_directory) { @package_directory } let(:machine_provider) { "machine-provider" } let(:default_provider) { "default-provider" } before do @package_directory = Dir.mktmpdir env["package.directory"] = @package_directory subject.instance_variable_set(:@env, env) allow(machine).to receive(:provider_name).and_return(machine_provider) allow(environment).to receive(:default_provider).and_return(default_provider) end after { FileUtils.rm_rf(@package_directory) } it "should not create a metadata.json file if it already exists" do expect(File).to receive(:exist?).with(metadata_path).and_return(true) expect(File).not_to receive(:write) subject.write_metadata_json end it "should write a metadata file" do expect(File).to receive(:write).with(metadata_path, any_args) subject.write_metadata_json end it "should write machine provider to metadata file" do subject.write_metadata_json content = JSON.load(File.read(metadata_path)) expect(content["provider"]).to eq(machine_provider) end context "when machine provider is unset" do let(:machine_provider) { nil } it "should write default provider to metadata file" do subject.write_metadata_json content = JSON.load(File.read(metadata_path)) expect(content["provider"]).to eq(default_provider) end end context "when machine provider and default provider are unset" do let(:machine_provider) { nil } let(:default_provider) { nil } it "should not write metadata file" do subject.write_metadata_json expect(File.exist?(metadata_path)).to be_falsey end end end describe "#setup_private_key" do let(:package_directory) { @package_directory } let(:private_key_path) { File.join(package_directory, "datadir", "private_key") } let(:new_key_path) { File.join(package_directory, "vagrant_private_key") } let(:vagrantfile_path) { File.join(package_directory, "Vagrantfile") } let(:data_dir) { Pathname.new(File.join(package_directory, "datadir")) } let(:config) { double("config", ssh: double("ssh", private_key_path: [private_key_path])) } before do @package_directory = Dir.mktmpdir env["package.directory"] = @package_directory FileUtils.mkdir(File.join(@package_directory, "datadir")) File.write(private_key_path, "SSH KEY") subject.instance_variable_set(:@env, env) allow(machine).to receive(:data_dir).and_return(data_dir) allow(machine).to receive(:config).and_return(config) end after { FileUtils.rm_rf(@package_directory) } it "should create a new Vagrantfile" do subject.setup_private_key expect(File.exist?(vagrantfile_path)).to be_truthy end it "should create the new private ssh key" do subject.setup_private_key expect(File.exist?(new_key_path)).to be_truthy end it "should copy the contents of the ssh key" do subject.setup_private_key expect(File.read(new_key_path)).to eq(File.read(private_key_path)) end context "with no machine provided" do before { env.delete(:machine) } it "should not create a private ssh key file" do subject.setup_private_key expect(File.exist?(new_key_path)).to be_falsey end end context "when vagrant_private_key exists" do let(:private_key_path) { File.join(package_directory, "datadir", "vagrant_private_key") } it "should create the new private ssh key" do subject.setup_private_key expect(File.exist?(new_key_path)).to be_truthy end it "should copy the contents of the ssh key" do subject.setup_private_key expect(File.read(new_key_path)).to eq(File.read(private_key_path)) end end end describe "#call" do let(:fullpath) { "FULLPATH" } before do allow(described_class).to receive(:validate!) allow(subject).to receive(:fullpath).and_return(fullpath) allow(subject).to receive(:package_with_folder_path) allow(subject).to receive(:copy_include_files) allow(subject).to receive(:setup_private_key) allow(subject).to receive(:write_metadata_json) allow(subject).to receive(:compress) end it "should validate required arguments" do expect(described_class).to receive(:validate!) subject.call(env) end it "should raise error if output path is a directory" do expect(File).to receive(:directory?).with(fullpath).and_return(true) expect { subject.call(env) }.to raise_error(Vagrant::Errors::PackageOutputDirectory) end it "should call the next middleware" do expect(app).to receive(:call) subject.call(env) end it "should notify of package compressing" do expect(ui).to receive(:info).and_call_original subject.call(env) end it "should copy include files" do expect(subject).to receive(:copy_include_files) subject.call(env) end it "should setup private ssh key" do expect(subject).to receive(:setup_private_key) subject.call(env) end it "should write metadata json file" do expect(subject).to receive(:write_metadata_json) subject.call(env) end it "should compress the box" do expect(subject).to receive(:compress) subject.call(env) end end end