diff --git a/test/unit/plugins/provisioners/ansible/config_test.rb b/test/unit/plugins/provisioners/ansible/config_test.rb new file mode 100644 index 000000000..5d00b255f --- /dev/null +++ b/test/unit/plugins/provisioners/ansible/config_test.rb @@ -0,0 +1,42 @@ +require_relative "../../../base" + +require Vagrant.source_root.join("plugins/provisioners/ansible/config") + +describe VagrantPlugins::Ansible::Config do + include_context "unit" + + subject { described_class.new } + + let(:machine) { double("machine", env: Vagrant::Environment.new) } + let(:file_that_exists) { File.expand_path(__FILE__) } + + describe "#validate" do + it "returns an error if playbook file does not exist" do + non_existing_file = "/this/does/not/exist" + + subject.playbook = non_existing_file + subject.finalize! + + result = subject.validate(machine) + expect(result["ansible provisioner"]).to eql([ + I18n.t("vagrant.provisioners.ansible.playbook_path_invalid", + :path => non_existing_file) + ]) + end + + it "returns an error if inventory_path is specified, but does not exist" do + non_existing_file = "/this/does/not/exist" + + subject.playbook = file_that_exists + subject.inventory_path = non_existing_file + subject.finalize! + + result = subject.validate(machine) + expect(result["ansible provisioner"]).to eql([ + I18n.t("vagrant.provisioners.ansible.inventory_path_invalid", + :path => non_existing_file) + ]) + end + + end +end diff --git a/test/unit/plugins/provisioners/ansible/provisioner_test.rb b/test/unit/plugins/provisioners/ansible/provisioner_test.rb new file mode 100644 index 000000000..6ec3eba31 --- /dev/null +++ b/test/unit/plugins/provisioners/ansible/provisioner_test.rb @@ -0,0 +1,180 @@ +require_relative "../../../base" + +require Vagrant.source_root.join("plugins/provisioners/ansible/provisioner") + +describe VagrantPlugins::Ansible::Provisioner do + include_context "unit" + + subject { described_class.new(machine, config) } + + let(:iso_env) do + # We have to create a Vagrantfile so there is a Vagrant Environment + env = isolated_environment + env.vagrantfile("") + env.create_vagrant_env + end + + let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) } + let(:config) { double("config") } + + let(:ssh_info) {{ + private_key_path: ['/path/to/my/key'], + username: 'testuser' + }} + + before do + machine.stub(ssh_info: ssh_info) + + config.stub(playbook: 'playbook.yml') + config.stub(extra_vars: nil) + config.stub(inventory_path: nil) + config.stub(ask_sudo_pass: nil) + config.stub(limit: nil) + config.stub(sudo: nil) + config.stub(sudo_user: nil) + config.stub(verbose: nil) + config.stub(tags: nil) + config.stub(skip_tags: nil) + config.stub(start_at_task: nil) + config.stub(groups: {}) + config.stub(host_key_checking: 'false') + config.stub(raw_arguments: nil) + config.stub(raw_ssh_args: nil) + end + + shared_examples_for "an ansible-playbook subprocess" do + it "sets default arguments" do + expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| + + index = args.find_index("ansible-playbook") + expect(index).to eql(0) + index = args.find_index("--private-key=/path/to/my/key") + expect(index).to eql(1) + index = args.find_index("--user=testuser") + expect(index).to eql(2) + + index = args.find_index("--limit=#{machine.name}") + expect(index).to be > 0 + + index = args.find_index("playbook.yml") + expect(index).to eql(args.length-2) + } + subject.provision + end + + it "exports default environment variables" do + expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| + cmd_opts = args.last + expect(cmd_opts[:env]['ANSIBLE_FORCE_COLOR']).to eql("true") + expect(cmd_opts[:env]['ANSIBLE_HOST_KEY_CHECKING']).to eql("false") + expect(cmd_opts[:env]['PYTHONUNBUFFERED']).to eql(1) + } + subject.provision + end + end + + shared_examples_for "SSH transport mode is not forced" do + it "does not export ANSIBLE_SSH_ARGS" do + expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| + cmd_opts = args.last + expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to be_nil + } + subject.provision + end + it "does not force SSH transport mode" do + expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| + index = args.find_index("--connection=ssh") + expect(index).to be_nil + } + subject.provision + end + end + + shared_examples_for "SSH transport mode is forced" do + it "sets --connection=ssh argument" do + expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| + index = args.find_index("--connection=ssh") + expect(index).to be > 0 + } + subject.provision + end + it "enables ControlPersist (like Ansible defaults) via ANSIBLE_SSH_ARGS" do + expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| + cmd_opts = args.last + expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o ControlMaster=auto") + expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o ControlPersist=60s") + } + subject.provision + end + end + + describe "#provision" do + + let(:result) { Vagrant::Util::Subprocess::Result.new(0, "", "") } + + before do + Vagrant::Util::Subprocess.stub(execute: result) + end + + it "doesn't raise an error if it succeeds" do + subject.provision + end + + it "raises an error if the exit code is non-zero" do + Vagrant::Util::Subprocess.stub( + execute: Vagrant::Util::Subprocess::Result.new(1, "", "")) + + expect {subject.provision}. + to raise_error(Vagrant::Errors::AnsibleFailed) + end + + describe "with default options" do + it_behaves_like 'an ansible-playbook subprocess' + it_behaves_like 'SSH transport mode is not forced' + + it "generates the inventory" do + expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| + index = args.find_index("--inventory-file=#{File.join(machine.env.local_data_path, %w(provisioners ansible inventory vagrant_ansible_inventory))}") + expect(index).to be > 0 + } + subject.provision + end + end + + describe "with multiple SSH identities" do + before do + ssh_info[:private_key_path] = ['/path/to/my/key', '/an/other/identity', '/yet/an/other/key'] + end + + it_behaves_like 'an ansible-playbook subprocess' + it_behaves_like 'SSH transport mode is forced' + + it "passes additional Identity Files via ANSIBLE_SSH_ARGS" do + expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| + cmd_opts = args.last + expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o IdentityFile=/an/other/identity") + expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o IdentityFile=/yet/an/other/key") + } + subject.provision + end + end + + describe "with ssh forwarding enabled" do + before do + ssh_info[:forward_agent] = true + end + + it_behaves_like 'an ansible-playbook subprocess' + it_behaves_like 'SSH transport mode is forced' + + it "enables SSH-Forwarding via ANSIBLE_SSH_ARGS" do + expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| + cmd_opts = args.last + expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o ForwardAgent=yes") + } + subject.provision + end + end + + end +end