From 3dd90aa95512e75766019a1ead4470bf6e971bf3 Mon Sep 17 00:00:00 2001 From: Evgeny Sinelnikov Date: Sun, 2 Jul 2017 18:38:20 +0300 Subject: [PATCH 01/51] Add ALT Linux platforms guest detection and support * ALT Linux platforms is an original rpm based distribution forked as Mandrake Russian Edition Spring at 2001 year * Distributions of ALT Linux use etcnet (https://www.altlinux.org/Etcnet) as internal network configuration system needs for configure_networks support --- plugins/guests/alt/cap/change_host_name.rb | 44 +++++++ plugins/guests/alt/cap/configure_networks.rb | 118 ++++++++++++++++++ plugins/guests/alt/cap/flavor.rb | 37 ++++++ plugins/guests/alt/cap/network_scripts_dir.rb | 11 ++ plugins/guests/alt/cap/rsync.rb | 13 ++ plugins/guests/alt/guest.rb | 9 ++ plugins/guests/alt/plugin.rb | 40 ++++++ templates/guests/alt/network_dhcp.erb | 7 ++ templates/guests/alt/network_ipv4address.erb | 3 + templates/guests/alt/network_ipv4route.erb | 5 + templates/guests/alt/network_static.erb | 7 ++ 11 files changed, 294 insertions(+) create mode 100644 plugins/guests/alt/cap/change_host_name.rb create mode 100644 plugins/guests/alt/cap/configure_networks.rb create mode 100644 plugins/guests/alt/cap/flavor.rb create mode 100644 plugins/guests/alt/cap/network_scripts_dir.rb create mode 100644 plugins/guests/alt/cap/rsync.rb create mode 100644 plugins/guests/alt/guest.rb create mode 100644 plugins/guests/alt/plugin.rb create mode 100644 templates/guests/alt/network_dhcp.erb create mode 100644 templates/guests/alt/network_ipv4address.erb create mode 100644 templates/guests/alt/network_ipv4route.erb create mode 100644 templates/guests/alt/network_static.erb diff --git a/plugins/guests/alt/cap/change_host_name.rb b/plugins/guests/alt/cap/change_host_name.rb new file mode 100644 index 000000000..3d0333a37 --- /dev/null +++ b/plugins/guests/alt/cap/change_host_name.rb @@ -0,0 +1,44 @@ +module VagrantPlugins + module GuestALT + module Cap + class ChangeHostName + def self.change_host_name(machine, name) + comm = machine.communicate + + if !comm.test("hostname -f | grep '^#{name}$'", sudo: false) + basename = name.split('.', 2)[0] + comm.sudo <<-EOH.gsub(/^ {14}/, '') + # Save current hostname saved in /etc/hosts + CURRENT_HOSTNAME_FULL="$(hostname -f)" + CURRENT_HOSTNAME_SHORT="$(hostname -s)" + + # New hostname to be saved in /etc/hosts + NEW_HOSTNAME_FULL='#{name}' + NEW_HOSTNAME_SHORT="${NEW_HOSTNAME_FULL%%.*}" + + # Update sysconfig + sed -i 's/\\(HOSTNAME=\\).*/\\1#{name}/' /etc/sysconfig/network + + # Set the hostname - use hostnamectl if available + if command -v hostnamectl; then + hostnamectl set-hostname --static '#{name}' + hostnamectl set-hostname --transient '#{name}' + else + hostname '#{name}' + fi + + # Update ourselves in /etc/hosts + if grep -w "$CURRENT_HOSTNAME_FULL" /etc/hosts; then + sed -i -e "s/\(\s\)$CURRENT_HOSTNAME_FULL\(\s\)/\1$NEW_HOSTNAME_FULL\2/g" -e "s/\(\s\)$CURRENT_HOSTNAME_FULL$/\1$NEW_HOSTNAME_FULL/g" /etc/hosts + fi + if grep -w "$CURRENT_HOSTNAME_SHORT" /etc/hosts; then + sed -i -e "s/\(\s\)$CURRENT_HOSTNAME_SHORT\(\s\)/\1$NEW_HOSTNAME_SHORT\2/g" -e "s/\(\s\)$CURRENT_HOSTNAME_SHORT$/\1$NEW_HOSTNAME_SHORT/g" /etc/hosts + fi + + EOH + end + end + end + end + end +end diff --git a/plugins/guests/alt/cap/configure_networks.rb b/plugins/guests/alt/cap/configure_networks.rb new file mode 100644 index 000000000..acca141db --- /dev/null +++ b/plugins/guests/alt/cap/configure_networks.rb @@ -0,0 +1,118 @@ +require "tempfile" + +require_relative "../../../../lib/vagrant/util/template_renderer" + +module VagrantPlugins + module GuestALT + module Cap + class ConfigureNetworks + include Vagrant::Util + extend Vagrant::Util::GuestInspection::Linux + + def self.configure_networks(machine, networks) + comm = machine.communicate + + network_scripts_dir = machine.guest.capability(:network_scripts_dir) + + commands = {:start => [], :middle => [], :end => []} + interfaces = machine.guest.capability(:network_interfaces) + + # Check if NetworkManager is installed on the system + nmcli_installed = nmcli?(comm) + networks.each.with_index do |network, i| + network[:device] = interfaces[network[:interface]] + extra_opts = machine.config.vm.networks[i].last.dup + + if nmcli_installed + # Now check if the device is actively being managed by NetworkManager + nm_controlled = nm_controlled?(comm, network[:device]) + end + + if !extra_opts.key?(:nm_controlled) + extra_opts[:nm_controlled] = !!nm_controlled + end + + extra_opts[:nm_controlled] = case extra_opts[:nm_controlled] + when true + "yes" + when false, nil + "no" + else + extra_opts[:nm_controlled].to_s + end + + if extra_opts[:nm_controlled] == "yes" && !nmcli_installed + raise Vagrant::Errors::NetworkManagerNotInstalled, device: network[:device] + end + + # Render a new configuration + template_options = network.merge(extra_opts) + options_entry = TemplateRenderer.render("guests/alt/network_#{network[:type]}", options: template_options) + + # Upload the new configuration + options_remote_path = "/tmp/vagrant-network-entry-#{network[:device]}-#{Time.now.to_i}-#{i}" + ipv4_address_remote_path = "/tmp/vagrant-network-ipv4-address-entry-#{network[:device]}-#{Time.now.to_i}-#{i}" + ipv4_route_remote_path = "/tmp/vagrant-network-ipv4-route-entry-#{network[:device]}-#{Time.now.to_i}-#{i}" + + Tempfile.open("vagrant-alt-configure-networks") do |f| + f.binmode + f.write(options_entry) + f.fsync + f.close + machine.communicate.upload(f.path, options_remote_path) + end + + # Add the new interface and bring it back up + iface_path = "#{network_scripts_dir}/ifaces/#{network[:device]}" + + if network[:type].to_sym == :static + ipv4_address_entry = TemplateRenderer.render("guests/alt/network_ipv4address", options: template_options) + + # Upload the new ipv4address configuration + Tempfile.open("vagrant-alt-configure-ipv4-address") do |f| + f.binmode + f.write(ipv4_address_entry) + f.fsync + f.close + machine.communicate.upload(f.path, ipv4_address_remote_path) + end + + ipv4_route_entry = TemplateRenderer.render("guests/alt/network_ipv4route", options: template_options) + + # Upload the new ipv4route configuration + Tempfile.open("vagrant-alt-configure-ipv4-route") do |f| + f.binmode + f.write(ipv4_route_entry) + f.fsync + f.close + machine.communicate.upload(f.path, ipv4_route_remote_path) + end + end + + if nm_controlled + commands[:start] << "nmcli d disconnect iface '#{network[:device]}'" + else + commands[:start] << "/sbin/ifdown '#{network[:device]}'" + end + commands[:middle] << "mkdir -p '#{iface_path}'" + commands[:middle] << "mv -f '#{options_remote_path}' '#{iface_path}/options'" + if network[:type].to_sym == :static + commands[:middle] << "mv -f '#{ipv4_address_remote_path}' '#{iface_path}/ipv4address'" + commands[:middle] << "mv -f '#{ipv4_route_remote_path}' '#{iface_path}/ipv4route'" + end + if extra_opts[:nm_controlled] == "no" + commands[:end] << "/sbin/ifup '#{network[:device]}'" + end + end + if nmcli_installed + commands[:middle] << "(test -f /etc/init.d/NetworkManager && /etc/init.d/NetworkManager restart) || " \ + "((systemctl | grep NetworkManager.service) && systemctl restart NetworkManager)" + end + commands = commands[:start] + commands[:middle] + commands[:end] + comm.sudo(commands.join("\n")) + comm.wait_for_ready(5) + end + end + end + end +end diff --git a/plugins/guests/alt/cap/flavor.rb b/plugins/guests/alt/cap/flavor.rb new file mode 100644 index 000000000..a9b1ae017 --- /dev/null +++ b/plugins/guests/alt/cap/flavor.rb @@ -0,0 +1,37 @@ +module VagrantPlugins + module GuestALT + module Cap + class Flavor + def self.flavor(machine) + # Read the version file + if !comm.test("test -f /etc/os-release") + version = nil + machine.communicate.sudo("grep VERSION_ID /etc/os-release") do |type, data| + if type == :stdout + version = data.split("=")[1].chomp.to_i + end + end + + if version.nil? + return :alt + else + return :"alt_#{version}" + end + else + output = "" + machine.communicate.sudo("cat /etc/altlinux-release") do |_, data| + output = data + end + + # Detect various flavors we care about + if output =~ /(ALT Workstation K|ALT Linux starter kit)\s*8( .+)?/i + return :alt_8 + else + return :alt + end + end + end + end + end + end +end diff --git a/plugins/guests/alt/cap/network_scripts_dir.rb b/plugins/guests/alt/cap/network_scripts_dir.rb new file mode 100644 index 000000000..1a867c4e9 --- /dev/null +++ b/plugins/guests/alt/cap/network_scripts_dir.rb @@ -0,0 +1,11 @@ +module VagrantPlugins + module GuestALT + module Cap + class NetworkScriptsDir + def self.network_scripts_dir(machine) + "/etc/net" + end + end + end + end +end diff --git a/plugins/guests/alt/cap/rsync.rb b/plugins/guests/alt/cap/rsync.rb new file mode 100644 index 000000000..998315142 --- /dev/null +++ b/plugins/guests/alt/cap/rsync.rb @@ -0,0 +1,13 @@ +module VagrantPlugins + module GuestALT + module Cap + class RSync + def self.rsync_install(machine) + machine.communicate.sudo <<-EOH.gsub(/^ {12}/, '') + apt-get install -y -qq install rsync + EOH + end + end + end + end +end diff --git a/plugins/guests/alt/guest.rb b/plugins/guests/alt/guest.rb new file mode 100644 index 000000000..08525d5e6 --- /dev/null +++ b/plugins/guests/alt/guest.rb @@ -0,0 +1,9 @@ +module VagrantPlugins + module GuestALT + class Guest < Vagrant.plugin("2", :guest) + def detect?(machine) + machine.communicate.test("cat /etc/altlinux-release") + end + end + end +end diff --git a/plugins/guests/alt/plugin.rb b/plugins/guests/alt/plugin.rb new file mode 100644 index 000000000..d6375f5ea --- /dev/null +++ b/plugins/guests/alt/plugin.rb @@ -0,0 +1,40 @@ +require "vagrant" + +module VagrantPlugins + module GuestALT + class Plugin < Vagrant.plugin("2") + name "ALT Platform guest" + description "ALT Platform guest support." + + guest(:alt, :redhat) do + require_relative "guest" + Guest + end + + guest_capability(:alt, :change_host_name) do + require_relative "cap/change_host_name" + Cap::ChangeHostName + end + + guest_capability(:alt, :configure_networks) do + require_relative "cap/configure_networks" + Cap::ConfigureNetworks + end + + guest_capability(:alt, :flavor) do + require_relative "cap/flavor" + Cap::Flavor + end + + guest_capability(:alt, :network_scripts_dir) do + require_relative "cap/network_scripts_dir" + Cap::NetworkScriptsDir + end + + guest_capability(:alt, :rsync_install) do + require_relative "cap/rsync" + Cap::RSync + end + end + end +end diff --git a/templates/guests/alt/network_dhcp.erb b/templates/guests/alt/network_dhcp.erb new file mode 100644 index 000000000..8d9f1a93a --- /dev/null +++ b/templates/guests/alt/network_dhcp.erb @@ -0,0 +1,7 @@ +#VAGRANT-BEGIN +# The contents below are automatically generated by Vagrant. Do not modify. +TYPE=eth +NM_CONTROLLED=<%= options.fetch(:nm_controlled, "no") %> +BOOTPROTO=dhcp +ONBOOT=yes +#VAGRANT-END diff --git a/templates/guests/alt/network_ipv4address.erb b/templates/guests/alt/network_ipv4address.erb new file mode 100644 index 000000000..dbd3340b4 --- /dev/null +++ b/templates/guests/alt/network_ipv4address.erb @@ -0,0 +1,3 @@ +#VAGRANT-BEGIN +<%= options[:ip] %>/<%= options[:netmask] %> +#VAGRANT-END diff --git a/templates/guests/alt/network_ipv4route.erb b/templates/guests/alt/network_ipv4route.erb new file mode 100644 index 000000000..fe0bea446 --- /dev/null +++ b/templates/guests/alt/network_ipv4route.erb @@ -0,0 +1,5 @@ +#VAGRANT-BEGIN +<% if options[:gateway] %> +default via <%= options[:gateway] %> +<% end %> +#VAGRANT-END diff --git a/templates/guests/alt/network_static.erb b/templates/guests/alt/network_static.erb new file mode 100644 index 000000000..84ceac55c --- /dev/null +++ b/templates/guests/alt/network_static.erb @@ -0,0 +1,7 @@ +#VAGRANT-BEGIN +# The contents below are automatically generated by Vagrant. Do not modify. +TYPE=eth +NM_CONTROLLED=<%= options.fetch(:nm_controlled, "no") %> +BOOTPROTO=static +ONBOOT=yes +#VAGRANT-END From 88c0cb855f33c605503a004f9ff3a2ed7dff2518 Mon Sep 17 00:00:00 2001 From: Evgeny Sinelnikov Date: Sat, 8 Jul 2017 21:27:19 +0300 Subject: [PATCH 02/51] guests/alt: Update network configuration Fix network mask to CIDR notation and network configure stop and restart commands with NetworkManager --- plugins/guests/alt/cap/configure_networks.rb | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/plugins/guests/alt/cap/configure_networks.rb b/plugins/guests/alt/cap/configure_networks.rb index acca141db..851849700 100644 --- a/plugins/guests/alt/cap/configure_networks.rb +++ b/plugins/guests/alt/cap/configure_networks.rb @@ -47,6 +47,14 @@ module VagrantPlugins # Render a new configuration template_options = network.merge(extra_opts) + + # ALT expects netmasks to be in the CIDR notation, but users may + # specify IPV4 netmasks like "255.255.255.0". This magic converts + # the netmask to the proper value. + if template_options[:netmask] && template_options[:netmask].to_s.include?(".") + template_options[:netmask] = (32-Math.log2((IPAddr.new(template_options[:netmask], Socket::AF_INET).to_i^0xffffffff)+1)).to_i + end + options_entry = TemplateRenderer.render("guests/alt/network_#{network[:type]}", options: template_options) # Upload the new configuration @@ -89,7 +97,7 @@ module VagrantPlugins end end - if nm_controlled + if nm_controlled and extra_opts[:nm_controlled] == "yes" commands[:start] << "nmcli d disconnect iface '#{network[:device]}'" else commands[:start] << "/sbin/ifdown '#{network[:device]}'" @@ -105,8 +113,8 @@ module VagrantPlugins end end if nmcli_installed - commands[:middle] << "(test -f /etc/init.d/NetworkManager && /etc/init.d/NetworkManager restart) || " \ - "((systemctl | grep NetworkManager.service) && systemctl restart NetworkManager)" + commands[:middle] << "((systemctl | grep NetworkManager.service) && systemctl restart NetworkManager) || " \ + "(test -f /etc/init.d/NetworkManager && /etc/init.d/NetworkManager restart)" end commands = commands[:start] + commands[:middle] + commands[:end] comm.sudo(commands.join("\n")) From 26f7a04b86febf34b6654ab55b184dbec2361592 Mon Sep 17 00:00:00 2001 From: Evgeny Sinelnikov Date: Sat, 8 Jul 2017 21:36:18 +0300 Subject: [PATCH 03/51] hosts/alt: Add hosts capabilities for ALT Linux platforms --- plugins/hosts/alt/cap/nfs.rb | 43 ++++++++++++++++++++++++++++++++++++ plugins/hosts/alt/host.rb | 11 +++++++++ plugins/hosts/alt/plugin.rb | 32 +++++++++++++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 plugins/hosts/alt/cap/nfs.rb create mode 100644 plugins/hosts/alt/host.rb create mode 100644 plugins/hosts/alt/plugin.rb diff --git a/plugins/hosts/alt/cap/nfs.rb b/plugins/hosts/alt/cap/nfs.rb new file mode 100644 index 000000000..ed68f4a92 --- /dev/null +++ b/plugins/hosts/alt/cap/nfs.rb @@ -0,0 +1,43 @@ +require "vagrant/util/subprocess" +require "vagrant/util/which" + +module VagrantPlugins + module HostALT + module Cap + class NFS + def self.nfs_check_command(env) + if systemd? + return "systemctl status --no-pager nfs-server.service" + else + return "/etc/init.d/nfs status" + end + end + + def self.nfs_start_command(env) + if systemd? + return "systemctl start rpcbind nfs-server.service" + else + return "/etc/init.d/nfs restart" + end + end + + def self.nfs_installed(environment) + if systemd? + system("systemctl --no-pager --no-legend --plain list-unit-files --all --type=service | grep --fixed-strings --quiet nfs-server.service") + else + system("rpm -q nfs-server --quiet 2>&1") + end + end + + protected + + # This tests to see if systemd is used on the system. This is used + # in newer versions of ALT, and requires a change in behavior. + def self.systemd? + result = Vagrant::Util::Subprocess.execute("ps", "-o", "comm=", "1") + return result.stdout.chomp == "systemd" + end + end + end + end +end diff --git a/plugins/hosts/alt/host.rb b/plugins/hosts/alt/host.rb new file mode 100644 index 000000000..2719f2b7e --- /dev/null +++ b/plugins/hosts/alt/host.rb @@ -0,0 +1,11 @@ +require "vagrant" + +module VagrantPlugins + module HostALT + class Host < Vagrant.plugin("2", :host) + def detect?(env) + File.exist?("/etc/altlinux-release") + end + end + end +end diff --git a/plugins/hosts/alt/plugin.rb b/plugins/hosts/alt/plugin.rb new file mode 100644 index 000000000..b8c8886b7 --- /dev/null +++ b/plugins/hosts/alt/plugin.rb @@ -0,0 +1,32 @@ +require "vagrant" + +module VagrantPlugins + module HostALT + class Plugin < Vagrant.plugin("2") + name "ALT Platform host" + description "ALT Platform host support." + + host("alt", "linux") do + require_relative "host" + Host + end + + host_capability("alt", "nfs_installed") do + require_relative "cap/nfs" + Cap::NFS + end + + # Linux-specific helpers we need to determine paths that can + # be overriden. + host_capability("alt", "nfs_check_command") do + require_relative "cap/nfs" + Cap::NFS + end + + host_capability("alt", "nfs_start_command") do + require_relative "cap/nfs" + Cap::NFS + end + end + end +end From 6ef55c172d1c98caefb5b270c2dab98edd3db1f1 Mon Sep 17 00:00:00 2001 From: Evgeny Sinelnikov Date: Sun, 9 Jul 2017 18:16:25 +0300 Subject: [PATCH 04/51] guests/alt: Adjust flavour detect and restart network after set hostname --- plugins/guests/alt/cap/change_host_name.rb | 2 ++ plugins/guests/alt/cap/flavor.rb | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/plugins/guests/alt/cap/change_host_name.rb b/plugins/guests/alt/cap/change_host_name.rb index 3d0333a37..a2c7b8453 100644 --- a/plugins/guests/alt/cap/change_host_name.rb +++ b/plugins/guests/alt/cap/change_host_name.rb @@ -35,6 +35,8 @@ module VagrantPlugins sed -i -e "s/\(\s\)$CURRENT_HOSTNAME_SHORT\(\s\)/\1$NEW_HOSTNAME_SHORT\2/g" -e "s/\(\s\)$CURRENT_HOSTNAME_SHORT$/\1$NEW_HOSTNAME_SHORT/g" /etc/hosts fi + # Restart network + service network restart EOH end end diff --git a/plugins/guests/alt/cap/flavor.rb b/plugins/guests/alt/cap/flavor.rb index a9b1ae017..77723f860 100644 --- a/plugins/guests/alt/cap/flavor.rb +++ b/plugins/guests/alt/cap/flavor.rb @@ -5,6 +5,17 @@ module VagrantPlugins def self.flavor(machine) # Read the version file if !comm.test("test -f /etc/os-release") + name = nil + machine.communicate.sudo("grep NAME /etc/os-release") do |type, data| + if type == :stdout + name = data.split("=")[1].chomp.to_i + end + end + + if name.nil? and name == "Sisyphus" + return :alt + end + version = nil machine.communicate.sudo("grep VERSION_ID /etc/os-release") do |type, data| if type == :stdout @@ -24,7 +35,9 @@ module VagrantPlugins end # Detect various flavors we care about - if output =~ /(ALT Workstation K|ALT Linux starter kit)\s*8( .+)?/i + if output =~ /(ALT SP|ALT Workstation|ALT Workstation K|ALT Linux starter kit)\s*8( .+)?/i + return :alt_8 + elsif output =~ /ALT\s*8( .+)?\s.+/i return :alt_8 else return :alt From 5ff176a82ccd8dc9573b23b5308f6b213c83e8f8 Mon Sep 17 00:00:00 2001 From: Evgeny Sinelnikov Date: Mon, 10 Jul 2017 00:52:24 +0300 Subject: [PATCH 05/51] guests/alt: capability tests for ALT Platforms --- .../guests/alt/cap/change_host_name_test.rb | 42 ++++ .../guests/alt/cap/configure_networks_test.rb | 214 ++++++++++++++++++ .../plugins/guests/alt/cap/flavor_test.rb | 43 ++++ .../alt/cap/network_scripts_dir_test.rb | 21 ++ .../unit/plugins/guests/alt/cap/rsync_test.rb | 29 +++ 5 files changed, 349 insertions(+) create mode 100644 test/unit/plugins/guests/alt/cap/change_host_name_test.rb create mode 100644 test/unit/plugins/guests/alt/cap/configure_networks_test.rb create mode 100644 test/unit/plugins/guests/alt/cap/flavor_test.rb create mode 100644 test/unit/plugins/guests/alt/cap/network_scripts_dir_test.rb create mode 100644 test/unit/plugins/guests/alt/cap/rsync_test.rb diff --git a/test/unit/plugins/guests/alt/cap/change_host_name_test.rb b/test/unit/plugins/guests/alt/cap/change_host_name_test.rb new file mode 100644 index 000000000..51eb814b6 --- /dev/null +++ b/test/unit/plugins/guests/alt/cap/change_host_name_test.rb @@ -0,0 +1,42 @@ +require_relative "../../../../base" + +describe "VagrantPlugins::GuestALT::Cap::ChangeHostName" do + let(:caps) do + VagrantPlugins::GuestALT::Plugin + .components + .guest_capabilities[:alt] + end + + let(:machine) { double("machine") } + let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) } + + before do + allow(machine).to receive(:communicate).and_return(comm) + end + + after do + comm.verify_expectations! + end + + describe ".change_host_name" do + let(:cap) { caps.get(:change_host_name) } + + let(:name) { "banana-rama.example.com" } + + it "sets the hostname" do + comm.stub_command("hostname -f | grep '^#{name}$'", exit_code: 1) + + cap.change_host_name(machine, name) + expect(comm.received_commands[1]).to match(/\/etc\/sysconfig\/network/) + expect(comm.received_commands[1]).to match(/hostnamectl set-hostname --static '#{name}'/) + expect(comm.received_commands[1]).to match(/hostnamectl set-hostname --transient '#{name}'/) + expect(comm.received_commands[1]).to match(/service network restart/) + end + + it "does not change the hostname if already set" do + comm.stub_command("hostname -f | grep '^#{name}$'", exit_code: 0) + cap.change_host_name(machine, name) + expect(comm.received_commands.size).to eq(1) + end + end +end diff --git a/test/unit/plugins/guests/alt/cap/configure_networks_test.rb b/test/unit/plugins/guests/alt/cap/configure_networks_test.rb new file mode 100644 index 000000000..c736a5f3f --- /dev/null +++ b/test/unit/plugins/guests/alt/cap/configure_networks_test.rb @@ -0,0 +1,214 @@ +require_relative "../../../../base" + +describe "VagrantPlugins::GuestALT::Cap::ConfigureNetworks" do + let(:caps) do + VagrantPlugins::GuestALT::Plugin + .components + .guest_capabilities[:alt] + end + + let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) } + let(:config) { double("config", vm: vm) } + let(:guest) { double("guest") } + let(:machine) { double("machine", guest: guest, config: config) } + let(:networks){ [[{}], [{}]] } + let(:vm){ double("vm", networks: networks) } + + before do + allow(machine).to receive(:communicate).and_return(comm) + end + + after do + comm.verify_expectations! + end + + describe ".configure_networks" do + let(:cap) { caps.get(:configure_networks) } + + before do + allow(guest).to receive(:capability) + .with(:flavor) + .and_return(:alt) + + allow(guest).to receive(:capability) + .with(:network_scripts_dir) + .and_return("/etc/net") + + allow(guest).to receive(:capability) + .with(:network_interfaces) + .and_return(["eth1", "eth2"]) + end + + let(:network_1) do + { + interface: 0, + type: "dhcp", + } + end + + let(:network_2) do + { + interface: 1, + type: "static", + ip: "33.33.33.10", + netmask: "255.255.0.0", + gateway: "33.33.0.1", + } + end + + context "with NetworkManager installed" do + before do + allow(cap).to receive(:nmcli?).and_return true + end + + context "with devices managed by NetworkManager" do + before do + allow(cap).to receive(:nm_controlled?).and_return true + end + + context "with nm_controlled option omitted" do + it "downs networks manually, creates ifaces, starts networks manually and restart NetworksManager" do + cap.configure_networks(machine, [network_1, network_2]) + expect(comm.received_commands[0]).to match(/ifdown/) + expect(comm.received_commands[0]).to match(/mkdir.*\/etc\/net\/ifaces/) + expect(comm.received_commands[0]).to match(/ifup/) + expect(comm.received_commands[0]).to match(/NetworkManager/) + expect(comm.received_commands[0]).to_not match(/nmcli/) + end + end + + context "with nm_controlled option set to true" do + let(:networks){ [[{nm_controlled: true}], [{nm_controlled: true}]] } + + it "downs networks via nmcli, creates ifaces and restart NetworksManager" do + cap.configure_networks(machine, [network_1, network_2]) + expect(comm.received_commands[0]).to match(/nmcli.*disconnect/) + expect(comm.received_commands[0]).to match(/mkdir.*\/etc\/net\/ifaces/) + expect(comm.received_commands[0]).to match(/NetworkManager/) + expect(comm.received_commands[0]).to_not match(/(ifdown|ifup)/) + end + end + + context "with nm_controlled option set to false" do + let(:networks){ [[{nm_controlled: false}], [{nm_controlled: false}]] } + + it "downs networks manually, creates ifaces, starts networks manually and restart NetworksManager" do + cap.configure_networks(machine, [network_1, network_2]) + expect(comm.received_commands[0]).to match(/ifdown/) + expect(comm.received_commands[0]).to match(/mkdir.*\/etc\/net\/ifaces/) + expect(comm.received_commands[0]).to match(/ifup/) + expect(comm.received_commands[0]).to match(/NetworkManager/) + expect(comm.received_commands[0]).to_not match(/nmcli/) + end + end + + context "with nm_controlled option set to false on first device" do + let(:networks){ [[{nm_controlled: false}], [{nm_controlled: true}]] } + + it "downs networks, creates ifaces and starts the networks with one managed manually and one NetworkManager controlled" do + cap.configure_networks(machine, [network_1, network_2]) + expect(comm.received_commands[0]).to match(/ifdown/) + expect(comm.received_commands[0]).to match(/nmcli.*disconnect/) + expect(comm.received_commands[0]).to match(/mkdir.*\/etc\/net\/ifaces/) + expect(comm.received_commands[0]).to match(/ifup/) + expect(comm.received_commands[0]).to match(/NetworkManager/) + end + end + end + + context "with devices not managed by NetworkManager" do + before do + allow(cap).to receive(:nm_controlled?).and_return false + end + + context "with nm_controlled option omitted" do + it "creates and starts the networks manually" do + cap.configure_networks(machine, [network_1, network_2]) + expect(comm.received_commands[0]).to match(/ifdown/) + expect(comm.received_commands[0]).to match(/mkdir.*\/etc\/net\/ifaces/) + expect(comm.received_commands[0]).to match(/ifup/) + expect(comm.received_commands[0]).to match(/NetworkManager/) + expect(comm.received_commands[0]).to_not match(/nmcli/) + end + end + + context "with nm_controlled option set to true" do + let(:networks){ [[{nm_controlled: true}], [{nm_controlled: true}]] } + + it "creates and starts the networks via nmcli" do + cap.configure_networks(machine, [network_1, network_2]) + expect(comm.received_commands[0]).to match(/ifdown/) + expect(comm.received_commands[0]).to match(/mkdir.*\/etc\/net\/ifaces/) + expect(comm.received_commands[0]).to match(/NetworkManager/) + expect(comm.received_commands[0]).to_not match(/ifup/) + end + end + + context "with nm_controlled option set to false" do + let(:networks){ [[{nm_controlled: false}], [{nm_controlled: false}]] } + + it "creates and starts the networks via ifup " do + cap.configure_networks(machine, [network_1, network_2]) + expect(comm.received_commands[0]).to match(/ifdown/) + expect(comm.received_commands[0]).to match(/mkdir.*\/etc\/net\/ifaces/) + expect(comm.received_commands[0]).to match(/ifup/) + expect(comm.received_commands[0]).to match(/NetworkManager/) + expect(comm.received_commands[0]).to_not match(/nmcli/) + end + end + + context "with nm_controlled option set to false on first device" do + let(:networks){ [[{nm_controlled: false}], [{nm_controlled: true}]] } + + it "creates and starts the networks with one managed manually and one NetworkManager controlled" do + cap.configure_networks(machine, [network_1, network_2]) + expect(comm.received_commands[0]).to match(/ifdown/) + expect(comm.received_commands[0]).to match(/mkdir.*\/etc\/net\/ifaces/) + expect(comm.received_commands[0]).to match(/ifup/) + expect(comm.received_commands[0]).to match(/NetworkManager/) + expect(comm.received_commands[0]).to_not match(/nmcli.*disconnect/) + end + end + end + end + + context "without NetworkManager installed" do + before do + allow(cap).to receive(:nmcli?).and_return false + end + + context "with nm_controlled option omitted" do + + it "creates and starts the networks manually" do + cap.configure_networks(machine, [network_1, network_2]) + expect(comm.received_commands[0]).to match(/ifdown/) + expect(comm.received_commands[0]).to match(/mkdir.*\/etc\/net\/ifaces/) + expect(comm.received_commands[0]).to match(/ifup/) + expect(comm.received_commands[0]).to_not match(/nmcli/) + expect(comm.received_commands[0]).to_not match(/NetworkManager/) + end + end + + context "with nm_controlled option omitted" do + let(:networks){ [[{nm_controlled: false}], [{nm_controlled: false}]] } + + it "creates and starts the networks manually" do + cap.configure_networks(machine, [network_1, network_2]) + expect(comm.received_commands[0]).to match(/ifdown/) + expect(comm.received_commands[0]).to match(/mkdir.*\/etc\/net\/ifaces/) + expect(comm.received_commands[0]).to match(/ifup/) + expect(comm.received_commands[0]).to_not match(/nmcli/) + expect(comm.received_commands[0]).to_not match(/NetworkManager/) + end + end + + context "with nm_controlled option set" do + let(:networks){ [[{nm_controlled: false}], [{nm_controlled: true}]] } + + it "raises an error" do + expect{ cap.configure_networks(machine, [network_1, network_2]) }.to raise_error(Vagrant::Errors::NetworkManagerNotInstalled) + end + end + end + end +end diff --git a/test/unit/plugins/guests/alt/cap/flavor_test.rb b/test/unit/plugins/guests/alt/cap/flavor_test.rb new file mode 100644 index 000000000..d7dacd728 --- /dev/null +++ b/test/unit/plugins/guests/alt/cap/flavor_test.rb @@ -0,0 +1,43 @@ +require_relative "../../../../base" + +describe "VagrantPlugins::GuestALT::Cap::Flavor" do + let(:caps) do + VagrantPlugins::GuestALT::Plugin + .components + .guest_capabilities[:alt] + end + + let(:machine) { double("machine") } + let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) } + + before do + allow(machine).to receive(:communicate).and_return(comm) + end + + after do + comm.verify_expectations! + end + + describe ".flavor" do + let(:cap) { caps.get(:flavor) } + + { + "ALT Education 8.1" => :alt, + "ALT Workstation 8.1" => :alt, + "ALT Workstation K 8.1 (Centaurea Ruthenica)" => :alt, + "ALT Linux p8 (Hypericum)" => :alt, + "ALT Sisyphus (unstable) (sisyphus)" => :alt, + + "ALT Linux 6.0.1 Spt (separator)" => :alt, + "ALT Linux 7.0.5 School Master" => :alt, + "ALT starter kit (Hypericum)" => :alt, + + "ALT" => :alt, + }.each do |str, expected| + it "returns #{expected} for #{str}" do + comm.stub_command("cat /etc/altlinux-release", stdout: str) + expect(cap.flavor(machine)).to be(expected) + end + end + end +end diff --git a/test/unit/plugins/guests/alt/cap/network_scripts_dir_test.rb b/test/unit/plugins/guests/alt/cap/network_scripts_dir_test.rb new file mode 100644 index 000000000..4aac58069 --- /dev/null +++ b/test/unit/plugins/guests/alt/cap/network_scripts_dir_test.rb @@ -0,0 +1,21 @@ +require_relative "../../../../base" + +describe "VagrantPlugins::GuestALT::Cap::NetworkScriptsDir" do + let(:caps) do + VagrantPlugins::GuestALT::Plugin + .components + .guest_capabilities[:alt] + end + + let(:machine) { double("machine") } + + describe ".network_scripts_dir" do + let(:cap) { caps.get(:network_scripts_dir) } + + let(:name) { "banana-rama.example.com" } + + it "is /etc/net" do + expect(cap.network_scripts_dir(machine)).to eq("/etc/net") + end + end +end diff --git a/test/unit/plugins/guests/alt/cap/rsync_test.rb b/test/unit/plugins/guests/alt/cap/rsync_test.rb new file mode 100644 index 000000000..126db9963 --- /dev/null +++ b/test/unit/plugins/guests/alt/cap/rsync_test.rb @@ -0,0 +1,29 @@ +require_relative "../../../../base" + +describe "VagrantPlugins::GuestALT::Cap:RSync" do + let(:caps) do + VagrantPlugins::GuestALT::Plugin + .components + .guest_capabilities[:alt] + end + + let(:machine) { double("machine") } + let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) } + + before do + allow(machine).to receive(:communicate).and_return(comm) + end + + after do + comm.verify_expectations! + end + + describe ".rsync_install" do + let(:cap) { caps.get(:rsync_install) } + + it "installs rsync" do + cap.rsync_install(machine) + expect(comm.received_commands[0]).to match(/install rsync/) + end + end +end From 015d98e76c394aa1828d0ac33eca8708bcf1c4b3 Mon Sep 17 00:00:00 2001 From: Evgeny Sinelnikov Date: Mon, 10 Jul 2017 03:08:27 +0300 Subject: [PATCH 06/51] guests/alt: Fix flavor and network configure with unit tests --- plugins/guests/alt/cap/flavor.rb | 33 +++++++---- .../guests/alt/cap/configure_networks_test.rb | 7 +-- .../plugins/guests/alt/cap/flavor_test.rb | 57 ++++++++++++++----- 3 files changed, 69 insertions(+), 28 deletions(-) diff --git a/plugins/guests/alt/cap/flavor.rb b/plugins/guests/alt/cap/flavor.rb index 77723f860..9cbff908c 100644 --- a/plugins/guests/alt/cap/flavor.rb +++ b/plugins/guests/alt/cap/flavor.rb @@ -3,41 +3,54 @@ module VagrantPlugins module Cap class Flavor def self.flavor(machine) + comm = machine.communicate + # Read the version file - if !comm.test("test -f /etc/os-release") + if comm.test("test -f /etc/os-release") name = nil - machine.communicate.sudo("grep NAME /etc/os-release") do |type, data| + comm.sudo("grep NAME /etc/os-release") do |type, data| if type == :stdout - name = data.split("=")[1].chomp.to_i + name = data.split("=")[1].gsub!(/\A"|"\Z/, '') end end - if name.nil? and name == "Sisyphus" + if !name.nil? and name == "Sisyphus" return :alt end version = nil - machine.communicate.sudo("grep VERSION_ID /etc/os-release") do |type, data| + comm.sudo("grep VERSION_ID /etc/os-release") do |type, data| if type == :stdout - version = data.split("=")[1].chomp.to_i + verstr = data.split("=")[1] + if verstr == "p8" + version = 8 + elsif verstr =~ /^[[\d]]/ + version = verstr.chomp.to_i + subversion = verstr.chomp.split(".")[1].to_i + if subversion > 90 + version += 1 + end + end end end - if version.nil? + if version.nil? or version == 0 return :alt else return :"alt_#{version}" end else output = "" - machine.communicate.sudo("cat /etc/altlinux-release") do |_, data| + comm.sudo("cat /etc/altlinux-release") do |_, data| output = data end # Detect various flavors we care about - if output =~ /(ALT SP|ALT Workstation|ALT Workstation K|ALT Linux starter kit)\s*8( .+)?/i + if output =~ /(ALT SP|ALT Education|ALT Workstation|ALT Workstation K|ALT Linux starter kit)\s*8(\.[1-9])?( .+)?/i return :alt_8 - elsif output =~ /ALT\s*8( .+)?\s.+/i + elsif output =~ /ALT\s+8(\.[1-9])?( .+)?\s.+/i + return :alt_8 + elsif output =~ /ALT Linux p8( .+)?/i return :alt_8 else return :alt diff --git a/test/unit/plugins/guests/alt/cap/configure_networks_test.rb b/test/unit/plugins/guests/alt/cap/configure_networks_test.rb index c736a5f3f..f19924d0f 100644 --- a/test/unit/plugins/guests/alt/cap/configure_networks_test.rb +++ b/test/unit/plugins/guests/alt/cap/configure_networks_test.rb @@ -67,13 +67,12 @@ describe "VagrantPlugins::GuestALT::Cap::ConfigureNetworks" do end context "with nm_controlled option omitted" do - it "downs networks manually, creates ifaces, starts networks manually and restart NetworksManager" do + it "downs networks via nmcli, creates ifaces and restart NetworksManager" do cap.configure_networks(machine, [network_1, network_2]) - expect(comm.received_commands[0]).to match(/ifdown/) + expect(comm.received_commands[0]).to match(/nmcli.*disconnect/) expect(comm.received_commands[0]).to match(/mkdir.*\/etc\/net\/ifaces/) - expect(comm.received_commands[0]).to match(/ifup/) expect(comm.received_commands[0]).to match(/NetworkManager/) - expect(comm.received_commands[0]).to_not match(/nmcli/) + expect(comm.received_commands[0]).to_not match(/ifdown|ifup/) end end diff --git a/test/unit/plugins/guests/alt/cap/flavor_test.rb b/test/unit/plugins/guests/alt/cap/flavor_test.rb index d7dacd728..65538f0fe 100644 --- a/test/unit/plugins/guests/alt/cap/flavor_test.rb +++ b/test/unit/plugins/guests/alt/cap/flavor_test.rb @@ -21,22 +21,51 @@ describe "VagrantPlugins::GuestALT::Cap::Flavor" do describe ".flavor" do let(:cap) { caps.get(:flavor) } - { - "ALT Education 8.1" => :alt, - "ALT Workstation 8.1" => :alt, - "ALT Workstation K 8.1 (Centaurea Ruthenica)" => :alt, - "ALT Linux p8 (Hypericum)" => :alt, - "ALT Sisyphus (unstable) (sisyphus)" => :alt, + context "without /etc/os-release file" do + { + "ALT 8.1 Server" => :alt_8, + "ALT Education 8.1" => :alt_8, + "ALT Workstation 8.1" => :alt_8, + "ALT Workstation K 8.1 (Centaurea Ruthenica)" => :alt_8, + "ALT Linux p8 (Hypericum)" => :alt_8, - "ALT Linux 6.0.1 Spt (separator)" => :alt, - "ALT Linux 7.0.5 School Master" => :alt, - "ALT starter kit (Hypericum)" => :alt, + "ALT Sisyphus (unstable) (sisyphus)" => :alt, + "ALT Linux Sisyphus (unstable)" => :alt, + "ALT Linux 6.0.1 Spt (separator)" => :alt, + "ALT Linux 7.0.5 School Master" => :alt, + "ALT starter kit (Hypericum)" => :alt, - "ALT" => :alt, - }.each do |str, expected| - it "returns #{expected} for #{str}" do - comm.stub_command("cat /etc/altlinux-release", stdout: str) - expect(cap.flavor(machine)).to be(expected) + "ALT" => :alt, + "Simply" => :alt, + }.each do |str, expected| + it "returns #{expected} for #{str} in /etc/altlinux-release" do + comm.stub_command("test -f /etc/os-release", exit_code: 1) + comm.stub_command("cat /etc/altlinux-release", stdout: str) + expect(cap.flavor(machine)).to be(expected) + end + end + end + + context "with /etc/os-release file" do + { + [ "NAME=\"Sisyphus\"", "VERSION_ID=20161130" ] => :alt, + + [ "NAME=\"ALT Education\"", "VERSION_ID=8.1" ] => :alt_8, + [ "NAME=\"ALT Server\"", "VERSION_ID=8.1" ] => :alt_8, + [ "NAME=\"ALT SPServer\"", "VERSION_ID=8.0" ] => :alt_8, + [ "NAME=\"starter kit\"", "VERSION_ID=p8" ] => :alt_8, + [ "NAME=\"ALT Linux\"", "VERSION_ID=8.0.0" ] => :alt_8, + [ "NAME=\"Simply Linux\"", "VERSION_ID=7.95.0" ] => :alt_8, + + [ "NAME=\"ALT Linux\"", "VERSION_ID=7.0.5" ] => :alt_7, + [ "NAME=\"School Junior\"", "VERSION_ID=7.0.5" ] => :alt_7, + }.each do |strs, expected| + it "returns #{expected} for #{strs[0]} and #{strs[1]} in /etc/os-release" do + comm.stub_command("test -f /etc/os-release", exit_code: 0) + comm.stub_command("grep NAME /etc/os-release", stdout: strs[0]) + comm.stub_command("grep VERSION_ID /etc/os-release", stdout: strs[1]) + expect(cap.flavor(machine)).to be(expected) + end end end end From 085feb65262a91c35dd8ffd8e28defa34943526e Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Mon, 28 Aug 2017 13:14:45 -0700 Subject: [PATCH 07/51] (#8923) Quote path passed into IdentityFile for ssh command Prior to this commit, a change to how the IdentityFile setting for the ssh command broke when a path with a space was used. This commit fixes that by quoting the path used to set the IdentityFile so that it uses the full path instead of part of the path after the space. --- lib/vagrant/util/ssh.rb | 3 ++- test/unit/vagrant/util/ssh_test.rb | 14 +++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/vagrant/util/ssh.rb b/lib/vagrant/util/ssh.rb index 31356b094..a32125505 100644 --- a/lib/vagrant/util/ssh.rb +++ b/lib/vagrant/util/ssh.rb @@ -139,7 +139,8 @@ module Vagrant # Use '-o' instead of '-i' because '-i' does not call # percent_expand in misc.c, but '-o' does. when passing the path, # replace '%' in the path with '%%' to escape the '%' - command_options += ["-o", "IdentityFile=%s" % [path.to_s.gsub('%', '%%')]] + path = path.to_s.gsub('%', '%%') + command_options += ["-o", "IdentityFile=\"#{path}\""] end end diff --git a/test/unit/vagrant/util/ssh_test.rb b/test/unit/vagrant/util/ssh_test.rb index eb9da43d8..97de4f379 100644 --- a/test/unit/vagrant/util/ssh_test.rb +++ b/test/unit/vagrant/util/ssh_test.rb @@ -67,7 +67,7 @@ describe Vagrant::Util::SSH do expect(described_class.exec(ssh_info)).to eq(nil) expect(Vagrant::Util::SafeExec).to have_received(:exec) - .with("ssh", "vagrant@localhost", "-p", "2222", "-o", "LogLevel=FATAL","-o", "Compression=yes", "-o", "DSAAuthentication=yes", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-o", "IdentityFile=#{ssh_info[:private_key_path][0]}") + .with("ssh", "vagrant@localhost", "-p", "2222", "-o", "LogLevel=FATAL","-o", "Compression=yes", "-o", "DSAAuthentication=yes", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-o", "IdentityFile=\"#{ssh_info[:private_key_path][0]}\"") end context "when disabling compression or dsa_authentication flags" do @@ -85,7 +85,7 @@ describe Vagrant::Util::SSH do expect(described_class.exec(ssh_info)).to eq(nil) expect(Vagrant::Util::SafeExec).to have_received(:exec) - .with("ssh", "vagrant@localhost", "-p", "2222", "-o", "LogLevel=FATAL", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-o", "IdentityFile=#{ssh_info[:private_key_path][0]}") + .with("ssh", "vagrant@localhost", "-p", "2222", "-o", "LogLevel=FATAL", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-o", "IdentityFile=\"#{ssh_info[:private_key_path][0]}\"") end end @@ -103,7 +103,7 @@ describe Vagrant::Util::SSH do expect(described_class.exec(ssh_info)).to eq(nil) expect(Vagrant::Util::SafeExec).to have_received(:exec) - .with("ssh", "vagrant@localhost", "-p", "2222", "-o", "LogLevel=FATAL", "-o", "IdentityFile=#{ssh_info[:private_key_path][0]}") + .with("ssh", "vagrant@localhost", "-p", "2222", "-o", "LogLevel=FATAL", "-o", "IdentityFile=\"#{ssh_info[:private_key_path][0]}\"") end end @@ -140,7 +140,7 @@ describe Vagrant::Util::SSH do expect(described_class.exec(ssh_info)).to eq(nil) expect(Vagrant::Util::SafeExec).to have_received(:exec) - .with("ssh", "vagrant@localhost", "-p", "2222", "-o", "LogLevel=FATAL", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-o", "IdentityFile=#{ssh_info[:private_key_path][0]}","-o", "ForwardX11=yes", "-o", "ForwardX11Trusted=yes") + .with("ssh", "vagrant@localhost", "-p", "2222", "-o", "LogLevel=FATAL", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-o", "IdentityFile=\"#{ssh_info[:private_key_path][0]}\"","-o", "ForwardX11=yes", "-o", "ForwardX11Trusted=yes") end end @@ -158,7 +158,7 @@ describe Vagrant::Util::SSH do expect(described_class.exec(ssh_info)).to eq(nil) expect(Vagrant::Util::SafeExec).to have_received(:exec) - .with("ssh", "vagrant@localhost", "-p", "2222", "-o", "LogLevel=FATAL", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-o", "IdentityFile=#{ssh_info[:private_key_path][0]}","-o", "ForwardAgent=yes") + .with("ssh", "vagrant@localhost", "-p", "2222", "-o", "LogLevel=FATAL", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-o", "IdentityFile=\"#{ssh_info[:private_key_path][0]}\"","-o", "ForwardAgent=yes") end end @@ -176,7 +176,7 @@ describe Vagrant::Util::SSH do expect(described_class.exec(ssh_info)).to eq(nil) expect(Vagrant::Util::SafeExec).to have_received(:exec) - .with("ssh", "vagrant@localhost", "-p", "2222", "-o", "LogLevel=FATAL", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-o", "IdentityFile=#{ssh_info[:private_key_path][0]}", "-L", "8008:localhost:80") + .with("ssh", "vagrant@localhost", "-p", "2222", "-o", "LogLevel=FATAL", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-o", "IdentityFile=\"#{ssh_info[:private_key_path][0]}\"", "-L", "8008:localhost:80") end end @@ -194,7 +194,7 @@ describe Vagrant::Util::SSH do expect(described_class.exec(ssh_info)).to eq(nil) expect(Vagrant::Util::SafeExec).to have_received(:exec) - .with("ssh", "vagrant@localhost", "-p", "2222", "-o", "LogLevel=FATAL", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-o", "IdentityFile=#{ssh_info[:private_key_path][0]}", "-6") + .with("ssh", "vagrant@localhost", "-p", "2222", "-o", "LogLevel=FATAL", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-o", "IdentityFile=\"#{ssh_info[:private_key_path][0]}\"", "-6") end end From 3df431dbc75903d2a8f967848e0de98460267d01 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Tue, 29 Aug 2017 11:16:19 -0700 Subject: [PATCH 08/51] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 346e2a131..209a4dd34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ IMPROVEMENTS: BUG FIXES: - guests/shell_expand_guest_path : Properly expand guest paths that include relative path alias [GH-8918] +- util/ssh: Properly quote key path for IdentityFile option to allow for spaces [GH-8924] ## 1.9.8 (August 23, 2017) From 9a29d7be6bb7919b6c9bd20bf86a7eb6b1ec741f Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Tue, 29 Aug 2017 10:24:10 -0700 Subject: [PATCH 09/51] (#7836) Introduce salt_call_args option for salt provisioner This config option for the salt provisioner allows you to pass additional arguments to the salt-call executable. --- plugins/provisioners/salt/config.rb | 3 ++ plugins/provisioners/salt/provisioner.rb | 16 ++++++- .../provisioners/salt/provisioner_test.rb | 43 +++++++++++++++++++ website/source/docs/provisioning/salt.html.md | 2 + 4 files changed, 62 insertions(+), 2 deletions(-) diff --git a/plugins/provisioners/salt/config.rb b/plugins/provisioners/salt/config.rb index b42cfb00d..225888def 100644 --- a/plugins/provisioners/salt/config.rb +++ b/plugins/provisioners/salt/config.rb @@ -24,6 +24,7 @@ module VagrantPlugins attr_accessor :log_level attr_accessor :masterless attr_accessor :minion_id + attr_accessor :salt_call_args ## bootstrap options attr_accessor :temp_config_dir @@ -66,6 +67,7 @@ module VagrantPlugins @version = UNSET_VALUE @run_service = UNSET_VALUE @master_id = UNSET_VALUE + @salt_call_args = UNSET_VALUE end def finalize! @@ -91,6 +93,7 @@ module VagrantPlugins @version = nil if @version == UNSET_VALUE @run_service = nil if @run_service == UNSET_VALUE @master_id = nil if @master_id == UNSET_VALUE + @salt_call_args = nil if @salt_call_args == UNSET_VALUE # NOTE: Optimistic defaults are set in the provisioner. UNSET_VALUEs # are converted there to allow proper detection of unset values. diff --git a/plugins/provisioners/salt/provisioner.rb b/plugins/provisioners/salt/provisioner.rb index deefa1428..b00cbb059 100644 --- a/plugins/provisioners/salt/provisioner.rb +++ b/plugins/provisioners/salt/provisioner.rb @@ -198,6 +198,18 @@ module VagrantPlugins return options end + # Append additional arguments to the salt-call command + def get_call_args + options = "" + if @config.salt_call_args + @config.salt_call_args.each do |opt| + options += " #{opt}" + end + end + + return options + end + # Copy master and minion configs to VM def upload_configs if @config.minion_config @@ -377,7 +389,7 @@ module VagrantPlugins @machine.communicate.execute("C:\\salt\\salt-call.bat saltutil.sync_all", opts) end # TODO: something equivalent to { error_key: :ssh_bad_exit_status_muted }? - @machine.communicate.execute("C:\\salt\\salt-call.bat state.highstate --retcode-passthrough#{get_masterless}#{get_loglevel}#{get_colorize}#{get_pillar}", opts) do |type, data| + @machine.communicate.execute("C:\\salt\\salt-call.bat state.highstate --retcode-passthrough#{get_masterless}#{get_loglevel}#{get_colorize}#{get_pillar}#{get_call_args}", opts) do |type, data| if @config.verbose @machine.env.ui.info(data.rstrip) end @@ -386,7 +398,7 @@ module VagrantPlugins unless @config.masterless? @machine.communicate.sudo("salt-call saltutil.sync_all") end - @machine.communicate.sudo("salt-call state.highstate --retcode-passthrough#{get_masterless}#{get_loglevel}#{get_colorize}#{get_pillar}", ssh_opts) do |type, data| + @machine.communicate.sudo("salt-call state.highstate --retcode-passthrough#{get_masterless}#{get_loglevel}#{get_colorize}#{get_pillar}#{get_call_args}", ssh_opts) do |type, data| if @config.verbose @machine.env.ui.info(data.rstrip) end diff --git a/test/unit/plugins/provisioners/salt/provisioner_test.rb b/test/unit/plugins/provisioners/salt/provisioner_test.rb index b203ea332..5920478ff 100644 --- a/test/unit/plugins/provisioners/salt/provisioner_test.rb +++ b/test/unit/plugins/provisioners/salt/provisioner_test.rb @@ -32,4 +32,47 @@ describe VagrantPlugins::Salt::Provisioner do describe "#provision" do end + + describe "#call_highstate" do + context "with masterless" do + it "passes along extra cli flags" do + allow(config).to receive(:run_highstate).and_return(true) + allow(config).to receive(:verbose).and_return(true) + allow(config).to receive(:masterless?).and_return(true) + allow(config).to receive(:masterless).and_return(true) + allow(config).to receive(:minion_id).and_return(nil) + allow(config).to receive(:log_level).and_return(nil) + allow(config).to receive(:colorize).and_return(false) + allow(config).to receive(:pillar_data).and_return([]) + + allow(config).to receive(:salt_call_args).and_return(["--output-dif"]) + allow(machine.communicate).to receive(:sudo) + allow(machine.config.vm).to receive(:communicator).and_return(:notwinrm) + allow(config).to receive(:install_master).and_return(false) + + expect(machine.communicate).to receive(:sudo).with("salt-call state.highstate --retcode-passthrough --local --log-level=debug --no-color --output-dif", {:error_key=>:ssh_bad_exit_status_muted}) + subject.call_highstate() + end + + it "has no additional cli flags if not included" do + allow(config).to receive(:run_highstate).and_return(true) + allow(config).to receive(:verbose).and_return(true) + allow(config).to receive(:masterless?).and_return(true) + allow(config).to receive(:masterless).and_return(true) + allow(config).to receive(:minion_id).and_return(nil) + allow(config).to receive(:log_level).and_return(nil) + allow(config).to receive(:colorize).and_return(false) + allow(config).to receive(:pillar_data).and_return([]) + + allow(config).to receive(:salt_call_args).and_return(nil) + allow(machine.communicate).to receive(:sudo) + allow(machine.config.vm).to receive(:communicator).and_return(:notwinrm) + allow(config).to receive(:install_master).and_return(false) + + expect(machine.communicate).to receive(:sudo).with("salt-call state.highstate --retcode-passthrough --local --log-level=debug --no-color", {:error_key=>:ssh_bad_exit_status_muted}) + subject.call_highstate() + end + end + end + end diff --git a/website/source/docs/provisioning/salt.html.md b/website/source/docs/provisioning/salt.html.md index 1f3ea1e4e..0bf1fe1f8 100644 --- a/website/source/docs/provisioning/salt.html.md +++ b/website/source/docs/provisioning/salt.html.md @@ -94,6 +94,8 @@ public key * `masterless` (boolean) - Calls state.highstate in local mode. Uses `minion_id` and `pillar_data` when provided. +* `salt_call_args` (array) - An array of additional command line flag arguments to be passed to the `salt-call` command when provisioning with masterless. + ## Master Options These only make sense when `install_master` is `true`. Not supported on Windows guest machines. From 432cb8d915052165ed4986d18608c7ee5eaf4632 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Tue, 29 Aug 2017 12:57:49 -0700 Subject: [PATCH 10/51] (#7826) Add salt_arg option for passing flags to salt tool This commit introduces the salt_arg option that allows a user to pass additional command line flags to the `salt` tool when provisioning with a master setup. It also adds additional config validation to ensure that both `salt_args` and `salt_call_args` is an array. --- plugins/provisioners/salt/config.rb | 11 +++++ plugins/provisioners/salt/provisioner.rb | 23 +++++----- templates/locales/en.yml | 2 + .../plugins/provisioners/salt/config_test.rb | 36 +++++++++++++++ .../provisioners/salt/provisioner_test.rb | 44 ++++++++++++++++++- website/source/docs/provisioning/salt.html.md | 2 + 6 files changed, 106 insertions(+), 12 deletions(-) diff --git a/plugins/provisioners/salt/config.rb b/plugins/provisioners/salt/config.rb index 225888def..2a8169312 100644 --- a/plugins/provisioners/salt/config.rb +++ b/plugins/provisioners/salt/config.rb @@ -25,6 +25,7 @@ module VagrantPlugins attr_accessor :masterless attr_accessor :minion_id attr_accessor :salt_call_args + attr_accessor :salt_args ## bootstrap options attr_accessor :temp_config_dir @@ -68,6 +69,7 @@ module VagrantPlugins @run_service = UNSET_VALUE @master_id = UNSET_VALUE @salt_call_args = UNSET_VALUE + @salt_args = UNSET_VALUE end def finalize! @@ -94,6 +96,7 @@ module VagrantPlugins @run_service = nil if @run_service == UNSET_VALUE @master_id = nil if @master_id == UNSET_VALUE @salt_call_args = nil if @salt_call_args == UNSET_VALUE + @salt_args = nil if @salt_args == UNSET_VALUE # NOTE: Optimistic defaults are set in the provisioner. UNSET_VALUEs # are converted there to allow proper detection of unset values. @@ -149,6 +152,14 @@ module VagrantPlugins errors << I18n.t("vagrant.provisioners.salt.must_accept_keys") end + if @salt_call_args && !@salt_call_args.is_a?(Array) + errors << I18n.t("vagrant.provisioners.salt.args_array") + end + + if @salt_args && !@salt_args.is_a?(Array) + errors << I18n.t("vagrant.provisioners.salt.args_array") + end + return {"salt provisioner" => errors} end end diff --git a/plugins/provisioners/salt/provisioner.rb b/plugins/provisioners/salt/provisioner.rb index b00cbb059..2f55099eb 100644 --- a/plugins/provisioners/salt/provisioner.rb +++ b/plugins/provisioners/salt/provisioner.rb @@ -198,16 +198,14 @@ module VagrantPlugins return options end + # Append additional arguments to the salt command + def get_salt_args + " " + Array(@config.salt_args).join(" ") + end + # Append additional arguments to the salt-call command def get_call_args - options = "" - if @config.salt_call_args - @config.salt_call_args.each do |opt| - options += " #{opt}" - end - end - - return options + " " + Array(@config.salt_call_args).join(" ") end # Copy master and minion configs to VM @@ -377,7 +375,8 @@ module VagrantPlugins unless @config.masterless? @machine.communicate.sudo("salt '*' saltutil.sync_all") end - @machine.communicate.sudo("salt '*' state.highstate --verbose#{get_masterless}#{get_loglevel}#{get_colorize}#{get_pillar}", ssh_opts) do |type, data| + options = "#{get_masterless}#{get_loglevel}#{get_colorize}#{get_pillar}#{get_salt_args}" + @machine.communicate.sudo("salt '*' state.highstate --verbose#{options}", ssh_opts) do |type, data| if @config.verbose @machine.env.ui.info(data.rstrip) end @@ -389,7 +388,8 @@ module VagrantPlugins @machine.communicate.execute("C:\\salt\\salt-call.bat saltutil.sync_all", opts) end # TODO: something equivalent to { error_key: :ssh_bad_exit_status_muted }? - @machine.communicate.execute("C:\\salt\\salt-call.bat state.highstate --retcode-passthrough#{get_masterless}#{get_loglevel}#{get_colorize}#{get_pillar}#{get_call_args}", opts) do |type, data| + options = "#{get_masterless}#{get_loglevel}#{get_colorize}#{get_pillar}#{get_call_args}" + @machine.communicate.execute("C:\\salt\\salt-call.bat state.highstate --retcode-passthrough#{options}", opts) do |type, data| if @config.verbose @machine.env.ui.info(data.rstrip) end @@ -398,7 +398,8 @@ module VagrantPlugins unless @config.masterless? @machine.communicate.sudo("salt-call saltutil.sync_all") end - @machine.communicate.sudo("salt-call state.highstate --retcode-passthrough#{get_masterless}#{get_loglevel}#{get_colorize}#{get_pillar}#{get_call_args}", ssh_opts) do |type, data| + options = "#{get_masterless}#{get_loglevel}#{get_colorize}#{get_pillar}#{get_call_args}" + @machine.communicate.sudo("salt-call state.highstate --retcode-passthrough#{options}", ssh_opts) do |type, data| if @config.verbose @machine.env.ui.info(data.rstrip) end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index ccab103f5..f012557d9 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -2406,6 +2406,8 @@ en: You must include both public and private keys. must_accept_keys: |- You must accept keys when running highstate with master! + args_array: |- + You must set this value as an array. pushes: file: diff --git a/test/unit/plugins/provisioners/salt/config_test.rb b/test/unit/plugins/provisioners/salt/config_test.rb index 24bf24796..2571bdbb2 100644 --- a/test/unit/plugins/provisioners/salt/config_test.rb +++ b/test/unit/plugins/provisioners/salt/config_test.rb @@ -78,5 +78,41 @@ describe VagrantPlugins::Salt::Config do expect(result[error_key]).to be_empty end end + + context "salt_call_args" do + it "fails if salt_call_args is not an array" do + subject.salt_call_args = "--flags" + subject.finalize! + + result = subject.validate(machine) + expect(result[error_key]).to_not be_empty + end + + it "is valid if is set and not missing" do + subject.salt_call_args = ["--flags"] + subject.finalize! + + result = subject.validate(machine) + expect(result[error_key]).to be_empty + end + end + + context "salt_args" do + it "fails if not an array" do + subject.salt_args = "--flags" + subject.finalize! + + result = subject.validate(machine) + expect(result[error_key]).to_not be_empty + end + + it "is valid if is set and not missing" do + subject.salt_args = ["--flags"] + subject.finalize! + + result = subject.validate(machine) + expect(result[error_key]).to be_empty + end + end end end diff --git a/test/unit/plugins/provisioners/salt/provisioner_test.rb b/test/unit/plugins/provisioners/salt/provisioner_test.rb index 5920478ff..8ebf3b846 100644 --- a/test/unit/plugins/provisioners/salt/provisioner_test.rb +++ b/test/unit/plugins/provisioners/salt/provisioner_test.rb @@ -34,6 +34,46 @@ describe VagrantPlugins::Salt::Provisioner do end describe "#call_highstate" do + context "master" do + it "passes along extra cli flags" do + allow(config).to receive(:run_highstate).and_return(true) + allow(config).to receive(:verbose).and_return(true) + allow(config).to receive(:masterless?).and_return(false) + allow(config).to receive(:masterless).and_return(false) + allow(config).to receive(:minion_id).and_return(nil) + allow(config).to receive(:log_level).and_return(nil) + allow(config).to receive(:colorize).and_return(false) + allow(config).to receive(:pillar_data).and_return([]) + allow(config).to receive(:install_master).and_return(true) + + allow(config).to receive(:salt_args).and_return(["--async"]) + allow(machine.communicate).to receive(:sudo) + allow(machine.config.vm).to receive(:communicator).and_return(:notwinrm) + + expect(machine.communicate).to receive(:sudo).with("salt '*' state.highstate --verbose --log-level=debug --no-color --async", {:error_key=>:ssh_bad_exit_status_muted}) + subject.call_highstate() + end + + it "has no additional cli flags if not included" do + allow(config).to receive(:run_highstate).and_return(true) + allow(config).to receive(:verbose).and_return(true) + allow(config).to receive(:masterless?).and_return(false) + allow(config).to receive(:masterless).and_return(false) + allow(config).to receive(:minion_id).and_return(nil) + allow(config).to receive(:log_level).and_return(nil) + allow(config).to receive(:colorize).and_return(false) + allow(config).to receive(:pillar_data).and_return([]) + allow(config).to receive(:install_master).and_return(true) + + allow(config).to receive(:salt_args).and_return(nil) + allow(machine.communicate).to receive(:sudo) + allow(machine.config.vm).to receive(:communicator).and_return(:notwinrm) + + expect(machine.communicate).to receive(:sudo).with("salt '*' state.highstate --verbose --log-level=debug --no-color ", {:error_key=>:ssh_bad_exit_status_muted}) + subject.call_highstate() + end + end + context "with masterless" do it "passes along extra cli flags" do allow(config).to receive(:run_highstate).and_return(true) @@ -45,6 +85,7 @@ describe VagrantPlugins::Salt::Provisioner do allow(config).to receive(:colorize).and_return(false) allow(config).to receive(:pillar_data).and_return([]) + allow(config).to receive(:salt_args).and_return(["--async"]) allow(config).to receive(:salt_call_args).and_return(["--output-dif"]) allow(machine.communicate).to receive(:sudo) allow(machine.config.vm).to receive(:communicator).and_return(:notwinrm) @@ -65,11 +106,12 @@ describe VagrantPlugins::Salt::Provisioner do allow(config).to receive(:pillar_data).and_return([]) allow(config).to receive(:salt_call_args).and_return(nil) + allow(config).to receive(:salt_args).and_return(nil) allow(machine.communicate).to receive(:sudo) allow(machine.config.vm).to receive(:communicator).and_return(:notwinrm) allow(config).to receive(:install_master).and_return(false) - expect(machine.communicate).to receive(:sudo).with("salt-call state.highstate --retcode-passthrough --local --log-level=debug --no-color", {:error_key=>:ssh_bad_exit_status_muted}) + expect(machine.communicate).to receive(:sudo).with("salt-call state.highstate --retcode-passthrough --local --log-level=debug --no-color ", {:error_key=>:ssh_bad_exit_status_muted}) subject.call_highstate() end end diff --git a/website/source/docs/provisioning/salt.html.md b/website/source/docs/provisioning/salt.html.md index 0bf1fe1f8..c32c13924 100644 --- a/website/source/docs/provisioning/salt.html.md +++ b/website/source/docs/provisioning/salt.html.md @@ -109,6 +109,8 @@ These only make sense when `install_master` is `true`. Not supported on Windows * `seed_master` (dictionary) - Upload keys to master, thereby pre-seeding it before use. Example: `{minion_name:/path/to/key.pub}` +* `salt_args` (array) - An array of additional command line flag arguments to be passed to the `salt` command when provisioning with masterless. + ## Execute States Either of the following may be used to actually execute states From 9a992ffe173f458a38960a1eb75037adf8c7bbc4 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Tue, 29 Aug 2017 14:28:38 -0700 Subject: [PATCH 11/51] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 209a4dd34..4d19b127f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ IMPROVEMENTS: - commands/ssh-config: Properly display windows path if invoked from msys2 or cygwin [GH-8915] - providers/salt: Remove duplicate stdout, stderr output from salt [GH-8767] +- providers/salt: Introduce salt_call_args and salt_args option for salt provisioner [GH-8927] BUG FIXES: From 0e091fee1684858f4bf31f172ca22cb352a0d123 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Wed, 30 Aug 2017 10:00:52 -0400 Subject: [PATCH 12/51] command/login: Store user/pass on client Will allow us to prompt for other info without forgetting this data. --- plugins/commands/login/client.rb | 7 +++--- plugins/commands/login/command.rb | 40 +++++++++++++++++++++---------- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/plugins/commands/login/client.rb b/plugins/commands/login/client.rb index f9b0b037e..8c03fdccd 100644 --- a/plugins/commands/login/client.rb +++ b/plugins/commands/login/client.rb @@ -7,6 +7,9 @@ module VagrantPlugins class Client include Vagrant::Util::Presence + attr_accessor :username_or_email + attr_accessor :password + # Initializes a login client with the given Vagrant::Environment. # # @param [Vagrant::Environment] env @@ -40,11 +43,9 @@ module VagrantPlugins # Login logs a user in and returns the token for that user. The token # is _not_ stored unless {#store_token} is called. # - # @param [String] username_or_email - # @param [String] password # @param [String] description # @return [String] token The access token, or nil if auth failed. - def login(username_or_email, password, description: nil) + def login(description: nil) @logger.info("Logging in '#{username_or_email}'") with_error_handling do diff --git a/plugins/commands/login/command.rb b/plugins/commands/login/command.rb index c6700e960..36e0f5c9d 100644 --- a/plugins/commands/login/command.rb +++ b/plugins/commands/login/command.rb @@ -17,6 +17,10 @@ module VagrantPlugins options[:check] = c end + o.on("-d", "--description DESCRIPTION", String, "Description for the Vagrant Cloud token") do |t| + options[:description] = t + end + o.on("-k", "--logout", "Logs you out if you're logged in") do |k| options[:logout] = k end @@ -24,6 +28,10 @@ module VagrantPlugins o.on("-t", "--token TOKEN", String, "Set the Vagrant Cloud token") do |t| options[:token] = t end + + o.on("-u", "--username USERNAME_OR_EMAIL", String, "Specify your Vagrant Cloud username or email address") do |t| + options[:login] = t + end end # Parse the options @@ -31,6 +39,7 @@ module VagrantPlugins return if !argv @client = Client.new(@env) + @client.username_or_email = options[:login] # Determine what task we're actually taking based on flags if options[:check] @@ -50,25 +59,30 @@ module VagrantPlugins end # Ask for the username - login = nil - password = nil - description = nil - while !login - login = @env.ui.ask("Vagrant Cloud Username: ") + if @client.username_or_email + @env.ui.output("Vagrant Cloud username or email: #{@client.username_or_email}") + end + until @client.username_or_email + @client.username_or_email = @env.ui.ask("Vagrant Cloud username or email: ") end - while !password - password = @env.ui.ask("Password (will be hidden): ", echo: false) + until @client.password + @client.password = @env.ui.ask("Password (will be hidden): ", echo: false) end - description_default = "Vagrant login from #{Socket.gethostname}" - while !description - description = - @env.ui.ask("Token description (Defaults to #{description_default.inspect}): ") + description = options[:description] + if description + @env.ui.output("Token description: #{description}") + else + description_default = "Vagrant login from #{Socket.gethostname}" + until description + description = + @env.ui.ask("Token description (Defaults to #{description_default.inspect}): ") + end + description = description_default if description.empty? end - description = description_default if description.empty? - token = @client.login(login, password, description: description) + token = @client.login(description: description) if !token @env.ui.error(I18n.t("login_command.invalid_login")) return 1 From 3ddc98c2e4fdc299e1e21fd78e8a5354bc5ec93c Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Wed, 30 Aug 2017 10:02:19 -0400 Subject: [PATCH 13/51] command/login: Support 2FA login on Vagrant Cloud --- plugins/commands/login/client.rb | 107 +++++++++++- plugins/commands/login/command.rb | 19 +- plugins/commands/login/errors.rb | 7 + plugins/commands/login/locales/en.yml | 3 + .../plugins/commands/login/client_test.rb | 165 +++++++++++++++--- 5 files changed, 264 insertions(+), 37 deletions(-) diff --git a/plugins/commands/login/client.rb b/plugins/commands/login/client.rb index 8c03fdccd..ebfe717b3 100644 --- a/plugins/commands/login/client.rb +++ b/plugins/commands/login/client.rb @@ -5,10 +5,14 @@ require "vagrant/util/presence" module VagrantPlugins module LoginCommand class Client + APP = "app".freeze + include Vagrant::Util::Presence attr_accessor :username_or_email attr_accessor :password + attr_reader :two_factor_default_delivery_method + attr_reader :two_factor_delivery_methods # Initializes a login client with the given Vagrant::Environment. # @@ -38,27 +42,67 @@ module VagrantPlugins RestClient.get(url, content_type: :json) true end + rescue Errors::Unauthorized + false end # Login logs a user in and returns the token for that user. The token # is _not_ stored unless {#store_token} is called. # # @param [String] description + # @param [String] code # @return [String] token The access token, or nil if auth failed. - def login(description: nil) + def login(description: nil, code: nil) @logger.info("Logging in '#{username_or_email}'") - with_error_handling do - url = "#{Vagrant.server_url}/api/v1/authenticate" - request = { + response = post( + "/api/v1/authenticate", { user: { login: username_or_email, password: password }, token: { description: description + }, + two_factor: { + code: code } } + ) + + response["token"] + end + + # Requests a 2FA code + # @param [String] delivery_method + def request_code(delivery_method) + @env.ui.warn("Requesting 2FA code via #{delivery_method.upcase}...") + + response = post( + "/api/v1/two-factor/request-code", { + user: { + login: username_or_email, + password: password + }, + two_factor: { + delivery_method: delivery_method.downcase + } + } + ) + + two_factor = response['two_factor'] + obfuscated_destination = two_factor['obfuscated_destination'] + + @env.ui.success("2FA code sent to #{obfuscated_destination}.") + end + + # Issues a post to a Vagrant Cloud path with the given payload. + # @param [String] path + # @param [Hash] payload + # @return [Hash] response data + def post(path, payload) + with_error_handling do + url = File.join(Vagrant.server_url, path) proxy = nil proxy ||= ENV["HTTPS_PROXY"] || ENV["https_proxy"] @@ -68,7 +112,7 @@ module VagrantPlugins response = RestClient::Request.execute( method: :post, url: url, - payload: JSON.dump(request), + payload: JSON.dump(payload), proxy: proxy, headers: { accept: :json, @@ -77,8 +121,7 @@ module VagrantPlugins }, ) - data = JSON.load(response.to_s) - data["token"] + JSON.load(response.to_s) end end @@ -139,14 +182,33 @@ EOH yield rescue RestClient::Unauthorized @logger.debug("Unauthorized!") - false + raise Errors::Unauthorized + rescue RestClient::BadRequest => e + @logger.debug("Bad request:") + @logger.debug(e.message) + @logger.debug(e.backtrace.join("\n")) + parsed_response = JSON.parse(e.response) + errors = parsed_response["errors"].join("\n") + raise Errors::ServerError, errors: errors rescue RestClient::NotAcceptable => e @logger.debug("Got unacceptable response:") @logger.debug(e.message) @logger.debug(e.backtrace.join("\n")) + parsed_response = JSON.parse(e.response) + + if two_factor = parsed_response['two_factor'] + store_two_factor_information two_factor + + if two_factor_default_delivery_method != APP + request_code two_factor_default_delivery_method + end + + raise Errors::TwoFactorRequired + end + begin - errors = JSON.parse(e.response)["errors"].join("\n") + errors = parsed_response["errors"].join("\n") raise Errors::ServerError, errors: errors rescue JSON::ParserError; end @@ -159,6 +221,33 @@ EOH def token_path @env.data_dir.join("vagrant_login_token") end + + def store_two_factor_information(two_factor) + @two_factor_default_delivery_method = + two_factor['default_delivery_method'] + + @two_factor_delivery_methods = + two_factor['delivery_methods'] + + @env.ui.warn "2FA is enabled for your account." + if two_factor_default_delivery_method == APP + @env.ui.info "Enter the code from your authenticator." + else + @env.ui.info "Default method is " \ + "'#{two_factor_default_delivery_method}'." + end + + other_delivery_methods = + two_factor_delivery_methods - [APP] + + if other_delivery_methods.any? + other_delivery_methods_sentence = other_delivery_methods + .map { |word| "'#{word}'" } + .join(' or ') + @env.ui.info "You can also type #{other_delivery_methods_sentence} " \ + "to request a new code." + end + end end end end diff --git a/plugins/commands/login/command.rb b/plugins/commands/login/command.rb index 36e0f5c9d..10a8ef13f 100644 --- a/plugins/commands/login/command.rb +++ b/plugins/commands/login/command.rb @@ -82,10 +82,21 @@ module VagrantPlugins description = description_default if description.empty? end - token = @client.login(description: description) - if !token - @env.ui.error(I18n.t("login_command.invalid_login")) - return 1 + code = nil + + begin + token = @client.login(description: description, code: code) + rescue Errors::TwoFactorRequired + until code + code = @env.ui.ask("2FA code: ") + + if @client.two_factor_delivery_methods.include?(code.downcase) + delivery_method, code = code, nil + @client.request_code delivery_method + end + end + + retry end @client.store_token(token) diff --git a/plugins/commands/login/errors.rb b/plugins/commands/login/errors.rb index 614c37cf6..4d56612bd 100644 --- a/plugins/commands/login/errors.rb +++ b/plugins/commands/login/errors.rb @@ -12,6 +12,13 @@ module VagrantPlugins class ServerUnreachable < Error error_key(:server_unreachable) end + + class Unauthorized < Error + error_key(:unauthorized) + end + + class TwoFactorRequired < Error + end end end end diff --git a/plugins/commands/login/locales/en.yml b/plugins/commands/login/locales/en.yml index d7decafd3..4f4d7e399 100644 --- a/plugins/commands/login/locales/en.yml +++ b/plugins/commands/login/locales/en.yml @@ -9,6 +9,9 @@ en: The Vagrant Cloud server is not currently accepting connections. Please check your network connection and try again later. + unauthorized: |- + Invalid username or password. Please try again. + check_logged_in: |- You are already logged in. check_not_logged_in: |- diff --git a/test/unit/plugins/commands/login/client_test.rb b/test/unit/plugins/commands/login/client_test.rb index 33436dfd9..06a110706 100644 --- a/test/unit/plugins/commands/login/client_test.rb +++ b/test/unit/plugins/commands/login/client_test.rb @@ -7,7 +7,12 @@ describe VagrantPlugins::LoginCommand::Client do let(:env) { isolated_environment.create_vagrant_env } - subject { described_class.new(env) } + subject(:client) { described_class.new(env) } + + before(:all) do + I18n.load_path << Vagrant.source_root.join("plugins/commands/login/locales/en.yml") + I18n.reload! + end before do stub_env("ATLAS_TOKEN" => nil) @@ -38,7 +43,7 @@ describe VagrantPlugins::LoginCommand::Client do expect(subject.logged_in?).to be(true) end - it "returns false if the endpoint returns a non-200" do + it "raises an error if the endpoint returns a non-200" do stub_request(:get, url) .with(headers: headers) .to_return(body: JSON.pretty_generate("bad" => true), status: 401) @@ -55,47 +60,159 @@ describe VagrantPlugins::LoginCommand::Client do end describe "#login" do - it "returns the access token after successful login" do - request = { - "user" => { - "login" => "foo", - "password" => "bar", + let(:request) { + { + user: { + login: login, + password: password, }, - "token" => { - "description" => "Token description" + token: { + description: description, + }, + two_factor: { + code: nil } } + } - response = { - "token" => "baz", - } + let(:login) { "foo" } + let(:password) { "bar" } + let(:description) { "Token description" } - headers = { + let(:headers) { + { "Accept" => "application/json", "Content-Type" => "application/json", } + } + let(:response) { + { + token: "baz" + } + } + it "returns the access token after successful login" do stub_request(:post, "#{Vagrant.server_url}/api/v1/authenticate"). with(body: JSON.dump(request), headers: headers). to_return(status: 200, body: JSON.dump(response)) - expect(subject.login("foo", "bar", description: "Token description")) - .to eq("baz") + client.username_or_email = login + client.password = password + + expect(client.login(description: "Token description")).to eq("baz") end - it "returns nil on bad login" do - stub_request(:post, "#{Vagrant.server_url}/api/v1/authenticate"). - to_return(status: 401, body: "") + context "when 2fa is required" do + let(:response) { + { + two_factor: { + default_delivery_method: default_delivery_method, + delivery_methods: delivery_methods + } + } + } + let(:default_delivery_method) { "app" } + let(:delivery_methods) { ["app"] } - expect(subject.login("foo", "bar")).to be(false) + before do + stub_request(:post, "#{Vagrant.server_url}/api/v1/authenticate"). + to_return(status: 406, body: JSON.dump(response)) + end + + it "raises a two-factor required error" do + expect { + client.login + }.to raise_error(VagrantPlugins::LoginCommand::Errors::TwoFactorRequired) + end + + context "when the default delivery method is not app" do + let(:default_delivery_method) { "sms" } + let(:delivery_methods) { ["app", "sms"] } + + it "requests a code and then raises a two-factor required error" do + expect(client) + .to receive(:request_code) + .with(default_delivery_method) + + expect { + client.login + }.to raise_error(VagrantPlugins::LoginCommand::Errors::TwoFactorRequired) + end + end end - it "raises an exception if it can't reach the sever" do - stub_request(:post, "#{Vagrant.server_url}/api/v1/authenticate"). - to_raise(SocketError) + context "on bad login" do + before do + stub_request(:post, "#{Vagrant.server_url}/api/v1/authenticate"). + to_return(status: 401, body: "") + end - expect { subject.login("foo", "bar") }. - to raise_error(VagrantPlugins::LoginCommand::Errors::ServerUnreachable) + it "raises an error" do + expect { + client.login + }.to raise_error(VagrantPlugins::LoginCommand::Errors::Unauthorized) + end + end + + context "if it can't reach the server" do + before do + stub_request(:post, "#{Vagrant.server_url}/api/v1/authenticate"). + to_raise(SocketError) + end + + it "raises an exception" do + expect { + subject.login + }.to raise_error(VagrantPlugins::LoginCommand::Errors::ServerUnreachable) + end + end + end + + describe "#request_code" do + let(:request) { + { + user: { + login: login, + password: password, + }, + two_factor: { + delivery_method: delivery_method + } + } + } + + let(:login) { "foo" } + let(:password) { "bar" } + let(:delivery_method) { "sms" } + + let(:headers) { + { + "Accept" => "application/json", + "Content-Type" => "application/json" + } + } + + let(:response) { + { + two_factor: { + obfuscated_destination: "SMS number ending in 1234" + } + } + } + + it "displays that the code was sent" do + expect(env.ui) + .to receive(:success) + .with("2FA code sent to SMS number ending in 1234.") + + stub_request(:post, "#{Vagrant.server_url}/api/v1/two-factor/request-code"). + with(body: JSON.dump(request), headers: headers). + to_return(status: 201, body: JSON.dump(response)) + + client.username_or_email = login + client.password = password + + client.request_code delivery_method end end From f8fa7e3b29dfe549f843d6c4d3ae581491c855e4 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Wed, 30 Aug 2017 10:02:37 -0400 Subject: [PATCH 14/51] command/login: Fix typo in server_error --- plugins/commands/login/locales/en.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/commands/login/locales/en.yml b/plugins/commands/login/locales/en.yml index 4f4d7e399..76fa01281 100644 --- a/plugins/commands/login/locales/en.yml +++ b/plugins/commands/login/locales/en.yml @@ -2,7 +2,7 @@ en: login_command: errors: server_error: |- - The Vagrant Cloud server responded with an not-OK response: + The Vagrant Cloud server responded with a not-OK response: %{errors} server_unreachable: |- From 31051bc0f5ca10c25ddb15177a67e0749a963d88 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Wed, 30 Aug 2017 14:20:21 -0400 Subject: [PATCH 15/51] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d19b127f..089a16085 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ FEATURES: IMPROVEMENTS: +- commands/login: Support `vagrant login` when 2FA is enabled on a user's Vagrant Cloud account [GH-8935] - commands/ssh-config: Properly display windows path if invoked from msys2 or cygwin [GH-8915] - providers/salt: Remove duplicate stdout, stderr output from salt [GH-8767] - providers/salt: Introduce salt_call_args and salt_args option for salt provisioner [GH-8927] From 2170d40bb29f753936d0b61de11a7d048ccac324 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Wed, 30 Aug 2017 14:20:52 -0400 Subject: [PATCH 16/51] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 089a16085..f8fdf7e29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ FEATURES: IMPROVEMENTS: -- commands/login: Support `vagrant login` when 2FA is enabled on a user's Vagrant Cloud account [GH-8935] +- commands/login: Add support for two-factor authentication [GH-8935] - commands/ssh-config: Properly display windows path if invoked from msys2 or cygwin [GH-8915] - providers/salt: Remove duplicate stdout, stderr output from salt [GH-8767] - providers/salt: Introduce salt_call_args and salt_args option for salt provisioner [GH-8927] From a9564b21370769f5377703b6975e4b9aada098f7 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Wed, 30 Aug 2017 15:48:46 -0700 Subject: [PATCH 17/51] (#8933) Align file provisioner functionality on all platforms This commit aligns how the file provisioner should work on all host machines. It ensures that a `/.` is only applied if the user intended to upload a folder to a destination under a different name. It ensures that if uploading to a windows guest with a different destination folder name, it does not nest the source folder under that name so that it works the same as it does on linux platforms. It also updates the behavior of the winrm upload communicator by allowing an array of paths to be uploaded instead of a single file or folder to allow for this new functionality for windows guests. --- plugins/communicators/winrm/shell.rb | 15 +++- plugins/provisioners/file/provisioner.rb | 23 +++++-- .../plugins/communicators/winrm/shell_test.rb | 31 ++++++++- .../provisioners/file/provisioner_test.rb | 34 +++++++++ website/source/docs/provisioning/file.html.md | 69 +++++++++++++++++++ 5 files changed, 165 insertions(+), 7 deletions(-) diff --git a/plugins/communicators/winrm/shell.rb b/plugins/communicators/winrm/shell.rb index 7a4444a8f..949908a79 100644 --- a/plugins/communicators/winrm/shell.rb +++ b/plugins/communicators/winrm/shell.rb @@ -83,9 +83,22 @@ module VagrantPlugins raise_winrm_exception(e, "run_wql", query) end + # @param from [Array, String] a single path or folder, or an + # array of paths and folders to upload to the guest + # @param to [String] a path or folder on the guest to upload to + # @return [FixNum] Total size transfered from host to guest def upload(from, to) file_manager = WinRM::FS::FileManager.new(connection) - file_manager.upload(from, to) + if from.is_a?(Array) + # Preserve return FixNum of bytes transfered + return_bytes = 0 + from.each do |file| + return_bytes += file_manager.upload(file, to) + end + return return_bytes + else + file_manager.upload(from, to) + end end def download(from, to) diff --git a/plugins/provisioners/file/provisioner.rb b/plugins/provisioners/file/provisioner.rb index 46355c11b..8391a4503 100644 --- a/plugins/provisioners/file/provisioner.rb +++ b/plugins/provisioners/file/provisioner.rb @@ -11,15 +11,28 @@ module VagrantPlugins if File.directory?(source) # We need to make sure the actual destination folder # also exists before uploading, otherwise - # you will get nested folders. We also need to append - # a './' to the source folder so we copy the contents - # rather than the folder itself, in case a users destination - # folder differs from its source. + # you will get nested folders # # https://serverfault.com/questions/538368/make-scp-always-overwrite-or-create-directory # https://unix.stackexchange.com/questions/292641/get-scp-path-behave-like-rsync-path/292732 command = "mkdir -p \"%s\"" % destination - source << "/." + if !destination.end_with?(File::SEPARATOR) && + !source.end_with?("#{File::SEPARATOR}.") + # We also need to append a '/.' to the source folder so we copy + # the contents rather than the folder itself, in case a users + # destination folder differs from its source + # + # If source is set as `source/` it will lose the trailing + # slash due to how `File.expand_path` works, so we don't need + # a conditional for that case. + if @machine.config.vm.communicator == :winrm + # windows needs an array of paths because of the + # winrm-fs function Vagrant is using to upload file/folder. + source = Dir["#{source}#{File::SEPARATOR}*"] + else + source << "#{File::SEPARATOR}." + end + end else command = "mkdir -p \"%s\"" % File.dirname(destination) end diff --git a/test/unit/plugins/communicators/winrm/shell_test.rb b/test/unit/plugins/communicators/winrm/shell_test.rb index 988c07a97..4d2d53b21 100644 --- a/test/unit/plugins/communicators/winrm/shell_test.rb +++ b/test/unit/plugins/communicators/winrm/shell_test.rb @@ -31,6 +31,35 @@ describe VagrantPlugins::CommunicatorWinRM::WinRMShell do end end + describe "#upload" do + let(:fm) { double("file_manager") } + it "should call file_manager.upload for each passed in path" do + from = ["/path", "/path/folder", "/path/folder/file.py"] + to = "/destination" + size = 80 + + allow(WinRM::FS::FileManager).to receive(:new).with(connection) + .and_return(fm) + allow(fm).to receive(:upload).and_return(size) + + expect(fm).to receive(:upload).exactly(from.size).times + expect(subject.upload(from, to)).to eq(size*from.size) + end + + it "should call file_manager.upload once for a single path" do + from = "/path/folder/file.py" + to = "/destination" + size = 80 + + allow(WinRM::FS::FileManager).to receive(:new).with(connection) + .and_return(fm) + allow(fm).to receive(:upload).and_return(size) + + expect(fm).to receive(:upload).exactly(1).times + expect(subject.upload(from, to)).to eq(size) + end + end + describe ".powershell" do it "should call winrm powershell" do expect(shell).to receive(:run).with("dir").and_return(output) @@ -66,7 +95,7 @@ describe VagrantPlugins::CommunicatorWinRM::WinRMShell do end end - describe ".cmd" do + describe ".cmd" do it "should call winrm cmd" do expect(connection).to receive(:shell).with(:cmd, { }) expect(shell).to receive(:run).with("dir").and_return(output) diff --git a/test/unit/plugins/provisioners/file/provisioner_test.rb b/test/unit/plugins/provisioners/file/provisioner_test.rb index 8da38e6d7..0aedb8fe8 100644 --- a/test/unit/plugins/provisioners/file/provisioner_test.rb +++ b/test/unit/plugins/provisioners/file/provisioner_test.rb @@ -89,5 +89,39 @@ describe VagrantPlugins::FileUpload::Provisioner do subject.provision end + + it "appends a '/.' if the destination doesnt end with a file separator" do + allow(config).to receive(:source).and_return("/source") + allow(config).to receive(:destination).and_return("/foo/bar") + allow(File).to receive(:directory?).with("/source").and_return(true) + + expect(guest).to receive(:capability?). + with(:shell_expand_guest_path).and_return(true) + expect(guest).to receive(:capability). + with(:shell_expand_guest_path, "/foo/bar").and_return("/foo/bar") + + expect(communicator).to receive(:upload).with("/source/.", "/foo/bar") + + subject.provision + end + + it "sends an array of files and folders if winrm and destination doesn't end with file separator" do + files = ["/source/file.py", "/source/folder"] + allow(Dir).to receive(:[]).and_return(files) + allow(config).to receive(:source).and_return("/source") + allow(config).to receive(:destination).and_return("/foo/bar") + allow(File).to receive(:directory?).with("/source").and_return(true) + allow(machine.config.vm).to receive(:communicator).and_return(:winrm) + + expect(guest).to receive(:capability?). + with(:shell_expand_guest_path).and_return(true) + expect(guest).to receive(:capability). + with(:shell_expand_guest_path, "/foo/bar").and_return("/foo/bar") + + expect(communicator).to receive(:upload) + .with(files, "/foo/bar") + + subject.provision + end end end diff --git a/website/source/docs/provisioning/file.html.md b/website/source/docs/provisioning/file.html.md index d88d03184..4442b3a9e 100644 --- a/website/source/docs/provisioning/file.html.md +++ b/website/source/docs/provisioning/file.html.md @@ -25,6 +25,42 @@ new VM. config.vm.provision "file", source: "~/.gitconfig", destination: ".gitconfig" end +If you want to upload a folder to your guest system, it can be accomplished by +using a file provisioner seen below. When copied, the resulting folder on the guest will +replace `folder` as `newfolder` and place its on the guest machine. Note that if +you'd like the same folder name on your guest machine, make sure that the destination +path has the same name as the folder on your host. + + Vagrant.configure("2") do |config| + # ... other configuration + + config.vm.provision "file", source: "~/path/to/host/folder", destination: "$HOME/remote/newfolder" + end + +Prior to copying `~/path/to/host/folder` to the guest machine: + + folder + ├── script.sh + ├── otherfolder + │   └── hello.sh + ├── goodbye.sh + ├── hello.sh + └── woot.sh + + 1 directory, 5 files + +After to copying `~/path/to/host/folder` into `$HOME/remote/newfolder` to the guest machine: + + newfolder + ├── script.sh + ├── otherfolder + │   └── hello.sh + ├── goodbye.sh + ├── hello.sh + └── woot.sh + + 1 directory, 5 files + Note that, unlike with synced folders, files or directories that are uploaded will not be kept in sync. Continuing with the example above, if you make further changes to your local ~/.gitconfig, they will not be immediately @@ -49,3 +85,36 @@ The file provisioner takes only two options, both of which are required: the source will be uploaded to. The file/folder is uploaded as the SSH user over SCP, so this location must be writable to that user. The SSH user can be determined by running `vagrant ssh-config`, and defaults to "vagrant". + +## Caveats + +While the file provisioner does support trailing slashes or "globing", this can +lead to some confusing results due to the underlying tool used to copy files and +folders between the host and guests. For example, if you have a source and +destination with a trailing slash defined below: + + config.vm.provision "file", source: "~/pathfolder", destination: "/remote/newlocation/" + +You are telling vagrant to upload `~/pathfolder` under the remote dir `/remote/newlocation`, +which will look like: + + newlocation + ├── pathfolder + │   └── file.sh + + 1 directory, 2 files + +This behavior can also be achieved by defining your file provisioner below: + + config.vm.provision "file", source: "~/pathfolder", destination: "/remote/newlocation/pathfolder" + +Another example is using globing on the host machine to grab all files within a +folder, but not the top level folder itself: + + config.vm.provision "file", source: "~/otherfolder/.", destination: "/remote/otherlocation" + +The file provisioner is defined to include all files under `~/otherfolder` +to the new location `/remote/otherlocation`. This idea can be achieved by simply +having your destination folder differ from the source folder: + + config.vm.provision "file", source: "/otherfolder", destination: "/remote/otherlocation" From b741ff799940064f12315f3f57b2298acfdb8318 Mon Sep 17 00:00:00 2001 From: Gilles Cornu Date: Sun, 3 Sep 2017 17:09:16 +0200 Subject: [PATCH 18/51] provisoners/ansible(both): Accept 'all:vars' The patterns "all" is a special keyword that target all hosts in the inventory. Therefore it makes sense to accept "all:vars" as a group variable name. Note that "*:vars" pattern is not valid in an Ansible inventory. See http://docs.ansible.com/ansible/latest/intro_patterns.html#patterns Fix #7730 --- CHANGELOG.md | 1 + plugins/provisioners/ansible/provisioner/base.rb | 2 +- .../provisioners/ansible/provisioner_test.rb | 13 +++++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8fdf7e29..1d40c57f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ IMPROVEMENTS: BUG FIXES: - guests/shell_expand_guest_path : Properly expand guest paths that include relative path alias [GH-8918] +- provisioners/ansible(both): Add the "all:vars" section to the inventory when defined in `groups` option [GH-7730] - util/ssh: Properly quote key path for IdentityFile option to allow for spaces [GH-8924] ## 1.9.8 (August 23, 2017) diff --git a/plugins/provisioners/ansible/provisioner/base.rb b/plugins/provisioners/ansible/provisioner/base.rb index 8e52d751f..78f6e40f1 100644 --- a/plugins/provisioners/ansible/provisioner/base.rb +++ b/plugins/provisioners/ansible/provisioner/base.rb @@ -228,7 +228,7 @@ module VagrantPlugins end group_vars.each_pair do |gname, gmembers| - if defined_groups.include?(gname.sub(/:vars$/, "")) + if defined_groups.include?(gname.sub(/:vars$/, "")) || gname == "all:vars" inventory_groups += "\n[#{gname}]\n" + gmembers.join("\n") + "\n" end end diff --git a/test/unit/plugins/provisioners/ansible/provisioner_test.rb b/test/unit/plugins/provisioners/ansible/provisioner_test.rb index 4a68993db..458ceb3ac 100644 --- a/test/unit/plugins/provisioners/ansible/provisioner_test.rb +++ b/test/unit/plugins/provisioners/ansible/provisioner_test.rb @@ -396,6 +396,19 @@ VF expect(inventory_content).to include("[group3:vars]\nstringvar1=stringvalue1\nstringvar2=stringvalue2\n") }.and_return(default_execute_result) end + + it "adds 'all:vars' section to the generated inventory" do + config.groups = { + "all:vars" => { "var1" => "value1", "var2" => "value2" } + } + + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| + inventory_content = File.read(generated_inventory_file) + + expect(inventory_content).to include("[all:vars]\nvar1=value1\nvar2=value2\n") + + }.and_return(default_execute_result) + end end describe "with host_key_checking option enabled" do From c4d8c7a4dca1a82856fdd7b023538b414ff2d1f7 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Tue, 5 Sep 2017 15:55:41 -0700 Subject: [PATCH 19/51] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d40c57f8..a9f4875e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ BUG FIXES: - guests/shell_expand_guest_path : Properly expand guest paths that include relative path alias [GH-8918] - provisioners/ansible(both): Add the "all:vars" section to the inventory when defined in `groups` option [GH-7730] +- provisioners/file: Align file provisioner functionality on all platforms [GH-8939] - util/ssh: Properly quote key path for IdentityFile option to allow for spaces [GH-8924] ## 1.9.8 (August 23, 2017) From f0f60a10759633608042a9a15d939e3cdbe8f192 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Fri, 1 Sep 2017 16:56:34 -0700 Subject: [PATCH 20/51] (#4666) Remove duplicate export folders before writing /etc/exports Prior to this commit, if you set up multiple folders to export with NFS on linux with the exact same hostpath, the template used to write /etc/exports would end up placing the same path with the same IP in /etc/exports and cause an error preventing the folders from being properly mounted. This commit fixes that by first looking at which folders are being exported and if there are any duplicates. If so, remove the duplicates and only export 1 hostpath folder. If these duplicate folders have differing nfs linux options, an exception must be thrown because we cannot assume which options the user intended to export with. --- lib/vagrant/errors.rb | 4 +++ plugins/hosts/linux/cap/nfs.rb | 32 +++++++++++++++++ templates/locales/en.yml | 3 ++ test/unit/plugins/hosts/linux/cap/nfs_test.rb | 36 +++++++++++++++++++ 4 files changed, 75 insertions(+) diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index ab05f27ab..ee0d8ea25 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -468,6 +468,10 @@ module Vagrant error_key(:nfs_bad_exports) end + class NFSDupePerms < VagrantError + error_key(:nfs_dupe_permissions) + end + class NFSExportsFailed < VagrantError error_key(:nfs_exports_failed) end diff --git a/plugins/hosts/linux/cap/nfs.rb b/plugins/hosts/linux/cap/nfs.rb index 731399b8e..45701f02b 100644 --- a/plugins/hosts/linux/cap/nfs.rb +++ b/plugins/hosts/linux/cap/nfs.rb @@ -29,6 +29,7 @@ module VagrantPlugins nfs_start_command = env.host.capability(:nfs_start_command) nfs_opts_setup(folders) + folders = folder_dupe_check(folders) output = Vagrant::Util::TemplateRenderer.render('nfs/exports_linux', uuid: id, ips: ips, @@ -84,6 +85,37 @@ module VagrantPlugins protected + # Takes a hash of folders and removes any duplicate exports that + # share the same hostpath to avoid duplicate entries in /etc/exports + # ref: GH-4666 + def self.folder_dupe_check(folders) + return_folders = {} + # Group by hostpath to see if there are multiple exports coming + # from the same folder + export_groups = folders.values.group_by { |h| h[:hostpath] } + + # We need to check that each group key only has 1 value, + # and if not, check each nfs option. If all nfs options are the same + # we're good, otherwise throw an exception + export_groups.each do |path,group| + if group.size > 1 + # if the linux nfs options aren't all the same throw an exception + group1_opts = group.first[:linux__nfs_options] + + if !group.all? {|g| g[:linux__nfs_options] == group1_opts} + raise Vagrant::Errors::NFSDupePerms, hostpath: group.first[:hostpath] + else + # if they're the same just pick the first one + return_folders[path] = group.first + end + else + # just return folder, there are no duplicates + return_folders[path] = group.first + end + end + return_folders + end + def self.nfs_cleanup(remove_ids) return if !File.exist?(NFS_EXPORTS_PATH) diff --git a/templates/locales/en.yml b/templates/locales/en.yml index f012557d9..4a247e778 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -912,6 +912,9 @@ en: command: %{command} stdout: %{stdout} stderr: %{stderr} + nfs_dupe_permissions: |- + You have attempted to export the same nfs host path at %{hostpath} with + different nfs permissions. Please pick one permission and reload your guest. nfs_cant_read_exports: |- Vagrant can't read your current NFS exports! The exports file should be readable by any user. This is usually caused by invalid permissions diff --git a/test/unit/plugins/hosts/linux/cap/nfs_test.rb b/test/unit/plugins/hosts/linux/cap/nfs_test.rb index eed8b7221..557a470d5 100644 --- a/test/unit/plugins/hosts/linux/cap/nfs_test.rb +++ b/test/unit/plugins/hosts/linux/cap/nfs_test.rb @@ -79,6 +79,42 @@ EOH expect(exports_content).to include("/tmp") expect(exports_content).not_to include("/var") end + + it "throws an exception with at least 2 different nfs options" do + folders = {"/vagrant"=> + {:hostpath=>"/home/vagrant", + :linux__nfs_options=>["rw","all_squash"]}, + "/var/www/project"=> + {:hostpath=>"/home/vagrant", + :linux__nfs_options=>["rw","sync"]}} + + expect { cap.nfs_export(env, ui, SecureRandom.uuid, ["127.0.0.1"], folders) }. + to raise_error Vagrant::Errors::NFSDupePerms + end + + it "writes only 1 hostpath for multiple exports" do + folders = {"/vagrant"=> + {:hostpath=>"/home/vagrant", + :linux__nfs_options=>["rw","all_squash"]}, + "/var/www/otherproject"=> + {:hostpath=>"/newhome/otherproject", + :linux__nfs_options=>["rw","all_squash"]}, + "/var/www/project"=> + {:hostpath=>"/home/vagrant", + :linux__nfs_options=>["rw","all_squash"]}} + valid_id = SecureRandom.uuid + content =<<-EOH +\n# VAGRANT-BEGIN: #{Process.uid} #{valid_id} +"/home/vagrant" 127.0.0.1(rw,all_squash,anonuid=,anongid=,fsid=) +"/newhome/otherproject" 127.0.0.1(rw,all_squash,anonuid=,anongid=,fsid=) +# VAGRANT-END: #{Process.uid} #{valid_id} +EOH + + cap.nfs_export(env, ui, valid_id, ["127.0.0.1"], folders) + exports_content = File.read(exports_path) + expect(exports_content).to eq(content) + end + end describe ".nfs_prune" do From e09848ca59585396d60955a719e1a1947d5adc34 Mon Sep 17 00:00:00 2001 From: Gilles Cornu Date: Wed, 6 Sep 2017 16:25:05 +0200 Subject: [PATCH 21/51] provisioners/ansible_local: Shellescape extra-vars Fix #7735 Further work for proper shell-escaping of other ansible-playbook and ansible-galaxy command line arguments will be addressed via #8949. --- CHANGELOG.md | 1 + plugins/provisioners/ansible/provisioner/base.rb | 2 +- .../plugins/provisioners/ansible/provisioner_test.rb | 10 +++++----- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9f4875e1..fd30e3e94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ BUG FIXES: - guests/shell_expand_guest_path : Properly expand guest paths that include relative path alias [GH-8918] - provisioners/ansible(both): Add the "all:vars" section to the inventory when defined in `groups` option [GH-7730] +- provisioners/ansible_local: Extra variables are no longer truncated when a dollar ($) character is present [GH-7735] - provisioners/file: Align file provisioner functionality on all platforms [GH-8939] - util/ssh: Properly quote key path for IdentityFile option to allow for spaces [GH-8924] diff --git a/plugins/provisioners/ansible/provisioner/base.rb b/plugins/provisioners/ansible/provisioner/base.rb index 78f6e40f1..c2add455a 100644 --- a/plugins/provisioners/ansible/provisioner/base.rb +++ b/plugins/provisioners/ansible/provisioner/base.rb @@ -70,7 +70,7 @@ module VagrantPlugins if arg =~ /(--start-at-task|--limit)=(.+)/ shell_args << %Q(#{$1}="#{$2}") elsif arg =~ /(--extra-vars)=(.+)/ - shell_args << %Q(%s="%s") % [$1, $2.gsub('\\', '\\\\\\').gsub('"', %Q(\\"))] + shell_args << %Q(%s=%s) % [$1, $2.shellescape] else shell_args << arg end diff --git a/test/unit/plugins/provisioners/ansible/provisioner_test.rb b/test/unit/plugins/provisioners/ansible/provisioner_test.rb index 458ceb3ac..861a4e1c6 100644 --- a/test/unit/plugins/provisioners/ansible/provisioner_test.rb +++ b/test/unit/plugins/provisioners/ansible/provisioner_test.rb @@ -867,10 +867,10 @@ VF context "with extra_vars option defined" do describe "with a hash value" do before do - config.extra_vars = { var1: %Q(string with 'apostrophes', \\, " and =), var2: { x: 42 } } + config.extra_vars = { var1: %Q(string with 'apo$trophe$', \\, " and =), var2: { x: 42 } } end - it_should_set_optional_arguments({ "extra_vars" => "--extra-vars={\"var1\":\"string with 'apostrophes', \\\\, \\\" and =\",\"var2\":{\"x\":42}}" }) + it_should_set_optional_arguments({ "extra_vars" => "--extra-vars={\"var1\":\"string with 'apo$trophe$', \\\\, \\\" and =\",\"var2\":{\"x\":42}}" }) end describe "with a string value referring to file path (with the '@' prefix)" do @@ -892,7 +892,7 @@ VF # command line arguments config.galaxy_roles_path = "/up/to the stars" - config.extra_vars = { var1: %Q(string with 'apostrophes', \\, " and =), var2: { x: 42 } } + config.extra_vars = { var1: %Q(string with 'apo$trophe$', \\, " and =), var2: { x: 42 } } config.sudo = true config.sudo_user = 'deployer' config.verbose = "vvv" @@ -913,7 +913,7 @@ VF it_should_set_arguments_and_environment_variables 21, 6, true it_should_explicitly_enable_ansible_ssh_control_persist_defaults - it_should_set_optional_arguments({ "extra_vars" => "--extra-vars={\"var1\":\"string with 'apostrophes', \\\\, \\\" and =\",\"var2\":{\"x\":42}}", + it_should_set_optional_arguments({ "extra_vars" => "--extra-vars={\"var1\":\"string with 'apo$trophe$', \\\\, \\\" and =\",\"var2\":{\"x\":42}}", "sudo" => "--sudo", "sudo_user" => "--sudo-user=deployer", "verbose" => "-vvv", @@ -938,7 +938,7 @@ VF it "shows the ansible-playbook command, with additional quotes when required" do expect(machine.env.ui).to receive(:detail) - .with(%Q(PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ANSIBLE_ROLES_PATH='/up/to the stars' ANSIBLE_CONFIG='#{existing_file}' ANSIBLE_HOST_KEY_CHECKING=true ANSIBLE_SSH_ARGS='-o IdentitiesOnly=yes -o IdentityFile=/my/key1 -o IdentityFile=/my/key2 -o ForwardAgent=yes -o ControlMaster=no -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --connection=ssh --timeout=30 --ask-sudo-pass --ask-vault-pass --limit="machine*:&vagrant:!that_one" --inventory-file=#{generated_inventory_dir} --extra-vars="{\\"var1\\":\\"string with 'apostrophes', \\\\\\\\, \\\\\\" and =\\",\\"var2\\":{\\"x\\":42}}" --sudo --sudo-user=deployer -vvv --vault-password-file=#{existing_file} --tags=db,www --skip-tags=foo,bar --start-at-task="joe's awesome task" --why-not --su-user=foot --ask-su-pass --limit=all --private-key=./myself.key --extra-vars='{\"var3\":\"foo\"}' playbook.yml)) + .with(%Q(PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ANSIBLE_ROLES_PATH='/up/to the stars' ANSIBLE_CONFIG='#{existing_file}' ANSIBLE_HOST_KEY_CHECKING=true ANSIBLE_SSH_ARGS='-o IdentitiesOnly=yes -o IdentityFile=/my/key1 -o IdentityFile=/my/key2 -o ForwardAgent=yes -o ControlMaster=no -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --connection=ssh --timeout=30 --ask-sudo-pass --ask-vault-pass --limit="machine*:&vagrant:!that_one" --inventory-file=#{generated_inventory_dir} --extra-vars=\\{\\"var1\\":\\"string\\ with\\ \\'apo\\$trophe\\$\\',\\ \\\\\\\\,\\ \\\\\\"\\ and\\ \\=\\",\\"var2\\":\\{\\"x\\":42\\}\\} --sudo --sudo-user=deployer -vvv --vault-password-file=#{existing_file} --tags=db,www --skip-tags=foo,bar --start-at-task="joe's awesome task" --why-not --su-user=foot --ask-su-pass --limit=all --private-key=./myself.key --extra-vars='{\"var3\":\"foo\"}' playbook.yml)) end end From 8834afbd8e6198b49b0a0ad2b3756c139a8d27b7 Mon Sep 17 00:00:00 2001 From: Gilles Cornu Date: Sun, 13 Nov 2016 20:58:26 +0100 Subject: [PATCH 22/51] provisioners/ansible(both): Add compatibility mode With this change, it is now possible to get rid of many deprecation messages successively introduced in Ansible 1.9, and 2.0. More interesting, the generated inventory will contain the recommended variable names (e.g. `ansible_host` instead of `ansible_ssh_host`) when the compatibility mode is set to '2.0'. Details: - Add `compatibility_mode` option to control the Ansible parameters format to be used. The value corresponds to the minimal version supported. For the moment, possible values are '1.8' (corresponding to Vagrant's former behaviour) or '2.0'. Note that a dynamic inventory generated in compatibility mode '2.0' is not supported by Ansible 1.x. On the other hand, Ansible 2.x so far supports inventory format generated by the compatibility mode '1.8'. - Add compatibility mode auto-detection, based on the available Ansible version. This is the default behaviour in order to bring a maximum of user friendliness. The drawback of this approach is to let potential compatibility breaking risks, for `ansible` provisioner setups that already integrate Ansible 2.x **AND** rely on the existence of the generated `_ssh` variable names. Thanks to the vagrant warnings (and its release notes), I argue that it is worth to offer auto-detection by default, which offers a sweet transition to most users. - Add `become`, `become_user` and `ask_become_pass` options and their backwards compatible aliases. The legacy options are now deprecated. Note that we intentionally didn't provide a '1.9' compatibility mode, as it would add extra-complexity for practically no added-value. To my knowledge, the Ansible 2.x series haven't introduced yet any major changes or deprecations that would motivate to introduce a higher version compatibility mode (to be confirmed/verified). Resolve GH-6570 Still Pending: - Optimization: Reduce the number of `ansible` command executions. Currently two exec calls will be performed when the compatibility mode auto-detection is enabled (i.e. by default). We could make the provisioner a little bit smarter to only execute `ansible` only once in any situation (by combining "presence" and "version" checks). - User-friendliness: Add better validator on `compatibility_mode` option, and shows a warning or an error instead of the silent fallback on the auto-detection modus. - Test coverage: All the added behaviours are not fully covered yet. --- plugins/provisioners/ansible/config/base.rb | 39 ++- plugins/provisioners/ansible/config/host.rb | 15 +- plugins/provisioners/ansible/constants.rb | 9 + .../provisioners/ansible/provisioner/base.rb | 75 ++++- .../provisioners/ansible/provisioner/guest.rb | 17 +- .../provisioners/ansible/provisioner/host.rb | 43 ++- templates/locales/en.yml | 14 + .../provisioners/ansible/config/guest_test.rb | 5 +- .../provisioners/ansible/config/host_test.rb | 16 +- .../provisioners/ansible/config/shared.rb | 39 ++- .../provisioners/ansible/provisioner_test.rb | 274 ++++++++++++++---- .../source/docs/provisioning/ansible.html.md | 9 +- .../docs/provisioning/ansible_common.html.md | 41 ++- 13 files changed, 503 insertions(+), 93 deletions(-) create mode 100644 plugins/provisioners/ansible/constants.rb diff --git a/plugins/provisioners/ansible/config/base.rb b/plugins/provisioners/ansible/config/base.rb index 36fb5b903..1b58a60d4 100644 --- a/plugins/provisioners/ansible/config/base.rb +++ b/plugins/provisioners/ansible/config/base.rb @@ -1,3 +1,5 @@ +require_relative "../constants" + module VagrantPlugins module Ansible module Config @@ -6,6 +8,9 @@ module VagrantPlugins GALAXY_COMMAND_DEFAULT = "ansible-galaxy install --role-file=%{role_file} --roles-path=%{roles_path} --force".freeze PLAYBOOK_COMMAND_DEFAULT = "ansible-playbook".freeze + attr_accessor :become + attr_accessor :become_user + attr_accessor :compatibility_mode attr_accessor :config_file attr_accessor :extra_vars attr_accessor :galaxy_role_file @@ -20,13 +25,28 @@ module VagrantPlugins attr_accessor :raw_arguments attr_accessor :skip_tags attr_accessor :start_at_task - attr_accessor :sudo - attr_accessor :sudo_user attr_accessor :tags attr_accessor :vault_password_file attr_accessor :verbose + # + # Deprecated options + # + alias :sudo :become + def sudo=(value) + show_deprecation_info 'sudo', 'become' + @become = value + end + alias :sudo_user :become_user + def sudo_user=(value) + show_deprecation_info 'sudo_user', 'become_user' + @become_user = value + end + def initialize + @become = UNSET_VALUE + @become_user = UNSET_VALUE + @compatibility_mode = UNSET_VALUE @config_file = UNSET_VALUE @extra_vars = UNSET_VALUE @galaxy_role_file = UNSET_VALUE @@ -41,14 +61,15 @@ module VagrantPlugins @raw_arguments = UNSET_VALUE @skip_tags = UNSET_VALUE @start_at_task = UNSET_VALUE - @sudo = UNSET_VALUE - @sudo_user = UNSET_VALUE @tags = UNSET_VALUE @vault_password_file = UNSET_VALUE @verbose = UNSET_VALUE end def finalize! + @become = false if @become != true + @become_user = nil if @become_user == UNSET_VALUE + @compatibility_mode = nil unless Ansible::COMPATIBILITY_MODES.include?(@compatibility_mode) @config_file = nil if @config_file == UNSET_VALUE @extra_vars = nil if @extra_vars == UNSET_VALUE @galaxy_role_file = nil if @galaxy_role_file == UNSET_VALUE @@ -63,8 +84,6 @@ module VagrantPlugins @raw_arguments = nil if @raw_arguments == UNSET_VALUE @skip_tags = nil if @skip_tags == UNSET_VALUE @start_at_task = nil if @start_at_task == UNSET_VALUE - @sudo = false if @sudo != true - @sudo_user = nil if @sudo_user == UNSET_VALUE @tags = nil if @tags == UNSET_VALUE @vault_password_file = nil if @vault_password_file == UNSET_VALUE @verbose = false if @verbose == UNSET_VALUE @@ -112,6 +131,14 @@ module VagrantPlugins end end + + protected + + def show_deprecation_info(deprecated_option, new_option) + puts "DEPRECATION: The '#{deprecated_option}' option for the Ansible provisioner is deprecated." + puts "Please use the '#{new_option}' option instead." + puts "The '#{deprecated_option}' option will be removed in a future release of Vagrant.\n\n" + end end end end diff --git a/plugins/provisioners/ansible/config/host.rb b/plugins/provisioners/ansible/config/host.rb index 4e075ae64..06df0d1c4 100644 --- a/plugins/provisioners/ansible/config/host.rb +++ b/plugins/provisioners/ansible/config/host.rb @@ -5,16 +5,25 @@ module VagrantPlugins module Config class Host < Base - attr_accessor :ask_sudo_pass + attr_accessor :ask_become_pass attr_accessor :ask_vault_pass attr_accessor :force_remote_user attr_accessor :host_key_checking attr_accessor :raw_ssh_args + # + # Deprecated options + # + alias :ask_sudo_pass :ask_become_pass + def ask_sudo_pass=(value) + show_deprecation_warning 'ask_sudo_pass', 'ask_become_pass' + @ask_become_pass = value + end + def initialize super - @ask_sudo_pass = false + @ask_become_pass = false @ask_vault_pass = false @force_remote_user = true @host_key_checking = false @@ -24,7 +33,7 @@ module VagrantPlugins def finalize! super - @ask_sudo_pass = false if @ask_sudo_pass != true + @ask_become_pass = false if @ask_become_pass != true @ask_vault_pass = false if @ask_vault_pass != true @force_remote_user = true if @force_remote_user != false @host_key_checking = false if @host_key_checking != true diff --git a/plugins/provisioners/ansible/constants.rb b/plugins/provisioners/ansible/constants.rb new file mode 100644 index 000000000..5e1d9e3f8 --- /dev/null +++ b/plugins/provisioners/ansible/constants.rb @@ -0,0 +1,9 @@ + +module VagrantPlugins + module Ansible + COMPATIBILITY_MODE_V1_8 = "1.8".freeze + COMPATIBILITY_MODE_V2_0 = "2.0".freeze + DEFAULT_COMPATIBILITY_MODE = COMPATIBILITY_MODE_V1_8 + COMPATIBILITY_MODES = [COMPATIBILITY_MODE_V1_8, COMPATIBILITY_MODE_V2_0].freeze + end +end \ No newline at end of file diff --git a/plugins/provisioners/ansible/provisioner/base.rb b/plugins/provisioners/ansible/provisioner/base.rb index c2add455a..8d6c8f413 100644 --- a/plugins/provisioners/ansible/provisioner/base.rb +++ b/plugins/provisioners/ansible/provisioner/base.rb @@ -1,3 +1,4 @@ +require_relative "../constants" require_relative "../errors" require_relative "../helpers" @@ -14,6 +15,27 @@ module VagrantPlugins RANGE_PATTERN = %r{(?:\[[a-z]:[a-z]\]|\[[0-9]+?:[0-9]+?\])}.freeze + ANSIBLE_PARAMETER_NAMES = { + Ansible::COMPATIBILITY_MODE_V1_8 => { + ansible_host: "ansible_ssh_host", + ansible_password: "ansible_ssh_pass", + ansible_port: "ansible_ssh_port", + ansible_user: "ansible_ssh_user", + ask_become_pass: "ask-sudo-pass", + become: "sudo", + become_user: "sudo-user", + }, + Ansible::COMPATIBILITY_MODE_V2_0 => { + ansible_host: "ansible_host", + ansible_password: "ansible_password", + ansible_port: "ansible_port", + ansible_user: "ansible_user", + ask_become_pass: "ask-become-pass", + become: "become", + become_user: "become-user", + } + } + protected def initialize(machine, config) @@ -25,6 +47,55 @@ module VagrantPlugins @inventory_path = nil end + def set_compatibility_mode + unless config.compatibility_mode + detect_compatibility_mode(gather_ansible_version) + end + + unless Ansible::COMPATIBILITY_MODES.include?(config.compatibility_mode) + raise "Programming Error: compatibility_mode must correctly set at this stage!" + end + + @lexicon = ANSIBLE_PARAMETER_NAMES[config.compatibility_mode] + end + + def detect_compatibility_mode(ansible_version_stdoutput) + if config.compatibility_mode + raise "Programming Error: detect_compatibility_mode() shouldn't have been called." + end + + begin + first_line = ansible_version_stdoutput.lines[0] + full_version = first_line.match(/ansible (\d)(\.\d+){1,}/) + + if full_version + major_version, _ = full_version.captures + + if major_version.to_i <= 1 + config.compatibility_mode = Ansible::COMPATIBILITY_MODE_V1_8 + else + config.compatibility_mode = Ansible::COMPATIBILITY_MODE_V2_0 + end + + @machine.env.ui.warn(I18n.t("vagrant.provisioners.ansible.compatibility_mode_warning", + compatibility_mode: config.compatibility_mode, + ansible_version: full_version) + + "\n") + end + rescue + # Nothing to do here, the fallback to default compatibility_mode is done below + end + + unless config.compatibility_mode + config.compatibility_mode = Ansible::DEFAULT_COMPATIBILITY_MODE + + @machine.env.ui.warn(I18n.t("vagrant.provisioners.ansible.compatibility_mode_not_detected", + compatibility_mode: config.compatibility_mode, + gathered_version: ansible_version_stdoutput) + + "\n") + end + end + def check_files_existence check_path_is_a_file(config.playbook, :playbook) @@ -97,8 +168,8 @@ module VagrantPlugins @command_arguments << "--inventory-file=#{inventory_path}" @command_arguments << "--extra-vars=#{extra_vars_argument}" if config.extra_vars - @command_arguments << "--sudo" if config.sudo - @command_arguments << "--sudo-user=#{config.sudo_user}" if config.sudo_user + @command_arguments << "--#{@lexicon[:become]}" if config.become + @command_arguments << "--#{@lexicon[:become_user]}=#{config.become_user}" if config.become_user @command_arguments << "#{verbosity_argument}" if verbosity_is_enabled? @command_arguments << "--vault-password-file=#{config.vault_password_file}" if config.vault_password_file @command_arguments << "--tags=#{Helpers::as_list_argument(config.tags)}" if config.tags diff --git a/plugins/provisioners/ansible/provisioner/guest.rb b/plugins/provisioners/ansible/provisioner/guest.rb index cdf50d9bc..863095dbb 100644 --- a/plugins/provisioners/ansible/provisioner/guest.rb +++ b/plugins/provisioners/ansible/provisioner/guest.rb @@ -14,8 +14,10 @@ module VagrantPlugins end def provision - check_files_existence check_and_install_ansible + check_files_existence + set_compatibility_mode + execute_ansible_galaxy_on_guest if config.galaxy_role_file execute_ansible_playbook_on_guest end @@ -67,6 +69,19 @@ module VagrantPlugins end end + def gather_ansible_version + raw_output = nil + result = @machine.communicate.execute("ansible --version", error_check: false) do |type, output| + if type == :stdout && output.lines[0] + raw_output = output.lines[0] + end + end + if result != 0 + raw_output = nil + end + raw_output + end + def get_provisioning_working_directory config.provisioning_path end diff --git a/plugins/provisioners/ansible/provisioner/host.rb b/plugins/provisioners/ansible/provisioner/host.rb index 352a13664..7682a2af3 100644 --- a/plugins/provisioners/ansible/provisioner/host.rb +++ b/plugins/provisioners/ansible/provisioner/host.rb @@ -18,8 +18,9 @@ module VagrantPlugins # At this stage, the SSH access is guaranteed to be ready @ssh_info = @machine.ssh_info - check_files_existence warn_for_unsupported_platform + check_files_existence + set_compatibility_mode execute_ansible_galaxy_from_host if config.galaxy_role_file execute_ansible_playbook_from_host @@ -31,7 +32,7 @@ module VagrantPlugins def warn_for_unsupported_platform if Vagrant::Util::Platform.windows? - @machine.env.ui.warn(I18n.t("vagrant.provisioners.ansible.windows_not_supported_for_control_machine")) + @machine.env.ui.warn(I18n.t("vagrant.provisioners.ansible.windows_not_supported_for_control_machine") + "\n") end end @@ -49,15 +50,15 @@ module VagrantPlugins if !config.force_remote_user # Pass the vagrant ssh username as Ansible default remote user, because - # the ansible_ssh_user parameter won't be added to the auto-generated inventory. + # the ansible_ssh_user/ansible_user parameter won't be added to the auto-generated inventory. @command_arguments << "--user=#{@ssh_info[:username]}" elsif config.inventory_path # Using an extra variable is the only way to ensure that the Ansible remote user # is overridden (as the ansible inventory is not under vagrant control) - @command_arguments << "--extra-vars=ansible_ssh_user='#{@ssh_info[:username]}'" + @command_arguments << "--extra-vars=#{@lexicon[:ansible_user]}='#{@ssh_info[:username]}'" end - @command_arguments << "--ask-sudo-pass" if config.ask_sudo_pass + @command_arguments << "--#{@lexicon[:ask_become_pass]}" if config.ask_become_pass @command_arguments << "--ask-vault-pass" if config.ask_vault_pass prepare_common_command_arguments @@ -88,6 +89,30 @@ module VagrantPlugins end end + def gather_ansible_version + raw_output = nil + command = %w(ansible --version) + + command << { + notify: [:stdout, :stderr] + } + + begin + result = Vagrant::Util::Subprocess.execute(*command) do |type, output| + if type == :stdout && output.lines[0] + raw_output = output + end + end + if result.exit_code != 0 + raw_output = nil + end + rescue Vagrant::Errors::CommandUnavailable + raise Ansible::Errors::AnsibleNotFoundOnHost + end + + raw_output + end + def execute_ansible_galaxy_from_host prepare_ansible_config_environment_variable @@ -199,19 +224,19 @@ module VagrantPlugins def get_inventory_ssh_machine(machine, ssh_info) forced_remote_user = "" if config.force_remote_user - forced_remote_user = "ansible_ssh_user='#{ssh_info[:username]}' " + forced_remote_user = "#{@lexicon[:ansible_user]}='#{ssh_info[:username]}' " end - "#{machine.name} ansible_ssh_host=#{ssh_info[:host]} ansible_ssh_port=#{ssh_info[:port]} #{forced_remote_user}ansible_ssh_private_key_file='#{ssh_info[:private_key_path][0]}'\n" + "#{machine.name} #{@lexicon[:ansible_host]}=#{ssh_info[:host]} #{@lexicon[:ansible_port]}=#{ssh_info[:port]} #{forced_remote_user}ansible_ssh_private_key_file='#{ssh_info[:private_key_path][0]}'\n" end def get_inventory_winrm_machine(machine, winrm_net_info) forced_remote_user = "" if config.force_remote_user - forced_remote_user = "ansible_ssh_user='#{machine.config.winrm.username}' " + forced_remote_user = "#{@lexicon[:ansible_user]}='#{machine.config.winrm.username}' " end - "#{machine.name} ansible_connection=winrm ansible_ssh_host=#{winrm_net_info[:host]} ansible_ssh_port=#{winrm_net_info[:port]} #{forced_remote_user}ansible_ssh_pass='#{machine.config.winrm.password}'\n" + "#{machine.name} ansible_connection=winrm #{@lexicon[:ansible_host]}=#{winrm_net_info[:host]} #{@lexicon[:ansible_port]}=#{winrm_net_info[:port]} #{forced_remote_user}#{@lexicon[:ansible_password]}='#{machine.config.winrm.password}'\n" end def ansible_ssh_args diff --git a/templates/locales/en.yml b/templates/locales/en.yml index f012557d9..37a21c712 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -2387,6 +2387,20 @@ en: windows_not_supported_for_control_machine: |- Windows is not officially supported for the Ansible Control Machine. Please check https://docs.ansible.com/intro_installation.html#control-machine-requirements + compatibility_mode_not_detected: |- + Vagrant gathered an unknown Ansible version: + + %{gathered_version} + and falls back on the compatibility mode '%{compatibility_mode}'. + + Alternatively, the compatibility mode can be specified in your Vagrantfile: + https://www.vagrantup.com/docs/provisioning/ansible_common.html#compatibility_mode + compatibility_mode_warning: |- + Vagrant has automatically selected the compatibility mode '%{compatibility_mode}' + according to the Ansible version installed (%{ansible_version}). + + Alternatively, the compatibility mode can be specified in your Vagrantfile: + https://www.vagrantup.com/docs/provisioning/ansible_common.html#compatibility_mode docker: wrong_provisioner: |- diff --git a/test/unit/plugins/provisioners/ansible/config/guest_test.rb b/test/unit/plugins/provisioners/ansible/config/guest_test.rb index 92b7851c5..a9ea5d9da 100644 --- a/test/unit/plugins/provisioners/ansible/config/guest_test.rb +++ b/test/unit/plugins/provisioners/ansible/config/guest_test.rb @@ -16,7 +16,10 @@ describe VagrantPlugins::Ansible::Config::Guest do let(:existing_file) { "this/path/is/a/stub" } it "supports a list of options" do - supported_options = %w( config_file + supported_options = %w( become + become_user + compatibility_mode + config_file extra_vars galaxy_command galaxy_role_file diff --git a/test/unit/plugins/provisioners/ansible/config/host_test.rb b/test/unit/plugins/provisioners/ansible/config/host_test.rb index 1a23fecfe..2e18005ee 100644 --- a/test/unit/plugins/provisioners/ansible/config/host_test.rb +++ b/test/unit/plugins/provisioners/ansible/config/host_test.rb @@ -13,8 +13,12 @@ describe VagrantPlugins::Ansible::Config::Host, :skip_windows => true do let(:existing_file) { File.expand_path(__FILE__) } it "supports a list of options" do - supported_options = %w( ask_sudo_pass + supported_options = %w( ask_become_pass + ask_sudo_pass ask_vault_pass + become + become_user + compatibility_mode config_file extra_vars force_remote_user @@ -47,7 +51,8 @@ describe VagrantPlugins::Ansible::Config::Host, :skip_windows => true do it "assigns default values to unset host-specific options" do subject.finalize! - expect(subject.ask_sudo_pass).to be(false) + expect(subject.ask_become_pass).to be(false) + expect(subject.ask_sudo_pass).to be(false) # deprecated expect(subject.ask_vault_pass).to be(false) expect(subject.force_remote_user).to be(true) expect(subject.host_key_checking).to be(false) @@ -61,7 +66,14 @@ describe VagrantPlugins::Ansible::Config::Host, :skip_windows => true do describe "host_key_checking option" do it_behaves_like "any VagrantConfigProvisioner strict boolean attribute", :host_key_checking, false end + describe "ask_become_pass option" do + it_behaves_like "any VagrantConfigProvisioner strict boolean attribute", :ask_become_pass, false + end describe "ask_sudo_pass option" do + before do + # Filter the deprecation notice + allow($stdout).to receive(:puts) + end it_behaves_like "any VagrantConfigProvisioner strict boolean attribute", :ask_sudo_pass, false end describe "ask_vault_pass option" do diff --git a/test/unit/plugins/provisioners/ansible/config/shared.rb b/test/unit/plugins/provisioners/ansible/config/shared.rb index d9bd8cca3..c30d64f2f 100644 --- a/test/unit/plugins/provisioners/ansible/config/shared.rb +++ b/test/unit/plugins/provisioners/ansible/config/shared.rb @@ -3,6 +3,9 @@ shared_examples_for 'options shared by both Ansible provisioners' do it "assigns default values to unset common options" do subject.finalize! + expect(subject.become).to be(false) + expect(subject.become_user).to be_nil + expect(subject.compatibility_mode).to be_nil expect(subject.config_file).to be_nil expect(subject.extra_vars).to be_nil expect(subject.galaxy_command).to eql("ansible-galaxy install --role-file=%{role_file} --roles-path=%{roles_path} --force") @@ -17,8 +20,8 @@ shared_examples_for 'options shared by both Ansible provisioners' do expect(subject.raw_arguments).to be_nil expect(subject.skip_tags).to be_nil expect(subject.start_at_task).to be_nil - expect(subject.sudo).to be(false) - expect(subject.sudo_user).to be_nil + expect(subject.sudo).to be(false) # deprecated + expect(subject.sudo_user).to be_nil # deprecated expect(subject.tags).to be_nil expect(subject.vault_password_file).to be_nil expect(subject.verbose).to be(false) @@ -41,6 +44,30 @@ shared_examples_for 'an Ansible provisioner' do | path_prefix, ansible_setup | ]) end + describe "compatibility_mode option" do + + %w(1.8 2.0).each do |minimal_version| + it "supports compatibility mode '#{minimal_version}'" do + subject.compatibility_mode = minimal_version + subject.finalize! + + result = subject.validate(machine) + expect(subject.compatibility_mode).to eql(minimal_version) + end + end + + %w(invalid 1.9 2.3).each do |invalid_mode| + it "silently forces the compatibility mode detection for invalid mode '#{invalid_mode}'" do + subject.compatibility_mode = invalid_mode + subject.finalize! + + result = subject.validate(machine) + expect(subject.compatibility_mode).to be_nil + end + end + + end + it "passes if the extra_vars option is a hash" do subject.extra_vars = { var1: 1, var2: "foo" } subject.finalize! @@ -102,7 +129,15 @@ shared_examples_for 'an Ansible provisioner' do | path_prefix, ansible_setup | value: subject.raw_arguments.to_s)) end + describe "become option" do + it_behaves_like "any VagrantConfigProvisioner strict boolean attribute", :become, false + end + describe "sudo option" do + before do + # Filter the deprecation notice + allow($stdout).to receive(:puts) + end it_behaves_like "any VagrantConfigProvisioner strict boolean attribute", :sudo, false end diff --git a/test/unit/plugins/provisioners/ansible/provisioner_test.rb b/test/unit/plugins/provisioners/ansible/provisioner_test.rb index 861a4e1c6..264a0bb96 100644 --- a/test/unit/plugins/provisioners/ansible/provisioner_test.rb +++ b/test/unit/plugins/provisioners/ansible/provisioner_test.rb @@ -60,6 +60,8 @@ VF stubbed_ui = Vagrant::UI::Colored.new allow(stubbed_ui).to receive(:detail).and_return("") + allow(stubbed_ui).to receive(:warn).and_return("") + allow(machine.env).to receive(:ui).and_return(stubbed_ui) config.playbook = 'playbook.yml' @@ -69,6 +71,15 @@ VF # Class methods for code reuse across examples # + def self.it_should_check_ansible_version() + it "execute 'ansible --version' before executing 'ansible-playbook'" do + expect(Vagrant::Util::Subprocess).to receive(:execute). + once.with('ansible', '--version', { :notify => [:stdout, :stderr] }) + expect(Vagrant::Util::Subprocess).to receive(:execute). + once.with('ansible-playbook', any_args) + end + end + def self.it_should_set_arguments_and_environment_variables( expected_args_count = 5, expected_vars_count = 4, @@ -76,9 +87,7 @@ VF expected_transport_mode = "ssh") it "sets implicit arguments in a specific order" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| - - expect(args[0]).to eq("ansible-playbook") + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| expect(args[1]).to eq("--connection=ssh") expect(args[2]).to eq("--timeout=30") @@ -90,7 +99,7 @@ VF end it "sets --limit argument" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| all_limits = args.select { |x| x =~ /^(--limit=|-l)/ } if config.raw_arguments raw_limits = config.raw_arguments.select { |x| x =~ /^(--limit=|-l)/ } @@ -108,7 +117,7 @@ VF end it "exports environment variables" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| cmd_opts = args.last if expected_host_key_checking @@ -116,6 +125,7 @@ VF else expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o UserKnownHostsFile=/dev/null") end + expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o IdentitiesOnly=yes") expect(cmd_opts[:env]['ANSIBLE_FORCE_COLOR']).to eql("true") expect(cmd_opts[:env]).to_not include("ANSIBLE_NOCOLOR") @@ -126,14 +136,14 @@ VF # "roughly" verify that only expected args/vars have been defined by the provisioner it "sets the expected number of arguments and environment variables" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| - expect(args.length-2).to eq(expected_args_count) + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| + expect(args.length - 2).to eq(expected_args_count) expect(args.last[:env].length).to eq(expected_vars_count) }.and_return(default_execute_result) end it "enables '#{expected_transport_mode}' as default transport mode" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| index = args.rindex("--connection=#{expected_transport_mode}") expect(index).to be > 0 expect(find_last_argument_after(index, args, /--connection=\w+/)).to be(false) @@ -144,7 +154,7 @@ VF def self.it_should_set_optional_arguments(arg_map) it "sets optional arguments" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| arg_map.each_pair do |vagrant_option, ansible_argument| index = args.index(ansible_argument) if config.send(vagrant_option) @@ -159,7 +169,7 @@ VF def self.it_should_explicitly_enable_ansible_ssh_control_persist_defaults it "configures ControlPersist (like Ansible defaults) via ANSIBLE_SSH_ARGS" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*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") @@ -167,23 +177,24 @@ VF end end - def self.it_should_create_and_use_generated_inventory(with_ssh_user = true) + def self.it_should_create_and_use_generated_inventory(with_user = true) it "generates an inventory with all active machines" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| expect(config.inventory_path).to be_nil expect(File.exists?(generated_inventory_file)).to be(true) inventory_content = File.read(generated_inventory_file) - if with_ssh_user - expect(inventory_content).to include("#{machine.name} ansible_ssh_host=#{machine.ssh_info[:host]} ansible_ssh_port=#{machine.ssh_info[:port]} ansible_ssh_user='#{machine.ssh_info[:username]}' ansible_ssh_private_key_file='#{machine.ssh_info[:private_key_path][0]}'\n") + _ssh = config.compatibility_mode == VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0 ? "" : "_ssh" + if with_user + expect(inventory_content).to include("#{machine.name} ansible#{_ssh}_host=#{machine.ssh_info[:host]} ansible#{_ssh}_port=#{machine.ssh_info[:port]} ansible#{_ssh}_user='#{machine.ssh_info[:username]}' ansible_ssh_private_key_file='#{machine.ssh_info[:private_key_path][0]}'\n") else - expect(inventory_content).to include("#{machine.name} ansible_ssh_host=#{machine.ssh_info[:host]} ansible_ssh_port=#{machine.ssh_info[:port]} ansible_ssh_private_key_file='#{machine.ssh_info[:private_key_path][0]}'\n") + expect(inventory_content).to include("#{machine.name} ansible#{_ssh}_host=#{machine.ssh_info[:host]} ansible#{_ssh}_port=#{machine.ssh_info[:port]} ansible_ssh_private_key_file='#{machine.ssh_info[:private_key_path][0]}'\n") end expect(inventory_content).to include("# MISSING: '#{iso_env.machine_names[1]}' machine was probably removed without using Vagrant. This machine should be recreated.\n") }.and_return(default_execute_result) end it "sets as ansible inventory the directory containing the auto-generated inventory file" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| inventory_index = args.rindex("--inventory-file=#{generated_inventory_dir}") expect(inventory_index).to be > 0 expect(find_last_argument_after(inventory_index, args, /--inventory-file=\w+/)).to be(false) @@ -260,11 +271,12 @@ VF end describe "with default options" do + it_should_check_ansible_version it_should_set_arguments_and_environment_variables it_should_create_and_use_generated_inventory it "does not add any group section to the generated inventory" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { inventory_content = File.read(generated_inventory_file) expect(inventory_content).to_not match(/^\s*\[^\\+\]\s*$/) }.and_return(default_execute_result) @@ -275,14 +287,139 @@ VF end end + describe "deprecated 'sudo' options are aliases for equivalent 'become' options" do + before do + # Filter the deprecation notices + allow($stdout).to receive(:puts) + + config.sudo = true + config.sudo_user = 'deployer' + config.ask_sudo_pass = true + end + + it_should_set_optional_arguments({"sudo" => "--sudo", + "sudo_user" => "--sudo-user=deployer", + "ask_sudo_pass" => "--ask-sudo-pass", + "become" => "--sudo", + "become_user" => "--sudo-user=deployer", + "ask_become_pass" => "--ask-sudo-pass"}) + end + + context "with no compatibility_mode defined" do + before do + config.compatibility_mode = nil + end + + valid_versions = { + "0.6": VagrantPlugins::Ansible::COMPATIBILITY_MODE_V1_8, + "1.9.4": VagrantPlugins::Ansible::COMPATIBILITY_MODE_V1_8, + "2.2.1.0": VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0, + "4.3.2.1": VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0, + } + valid_versions.each_pair do |ansible_version, mode| + describe "and ansible version #{ansible_version}" do + before do + allow(subject).to receive(:gather_ansible_version).and_return("ansible #{ansible_version}") + end + + it "detects the compatibility mode #{mode}" do + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| + expect(config.compatibility_mode).to eq(mode) + }.and_return(default_execute_result) + end + + it "warns about compatibility mode auto-detection being used" do + expect(machine.env.ui).to receive(:warn).with( + I18n.t("vagrant.provisioners.ansible.compatibility_mode_warning", + compatibility_mode: mode, ansible_version: "ansible #{ansible_version}") + + "\n") + end + end + end + + invalid_versions = [ + "ansible devel", + "ansible 2.x.y.z", + "2.9.2.1", + ] + invalid_versions.each do |unknown_ansible_version| + describe "and `ansible --version` returning '#{unknown_ansible_version}'" do + before do + allow(subject).to receive(:gather_ansible_version).and_return(unknown_ansible_version) + end + + it "applies the default compatibility mode ('#{VagrantPlugins::Ansible::DEFAULT_COMPATIBILITY_MODE}')" do + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| + expect(config.compatibility_mode).to eq(VagrantPlugins::Ansible::DEFAULT_COMPATIBILITY_MODE) + }.and_return(default_execute_result) + end + + it "warns about not being able to detect the best compatibility mode" do + expect(machine.env.ui).to receive(:warn).with( + I18n.t("vagrant.provisioners.ansible.compatibility_mode_not_detected", + compatibility_mode: VagrantPlugins::Ansible::DEFAULT_COMPATIBILITY_MODE, + gathered_version: unknown_ansible_version) + + "\n") + end + end + end + + end + + context "with compatibility_mode '#{VagrantPlugins::Ansible::COMPATIBILITY_MODE_V1_8}'" do + before do + config.compatibility_mode = VagrantPlugins::Ansible::COMPATIBILITY_MODE_V1_8 + end + + it_should_create_and_use_generated_inventory + + it "doesn't warn about compatibility mode auto-detection" do + expect(machine.env.ui).to_not receive(:warn) + end + end + + context "with compatibility_mode '#{VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0}'" do + before do + config.compatibility_mode = VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0 + end + + it_should_create_and_use_generated_inventory + + it "doesn't warn about compatibility mode auto-detection" do + expect(machine.env.ui).to_not receive(:warn) + end + + describe "deprecated 'sudo' options are aliases for equivalent 'become' options" do + before do + # Filter the deprecation notices + allow($stdout).to receive(:puts) + + config.sudo = true + config.sudo_user = 'deployer' + config.ask_sudo_pass = true + end + + it_should_set_optional_arguments({"sudo" => "--become", + "sudo_user" => "--become-user=deployer", + "ask_sudo_pass" => "--ask-become-pass", + "become" => "--become", + "become_user" => "--become-user=deployer", + "ask_become_pass" => "--ask-become-pass"}) + end + end + describe "with playbook_command option" do before do config.playbook_command = "custom-ansible-playbook" + + # set the compatibility mode to ensure that only ansible-playbook is excuted + config.compatibility_mode = VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0 end it "uses custom playbook_command to run playbooks" do expect(Vagrant::Util::Subprocess).to receive(:execute) .with("custom-ansible-playbook", any_args) + .and_return(default_execute_result) end end @@ -293,7 +430,7 @@ VF config.host_vars = { machine1: {"http_port" => 80, "comments" => "'some text with spaces'"} } - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { inventory_content = File.read(generated_inventory_file) expect(inventory_content).to match("^" + Regexp.quote(machine.name) + ".+http_port=80 comments='some text with spaces'$") }.and_return(default_execute_result) @@ -303,7 +440,8 @@ VF config.host_vars = { machine1: ["http_port=80", "maxRequestsPerChild=808"] } - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { + + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { inventory_content = File.read(generated_inventory_file) expect(inventory_content).to match("^" + Regexp.quote(machine.name) + ".+http_port=80 maxRequestsPerChild=808") }.and_return(default_execute_result) @@ -313,7 +451,8 @@ VF config.host_vars = { :machine1 => "http_port=80 maxRequestsPerChild=808" } - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { + + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { inventory_content = File.read(generated_inventory_file) expect(inventory_content).to match("^" + Regexp.quote(machine.name) + ".+http_port=80 maxRequestsPerChild=808") }.and_return(default_execute_result) @@ -323,7 +462,8 @@ VF config.host_vars = { "machine1" => "http_port=80 maxRequestsPerChild=808" } - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { inventory_content = File.read(generated_inventory_file) expect(inventory_content).to match("^" + Regexp.quote(machine.name) + ".+http_port=80 maxRequestsPerChild=808") }.and_return(default_execute_result) @@ -345,7 +485,7 @@ VF "bar:children" => ["group1", "group2", "group3", "group5"], } - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| inventory_content = File.read(generated_inventory_file) # Accept String instead of Array for group member list @@ -383,7 +523,7 @@ VF "group3:vars" => "stringvar1=stringvalue1 stringvar2=stringvalue2", } - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| inventory_content = File.read(generated_inventory_file) # Hash syntax @@ -421,18 +561,18 @@ VF describe "with boolean (flag) options disabled" do before do - config.sudo = false - config.ask_sudo_pass = false + config.become = false + config.ask_become_pass = false config.ask_vault_pass = false - config.sudo_user = 'root' + config.become_user = 'root' end it_should_set_arguments_and_environment_variables 6 - it_should_set_optional_arguments({ "sudo_user" => "--sudo-user=root" }) + it_should_set_optional_arguments({ "become_user" => "--sudo-user=root" }) it "it does not set boolean flag when corresponding option is set to false" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| expect(args.index("--sudo")).to be_nil expect(args.index("--ask-sudo-pass")).to be_nil expect(args.index("--ask-vault-pass")).to be_nil @@ -442,7 +582,7 @@ VF describe "with raw_arguments option" do before do - config.sudo = false + config.become = false config.force_remote_user = false config.skip_tags = %w(foo bar) config.limit = "all" @@ -461,7 +601,7 @@ VF it_should_set_arguments_and_environment_variables 17, 4, false, "paramiko" it "sets all raw arguments" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| config.raw_arguments.each do |raw_arg| expect(args).to include(raw_arg) end @@ -469,7 +609,7 @@ VF end it "sets raw arguments after arguments related to supported options" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| expect(args.index("--user=lion")).to be > args.index("--user=testuser") expect(args.index("--inventory-file=/forget/it/my/friend")).to be > args.index("--inventory-file=#{generated_inventory_dir}") expect(args.index("--limit=bar")).to be > args.index("--limit=all") @@ -478,7 +618,7 @@ VF end it "sets boolean flag (e.g. --sudo) defined in raw_arguments, even if corresponding option is set to false" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| expect(args).to include('--sudo') }.and_return(default_execute_result) end @@ -503,7 +643,7 @@ VF it_should_set_arguments_and_environment_variables 6 it "uses a --user argument to set a default remote user" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| expect(args).not_to include("--extra-vars=ansible_ssh_user='#{machine.ssh_info[:username]}'") expect(args).to include("--user=#{machine.ssh_info[:username]}") }.and_return(default_execute_result) @@ -534,8 +674,7 @@ VF it_should_set_arguments_and_environment_variables it "generates an inventory with winrm connection settings" do - - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| expect(config.inventory_path).to be_nil expect(File.exists?(generated_inventory_file)).to be(true) inventory_content = File.read(generated_inventory_file) @@ -550,7 +689,7 @@ VF end it "doesn't set the ansible remote user in inventory and use '--user' argument with the vagrant ssh username" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| inventory_content = File.read(generated_inventory_file) expect(inventory_content).to include("machine1 ansible_connection=winrm ansible_ssh_host=127.0.0.1 ansible_ssh_port=55986 ansible_ssh_pass='winword'\n") @@ -568,7 +707,7 @@ VF it_should_set_arguments_and_environment_variables 6 it "does not generate the inventory and uses given inventory path instead" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| expect(args).to include("--inventory-file=#{existing_file}") expect(args).not_to include("--inventory-file=#{generated_inventory_file}") expect(File.exists?(generated_inventory_file)).to be(false) @@ -576,7 +715,7 @@ VF end it "uses an --extra-vars argument to force ansible_ssh_user parameter" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| expect(args).not_to include("--user=#{machine.ssh_info[:username]}") expect(args).to include("--extra-vars=ansible_ssh_user='#{machine.ssh_info[:username]}'") }.and_return(default_execute_result) @@ -588,7 +727,7 @@ VF end it "uses a --user argument to set a default remote user" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| expect(args).not_to include("--extra-vars=ansible_ssh_user='#{machine.ssh_info[:username]}'") expect(args).to include("--user=#{machine.ssh_info[:username]}") }.and_return(default_execute_result) @@ -602,7 +741,7 @@ VF end it "sets ANSIBLE_CONFIG environment variable" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| cmd_opts = args.last expect(cmd_opts[:env]).to include("ANSIBLE_CONFIG") expect(cmd_opts[:env]['ANSIBLE_CONFIG']).to eql(existing_file) @@ -618,7 +757,7 @@ VF it_should_set_arguments_and_environment_variables 6 it "should ask the vault password" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| expect(args).to include("--ask-vault-pass") }.and_return(default_execute_result) end @@ -632,7 +771,7 @@ VF it_should_set_arguments_and_environment_variables 6 it "uses the given vault password file" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| expect(args).to include("--vault-password-file=#{existing_file}") }.and_return(default_execute_result) end @@ -647,7 +786,7 @@ VF it_should_explicitly_enable_ansible_ssh_control_persist_defaults it "passes custom SSH options via ANSIBLE_SSH_ARGS with the highest priority" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| cmd_opts = args.last raw_opt_index = cmd_opts[:env]['ANSIBLE_SSH_ARGS'].index("-o ControlMaster=no") default_opt_index = cmd_opts[:env]['ANSIBLE_SSH_ARGS'].index("-o ControlMaster=auto") @@ -661,7 +800,7 @@ VF end it "sets '-o ForwardAgent=yes' via ANSIBLE_SSH_ARGS with higher priority than raw_ssh_args values" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| cmd_opts = args.last forwardAgentYes = cmd_opts[:env]['ANSIBLE_SSH_ARGS'].index("-o ForwardAgent=yes") forwardAgentNo = cmd_opts[:env]['ANSIBLE_SSH_ARGS'].index("-o ForwardAgent=no") @@ -681,7 +820,7 @@ VF it_should_explicitly_enable_ansible_ssh_control_persist_defaults it "passes additional Identity Files via ANSIBLE_SSH_ARGS" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*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") @@ -695,7 +834,7 @@ VF end it "replaces `%` with `%%`" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| cmd_opts = args.last expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o IdentityFile=/foo%%bar/key") expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o IdentityFile=/bar%%%%buz/key") @@ -712,7 +851,7 @@ VF it_should_explicitly_enable_ansible_ssh_control_persist_defaults it "enables SSH-Forwarding via ANSIBLE_SSH_ARGS" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| cmd_opts = args.last expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o ForwardAgent=yes") }.and_return(default_execute_result) @@ -725,7 +864,7 @@ VF end it "sets '-o ProxyCommand' via ANSIBLE_SSH_ARGS" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| cmd_opts = args.last expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o ProxyCommand='ssh -W %h:%p -q user@remote_libvirt_host'") }.and_return(default_execute_result) @@ -795,10 +934,12 @@ VF describe "without colorized output" do before do allow(machine.env).to receive(:ui).and_return(Vagrant::UI::Basic.new) + + allow(machine.env.ui).to receive(:warn).and_return("") # hide the breaking change warning end it "disables ansible-playbook colored output" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| cmd_opts = args.last expect(cmd_opts[:env]).to_not include("ANSIBLE_FORCE_COLOR") expect(cmd_opts[:env]['ANSIBLE_NOCOLOR']).to eql("true") @@ -822,7 +963,11 @@ VF expect {subject.provision}.to raise_error(VagrantPlugins::Ansible::Errors::AnsibleCommandFailed) end - it "execute ansible-galaxy, and then ansible-playbook" do + it "execute three commands: ansible --version, ansible-galaxy, and ansible-playbook" do + expect(Vagrant::Util::Subprocess).to receive(:execute) + .once + .with('ansible', '--version', { :notify => [:stdout, :stderr] }) + .and_return(default_execute_result) expect(Vagrant::Util::Subprocess).to receive(:execute) .once .with('ansible-galaxy', any_args) @@ -856,7 +1001,7 @@ VF end it "sets ANSIBLE_ROLES_PATH with corresponding absolute path" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| cmd_opts = args.last expect(cmd_opts[:env]).to include("ANSIBLE_ROLES_PATH") expect(cmd_opts[:env]['ANSIBLE_ROLES_PATH']).to eql(File.join(machine.env.root_path, "my-roles")) @@ -893,10 +1038,10 @@ VF # command line arguments config.galaxy_roles_path = "/up/to the stars" config.extra_vars = { var1: %Q(string with 'apo$trophe$', \\, " and =), var2: { x: 42 } } - config.sudo = true - config.sudo_user = 'deployer' + config.become = true + config.become_user = 'deployer' config.verbose = "vvv" - config.ask_sudo_pass = true + config.ask_become_pass = true config.ask_vault_pass = true config.vault_password_file = existing_file config.tags = %w(db www) @@ -914,10 +1059,10 @@ VF it_should_set_arguments_and_environment_variables 21, 6, true it_should_explicitly_enable_ansible_ssh_control_persist_defaults it_should_set_optional_arguments({ "extra_vars" => "--extra-vars={\"var1\":\"string with 'apo$trophe$', \\\\, \\\" and =\",\"var2\":{\"x\":42}}", - "sudo" => "--sudo", - "sudo_user" => "--sudo-user=deployer", + "become" => "--sudo", + "become_user" => "--sudo-user=deployer", "verbose" => "-vvv", - "ask_sudo_pass" => "--ask-sudo-pass", + "ask_become_pass" => "--ask-sudo-pass", "ask_vault_pass" => "--ask-vault-pass", "vault_password_file" => "--vault-password-file=#{File.expand_path(__FILE__)}", "tags" => "--tags=db,www", @@ -927,7 +1072,7 @@ VF }) it "also includes given raw arguments" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| expect(args).to include("--why-not") expect(args).to include("--su-user=foot") expect(args).to include("--ask-su-pass") @@ -967,7 +1112,7 @@ VF end it "uses an SSH ProxyCommand to reach the VM" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| cmd_opts = args.last expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o ProxyCommand='ssh boot9docker@127.0.0.1 -p 2299 -i /path/to/docker/host/key -o Compression=yes -o ConnectTimeout=5 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no exec nc %h %p 2>/dev/null'") }.and_return(default_execute_result) @@ -982,11 +1127,14 @@ VF before do allow(Vagrant::Util::Platform).to receive(:windows?).and_return(true) allow(machine.ui).to receive(:warn) + + # Set the compatibility mode to only get the Windows warning + config.compatibility_mode = VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0 end it "warns that Windows is not officially supported for the Ansible control machine" do expect(machine.env.ui).to receive(:warn) - .with(I18n.t("vagrant.provisioners.ansible.windows_not_supported_for_control_machine")) + .with(I18n.t("vagrant.provisioners.ansible.windows_not_supported_for_control_machine") + "\n") end end @@ -996,7 +1144,7 @@ VF end it "does not set IdentitiesOnly=yes in ANSIBLE_SSH_ARGS" do - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| cmd_opts = args.last expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to_not include("-o IdentitiesOnly=yes") }.and_return(default_execute_result) @@ -1006,7 +1154,7 @@ VF it "does not set ANSIBLE_SSH_ARGS environment variable" do config.host_key_checking = true - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| cmd_opts = args.last expect(cmd_opts[:env]).to_not include('ANSIBLE_SSH_ARGS') }.and_return(Vagrant::Util::Subprocess::Result.new(0, "", "")) @@ -1019,7 +1167,7 @@ VF it 'does not set IdentitiesOnly=yes in ANSIBLE_SSH_ARGS' do ssh_info[:keys_only] = false - expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args| + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| cmd_opts = args.last expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to_not include("-o IdentitiesOnly=yes") }.and_return(default_execute_result) diff --git a/website/source/docs/provisioning/ansible.html.md b/website/source/docs/provisioning/ansible.html.md index 3dda49b31..ead77573c 100644 --- a/website/source/docs/provisioning/ansible.html.md +++ b/website/source/docs/provisioning/ansible.html.md @@ -53,10 +53,17 @@ end This section lists the _specific_ options for the Ansible (remote) provisioner. In addition to the options listed below, this provisioner supports the [**common options** for both Ansible provisioners](/docs/provisioning/ansible_common.html). -- `ask_sudo_pass` (boolean) - require Ansible to [prompt for a sudo password](https://docs.ansible.com/intro_getting_started.html#remote-connection-information). +- `ask_become_pass` (boolean) - require Ansible to [prompt for a password](https://docs.ansible.com/intro_getting_started.html#remote-connection-information) when switching to another user with the [become/sudo mechanism](http://docs.ansible.com/ansible/become.html). The default value is `false`. +- `ask_sudo_pass` (boolean) - Backwards compatible alias for the [ask_become_pass](#ask_become_pass) option. + +
+ Deprecation: + The `ask_sudo_pass` option is deprecated and will be removed in a future release. Please use the [**`ask_become_pass`**](#ask_become_pass) option instead. +
+ - `ask_vault_pass` (boolean) - require Ansible to [prompt for a vault password](https://docs.ansible.com/playbooks_vault.html#vault). The default value is `false`. diff --git a/website/source/docs/provisioning/ansible_common.html.md b/website/source/docs/provisioning/ansible_common.html.md index f6fb84f07..5d0536520 100644 --- a/website/source/docs/provisioning/ansible_common.html.md +++ b/website/source/docs/provisioning/ansible_common.html.md @@ -17,6 +17,33 @@ These options get passed to the `ansible-playbook` command that ships with Ansib Some of these options are for advanced usage only and should not be used unless you understand their purpose. +- `become` (boolean) - Cause Ansible to perform all the playbook tasks [as another user](http://docs.ansible.com/ansible/become.html), different from the one used to log into the guest system. + + The default value is `false`. + +- `become_user` (string) - Set the default username to be used by the Ansible `become` [privilege escalation](http://docs.ansible.com/ansible/become.html) mechanism. + + By default this option is not defined, and the Ansible default value (`root`) will be used. + +- `compatibility_mode` (string) - Set the **minimal** version of Ansible to be supported. Vagrant will use some parameters that are only compatible since the given version. + + Possible values: + + - `"1.8"` (Ansible versions prior to 1.8 should mostly work well, but some options might not be supported) + - `"2.0"` (The generated Ansible inventory will be incompatible with Ansible 1.x) + + By default this option is not set, and Vagrant will try to automatically set the optimal compatibilty mode by checking the Ansible version currently available. Note that Vagrant doesn't validate this option, and any unsupported value (e.g. "2.3") will also lead Vagrant to auto-detect the compatibility mode. + +
+ Compatibility Note: + This option was introduced in Vagrant 2.0. Previous Vagrant versions behave like if this option was set to `"1.8"`. +
+ +
+ Attention: + Vagrant doesn't perform any validation between the `compatibility_mode` value and the value of the ansible_local [`version`](/docs/provisioning/ansible_local.html#version) option. +
+ - `config_file` (string) - The path to an [Ansible Configuration file](https://docs.ansible.com/intro_configuration.html). By default, this option is not set, and Ansible will [search for a possible configuration file in some default locations](/docs/provisioning/ansible_intro.html#ANSIBLE_CONFIG). @@ -129,11 +156,19 @@ Some of these options are for advanced usage only and should not be used unless - `start_at_task` (string) - The task name where the [playbook execution will start](https://docs.ansible.com/playbooks_startnstep.html#start-at-task). -- `sudo` (boolean) - Cause Ansible to perform all the playbook tasks [using sudo](https://docs.ansible.com/glossary.html#sudo). +- `sudo` (boolean) - Backwards compatible alias for the [`become`](#become) option. - The default value is `false`. +
+ Deprecation: + The `sudo` option is deprecated and will be removed in a future release. Please use the [**`become`**](#become) option instead. +
-- `sudo_user` (string) - set the default username who should be used by the sudo command. +- `sudo_user` (string) - Backwards compatible alias for the [`become_user`](#become_user) option. + +
+ Deprecation: + The `sudo_user` option is deprecated and will be removed in a future release. Please use the [**`become_user`**](#become_user) option instead. +
- `tags` (string or array of strings) - Only plays, roles and tasks [tagged with these values will be executed](https://docs.ansible.com/playbooks_tags.html) . From e2621a42fc8774a68937451d12c50bce79345fdb Mon Sep 17 00:00:00 2001 From: Gilles Cornu Date: Thu, 24 Aug 2017 18:30:11 +0200 Subject: [PATCH 23/51] docs/ansible: Align the "warn/info" notices style Motivated by the 4d546a58e1243168f58e20ca53cab71b31e44ae9 changes. --- .../source/docs/provisioning/ansible.html.md | 18 ++++++++++-------- .../docs/provisioning/ansible_common.html.md | 5 ++++- .../docs/provisioning/ansible_local.html.md | 16 ++++++++++------ 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/website/source/docs/provisioning/ansible.html.md b/website/source/docs/provisioning/ansible.html.md index ead77573c..af13bb6f6 100644 --- a/website/source/docs/provisioning/ansible.html.md +++ b/website/source/docs/provisioning/ansible.html.md @@ -14,10 +14,8 @@ description: |- The Vagrant Ansible provisioner allows you to provision the guest using [Ansible](http://ansible.com) playbooks by executing **`ansible-playbook` from the Vagrant host**.
- Warning: If you are not familiar with Ansible and Vagrant already, - I recommend starting with the shell - provisioner. However, if you are comfortable with Vagrant already, Vagrant - is a great way to learn Ansible. + Warning: + If you are not familiar with Ansible and Vagrant already, I recommend starting with the shell provisioner. However, if you are comfortable with Vagrant already, Vagrant is a great way to learn Ansible.
## Setup Requirements @@ -74,7 +72,10 @@ This section lists the _specific_ options for the Ansible (remote) provisioner. The default value is `true`. - **Note:** This option was introduced in Vagrant 1.8.0. Previous Vagrant versions behave like if this option was set to `false`. +
+ Compatibility Note: + This option was introduced in Vagrant 1.8.0. Previous Vagrant versions behave like if this option was set to `false`. +
- `host_key_checking` (boolean) - require Ansible to [enable SSH host key checking](https://docs.ansible.com/intro_getting_started.html#host-key-checking). @@ -122,9 +123,10 @@ N = 3 end ``` -**Caveats:** - -If you apply this parallel provisioning pattern with a static Ansible inventory, you will have to organize the things so that [all the relevant private keys are provided to the `ansible-playbook` command](https://github.com/mitchellh/vagrant/pull/5765#issuecomment-120247738). The same kind of considerations applies if you are using multiple private keys for a same machine (see [`config.ssh.private_key_path` SSH setting](/docs/vagrantfile/ssh_settings.html)). +
+ Tip: + If you apply this parallel provisioning pattern with a static Ansible inventory, you will have to organize the things so that [all the relevant private keys are provided to the `ansible-playbook` command](https://github.com/mitchellh/vagrant/pull/5765#issuecomment-120247738). The same kind of considerations applies if you are using multiple private keys for a same machine (see [`config.ssh.private_key_path` SSH setting](/docs/vagrantfile/ssh_settings.html)). +
### Force Paramiko Connection Mode diff --git a/website/source/docs/provisioning/ansible_common.html.md b/website/source/docs/provisioning/ansible_common.html.md index 5d0536520..a8adf2399 100644 --- a/website/source/docs/provisioning/ansible_common.html.md +++ b/website/source/docs/provisioning/ansible_common.html.md @@ -150,7 +150,10 @@ Some of these options are for advanced usage only and should not be used unless - `['--check', '-M', '/my/modules']` - `["--connection=paramiko", "--forks=10"]` - **Caveat:** The `ansible` provisioner does not support whitespace characters in `raw_arguments` elements. Therefore **don't write** something like `["-c paramiko"]`, which will result with an invalid `" parmiko"` parameter value. +
+ Attention: + The `ansible` provisioner does not support whitespace characters in `raw_arguments` elements. Therefore **don't write** something like `["-c paramiko"]`, which will result with an invalid `" parmiko"` parameter value. +
- `skip_tags` (string or array of strings) - Only plays, roles and tasks that [*do not match* these values will be executed](https://docs.ansible.com/playbooks_tags.html). diff --git a/website/source/docs/provisioning/ansible_local.html.md b/website/source/docs/provisioning/ansible_local.html.md index c506fbd7e..20b5b42b2 100644 --- a/website/source/docs/provisioning/ansible_local.html.md +++ b/website/source/docs/provisioning/ansible_local.html.md @@ -14,10 +14,8 @@ description: |- The Vagrant Ansible Local provisioner allows you to provision the guest using [Ansible](http://ansible.com) playbooks by executing **`ansible-playbook` directly on the guest machine**.
- Warning: If you are not familiar with Ansible and Vagrant already, - I recommend starting with the shell - provisioner. However, if you are comfortable with Vagrant already, Vagrant - is a great way to learn Ansible. + Warning: + If you are not familiar with Ansible and Vagrant already, I recommend starting with the shell provisioner. However, if you are comfortable with Vagrant already, Vagrant is a great way to learn Ansible.
## Setup Requirements @@ -67,7 +65,10 @@ This section lists the _specific_ options for the Ansible Local provisioner. In - The `version` option is set to `"latest"`. - The current Ansible version does not correspond to the `version` option. - **Attention:** There is no guarantee that this automated installation will replace a custom Ansible setup, that might be already present on the Vagrant box. +
+ Attention: + There is no guarantee that this automated installation will replace a custom Ansible setup, that might be already present on the Vagrant box. +
- `install_mode` (`:default`, `:pip`, or `:pip_args_only`) - Select the way to automatically install Ansible on the guest system. @@ -148,7 +149,10 @@ This section lists the _specific_ options for the Ansible Local provisioner. In When this option is set to `"latest"`, no version check is applied. - **Warning:** It is currently possible to use this option to specify which version of Ansible must be automatically installed, but only in combination with the `install_mode` set to `:pip`. +
+ Tip: + It is currently possible to use this option to specify which version of Ansible must be automatically installed, but only in combination with the `install_mode` set to `:pip`. +
## Tips and Tricks From a327e348612102f23405024a3b168310fd70b987 Mon Sep 17 00:00:00 2001 From: Gilles Cornu Date: Sat, 26 Aug 2017 08:47:35 +0200 Subject: [PATCH 24/51] provisioners/ansible: Validate compatibility_mode - Use `'auto'` instead of `nil` for the auto-detection mode - Add strict validation and related error message --- plugins/provisioners/ansible/config/base.rb | 8 ++++- plugins/provisioners/ansible/constants.rb | 9 +++-- .../provisioners/ansible/provisioner/base.rb | 10 +++--- templates/locales/en.yml | 2 ++ .../provisioners/ansible/config/shared.rb | 34 ++++++++++++++----- .../provisioners/ansible/provisioner_test.rb | 10 +++--- .../docs/provisioning/ansible_common.html.md | 7 ++-- 7 files changed, 56 insertions(+), 24 deletions(-) diff --git a/plugins/provisioners/ansible/config/base.rb b/plugins/provisioners/ansible/config/base.rb index 1b58a60d4..22693122c 100644 --- a/plugins/provisioners/ansible/config/base.rb +++ b/plugins/provisioners/ansible/config/base.rb @@ -46,7 +46,7 @@ module VagrantPlugins def initialize @become = UNSET_VALUE @become_user = UNSET_VALUE - @compatibility_mode = UNSET_VALUE + @compatibility_mode = Ansible::COMPATIBILITY_MODE_AUTO @config_file = UNSET_VALUE @extra_vars = UNSET_VALUE @galaxy_role_file = UNSET_VALUE @@ -95,6 +95,12 @@ module VagrantPlugins def validate(machine) @errors = _detected_errors + # Validate that a compatibility mode was provided + if !compatibility_mode + @errors << I18n.t("vagrant.provisioners.ansible.errors.no_compatibility_mode", + valid_modes: Ansible::COMPATIBILITY_MODES.map { |s| "'#{s}'" }.join(', ')) + end + # Validate that a playbook path was provided if !playbook @errors << I18n.t("vagrant.provisioners.ansible.errors.no_playbook") diff --git a/plugins/provisioners/ansible/constants.rb b/plugins/provisioners/ansible/constants.rb index 5e1d9e3f8..17f72beb6 100644 --- a/plugins/provisioners/ansible/constants.rb +++ b/plugins/provisioners/ansible/constants.rb @@ -1,9 +1,14 @@ module VagrantPlugins module Ansible + COMPATIBILITY_MODE_AUTO = "auto".freeze COMPATIBILITY_MODE_V1_8 = "1.8".freeze COMPATIBILITY_MODE_V2_0 = "2.0".freeze - DEFAULT_COMPATIBILITY_MODE = COMPATIBILITY_MODE_V1_8 - COMPATIBILITY_MODES = [COMPATIBILITY_MODE_V1_8, COMPATIBILITY_MODE_V2_0].freeze + SAFE_COMPATIBILITY_MODE = COMPATIBILITY_MODE_V1_8 + COMPATIBILITY_MODES = [ + COMPATIBILITY_MODE_AUTO, + COMPATIBILITY_MODE_V1_8, + COMPATIBILITY_MODE_V2_0, + ].freeze end end \ No newline at end of file diff --git a/plugins/provisioners/ansible/provisioner/base.rb b/plugins/provisioners/ansible/provisioner/base.rb index 8d6c8f413..a810409c6 100644 --- a/plugins/provisioners/ansible/provisioner/base.rb +++ b/plugins/provisioners/ansible/provisioner/base.rb @@ -48,11 +48,11 @@ module VagrantPlugins end def set_compatibility_mode - unless config.compatibility_mode + if config.compatibility_mode == Ansible::COMPATIBILITY_MODE_AUTO detect_compatibility_mode(gather_ansible_version) end - unless Ansible::COMPATIBILITY_MODES.include?(config.compatibility_mode) + unless Ansible::COMPATIBILITY_MODES.slice(1..-1).include?(config.compatibility_mode) raise "Programming Error: compatibility_mode must correctly set at this stage!" end @@ -60,7 +60,7 @@ module VagrantPlugins end def detect_compatibility_mode(ansible_version_stdoutput) - if config.compatibility_mode + if config.compatibility_mode != Ansible::COMPATIBILITY_MODE_AUTO raise "Programming Error: detect_compatibility_mode() shouldn't have been called." end @@ -86,8 +86,8 @@ module VagrantPlugins # Nothing to do here, the fallback to default compatibility_mode is done below end - unless config.compatibility_mode - config.compatibility_mode = Ansible::DEFAULT_COMPATIBILITY_MODE + if config.compatibility_mode == Ansible::COMPATIBILITY_MODE_AUTO + config.compatibility_mode = Ansible::SAFE_COMPATIBILITY_MODE @machine.env.ui.warn(I18n.t("vagrant.provisioners.ansible.compatibility_mode_not_detected", compatibility_mode: config.compatibility_mode, diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 37a21c712..66e297a5d 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -2374,6 +2374,8 @@ en: `%{config_option}` does not exist on the %{system}: %{path} extra_vars_invalid: |- `extra_vars` must be a hash or a path to an existing file. Received: %{value} (as %{type}) + no_compatibility_mode: |- + `compatibility_mode` must be correctly set (possible values: %{valid_modes}). no_playbook: |- `playbook` file path must be set. raw_arguments_invalid: |- diff --git a/test/unit/plugins/provisioners/ansible/config/shared.rb b/test/unit/plugins/provisioners/ansible/config/shared.rb index c30d64f2f..659573af3 100644 --- a/test/unit/plugins/provisioners/ansible/config/shared.rb +++ b/test/unit/plugins/provisioners/ansible/config/shared.rb @@ -5,7 +5,7 @@ shared_examples_for 'options shared by both Ansible provisioners' do expect(subject.become).to be(false) expect(subject.become_user).to be_nil - expect(subject.compatibility_mode).to be_nil + expect(subject.compatibility_mode).to eql(VagrantPlugins::Ansible::COMPATIBILITY_MODE_AUTO) expect(subject.config_file).to be_nil expect(subject.extra_vars).to be_nil expect(subject.galaxy_command).to eql("ansible-galaxy install --role-file=%{role_file} --roles-path=%{roles_path} --force") @@ -46,23 +46,37 @@ shared_examples_for 'an Ansible provisioner' do | path_prefix, ansible_setup | describe "compatibility_mode option" do - %w(1.8 2.0).each do |minimal_version| - it "supports compatibility mode '#{minimal_version}'" do - subject.compatibility_mode = minimal_version + VagrantPlugins::Ansible::COMPATIBILITY_MODES.each do |valid_mode| + it "supports compatibility mode '#{valid_mode}'" do + subject.compatibility_mode = valid_mode subject.finalize! result = subject.validate(machine) - expect(subject.compatibility_mode).to eql(minimal_version) + expect(subject.compatibility_mode).to eql(valid_mode) end end + it "returns an error if the compatibility mode is not set" do + subject.compatibility_mode = nil + subject.finalize! + + result = subject.validate(machine) + expect(result[provisioner_label]).to eql([ + I18n.t("vagrant.provisioners.ansible.errors.no_compatibility_mode", + valid_modes: "'auto', '1.8', '2.0'") + ]) + end + %w(invalid 1.9 2.3).each do |invalid_mode| - it "silently forces the compatibility mode detection for invalid mode '#{invalid_mode}'" do + it "returns an error if the compatibility mode is invalid (e.g. '#{invalid_mode}')" do subject.compatibility_mode = invalid_mode subject.finalize! result = subject.validate(machine) - expect(subject.compatibility_mode).to be_nil + expect(result[provisioner_label]).to eql([ + I18n.t("vagrant.provisioners.ansible.errors.no_compatibility_mode", + valid_modes: "'auto', '1.8', '2.0'") + ]) end end @@ -109,6 +123,7 @@ shared_examples_for 'an Ansible provisioner' do | path_prefix, ansible_setup | end it "it collects and returns all detected errors" do + subject.compatibility_mode = nil subject.playbook = nil subject.extra_vars = ["var1", 3, "var2", 5] subject.raw_arguments = { arg1: 1, arg2: "foo" } @@ -116,7 +131,10 @@ shared_examples_for 'an Ansible provisioner' do | path_prefix, ansible_setup | result = subject.validate(machine) - expect(result[provisioner_label].size).to eql(3) + expect(result[provisioner_label].size).to eql(4) + expect(result[provisioner_label]).to include( + I18n.t("vagrant.provisioners.ansible.errors.no_compatibility_mode", + valid_modes: "'auto', '1.8', '2.0'")) expect(result[provisioner_label]).to include( I18n.t("vagrant.provisioners.ansible.errors.no_playbook")) expect(result[provisioner_label]).to include( diff --git a/test/unit/plugins/provisioners/ansible/provisioner_test.rb b/test/unit/plugins/provisioners/ansible/provisioner_test.rb index 264a0bb96..a63723a03 100644 --- a/test/unit/plugins/provisioners/ansible/provisioner_test.rb +++ b/test/unit/plugins/provisioners/ansible/provisioner_test.rb @@ -305,9 +305,9 @@ VF "ask_become_pass" => "--ask-sudo-pass"}) end - context "with no compatibility_mode defined" do + context "with compatibility_mode 'auto'" do before do - config.compatibility_mode = nil + config.compatibility_mode = VagrantPlugins::Ansible::COMPATIBILITY_MODE_AUTO end valid_versions = { @@ -348,16 +348,16 @@ VF allow(subject).to receive(:gather_ansible_version).and_return(unknown_ansible_version) end - it "applies the default compatibility mode ('#{VagrantPlugins::Ansible::DEFAULT_COMPATIBILITY_MODE}')" do + it "applies the safest compatibility mode ('#{VagrantPlugins::Ansible::SAFE_COMPATIBILITY_MODE}')" do expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args| - expect(config.compatibility_mode).to eq(VagrantPlugins::Ansible::DEFAULT_COMPATIBILITY_MODE) + expect(config.compatibility_mode).to eq(VagrantPlugins::Ansible::SAFE_COMPATIBILITY_MODE) }.and_return(default_execute_result) end it "warns about not being able to detect the best compatibility mode" do expect(machine.env.ui).to receive(:warn).with( I18n.t("vagrant.provisioners.ansible.compatibility_mode_not_detected", - compatibility_mode: VagrantPlugins::Ansible::DEFAULT_COMPATIBILITY_MODE, + compatibility_mode: VagrantPlugins::Ansible::SAFE_COMPATIBILITY_MODE, gathered_version: unknown_ansible_version) + "\n") end diff --git a/website/source/docs/provisioning/ansible_common.html.md b/website/source/docs/provisioning/ansible_common.html.md index a8adf2399..fbfa0e96c 100644 --- a/website/source/docs/provisioning/ansible_common.html.md +++ b/website/source/docs/provisioning/ansible_common.html.md @@ -29,10 +29,11 @@ Some of these options are for advanced usage only and should not be used unless Possible values: - - `"1.8"` (Ansible versions prior to 1.8 should mostly work well, but some options might not be supported) - - `"2.0"` (The generated Ansible inventory will be incompatible with Ansible 1.x) + - `"auto"` _(Vagrant will automatically select the optimal compatibilty mode by checking the Ansible version currently available)_ + - `"1.8"` _(Ansible versions prior to 1.8 should mostly work well, but some options might not be supported)_ + - `"2.0"` _(The generated Ansible inventory will be incompatible with Ansible 1.x)_ - By default this option is not set, and Vagrant will try to automatically set the optimal compatibilty mode by checking the Ansible version currently available. Note that Vagrant doesn't validate this option, and any unsupported value (e.g. "2.3") will also lead Vagrant to auto-detect the compatibility mode. + By default this option is set to `"auto"`. If Vagrant is not able to detect any supported Ansible version, it will falls back on the compatibility mode `"1.8"` with a warning.
Compatibility Note: From 15e74e264d0f5c7bdd60eb504b1e32eecc86d74c Mon Sep 17 00:00:00 2001 From: Gilles Cornu Date: Tue, 29 Aug 2017 05:40:25 +0200 Subject: [PATCH 25/51] provisioners/ansible: Fix test stubs Make the stubbed valid and invalid Ansible versions closer to the real "ansible --version" stdout output. --- test/unit/plugins/provisioners/ansible/provisioner_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/plugins/provisioners/ansible/provisioner_test.rb b/test/unit/plugins/provisioners/ansible/provisioner_test.rb index a63723a03..7e8c3aaaa 100644 --- a/test/unit/plugins/provisioners/ansible/provisioner_test.rb +++ b/test/unit/plugins/provisioners/ansible/provisioner_test.rb @@ -319,7 +319,7 @@ VF valid_versions.each_pair do |ansible_version, mode| describe "and ansible version #{ansible_version}" do before do - allow(subject).to receive(:gather_ansible_version).and_return("ansible #{ansible_version}") + allow(subject).to receive(:gather_ansible_version).and_return("ansible #{ansible_version}\n...\n") end it "detects the compatibility mode #{mode}" do @@ -339,7 +339,7 @@ VF invalid_versions = [ "ansible devel", - "ansible 2.x.y.z", + "ansible 2.x.y.z\n...\n", "2.9.2.1", ] invalid_versions.each do |unknown_ansible_version| From 8c0df3d046a3937b8e8ab9b55ded4c05b5c36a9b Mon Sep 17 00:00:00 2001 From: Gilles Cornu Date: Tue, 29 Aug 2017 05:32:38 +0200 Subject: [PATCH 26/51] provisioners/ansible: Move `version` to common options Before this change, only the ansible_local provisioner supported this option (for ansible version requirement, and pip installation). Now, the ansible host-based provisioner can also require a exact ansible version. Resolve #8914 Note: this has been added as part of #6570 resolution, since the introduction of the `compatibility_mode` auto-detection made both provisioners made capable to detect ansible version. Pending: optimize the code to avoid duplicated executions of "ansible --version" command. --- plugins/provisioners/ansible/config/base.rb | 3 ++ plugins/provisioners/ansible/config/guest.rb | 3 -- plugins/provisioners/ansible/errors.rb | 4 +- .../provisioners/ansible/provisioner/guest.rb | 2 +- .../provisioners/ansible/provisioner/host.rb | 14 +++++++ templates/locales/en.yml | 8 ++-- .../provisioners/ansible/config/guest_test.rb | 7 ++-- .../provisioners/ansible/config/host_test.rb | 7 +++- .../provisioners/ansible/config/shared.rb | 1 + .../provisioners/ansible/provisioner_test.rb | 40 +++++++++++++++++++ .../docs/provisioning/ansible_common.html.md | 15 ++++++- .../docs/provisioning/ansible_local.html.md | 19 ++------- 12 files changed, 91 insertions(+), 32 deletions(-) diff --git a/plugins/provisioners/ansible/config/base.rb b/plugins/provisioners/ansible/config/base.rb index 22693122c..201554bdf 100644 --- a/plugins/provisioners/ansible/config/base.rb +++ b/plugins/provisioners/ansible/config/base.rb @@ -28,6 +28,7 @@ module VagrantPlugins attr_accessor :tags attr_accessor :vault_password_file attr_accessor :verbose + attr_accessor :version # # Deprecated options @@ -64,6 +65,7 @@ module VagrantPlugins @tags = UNSET_VALUE @vault_password_file = UNSET_VALUE @verbose = UNSET_VALUE + @version = UNSET_VALUE end def finalize! @@ -87,6 +89,7 @@ module VagrantPlugins @tags = nil if @tags == UNSET_VALUE @vault_password_file = nil if @vault_password_file == UNSET_VALUE @verbose = false if @verbose == UNSET_VALUE + @version = "" if @version == UNSET_VALUE end # Just like the normal configuration "validate" method except that diff --git a/plugins/provisioners/ansible/config/guest.rb b/plugins/provisioners/ansible/config/guest.rb index 16992cb5a..a869779ae 100644 --- a/plugins/provisioners/ansible/config/guest.rb +++ b/plugins/provisioners/ansible/config/guest.rb @@ -11,7 +11,6 @@ module VagrantPlugins attr_accessor :install attr_accessor :install_mode attr_accessor :pip_args - attr_accessor :version def initialize super @@ -21,7 +20,6 @@ module VagrantPlugins @pip_args = UNSET_VALUE @provisioning_path = UNSET_VALUE @tmp_path = UNSET_VALUE - @version = UNSET_VALUE end def finalize! @@ -32,7 +30,6 @@ module VagrantPlugins @pip_args = "" if @pip_args == UNSET_VALUE @provisioning_path = "/vagrant" if provisioning_path == UNSET_VALUE @tmp_path = "/tmp/vagrant-ansible" if tmp_path == UNSET_VALUE - @version = "" if @version == UNSET_VALUE end def validate(machine) diff --git a/plugins/provisioners/ansible/errors.rb b/plugins/provisioners/ansible/errors.rb index be9e55fb9..efc177ef8 100644 --- a/plugins/provisioners/ansible/errors.rb +++ b/plugins/provisioners/ansible/errors.rb @@ -23,8 +23,8 @@ module VagrantPlugins error_key(:cannot_support_pip_install) end - class AnsibleVersionNotFoundOnGuest < AnsibleError - error_key(:ansible_version_not_found_on_guest) + class AnsibleVersionMismatch < AnsibleError + error_key(:ansible_version_mismatch) end end end diff --git a/plugins/provisioners/ansible/provisioner/guest.rb b/plugins/provisioners/ansible/provisioner/guest.rb index 863095dbb..1b8fc38a4 100644 --- a/plugins/provisioners/ansible/provisioner/guest.rb +++ b/plugins/provisioners/ansible/provisioner/guest.rb @@ -65,7 +65,7 @@ module VagrantPlugins if (!config.version.empty? && config.version.to_s.to_sym != :latest && !@machine.guest.capability(:ansible_installed, config.version)) - raise Ansible::Errors::AnsibleVersionNotFoundOnGuest, required_version: config.version.to_s + raise Ansible::Errors::AnsibleVersionMismatch, system: "guest", required_version: config.version.to_s end end diff --git a/plugins/provisioners/ansible/provisioner/host.rb b/plugins/provisioners/ansible/provisioner/host.rb index 7682a2af3..f25178729 100644 --- a/plugins/provisioners/ansible/provisioner/host.rb +++ b/plugins/provisioners/ansible/provisioner/host.rb @@ -19,6 +19,7 @@ module VagrantPlugins @ssh_info = @machine.ssh_info warn_for_unsupported_platform + check_required_ansible_version unless config.version.empty? check_files_existence set_compatibility_mode @@ -36,6 +37,19 @@ module VagrantPlugins end end + def check_required_ansible_version + if config.version.to_s.to_sym == :latest + @logger.debug("The :latest version requirement is not supported (yet) by the host-based provisioner") + return + end + + @logger.info("Checking for Ansible version on Vagrant host...") + found_version = gather_ansible_version + if (!found_version || "ansible #{config.version}\n" != found_version.lines[0]) + raise Ansible::Errors::AnsibleVersionMismatch, system: "host", required_version: config.version.to_s + end + end + def prepare_command_arguments # Connect with native OpenSSH client # Other modes (e.g. paramiko) are not officially supported, diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 66e297a5d..5cb65c0ad 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -2364,11 +2364,11 @@ en: to contribute back support. Thank you! https://github.com/mitchellh/vagrant - ansible_version_not_found_on_guest: |- - The requested Ansible version (%{required_version}) was not found on the guest. - Please check the ansible installation on your guest system, + ansible_version_mismatch: |- + The requested Ansible version (%{required_version}) was not found on the %{system}. + Please check the Ansible installation on your Vagrant %{system} system, or adapt the `version` option of this provisioner in your Vagrantfile. - See https://docs.vagrantup.com/v2/provisioning/ansible_local.html + See https://docs.vagrantup.com/v2/provisioning/ansible_common.html#version for more information. config_file_not_found: |- `%{config_option}` does not exist on the %{system}: %{path} diff --git a/test/unit/plugins/provisioners/ansible/config/guest_test.rb b/test/unit/plugins/provisioners/ansible/config/guest_test.rb index a9ea5d9da..19010ba94 100644 --- a/test/unit/plugins/provisioners/ansible/config/guest_test.rb +++ b/test/unit/plugins/provisioners/ansible/config/guest_test.rb @@ -16,7 +16,8 @@ describe VagrantPlugins::Ansible::Config::Guest do let(:existing_file) { "this/path/is/a/stub" } it "supports a list of options" do - supported_options = %w( become + supported_options = %w( + become become_user compatibility_mode config_file @@ -43,7 +44,8 @@ describe VagrantPlugins::Ansible::Config::Guest do tmp_path vault_password_file verbose - version ) + version + ) expect(get_provisioner_option_names(described_class)).to eql(supported_options) end @@ -58,7 +60,6 @@ describe VagrantPlugins::Ansible::Config::Guest do expect(subject.install_mode).to eql(:default) expect(subject.provisioning_path).to eql("/vagrant") expect(subject.tmp_path).to eql("/tmp/vagrant-ansible") - expect(subject.version).to be_empty end end diff --git a/test/unit/plugins/provisioners/ansible/config/host_test.rb b/test/unit/plugins/provisioners/ansible/config/host_test.rb index 2e18005ee..b868d2648 100644 --- a/test/unit/plugins/provisioners/ansible/config/host_test.rb +++ b/test/unit/plugins/provisioners/ansible/config/host_test.rb @@ -13,7 +13,8 @@ describe VagrantPlugins::Ansible::Config::Host, :skip_windows => true do let(:existing_file) { File.expand_path(__FILE__) } it "supports a list of options" do - supported_options = %w( ask_become_pass + supported_options = %w( + ask_become_pass ask_sudo_pass ask_vault_pass become @@ -40,7 +41,9 @@ describe VagrantPlugins::Ansible::Config::Host, :skip_windows => true do sudo_user tags vault_password_file - verbose ) + verbose + version + ) expect(get_provisioner_option_names(described_class)).to eql(supported_options) end diff --git a/test/unit/plugins/provisioners/ansible/config/shared.rb b/test/unit/plugins/provisioners/ansible/config/shared.rb index 659573af3..16902a138 100644 --- a/test/unit/plugins/provisioners/ansible/config/shared.rb +++ b/test/unit/plugins/provisioners/ansible/config/shared.rb @@ -25,6 +25,7 @@ shared_examples_for 'options shared by both Ansible provisioners' do expect(subject.tags).to be_nil expect(subject.vault_password_file).to be_nil expect(subject.verbose).to be(false) + expect(subject.version).to be_empty end end diff --git a/test/unit/plugins/provisioners/ansible/provisioner_test.rb b/test/unit/plugins/provisioners/ansible/provisioner_test.rb index 7e8c3aaaa..eafd8bf79 100644 --- a/test/unit/plugins/provisioners/ansible/provisioner_test.rb +++ b/test/unit/plugins/provisioners/ansible/provisioner_test.rb @@ -947,6 +947,46 @@ VF end end + + context "with version option set" do + before do + config.version = "2.3.4.5" + end + + describe "and the installed ansible version is correct" do + before do + allow(subject).to receive(:gather_ansible_version).and_return("ansible #{config.version}\n...\n") + end + + it "executes ansible-playbook command" do + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args).and_return(default_execute_result) + end + end + + describe "and there is an ansible version mismatch" do + before do + allow(subject).to receive(:gather_ansible_version).and_return("ansible 1.9.6\n...\n") + end + + it "raises an error about the ansible version mismatch", skip_before: true, skip_after: true do + config.finalize! + expect {subject.provision}.to raise_error(VagrantPlugins::Ansible::Errors::AnsibleVersionMismatch) + end + end + + describe "and the installed ansible version cannot be detected" do + before do + allow(subject).to receive(:gather_ansible_version).and_return(nil) + end + + it "raises an error about the ansible version mismatch", skip_before: true, skip_after: true do + config.finalize! + expect {subject.provision}.to raise_error(VagrantPlugins::Ansible::Errors::AnsibleVersionMismatch) + end + end + end + + # TODO: add more tests, now that we know how to deal with multiple Subprocess stub executions describe "with galaxy support" do before do diff --git a/website/source/docs/provisioning/ansible_common.html.md b/website/source/docs/provisioning/ansible_common.html.md index fbfa0e96c..17c047992 100644 --- a/website/source/docs/provisioning/ansible_common.html.md +++ b/website/source/docs/provisioning/ansible_common.html.md @@ -42,7 +42,7 @@ Some of these options are for advanced usage only and should not be used unless
Attention: - Vagrant doesn't perform any validation between the `compatibility_mode` value and the value of the ansible_local [`version`](/docs/provisioning/ansible_local.html#version) option. + Vagrant doesn't perform any validation between the `compatibility_mode` value and the value of the [`version`](#version) option.
- `config_file` (string) - The path to an [Ansible Configuration file](https://docs.ansible.com/intro_configuration.html). @@ -185,3 +185,16 @@ Some of these options are for advanced usage only and should not be used unless Examples: `true` (equivalent to `v`), `-vvv` (equivalent to `vvv`), `vvvv`. Note that when the `verbose` option is enabled, the `ansible-playbook` command used by Vagrant will be displayed. + +- `version` (string) - The expected Ansible version. + + This option is disabled by default. + + When an Ansible version is defined (e.g. `"2.1.6.0"`), the Ansible provisioner will be executed only if Ansible is installed at the requested version. + + When this option is set to `"latest"`, no version check is applied. + +
+ Tip: + With the `ansible_local` provisioner, it is currently possible to use this option to specify which version of Ansible must be automatically installed, but only in combination with the [**`install_mode`**](ansible_local.html#install_mode) set to `:pip`. +
diff --git a/website/source/docs/provisioning/ansible_local.html.md b/website/source/docs/provisioning/ansible_local.html.md index 20b5b42b2..224b2179c 100644 --- a/website/source/docs/provisioning/ansible_local.html.md +++ b/website/source/docs/provisioning/ansible_local.html.md @@ -62,8 +62,8 @@ This section lists the _specific_ options for the Ansible Local provisioner. In Vagrant will try to install (or upgrade) Ansible when one of these conditions are met: - Ansible is not installed (or cannot be found). - - The `version` option is set to `"latest"`. - - The current Ansible version does not correspond to the `version` option. + - The [`version`](/docs/provisioning/ansible_common.html#version) option is set to `"latest"`. + - The current Ansible version does not correspond to the [`version`](/docs/provisioning/ansible_common.html#version) option.
Attention: @@ -76,7 +76,7 @@ This section lists the _specific_ options for the Ansible Local provisioner. In - On Ubuntu-like systems, the latest Ansible release is installed from the `ppa:ansible/ansible` repository. - On RedHat-like systems, the latest Ansible release is installed from the [EPEL](http://fedoraproject.org/wiki/EPEL) repository. - - `:pip`: Ansible is installed from [PyPI](https://pypi.python.org/pypi) with [pip](https://pip.pypa.io) package installer. With this mode, Vagrant will systematically try to [install the latest pip version](https://pip.pypa.io/en/stable/installing/#installing-with-get-pip-py). With the `:pip` mode you can optionally install a specific Ansible release by setting the [`version`](#version) option. + - `:pip`: Ansible is installed from [PyPI](https://pypi.python.org/pypi) with [pip](https://pip.pypa.io) package installer. With this mode, Vagrant will systematically try to [install the latest pip version](https://pip.pypa.io/en/stable/installing/#installing-with-get-pip-py). With the `:pip` mode you can optionally install a specific Ansible release by setting the [`version`](/docs/provisioning/ansible_common.html#version) option. Example: @@ -141,19 +141,6 @@ This section lists the _specific_ options for the Ansible Local provisioner. In The default value is `/tmp/vagrant-ansible` -- `version` (string) - The expected Ansible version. - - This option is disabled by default. - - When an Ansible version is defined (e.g. `"1.8.2"`), the Ansible local provisioner will be executed only if Ansible is installed at the requested version. - - When this option is set to `"latest"`, no version check is applied. - -
- Tip: - It is currently possible to use this option to specify which version of Ansible must be automatically installed, but only in combination with the `install_mode` set to `:pip`. -
- ## Tips and Tricks ### Ansible Parallel Execution from a Guest From 71dd393134e08273f0d727fa9db8d7a18058cbe8 Mon Sep 17 00:00:00 2001 From: Gilles Cornu Date: Wed, 30 Aug 2017 07:47:54 +0200 Subject: [PATCH 27/51] unit/tests: Remove a TODO comment This was pushed by accident in 073898046542323eb11c2e129f37fa558ae201e1. --- test/unit/plugins/provisioners/ansible/provisioner_test.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/test/unit/plugins/provisioners/ansible/provisioner_test.rb b/test/unit/plugins/provisioners/ansible/provisioner_test.rb index eafd8bf79..79beba577 100644 --- a/test/unit/plugins/provisioners/ansible/provisioner_test.rb +++ b/test/unit/plugins/provisioners/ansible/provisioner_test.rb @@ -986,7 +986,6 @@ VF end end - # TODO: add more tests, now that we know how to deal with multiple Subprocess stub executions describe "with galaxy support" do before do From 270618284c47ccd7613ee80746a32a52f42302a4 Mon Sep 17 00:00:00 2001 From: Gilles Cornu Date: Wed, 30 Aug 2017 07:58:58 +0200 Subject: [PATCH 28/51] docs/ansible: Fix an internal hyperlink Fixing a typo made in 073898046542323eb11c2e129f37fa558ae201e1, after getting the confirmation that all the Vagrant docs will continue to use full path for internal links (for the moment). [ci skip] --- website/source/docs/provisioning/ansible_common.html.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/docs/provisioning/ansible_common.html.md b/website/source/docs/provisioning/ansible_common.html.md index 17c047992..a36f7527f 100644 --- a/website/source/docs/provisioning/ansible_common.html.md +++ b/website/source/docs/provisioning/ansible_common.html.md @@ -196,5 +196,5 @@ Some of these options are for advanced usage only and should not be used unless
Tip: - With the `ansible_local` provisioner, it is currently possible to use this option to specify which version of Ansible must be automatically installed, but only in combination with the [**`install_mode`**](ansible_local.html#install_mode) set to `:pip`. + With the `ansible_local` provisioner, it is currently possible to use this option to specify which version of Ansible must be automatically installed, but only in combination with the [**`install_mode`**](/docs/provisioning/ansible_local.html#install_mode) set to `:pip`.
From 36616fb2083e6684d107de0b15fd25af63d5cded Mon Sep 17 00:00:00 2001 From: Gilles Cornu Date: Wed, 30 Aug 2017 11:51:55 +0200 Subject: [PATCH 29/51] provisioners/ansible: Add @control_machine instance variable This minor addition will be used for upcoming shared code in base superclass and avoid "guest"/"host" repetitions. --- plugins/provisioners/ansible/provisioner/base.rb | 1 + plugins/provisioners/ansible/provisioner/guest.rb | 5 +++-- plugins/provisioners/ansible/provisioner/host.rb | 5 +++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/plugins/provisioners/ansible/provisioner/base.rb b/plugins/provisioners/ansible/provisioner/base.rb index a810409c6..021fc29e7 100644 --- a/plugins/provisioners/ansible/provisioner/base.rb +++ b/plugins/provisioners/ansible/provisioner/base.rb @@ -40,6 +40,7 @@ module VagrantPlugins def initialize(machine, config) super + @control_machine = nil @command_arguments = [] @environment_variables = {} diff --git a/plugins/provisioners/ansible/provisioner/guest.rb b/plugins/provisioners/ansible/provisioner/guest.rb index 1b8fc38a4..c821a256a 100644 --- a/plugins/provisioners/ansible/provisioner/guest.rb +++ b/plugins/provisioners/ansible/provisioner/guest.rb @@ -10,6 +10,7 @@ module VagrantPlugins def initialize(machine, config) super + @control_machine = "guest" @logger = Log4r::Logger.new("vagrant::provisioners::ansible_guest") end @@ -65,7 +66,7 @@ module VagrantPlugins if (!config.version.empty? && config.version.to_s.to_sym != :latest && !@machine.guest.capability(:ansible_installed, config.version)) - raise Ansible::Errors::AnsibleVersionMismatch, system: "guest", required_version: config.version.to_s + raise Ansible::Errors::AnsibleVersionMismatch, system: @control_machine, required_version: config.version.to_s end end @@ -174,7 +175,7 @@ module VagrantPlugins error_key: :config_file_not_found, config_option: option_name, path: remote_path, - system: "guest" + system: @control_machine ) end diff --git a/plugins/provisioners/ansible/provisioner/host.rb b/plugins/provisioners/ansible/provisioner/host.rb index f25178729..dceb92d45 100644 --- a/plugins/provisioners/ansible/provisioner/host.rb +++ b/plugins/provisioners/ansible/provisioner/host.rb @@ -11,6 +11,7 @@ module VagrantPlugins def initialize(machine, config) super + @control_machine = "host" @logger = Log4r::Logger.new("vagrant::provisioners::ansible_host") end @@ -46,7 +47,7 @@ module VagrantPlugins @logger.info("Checking for Ansible version on Vagrant host...") found_version = gather_ansible_version if (!found_version || "ansible #{config.version}\n" != found_version.lines[0]) - raise Ansible::Errors::AnsibleVersionMismatch, system: "host", required_version: config.version.to_s + raise Ansible::Errors::AnsibleVersionMismatch, system: @control_machine, required_version: config.version.to_s end end @@ -326,7 +327,7 @@ module VagrantPlugins _key: :config_file_not_found, config_option: option_name, path: expanded_path, - system: "host" + system: @control_machine end end From dc3b6341e2f9f410362a029d487e7409d04456a3 Mon Sep 17 00:00:00 2001 From: Gilles Cornu Date: Fri, 1 Sep 2017 08:05:50 +0200 Subject: [PATCH 30/51] provisioners/ansible: Check compatibility conflicts Vagrant will verify that the current Ansible version does support the requested compatibility mode (only applicable if not "auto", of course). As mentioned in the documentation, there is no sanity checks between `version` option and `compatibility_mode` option. With this change, the host-based provisioner is also improved to execute only once the "ansible" command (and store the gathered information for multiple usages like version requirement and compatibility checks). On the other hand, the guest-based provisioner can still potentially execute "ansible" twice (once in the AnsibleInstalled cap, and via "gather_ansible_version" function via Base::set_compatibility_mode). --- plugins/provisioners/ansible/errors.rb | 4 + .../provisioners/ansible/provisioner/base.rb | 93 ++++++++++++------- .../provisioners/ansible/provisioner/guest.rb | 9 +- .../provisioners/ansible/provisioner/host.rb | 19 ++-- templates/locales/en.yml | 7 +- .../provisioners/ansible/provisioner_test.rb | 44 ++++++--- .../docs/provisioning/ansible_common.html.md | 10 +- 7 files changed, 124 insertions(+), 62 deletions(-) diff --git a/plugins/provisioners/ansible/errors.rb b/plugins/provisioners/ansible/errors.rb index efc177ef8..a0504e58d 100644 --- a/plugins/provisioners/ansible/errors.rb +++ b/plugins/provisioners/ansible/errors.rb @@ -26,6 +26,10 @@ module VagrantPlugins class AnsibleVersionMismatch < AnsibleError error_key(:ansible_version_mismatch) end + + class AnsibleCompatibilityModeConflict < AnsibleError + error_key(:ansible_compatibility_mode_conflict) + end end end end \ No newline at end of file diff --git a/plugins/provisioners/ansible/provisioner/base.rb b/plugins/provisioners/ansible/provisioner/base.rb index 021fc29e7..fd4044da5 100644 --- a/plugins/provisioners/ansible/provisioner/base.rb +++ b/plugins/provisioners/ansible/provisioner/base.rb @@ -46,45 +46,31 @@ module VagrantPlugins @environment_variables = {} @inventory_machines = {} @inventory_path = nil + + @gathered_version_stdout = nil + @gathered_version_major = nil + @gathered_version = nil end def set_compatibility_mode - if config.compatibility_mode == Ansible::COMPATIBILITY_MODE_AUTO - detect_compatibility_mode(gather_ansible_version) - end - - unless Ansible::COMPATIBILITY_MODES.slice(1..-1).include?(config.compatibility_mode) - raise "Programming Error: compatibility_mode must correctly set at this stage!" - end - - @lexicon = ANSIBLE_PARAMETER_NAMES[config.compatibility_mode] - end - - def detect_compatibility_mode(ansible_version_stdoutput) - if config.compatibility_mode != Ansible::COMPATIBILITY_MODE_AUTO - raise "Programming Error: detect_compatibility_mode() shouldn't have been called." - end - begin - first_line = ansible_version_stdoutput.lines[0] - full_version = first_line.match(/ansible (\d)(\.\d+){1,}/) + set_gathered_ansible_version(gather_ansible_version) + rescue Exception => e + # Nothing to do here, as the fallback on safe compatibility_mode is done below + @logger.error("Error while gathering the ansible version: #{e.to_s}") + end - if full_version - major_version, _ = full_version.captures - - if major_version.to_i <= 1 - config.compatibility_mode = Ansible::COMPATIBILITY_MODE_V1_8 - else - config.compatibility_mode = Ansible::COMPATIBILITY_MODE_V2_0 - end - - @machine.env.ui.warn(I18n.t("vagrant.provisioners.ansible.compatibility_mode_warning", - compatibility_mode: config.compatibility_mode, - ansible_version: full_version) + - "\n") + if @gathered_version_major + if config.compatibility_mode == Ansible::COMPATIBILITY_MODE_AUTO + detect_compatibility_mode + elsif @gathered_version_major.to_i < 2 && config.compatibility_mode == Ansible::COMPATIBILITY_MODE_V2_0 + # A better version comparator will be needed + # when more compatibility modes come... but so far let's keep it simple! + raise Ansible::Errors::AnsibleCompatibilityModeConflict, + ansible_version: @gathered_version, + system: @control_machine, + compatibility_mode: config.compatibility_mode end - rescue - # Nothing to do here, the fallback to default compatibility_mode is done below end if config.compatibility_mode == Ansible::COMPATIBILITY_MODE_AUTO @@ -92,9 +78,15 @@ module VagrantPlugins @machine.env.ui.warn(I18n.t("vagrant.provisioners.ansible.compatibility_mode_not_detected", compatibility_mode: config.compatibility_mode, - gathered_version: ansible_version_stdoutput) + + gathered_version: @gathered_version_stdout) + "\n") end + + unless Ansible::COMPATIBILITY_MODES.slice(1..-1).include?(config.compatibility_mode) + raise "Programming Error: compatibility_mode must correctly set at this stage!" + end + + @lexicon = ANSIBLE_PARAMETER_NAMES[config.compatibility_mode] end def check_files_existence @@ -357,6 +349,39 @@ module VagrantPlugins end end + private + + def detect_compatibility_mode + if !@gathered_version_major || config.compatibility_mode != Ansible::COMPATIBILITY_MODE_AUTO + raise "Programming Error: detect_compatibility_mode() shouldn't have been called." + end + + if @gathered_version_major.to_i <= 1 + config.compatibility_mode = Ansible::COMPATIBILITY_MODE_V1_8 + else + config.compatibility_mode = Ansible::COMPATIBILITY_MODE_V2_0 + end + + @machine.env.ui.warn(I18n.t("vagrant.provisioners.ansible.compatibility_mode_warning", + compatibility_mode: config.compatibility_mode, + ansible_version: @gathered_version) + + "\n") + end + + def set_gathered_ansible_version(stdout_output) + @gathered_version_stdout = stdout_output + if !@gathered_version_stdout.empty? + first_line = @gathered_version_stdout.lines[0] + ansible_version_pattern = first_line.match(/(^ansible\s+)(.+)$/) + if ansible_version_pattern + _, @gathered_version, _ = ansible_version_pattern.captures + if @gathered_version + @gathered_version_major = @gathered_version.match(/^(\d)\..+$/).captures[0].to_i + end + end + end + end + end end end diff --git a/plugins/provisioners/ansible/provisioner/guest.rb b/plugins/provisioners/ansible/provisioner/guest.rb index c821a256a..ea8d89603 100644 --- a/plugins/provisioners/ansible/provisioner/guest.rb +++ b/plugins/provisioners/ansible/provisioner/guest.rb @@ -66,19 +66,22 @@ module VagrantPlugins if (!config.version.empty? && config.version.to_s.to_sym != :latest && !@machine.guest.capability(:ansible_installed, config.version)) - raise Ansible::Errors::AnsibleVersionMismatch, system: @control_machine, required_version: config.version.to_s + raise Ansible::Errors::AnsibleVersionMismatch, + system: @control_machine, + required_version: config.version, + current_version: @gathered_version end end def gather_ansible_version - raw_output = nil + raw_output = "" result = @machine.communicate.execute("ansible --version", error_check: false) do |type, output| if type == :stdout && output.lines[0] raw_output = output.lines[0] end end if result != 0 - raw_output = nil + raw_output = "" end raw_output end diff --git a/plugins/provisioners/ansible/provisioner/host.rb b/plugins/provisioners/ansible/provisioner/host.rb index dceb92d45..c8f77d190 100644 --- a/plugins/provisioners/ansible/provisioner/host.rb +++ b/plugins/provisioners/ansible/provisioner/host.rb @@ -20,9 +20,9 @@ module VagrantPlugins @ssh_info = @machine.ssh_info warn_for_unsupported_platform - check_required_ansible_version unless config.version.empty? check_files_existence set_compatibility_mode + check_required_ansible_version execute_ansible_galaxy_from_host if config.galaxy_role_file execute_ansible_playbook_from_host @@ -39,15 +39,16 @@ module VagrantPlugins end def check_required_ansible_version - if config.version.to_s.to_sym == :latest - @logger.debug("The :latest version requirement is not supported (yet) by the host-based provisioner") + # Skip this check when not required, nor possible + if !@gathered_version || config.version.empty? || config.version.to_s.to_sym == :latest return end - @logger.info("Checking for Ansible version on Vagrant host...") - found_version = gather_ansible_version - if (!found_version || "ansible #{config.version}\n" != found_version.lines[0]) - raise Ansible::Errors::AnsibleVersionMismatch, system: @control_machine, required_version: config.version.to_s + if config.version != @gathered_version + raise Ansible::Errors::AnsibleVersionMismatch, + system: @control_machine, + required_version: config.version, + current_version: @gathered_version end end @@ -105,7 +106,7 @@ module VagrantPlugins end def gather_ansible_version - raw_output = nil + raw_output = "" command = %w(ansible --version) command << { @@ -119,7 +120,7 @@ module VagrantPlugins end end if result.exit_code != 0 - raw_output = nil + raw_output = "" end rescue Vagrant::Errors::CommandUnavailable raise Ansible::Errors::AnsibleNotFoundOnHost diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 5cb65c0ad..eeea26770 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -2366,10 +2366,15 @@ en: https://github.com/mitchellh/vagrant ansible_version_mismatch: |- The requested Ansible version (%{required_version}) was not found on the %{system}. - Please check the Ansible installation on your Vagrant %{system} system, + Please check the Ansible installation on your Vagrant %{system} system (currently: %{current_version}), or adapt the `version` option of this provisioner in your Vagrantfile. See https://docs.vagrantup.com/v2/provisioning/ansible_common.html#version for more information. + ansible_compatibility_mode_conflict: |- + The requested Ansible compatibility mode (%{compatibility_mode}) is in conflict with + the Ansible installation on your Vagrant %{system} system (currently: %{ansible_version}). + See https://docs.vagrantup.com/v2/provisioning/ansible_common.html#compatibility_mode + for more information. config_file_not_found: |- `%{config_option}` does not exist on the %{system}: %{path} extra_vars_invalid: |- diff --git a/test/unit/plugins/provisioners/ansible/provisioner_test.rb b/test/unit/plugins/provisioners/ansible/provisioner_test.rb index 79beba577..deef6cb01 100644 --- a/test/unit/plugins/provisioners/ansible/provisioner_test.rb +++ b/test/unit/plugins/provisioners/ansible/provisioner_test.rb @@ -313,7 +313,8 @@ VF valid_versions = { "0.6": VagrantPlugins::Ansible::COMPATIBILITY_MODE_V1_8, "1.9.4": VagrantPlugins::Ansible::COMPATIBILITY_MODE_V1_8, - "2.2.1.0": VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0, + "2.5.0.0-rc1": VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0, + "2.x.y.z": VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0, "4.3.2.1": VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0, } valid_versions.each_pair do |ansible_version, mode| @@ -331,7 +332,7 @@ VF it "warns about compatibility mode auto-detection being used" do expect(machine.env.ui).to receive(:warn).with( I18n.t("vagrant.provisioners.ansible.compatibility_mode_warning", - compatibility_mode: mode, ansible_version: "ansible #{ansible_version}") + + compatibility_mode: mode, ansible_version: ansible_version) + "\n") end end @@ -339,7 +340,7 @@ VF invalid_versions = [ "ansible devel", - "ansible 2.x.y.z\n...\n", + "anything 1.2", "2.9.2.1", ] invalid_versions.each do |unknown_ansible_version| @@ -371,6 +372,7 @@ VF config.compatibility_mode = VagrantPlugins::Ansible::COMPATIBILITY_MODE_V1_8 end + it_should_check_ansible_version it_should_create_and_use_generated_inventory it "doesn't warn about compatibility mode auto-detection" do @@ -381,6 +383,7 @@ VF context "with compatibility_mode '#{VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0}'" do before do config.compatibility_mode = VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0 + allow(subject).to receive(:gather_ansible_version).and_return("ansible 2.3.0.0\n...\n") end it_should_create_and_use_generated_inventory @@ -389,6 +392,16 @@ VF expect(machine.env.ui).to_not receive(:warn) end + describe "and an incompatible ansible version" do + before do + allow(subject).to receive(:gather_ansible_version).and_return("ansible 1.9.3\n...\n") + end + + it "raises a compatibility conflict error", skip_before: false, skip_after: true do + expect {subject.provision}.to raise_error(VagrantPlugins::Ansible::Errors::AnsibleCompatibilityModeConflict) + end + end + describe "deprecated 'sudo' options are aliases for equivalent 'become' options" do before do # Filter the deprecation notices @@ -413,7 +426,7 @@ VF config.playbook_command = "custom-ansible-playbook" # set the compatibility mode to ensure that only ansible-playbook is excuted - config.compatibility_mode = VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0 + config.compatibility_mode = VagrantPlugins::Ansible::COMPATIBILITY_MODE_V1_8 end it "uses custom playbook_command to run playbooks" do @@ -968,20 +981,29 @@ VF allow(subject).to receive(:gather_ansible_version).and_return("ansible 1.9.6\n...\n") end - it "raises an error about the ansible version mismatch", skip_before: true, skip_after: true do - config.finalize! + it "raises an error about the ansible version mismatch", skip_before: false, skip_after: true do expect {subject.provision}.to raise_error(VagrantPlugins::Ansible::Errors::AnsibleVersionMismatch) end end describe "and the installed ansible version cannot be detected" do before do - allow(subject).to receive(:gather_ansible_version).and_return(nil) + allow(subject).to receive(:gather_ansible_version).and_return("") end - it "raises an error about the ansible version mismatch", skip_before: true, skip_after: true do - config.finalize! - expect {subject.provision}.to raise_error(VagrantPlugins::Ansible::Errors::AnsibleVersionMismatch) + it "skips the ansible version check and executes ansible-playbook command" do + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args).and_return(default_execute_result) + end + end + + describe "with special value: 'latest'" do + before do + config.version = :latest + allow(subject).to receive(:gather_ansible_version).and_return("ansible 2.2.0.1\n...\n") + end + + it "skips the ansible version check and executes ansible-playbook command" do + expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args).and_return(default_execute_result) end end end @@ -1168,7 +1190,7 @@ VF allow(machine.ui).to receive(:warn) # Set the compatibility mode to only get the Windows warning - config.compatibility_mode = VagrantPlugins::Ansible::COMPATIBILITY_MODE_V2_0 + config.compatibility_mode = VagrantPlugins::Ansible::COMPATIBILITY_MODE_V1_8 end it "warns that Windows is not officially supported for the Ansible control machine" do diff --git a/website/source/docs/provisioning/ansible_common.html.md b/website/source/docs/provisioning/ansible_common.html.md index a36f7527f..af3d95393 100644 --- a/website/source/docs/provisioning/ansible_common.html.md +++ b/website/source/docs/provisioning/ansible_common.html.md @@ -35,16 +35,18 @@ Some of these options are for advanced usage only and should not be used unless By default this option is set to `"auto"`. If Vagrant is not able to detect any supported Ansible version, it will falls back on the compatibility mode `"1.8"` with a warning. -
- Compatibility Note: - This option was introduced in Vagrant 2.0. Previous Vagrant versions behave like if this option was set to `"1.8"`. -
+ Vagrant will error if the specified compatibility mode is incompatible with the current Ansible version.
Attention: Vagrant doesn't perform any validation between the `compatibility_mode` value and the value of the [`version`](#version) option.
+
+ Compatibility Note: + This option was introduced in Vagrant 2.0. Previous Vagrant versions behave like if this option was set to `"1.8"`. +
+ - `config_file` (string) - The path to an [Ansible Configuration file](https://docs.ansible.com/intro_configuration.html). By default, this option is not set, and Ansible will [search for a possible configuration file in some default locations](/docs/provisioning/ansible_intro.html#ANSIBLE_CONFIG). From dec09f350be2c1518a054c58b8430c6374b1b61d Mon Sep 17 00:00:00 2001 From: Gilles Cornu Date: Fri, 1 Sep 2017 18:22:18 +0200 Subject: [PATCH 31/51] provisioners/ansible: Fix wording and link in i18n en.yml messages --- templates/locales/en.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/locales/en.yml b/templates/locales/en.yml index eeea26770..912112c61 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -2344,8 +2344,8 @@ en: If you haven't installed Ansible yet, please install Ansible on your Vagrant basebox, or enable the automated setup with the - `install` option of this provisioner. Please check - https://docs.vagrantup.com/v2/provisioning/ansible_local.html + ansible_local provisioner `install` option. Please check + https://docs.vagrantup.com/v2/provisioning/ansible_local.html#install for more information. ansible_not_found_on_host: |- The Ansible software could not be found! Please verify @@ -2367,7 +2367,7 @@ en: ansible_version_mismatch: |- The requested Ansible version (%{required_version}) was not found on the %{system}. Please check the Ansible installation on your Vagrant %{system} system (currently: %{current_version}), - or adapt the `version` option of this provisioner in your Vagrantfile. + or adapt the provisioner `version` option in your Vagrantfile. See https://docs.vagrantup.com/v2/provisioning/ansible_common.html#version for more information. ansible_compatibility_mode_conflict: |- From 9996ed6259588eab648e01082a3c046d0c144686 Mon Sep 17 00:00:00 2001 From: Gilles Cornu Date: Fri, 1 Sep 2017 18:24:01 +0200 Subject: [PATCH 32/51] provisioners/ansible_local: Fix an obsolete comment [ci skip] --- plugins/provisioners/ansible/provisioner/guest.rb | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/plugins/provisioners/ansible/provisioner/guest.rb b/plugins/provisioners/ansible/provisioner/guest.rb index ea8d89603..60169cb11 100644 --- a/plugins/provisioners/ansible/provisioner/guest.rb +++ b/plugins/provisioners/ansible/provisioner/guest.rb @@ -30,12 +30,10 @@ module VagrantPlugins # requested, and so on. This method will raise exceptions if things are wrong. # # Current limitations: - # - The installation of a specific Ansible version is not supported. - # Such feature is difficult to systematically provide via package repositories (apt, yum, ...). - # Installing via pip python packaging or directly from github source would be appropriate, - # but these approaches require more dependency burden. - # - There is no guarantee that the automated installation will replace - # a previous Ansible installation. + # - The installation of a specific Ansible version is only supported by + # the "pip" install_mode. + # - There is no abslute guarantee that the automated installation will replace + # a previous Ansible installation (although it works fine in many cases) # def check_and_install_ansible @logger.info("Checking for Ansible installation...") From 6bc0c85e9003e65423dd81d3f0ba1cdcf409b576 Mon Sep 17 00:00:00 2001 From: Gilles Cornu Date: Fri, 1 Sep 2017 20:45:10 +0200 Subject: [PATCH 33/51] provisioners/ansible_local: Optimize SSH commands With this change, the same remote command is used to: - verify that ansible is available - gather the ansible version details --- .../provisioners/ansible/provisioner/base.rb | 2 +- .../provisioners/ansible/provisioner/guest.rb | 23 +++++++++++-------- .../provisioners/ansible/provisioner/host.rb | 8 ++++--- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/plugins/provisioners/ansible/provisioner/base.rb b/plugins/provisioners/ansible/provisioner/base.rb index fd4044da5..4e742bef4 100644 --- a/plugins/provisioners/ansible/provisioner/base.rb +++ b/plugins/provisioners/ansible/provisioner/base.rb @@ -52,7 +52,7 @@ module VagrantPlugins @gathered_version = nil end - def set_compatibility_mode + def set_and_check_compatibility_mode begin set_gathered_ansible_version(gather_ansible_version) rescue Exception => e diff --git a/plugins/provisioners/ansible/provisioner/guest.rb b/plugins/provisioners/ansible/provisioner/guest.rb index 60169cb11..ad971910c 100644 --- a/plugins/provisioners/ansible/provisioner/guest.rb +++ b/plugins/provisioners/ansible/provisioner/guest.rb @@ -15,9 +15,8 @@ module VagrantPlugins end def provision - check_and_install_ansible check_files_existence - set_compatibility_mode + check_and_install_ansible execute_ansible_galaxy_on_guest if config.galaxy_role_file execute_ansible_playbook_on_guest @@ -28,6 +27,8 @@ module VagrantPlugins # # This handles verifying the Ansible installation, installing it if it was # requested, and so on. This method will raise exceptions if things are wrong. + # The compatibility mode checks are also performed here in order to fetch the + # Ansible version information only once. # # Current limitations: # - The installation of a specific Ansible version is only supported by @@ -53,17 +54,13 @@ module VagrantPlugins @machine.guest.capability(:ansible_install, config.install_mode, config.version, config.pip_args) end - # Check that Ansible Playbook command is available on the guest - @machine.communicate.execute( - "test -x \"$(command -v #{config.playbook_command})\"", - error_class: Ansible::Errors::AnsibleNotFoundOnGuest, - error_key: :ansible_not_found_on_guest - ) + # This step will also fetch the Ansible version data into related instance variables + set_and_check_compatibility_mode # Check if requested ansible version is available if (!config.version.empty? && config.version.to_s.to_sym != :latest && - !@machine.guest.capability(:ansible_installed, config.version)) + config.version != @gathered_version) raise Ansible::Errors::AnsibleVersionMismatch, system: @control_machine, required_version: config.version, @@ -73,14 +70,20 @@ module VagrantPlugins def gather_ansible_version raw_output = "" - result = @machine.communicate.execute("ansible --version", error_check: false) do |type, output| + + result = @machine.communicate.execute( + "ansible --version", + error_class: Ansible::Errors::AnsibleNotFoundOnGuest, + error_key: :ansible_not_found_on_guest) do |type, output| if type == :stdout && output.lines[0] raw_output = output.lines[0] end end + if result != 0 raw_output = "" end + raw_output end diff --git a/plugins/provisioners/ansible/provisioner/host.rb b/plugins/provisioners/ansible/provisioner/host.rb index c8f77d190..3e594b83c 100644 --- a/plugins/provisioners/ansible/provisioner/host.rb +++ b/plugins/provisioners/ansible/provisioner/host.rb @@ -21,8 +21,7 @@ module VagrantPlugins warn_for_unsupported_platform check_files_existence - set_compatibility_mode - check_required_ansible_version + check_ansible_version_and_compatibility execute_ansible_galaxy_from_host if config.galaxy_role_file execute_ansible_playbook_from_host @@ -38,7 +37,10 @@ module VagrantPlugins end end - def check_required_ansible_version + def check_ansible_version_and_compatibility + # This step will also fetch the Ansible version data into related instance variables + set_and_check_compatibility_mode + # Skip this check when not required, nor possible if !@gathered_version || config.version.empty? || config.version.to_s.to_sym == :latest return From 4dc3e59b087671f7f3af76a657aa053aa4b8dd2c Mon Sep 17 00:00:00 2001 From: Gilles Cornu Date: Wed, 6 Sep 2017 00:40:47 +0200 Subject: [PATCH 34/51] provisioners/ansible(both): Review of PR #8913 - Keep the Programming Errors with corresponding Exception class and en.yml message template. Fix the alphabetical order in errors.rb by the way. - Fix English wording in the documentation and en.yml messages - Use StandardError for unknown error rescuing. Thanks @chrisroberts! --- plugins/provisioners/ansible/errors.rb | 15 ++++++++---- .../provisioners/ansible/provisioner/base.rb | 13 +++++++--- templates/locales/en.yml | 24 ++++++++++++++----- .../docs/provisioning/ansible_common.html.md | 10 ++++---- 4 files changed, 43 insertions(+), 19 deletions(-) diff --git a/plugins/provisioners/ansible/errors.rb b/plugins/provisioners/ansible/errors.rb index a0504e58d..d23a2d84b 100644 --- a/plugins/provisioners/ansible/errors.rb +++ b/plugins/provisioners/ansible/errors.rb @@ -11,25 +11,30 @@ module VagrantPlugins error_key(:ansible_command_failed) end - class AnsibleNotFoundOnHost < AnsibleError - error_key(:ansible_not_found_on_host) + class AnsibleCompatibilityModeConflict < AnsibleError + error_key(:ansible_compatibility_mode_conflict) end class AnsibleNotFoundOnGuest < AnsibleError error_key(:ansible_not_found_on_guest) end + class AnsibleNotFoundOnHost < AnsibleError + error_key(:ansible_not_found_on_host) + end + class AnsiblePipInstallIsNotSupported < AnsibleError error_key(:cannot_support_pip_install) end + class AnsibleProgrammingError < AnsibleError + error_key(:ansible_programming_error) + end + class AnsibleVersionMismatch < AnsibleError error_key(:ansible_version_mismatch) end - class AnsibleCompatibilityModeConflict < AnsibleError - error_key(:ansible_compatibility_mode_conflict) - end end end end \ No newline at end of file diff --git a/plugins/provisioners/ansible/provisioner/base.rb b/plugins/provisioners/ansible/provisioner/base.rb index 4e742bef4..8e668d182 100644 --- a/plugins/provisioners/ansible/provisioner/base.rb +++ b/plugins/provisioners/ansible/provisioner/base.rb @@ -55,7 +55,7 @@ module VagrantPlugins def set_and_check_compatibility_mode begin set_gathered_ansible_version(gather_ansible_version) - rescue Exception => e + rescue StandardError => e # Nothing to do here, as the fallback on safe compatibility_mode is done below @logger.error("Error while gathering the ansible version: #{e.to_s}") end @@ -83,7 +83,9 @@ module VagrantPlugins end unless Ansible::COMPATIBILITY_MODES.slice(1..-1).include?(config.compatibility_mode) - raise "Programming Error: compatibility_mode must correctly set at this stage!" + raise Ansible::Errors::AnsibleProgrammingError, + message: "The config.compatibility_mode must be correctly set at this stage!", + details: "config.compatibility_mode: '#{config.compatibility_mode}'" end @lexicon = ANSIBLE_PARAMETER_NAMES[config.compatibility_mode] @@ -353,7 +355,12 @@ module VagrantPlugins def detect_compatibility_mode if !@gathered_version_major || config.compatibility_mode != Ansible::COMPATIBILITY_MODE_AUTO - raise "Programming Error: detect_compatibility_mode() shouldn't have been called." + raise Ansible::Errors::AnsibleProgrammingError, + message: "The detect_compatibility_mode() function shouldn't have been called!", + details: %Q(config.compatibility_mode: '#{config.compatibility_mode}' +gathered version major number: '#{@gathered_version_major}' +gathered version stdout version: +#{@gathered_version_stdout}) end if @gathered_version_major.to_i <= 1 diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 912112c61..9e9c65d60 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -2338,6 +2338,11 @@ en: ansible_command_failed: |- Ansible failed to complete successfully. Any error output should be visible above. Please fix these errors and try again. + ansible_compatibility_mode_conflict: |- + The requested Ansible compatibility mode (%{compatibility_mode}) is in conflict with + the Ansible installation on your Vagrant %{system} system (currently: %{ansible_version}). + See https://docs.vagrantup.com/v2/provisioning/ansible_common.html#compatibility_mode + for more information. ansible_not_found_on_guest: |- The Ansible software could not be found! Please verify that Ansible is correctly installed on your guest system. @@ -2355,6 +2360,18 @@ en: on your host system. Vagrant can't do this for you in a safe and automated way. Please check https://docs.ansible.com for more information. + ansible_programming_error: |- + Ansible Provisioner Programming Error: + + %{message} + + Internal Details: + + %{details} + + Sorry, but this Vagrant error should never occur. + Please check https://github.com/mitchellh/vagrant/issues for any + existing bug report. If needed, please create a new issue. Thank you! cannot_support_pip_install: |- Unfortunately Vagrant does not support yet installing Ansible from pip for the guest OS running in the machine. @@ -2370,17 +2387,12 @@ en: or adapt the provisioner `version` option in your Vagrantfile. See https://docs.vagrantup.com/v2/provisioning/ansible_common.html#version for more information. - ansible_compatibility_mode_conflict: |- - The requested Ansible compatibility mode (%{compatibility_mode}) is in conflict with - the Ansible installation on your Vagrant %{system} system (currently: %{ansible_version}). - See https://docs.vagrantup.com/v2/provisioning/ansible_common.html#compatibility_mode - for more information. config_file_not_found: |- `%{config_option}` does not exist on the %{system}: %{path} extra_vars_invalid: |- `extra_vars` must be a hash or a path to an existing file. Received: %{value} (as %{type}) no_compatibility_mode: |- - `compatibility_mode` must be correctly set (possible values: %{valid_modes}). + `compatibility_mode` must be a valid mode (possible values: %{valid_modes}). no_playbook: |- `playbook` file path must be set. raw_arguments_invalid: |- diff --git a/website/source/docs/provisioning/ansible_common.html.md b/website/source/docs/provisioning/ansible_common.html.md index af3d95393..272899373 100644 --- a/website/source/docs/provisioning/ansible_common.html.md +++ b/website/source/docs/provisioning/ansible_common.html.md @@ -17,15 +17,15 @@ These options get passed to the `ansible-playbook` command that ships with Ansib Some of these options are for advanced usage only and should not be used unless you understand their purpose. -- `become` (boolean) - Cause Ansible to perform all the playbook tasks [as another user](http://docs.ansible.com/ansible/become.html), different from the one used to log into the guest system. +- `become` (boolean) - Perform all the Ansible playbook tasks [as another user](http://docs.ansible.com/ansible/become.html), different from the user used to log into the guest system. The default value is `false`. - `become_user` (string) - Set the default username to be used by the Ansible `become` [privilege escalation](http://docs.ansible.com/ansible/become.html) mechanism. - By default this option is not defined, and the Ansible default value (`root`) will be used. + By default this option is not set, and the Ansible default value (`root`) will be used. -- `compatibility_mode` (string) - Set the **minimal** version of Ansible to be supported. Vagrant will use some parameters that are only compatible since the given version. +- `compatibility_mode` (string) - Set the **minimal** version of Ansible to be supported. Vagrant will only use parameters that are compatible with the given version. Possible values: @@ -33,7 +33,7 @@ Some of these options are for advanced usage only and should not be used unless - `"1.8"` _(Ansible versions prior to 1.8 should mostly work well, but some options might not be supported)_ - `"2.0"` _(The generated Ansible inventory will be incompatible with Ansible 1.x)_ - By default this option is set to `"auto"`. If Vagrant is not able to detect any supported Ansible version, it will falls back on the compatibility mode `"1.8"` with a warning. + By default this option is set to `"auto"`. If Vagrant is not able to detect any supported Ansible version, it will fall back on the compatibility mode `"1.8"` with a warning. Vagrant will error if the specified compatibility mode is incompatible with the current Ansible version. @@ -44,7 +44,7 @@ Some of these options are for advanced usage only and should not be used unless
Compatibility Note: - This option was introduced in Vagrant 2.0. Previous Vagrant versions behave like if this option was set to `"1.8"`. + This option was introduced in Vagrant 2.0. The behavior of previous Vagrant versions can be simulated by setting the `compatibility_mode` to `"1.8"`.
- `config_file` (string) - The path to an [Ansible Configuration file](https://docs.ansible.com/intro_configuration.html). From a0a09c6095efa93c57516e14e579dbbb9986f586 Mon Sep 17 00:00:00 2001 From: Gilles Cornu Date: Wed, 6 Sep 2017 14:30:56 +0200 Subject: [PATCH 35/51] minor: Fix a typo in a code comment [ci skip] --- plugins/provisioners/ansible/provisioner/guest.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/provisioners/ansible/provisioner/guest.rb b/plugins/provisioners/ansible/provisioner/guest.rb index ad971910c..1d03ff9b4 100644 --- a/plugins/provisioners/ansible/provisioner/guest.rb +++ b/plugins/provisioners/ansible/provisioner/guest.rb @@ -33,7 +33,7 @@ module VagrantPlugins # Current limitations: # - The installation of a specific Ansible version is only supported by # the "pip" install_mode. - # - There is no abslute guarantee that the automated installation will replace + # - There is no absolute guarantee that the automated installation will replace # a previous Ansible installation (although it works fine in many cases) # def check_and_install_ansible From acda71edd94d8934c1656d6d60fc51a5ad08a08a Mon Sep 17 00:00:00 2001 From: Antonio Terceiro Date: Wed, 6 Sep 2017 12:21:23 -0300 Subject: [PATCH 36/51] plugins/guests/kali: fix file permissions Those Ruby files are not standalone scripts, so there is no point in having them being executable. --- plugins/guests/kali/guest.rb | 0 plugins/guests/kali/plugin.rb | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 plugins/guests/kali/guest.rb mode change 100755 => 100644 plugins/guests/kali/plugin.rb diff --git a/plugins/guests/kali/guest.rb b/plugins/guests/kali/guest.rb old mode 100755 new mode 100644 diff --git a/plugins/guests/kali/plugin.rb b/plugins/guests/kali/plugin.rb old mode 100755 new mode 100644 From 6673c1491649b8b3a7ada7946312aa6138ea99ab Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Wed, 6 Sep 2017 08:36:02 -0700 Subject: [PATCH 37/51] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd30e3e94..3f4c4b648 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ IMPROVEMENTS: BUG FIXES: - guests/shell_expand_guest_path : Properly expand guest paths that include relative path alias [GH-8918] +- hosts/linux: Remove duplicate export folders before writing /etc/exports [GH-8945] - provisioners/ansible(both): Add the "all:vars" section to the inventory when defined in `groups` option [GH-7730] - provisioners/ansible_local: Extra variables are no longer truncated when a dollar ($) character is present [GH-7735] - provisioners/file: Align file provisioner functionality on all platforms [GH-8939] From fcd1aee9bb20bf775bb58ab4972677577b88644c Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Wed, 30 Aug 2017 16:59:46 -0700 Subject: [PATCH 38/51] Update linux host NFS capability Add support for systemd detection and using correct method for starting/checking host nfs service. --- lib/vagrant/util/guest_inspection.rb | 2 +- lib/vagrant/util/platform.rb | 13 ++++++ plugins/hosts/gentoo/cap/nfs.rb | 19 +++----- plugins/hosts/linux/cap/nfs.rb | 29 +++++++++---- plugins/hosts/redhat/cap/nfs.rb | 12 +++++- test/unit/plugins/hosts/linux/cap/nfs_test.rb | 43 ++++++++++++++++++- test/unit/vagrant/util/platform_test.rb | 23 ++++++++++ 7 files changed, 115 insertions(+), 26 deletions(-) diff --git a/lib/vagrant/util/guest_inspection.rb b/lib/vagrant/util/guest_inspection.rb index fdfc1a632..033bb3e97 100644 --- a/lib/vagrant/util/guest_inspection.rb +++ b/lib/vagrant/util/guest_inspection.rb @@ -8,7 +8,7 @@ module Vagrant ## systemd helpers - # systemd is in used + # systemd is in use # # @return [Boolean] def systemd?(comm) diff --git a/lib/vagrant/util/platform.rb b/lib/vagrant/util/platform.rb index 2fbcac838..778f3a7ab 100644 --- a/lib/vagrant/util/platform.rb +++ b/lib/vagrant/util/platform.rb @@ -448,6 +448,19 @@ module Vagrant end end + # systemd is in use + def systemd? + if !defined?(@_systemd) + if !windows? + result = Vagrant::Util::Subprocess.execute("ps", "-o", "comm=", "1") + @_systemd = result.stdout.chomp == "systemd" + else + @_systemd = false + end + end + @_systemd + end + # @private # Reset the cached values for platform. This is not considered a public # API and should only be used for testing. diff --git a/plugins/hosts/gentoo/cap/nfs.rb b/plugins/hosts/gentoo/cap/nfs.rb index 995e9b43c..bc4902206 100644 --- a/plugins/hosts/gentoo/cap/nfs.rb +++ b/plugins/hosts/gentoo/cap/nfs.rb @@ -6,30 +6,23 @@ module VagrantPlugins module Cap class NFS def self.nfs_check_command(env) - if systemd? - return "#{systemctl_path} status --no-pager nfs-server.service" + if Vagrant::Util::Platform.systemd? + "#{systemctl_path} status --no-pager nfs-server.service" else - return "/etc/init.d/nfs status" + "/etc/init.d/nfs status" end end def self.nfs_start_command(env) - if systemd? - return "#{systemctl_path} start rpcbind nfs-server.service" + if Vagrant::Util::Platform.systemd? + "#{systemctl_path} start rpcbind nfs-server.service" else - return "/etc/init.d/nfs restart" + "/etc/init.d/nfs restart" end end protected - # This tests to see if systemd is used on the system. This is used - # in newer versions of Arch, and requires a change in behavior. - def self.systemd? - result = Vagrant::Util::Subprocess.execute("ps", "-o", "comm=", "1") - return result.stdout.chomp == "systemd" - end - def self.systemctl_path path = Vagrant::Util::Which.which("systemctl") return path if path diff --git a/plugins/hosts/linux/cap/nfs.rb b/plugins/hosts/linux/cap/nfs.rb index 45701f02b..c4743c774 100644 --- a/plugins/hosts/linux/cap/nfs.rb +++ b/plugins/hosts/linux/cap/nfs.rb @@ -1,3 +1,4 @@ +require "shellwords" require "vagrant/util" require "vagrant/util/shell_quote" require "vagrant/util/retryable" @@ -15,11 +16,19 @@ module VagrantPlugins end def self.nfs_check_command(env) - "/etc/init.d/nfs-kernel-server status" + if Vagrant::Util::Platform.systemd? + "systemctl status --no-pager nfs-server.service" + else + "/etc/init.d/nfs-kernel-server status" + end end def self.nfs_start_command(env) - "/etc/init.d/nfs-kernel-server start" + if Vagrant::Util::Platform.systemd? + "systemctl start nfs-server.service" + else + "/etc/init.d/nfs-kernel-server start" + end end def self.nfs_export(env, ui, id, ips, folders) @@ -44,16 +53,20 @@ module VagrantPlugins nfs_write_exports(output) if nfs_running?(nfs_check_command) - system("sudo #{nfs_apply_command}") + Vagrant::Util::Subprocess.execute("sudo", *Shellwords.split(nfs_apply_command)).exit_code == 0 else - system("sudo #{nfs_start_command}") + Vagrant::Util::Subprocess.execute("sudo", *Shellwords.split(nfs_start_command)).exit_code == 0 end end def self.nfs_installed(environment) - retryable(tries: 10, on: TypeError) do - # Check procfs to see if NFSd is a supported filesystem - system("cat /proc/filesystems | grep nfsd > /dev/null 2>&1") + if Vagrant::Util::Platform.systemd? + Vagrant::Util::Subprocess.execute("/bin/sh", "-c", + "systemctl --no-pager --no-legend --plain list-unit-files --all --type=service " \ + "| grep nfs-server.service").exit_code == 0 + else + Vagrant::Util::Subprocess.execute("modinfo", "nfsd").exit_code == 0 || + Vagrant::Util::Subprocess.execute("grep", "nfsd", "/proc/filesystems").exit_code == 0 end end @@ -218,7 +231,7 @@ module VagrantPlugins end def self.nfs_running?(check_command) - system(check_command) + Vagrant::Util::Subprocess.execute(*Shellwords.split(check_command)).exit_code == 0 end end end diff --git a/plugins/hosts/redhat/cap/nfs.rb b/plugins/hosts/redhat/cap/nfs.rb index edcabe21c..8bc4e5411 100644 --- a/plugins/hosts/redhat/cap/nfs.rb +++ b/plugins/hosts/redhat/cap/nfs.rb @@ -5,11 +5,19 @@ module VagrantPlugins module Cap class NFS def self.nfs_check_command(env) - "#{nfs_server_binary} status" + if Vagrant::Util::Platform.systemd? + "systemctl status --no-pager nfs-server.service" + else + "#{nfs_server_binary} status" + end end def self.nfs_start_command(env) - "#{nfs_server_binary} start" + if Vagrant::Util::Platform.systemd? + "systemctl start nfs-server.service" + else + "#{nfs_server_binary} start" + end end protected diff --git a/test/unit/plugins/hosts/linux/cap/nfs_test.rb b/test/unit/plugins/hosts/linux/cap/nfs_test.rb index 557a470d5..bb8ca9079 100644 --- a/test/unit/plugins/hosts/linux/cap/nfs_test.rb +++ b/test/unit/plugins/hosts/linux/cap/nfs_test.rb @@ -33,6 +33,44 @@ describe VagrantPlugins::HostLinux::Cap::NFS do @tmp_exports = nil end + describe ".nfs_check_command" do + let(:cap){ caps.get(:nfs_check_command) } + + context "without systemd" do + before{ expect(Vagrant::Util::Platform).to receive(:systemd?).and_return(false) } + + it "should use init.d script" do + expect(cap.nfs_check_command(env)).to include("init.d") + end + end + context "with systemd" do + before{ expect(Vagrant::Util::Platform).to receive(:systemd?).and_return(true) } + + it "should use systemctl" do + expect(cap.nfs_check_command(env)).to include("systemctl") + end + end + end + + describe ".nfs_start_command" do + let(:cap){ caps.get(:nfs_start_command) } + + context "without systemd" do + before{ expect(Vagrant::Util::Platform).to receive(:systemd?).and_return(false) } + + it "should use init.d script" do + expect(cap.nfs_start_command(env)).to include("init.d") + end + end + context "with systemd" do + before{ expect(Vagrant::Util::Platform).to receive(:systemd?).and_return(true) } + + it "should use systemctl" do + expect(cap.nfs_start_command(env)).to include("systemctl") + end + end + end + describe ".nfs_export" do let(:cap){ caps.get(:nfs_export) } @@ -43,8 +81,9 @@ describe VagrantPlugins::HostLinux::Cap::NFS do allow(host).to receive(:capability).with(:nfs_check_command).and_return("/bin/true") allow(host).to receive(:capability).with(:nfs_start_command).and_return("/bin/true") allow(ui).to receive(:info) - allow(cap).to receive(:system).with("sudo /bin/true").and_return(true) - allow(cap).to receive(:system).with("/bin/true").and_return(true) + allow(Vagrant::Util::Subprocess).to receive(:execute).and_call_original + allow(Vagrant::Util::Subprocess).to receive(:execute).with("sudo", "/bin/true").and_return(double(:result, exit_code: 0)) + allow(Vagrant::Util::Subprocess).to receive(:execute).with("/bin/true").and_return(double(:result, exit_code: 0)) end it "should export new entries" do diff --git a/test/unit/vagrant/util/platform_test.rb b/test/unit/vagrant/util/platform_test.rb index 28511b2a1..b97f11ff9 100644 --- a/test/unit/vagrant/util/platform_test.rb +++ b/test/unit/vagrant/util/platform_test.rb @@ -133,4 +133,27 @@ describe Vagrant::Util::Platform do end end end + + describe ".systemd?" do + before{ allow(subject).to receive(:windows?).and_return(false) } + after{ subject.reset! } + + context "on windows" do + before{ expect(subject).to receive(:windows?).and_return(true) } + + it "should return false" do + expect(subject.systemd?).to be_falsey + end + end + + it "should return true if systemd is in use" do + expect(Vagrant::Util::Subprocess).to receive(:execute).and_return(double(:result, stdout: "systemd")) + expect(subject.systemd?).to be_truthy + end + + it "should return false if systemd is not in use" do + expect(Vagrant::Util::Subprocess).to receive(:execute).and_return(double(:result, stdout: "other")) + expect(subject.systemd?).to be_falsey + end + end end From 56c8b9269a9bd9c7bb4fbfdd85088ae11c2f1a04 Mon Sep 17 00:00:00 2001 From: "nico.vanelslande" Date: Tue, 31 May 2016 14:51:56 +0100 Subject: [PATCH 39/51] Improved resilience of the VirtualBox driver read_used_ports function by ignoring VMs that may have been deleted between the calls to 'vboxmanage list vms' and 'vboxmanage showvminfo' --- plugins/providers/virtualbox/driver/version_4_0.rb | 11 +++++++++-- plugins/providers/virtualbox/driver/version_4_1.rb | 11 +++++++++-- plugins/providers/virtualbox/driver/version_4_2.rb | 11 +++++++++-- plugins/providers/virtualbox/driver/version_4_3.rb | 11 +++++++++-- plugins/providers/virtualbox/driver/version_5_0.rb | 13 ++++++++++--- 5 files changed, 46 insertions(+), 11 deletions(-) diff --git a/plugins/providers/virtualbox/driver/version_4_0.rb b/plugins/providers/virtualbox/driver/version_4_0.rb index 66e9a37be..7c37a69b7 100644 --- a/plugins/providers/virtualbox/driver/version_4_0.rb +++ b/plugins/providers/virtualbox/driver/version_4_0.rb @@ -422,8 +422,15 @@ module VagrantPlugins # Ignore our own used ports next if uuid == @uuid - read_forwarded_ports(uuid, true).each do |_, _, hostport, _| - ports << hostport + begin + read_forwarded_ports(uuid, true).each do |_, _, hostport, _| + ports << hostport + end + rescue Vagrant::Errors::VBoxManageError => e + raise if !e.extra_data[:stderr].include?("VBOX_E_OBJECT_NOT_FOUND") + + # VirtualBox could not find the vm. It may have been deleted + # by another process after we called 'vboxmanage list vms'? Ignore this error. end end end diff --git a/plugins/providers/virtualbox/driver/version_4_1.rb b/plugins/providers/virtualbox/driver/version_4_1.rb index 79f4174ad..9093dc16a 100644 --- a/plugins/providers/virtualbox/driver/version_4_1.rb +++ b/plugins/providers/virtualbox/driver/version_4_1.rb @@ -525,8 +525,15 @@ module VagrantPlugins # Ignore our own used ports next if uuid == @uuid - read_forwarded_ports(uuid, true).each do |_, _, hostport, _| - ports << hostport + begin + read_forwarded_ports(uuid, true).each do |_, _, hostport, _| + ports << hostport + end + rescue Vagrant::Errors::VBoxManageError => e + raise if !e.extra_data[:stderr].include?("VBOX_E_OBJECT_NOT_FOUND") + + # VirtualBox could not find the vm. It may have been deleted + # by another process after we called 'vboxmanage list vms'? Ignore this error. end end end diff --git a/plugins/providers/virtualbox/driver/version_4_2.rb b/plugins/providers/virtualbox/driver/version_4_2.rb index 6ece1d91e..de2c46dad 100644 --- a/plugins/providers/virtualbox/driver/version_4_2.rb +++ b/plugins/providers/virtualbox/driver/version_4_2.rb @@ -458,8 +458,15 @@ module VagrantPlugins # Ignore our own used ports next if uuid == @uuid - read_forwarded_ports(uuid, true).each do |_, _, hostport, _| - ports << hostport + begin + read_forwarded_ports(uuid, true).each do |_, _, hostport, _| + ports << hostport + end + rescue Vagrant::Errors::VBoxManageError => e + raise if !e.extra_data[:stderr].include?("VBOX_E_OBJECT_NOT_FOUND") + + # VirtualBox could not find the vm. It may have been deleted + # by another process after we called 'vboxmanage list vms'? Ignore this error. end end end diff --git a/plugins/providers/virtualbox/driver/version_4_3.rb b/plugins/providers/virtualbox/driver/version_4_3.rb index 4f2caccf8..ce125625c 100644 --- a/plugins/providers/virtualbox/driver/version_4_3.rb +++ b/plugins/providers/virtualbox/driver/version_4_3.rb @@ -569,8 +569,15 @@ module VagrantPlugins # Ignore our own used ports next if uuid == @uuid - read_forwarded_ports(uuid, true).each do |_, _, hostport, _| - ports << hostport + begin + read_forwarded_ports(uuid, true).each do |_, _, hostport, _| + ports << hostport + end + rescue Vagrant::Errors::VBoxManageError => e + raise if !e.extra_data[:stderr].include?("VBOX_E_OBJECT_NOT_FOUND") + + # VirtualBox could not find the vm. It may have been deleted + # by another process after we called 'vboxmanage list vms'? Ignore this error. end end end diff --git a/plugins/providers/virtualbox/driver/version_5_0.rb b/plugins/providers/virtualbox/driver/version_5_0.rb index 7c5b9f16f..b77a5dc29 100644 --- a/plugins/providers/virtualbox/driver/version_5_0.rb +++ b/plugins/providers/virtualbox/driver/version_5_0.rb @@ -567,9 +567,16 @@ module VagrantPlugins # Ignore our own used ports next if uuid == @uuid - read_forwarded_ports(uuid, true).each do |_, _, hostport, _, hostip| - hostip = '*' if hostip.nil? || hostip.empty? - used_ports[hostport].add?(hostip) + begin + read_forwarded_ports(uuid, true).each do |_, _, hostport, _, hostip| + hostip = '*' if hostip.nil? || hostip.empty? + used_ports[hostport].add?(hostip) + end + rescue Vagrant::Errors::VBoxManageError => e + raise if !e.extra_data[:stderr].include?("VBOX_E_OBJECT_NOT_FOUND") + + # VirtualBox could not find the vm. It may have been deleted + # by another process after we called 'vboxmanage list vms'? Ignore this error. end end end From 9aecd5e1a4c7f12ca3a50edc771daa8aff518eda Mon Sep 17 00:00:00 2001 From: "nico.vanelslande" Date: Tue, 31 May 2016 14:54:25 +0100 Subject: [PATCH 40/51] Improved resilience of the VirtualBox driver delete_unused_host_only_networks function by ignoring VMs that may have been deleted between the calls to 'vboxmanage list vms' and 'vboxmanage showvminfo' --- .../providers/virtualbox/driver/version_4_0.rb | 15 +++++++++++---- .../providers/virtualbox/driver/version_4_1.rb | 15 +++++++++++---- .../providers/virtualbox/driver/version_4_2.rb | 15 +++++++++++---- .../providers/virtualbox/driver/version_4_3.rb | 15 +++++++++++---- .../providers/virtualbox/driver/version_5_0.rb | 15 +++++++++++---- 5 files changed, 55 insertions(+), 20 deletions(-) diff --git a/plugins/providers/virtualbox/driver/version_4_0.rb b/plugins/providers/virtualbox/driver/version_4_0.rb index 7c37a69b7..43db68f0d 100644 --- a/plugins/providers/virtualbox/driver/version_4_0.rb +++ b/plugins/providers/virtualbox/driver/version_4_0.rb @@ -85,11 +85,18 @@ module VagrantPlugins execute("list", "vms").split("\n").each do |line| if vm_name = line[/^".+?"\s+\{(.+?)\}$/, 1] - info = execute("showvminfo", vm_name, "--machinereadable", retryable: true) - info.split("\n").each do |line| - if network_name = line[/^hostonlyadapter\d+="(.+?)"$/, 1] - networks.delete(network_name) + begin + info = execute("showvminfo", vm_name, "--machinereadable", retryable: true) + info.split("\n").each do |line| + if network_name = line[/^hostonlyadapter\d+="(.+?)"$/, 1] + networks.delete(network_name) + end end + rescue Vagrant::Errors::VBoxManageError => e + raise if !e.extra_data[:stderr].include?("VBOX_E_OBJECT_NOT_FOUND") + + # VirtualBox could not find the vm. It may have been deleted + # by another process after we called 'vboxmanage list vms'? Ignore this error. end end end diff --git a/plugins/providers/virtualbox/driver/version_4_1.rb b/plugins/providers/virtualbox/driver/version_4_1.rb index 9093dc16a..ac07efd3f 100644 --- a/plugins/providers/virtualbox/driver/version_4_1.rb +++ b/plugins/providers/virtualbox/driver/version_4_1.rb @@ -176,11 +176,18 @@ module VagrantPlugins execute("list", "vms").split("\n").each do |line| if vm = line[/^".+?"\s+\{(.+?)\}$/, 1] - info = execute("showvminfo", vm, "--machinereadable", retryable: true) - info.split("\n").each do |line| - if adapter = line[/^hostonlyadapter\d+="(.+?)"$/, 1] - networks.delete(adapter) + begin + info = execute("showvminfo", vm, "--machinereadable", retryable: true) + info.split("\n").each do |line| + if adapter = line[/^hostonlyadapter\d+="(.+?)"$/, 1] + networks.delete(adapter) + end end + rescue Vagrant::Errors::VBoxManageError => e + raise if !e.extra_data[:stderr].include?("VBOX_E_OBJECT_NOT_FOUND") + + # VirtualBox could not find the vm. It may have been deleted + # by another process after we called 'vboxmanage list vms'? Ignore this error. end end end diff --git a/plugins/providers/virtualbox/driver/version_4_2.rb b/plugins/providers/virtualbox/driver/version_4_2.rb index de2c46dad..2cae69d43 100644 --- a/plugins/providers/virtualbox/driver/version_4_2.rb +++ b/plugins/providers/virtualbox/driver/version_4_2.rb @@ -83,11 +83,18 @@ module VagrantPlugins execute("list", "vms").split("\n").each do |line| if line =~ /^".+?"\s+\{(.+?)\}$/ - info = execute("showvminfo", $1.to_s, "--machinereadable", retryable: true) - info.split("\n").each do |inner_line| - if inner_line =~ /^hostonlyadapter\d+="(.+?)"$/ - networks.delete($1.to_s) + begin + info = execute("showvminfo", $1.to_s, "--machinereadable", retryable: true) + info.split("\n").each do |inner_line| + if inner_line =~ /^hostonlyadapter\d+="(.+?)"$/ + networks.delete($1.to_s) + end end + rescue Vagrant::Errors::VBoxManageError => e + raise if !e.extra_data[:stderr].include?("VBOX_E_OBJECT_NOT_FOUND") + + # VirtualBox could not find the vm. It may have been deleted + # by another process after we called 'vboxmanage list vms'? Ignore this error. end end end diff --git a/plugins/providers/virtualbox/driver/version_4_3.rb b/plugins/providers/virtualbox/driver/version_4_3.rb index ce125625c..8a1377055 100644 --- a/plugins/providers/virtualbox/driver/version_4_3.rb +++ b/plugins/providers/virtualbox/driver/version_4_3.rb @@ -180,11 +180,18 @@ module VagrantPlugins execute("list", "vms", retryable: true).split("\n").each do |line| if line =~ /^".+?"\s+\{(.+?)\}$/ - info = execute("showvminfo", $1.to_s, "--machinereadable", retryable: true) - info.split("\n").each do |inner_line| - if inner_line =~ /^hostonlyadapter\d+="(.+?)"$/ - networks.delete($1.to_s) + begin + info = execute("showvminfo", $1.to_s, "--machinereadable", retryable: true) + info.split("\n").each do |inner_line| + if inner_line =~ /^hostonlyadapter\d+="(.+?)"$/ + networks.delete($1.to_s) + end end + rescue Vagrant::Errors::VBoxManageError => e + raise if !e.extra_data[:stderr].include?("VBOX_E_OBJECT_NOT_FOUND") + + # VirtualBox could not find the vm. It may have been deleted + # by another process after we called 'vboxmanage list vms'? Ignore this error. end end end diff --git a/plugins/providers/virtualbox/driver/version_5_0.rb b/plugins/providers/virtualbox/driver/version_5_0.rb index b77a5dc29..7810307c8 100644 --- a/plugins/providers/virtualbox/driver/version_5_0.rb +++ b/plugins/providers/virtualbox/driver/version_5_0.rb @@ -176,11 +176,18 @@ module VagrantPlugins execute("list", "vms", retryable: true).split("\n").each do |line| if line =~ /^".+?"\s+\{(.+?)\}$/ - info = execute("showvminfo", $1.to_s, "--machinereadable", retryable: true) - info.split("\n").each do |inner_line| - if inner_line =~ /^hostonlyadapter\d+="(.+?)"$/ - networks.delete($1.to_s) + begin + info = execute("showvminfo", $1.to_s, "--machinereadable", retryable: true) + info.split("\n").each do |inner_line| + if inner_line =~ /^hostonlyadapter\d+="(.+?)"$/ + networks.delete($1.to_s) + end end + rescue Vagrant::Errors::VBoxManageError => e + raise if !e.extra_data[:stderr].include?("VBOX_E_OBJECT_NOT_FOUND") + + # VirtualBox could not find the vm. It may have been deleted + # by another process after we called 'vboxmanage list vms'? Ignore this error. end end end From 2e775e81018be99ac5b3ba79d32c7a9422252af1 Mon Sep 17 00:00:00 2001 From: Samuel Clark Date: Tue, 25 Apr 2017 12:30:08 -0700 Subject: [PATCH 41/51] 8468 - make more virtualbox commands retryable Issue: https://github.com/mitchellh/vagrant/issues/8468 A lot of vboxmanage commands are flakey and frequently cause bringing multiple machines up at once to fail, especially when the host system is under heavy load. Most commands are also safe to retry and just result in a no-op, so we can simply add 'retryable' to a lot of existing calls. For the others we need to do a little bit of cleanup or reevaluate the parameters before trying again. --- .../virtualbox/driver/version_5_0.rb | 185 +++++++++++------- 1 file changed, 115 insertions(+), 70 deletions(-) diff --git a/plugins/providers/virtualbox/driver/version_5_0.rb b/plugins/providers/virtualbox/driver/version_5_0.rb index 7810307c8..3e12c4665 100644 --- a/plugins/providers/virtualbox/driver/version_5_0.rb +++ b/plugins/providers/virtualbox/driver/version_5_0.rb @@ -17,19 +17,23 @@ module VagrantPlugins end def clear_forwarded_ports - args = [] - read_forwarded_ports(@uuid).each do |nic, name, _, _| - args.concat(["--natpf#{nic}", "delete", name]) - end + retryable(on: Vagrant::Errors::VBoxManageError, tries: 3, sleep: 1) do + args = [] + read_forwarded_ports(@uuid).each do |nic, name, _, _| + args.concat(["--natpf#{nic}", "delete", name]) + end - execute("modifyvm", @uuid, *args) if !args.empty? + execute("modifyvm", @uuid, *args) if !args.empty? + end end def clear_shared_folders - info = execute("showvminfo", @uuid, "--machinereadable", retryable: true) - info.split("\n").each do |line| - if line =~ /^SharedFolderNameMachineMapping\d+="(.+?)"$/ - execute("sharedfolder", "remove", @uuid, "--name", $1.to_s) + retryable(on: Vagrant::Errors::VBoxManageError, tries: 3, sleep: 1) do + info = execute("showvminfo", @uuid, "--machinereadable", retryable: true) + info.split("\n").each do |line| + if line =~ /^SharedFolderNameMachineMapping\d+="(.+?)"$/ + execute("sharedfolder", "remove", @uuid, "--name", $1.to_s) + end end end end @@ -41,22 +45,29 @@ module VagrantPlugins args += ["--snapshot", snapshot_name, "--options", "link"] end - execute("clonevm", master_id, *args) + execute("clonevm", master_id, *args, retryable: true) return get_machine_id(machine_name) end def create_dhcp_server(network, options) - execute("dhcpserver", "add", "--ifname", network, - "--ip", options[:dhcp_ip], - "--netmask", options[:netmask], - "--lowerip", options[:dhcp_lower], - "--upperip", options[:dhcp_upper], - "--enable") + retryable(on: Vagrant::Errors::VBoxManageError, tries: 3, sleep: 1) do + begin + execute("dhcpserver", "add", "--ifname", network, + "--ip", options[:dhcp_ip], + "--netmask", options[:netmask], + "--lowerip", options[:dhcp_lower], + "--upperip", options[:dhcp_upper], + "--enable") + rescue Vagrant::Errors::VBoxManageError => e + return if e.extra_data[:stderr] == 'VBoxManage: error: DHCP server already exists' + raise + end + end end def create_host_only_network(options) # Create the interface - execute("hostonlyif", "create") =~ /^Interface '(.+?)' was successfully created$/ + execute("hostonlyif", "create", retryable: true) =~ /^Interface '(.+?)' was successfully created$/ name = $1.to_s # Get the IP so we can determine v4 vs v6 @@ -66,11 +77,13 @@ module VagrantPlugins if ip.ipv4? execute("hostonlyif", "ipconfig", name, "--ip", options[:adapter_ip], - "--netmask", options[:netmask]) + "--netmask", options[:netmask], + retryable: true) elsif ip.ipv6? execute("hostonlyif", "ipconfig", name, "--ipv6", options[:adapter_ip], - "--netmasklengthv6", options[:netmask].to_s) + "--netmasklengthv6", options[:netmask].to_s, + retryable: true) else raise "BUG: Unknown IP type: #{ip.inspect}" end @@ -85,7 +98,26 @@ module VagrantPlugins end def create_snapshot(machine_id, snapshot_name) - execute("snapshot", machine_id, "take", snapshot_name) + execute("snapshot", machine_id, "take", snapshot_name, retryable: true) do |type, data| + if type == :stderr + # Append the data so we can see the full view + total << data.gsub("\r", "") + + # Break up the lines. We can't get the progress until we see an "OK" + lines = total.split("\n") + + # The progress of the import will be in the last line. Do a greedy + # regular expression to find what we're looking for. + match = /.+(\d{2})%/.match(lines.last) + if match + current = match[1].to_i + if current > last + last = current + yield current if block_given? + end + end + end + end end def delete_snapshot(machine_id, snapshot_name) @@ -95,7 +127,7 @@ module VagrantPlugins yield 0 if block_given? # Snapshot and report the % progress - execute("snapshot", machine_id, "delete", snapshot_name) do |type, data| + execute("snapshot", machine_id, "delete", snapshot_name, retryable: true) do |type, data| if type == :stderr # Append the data so we can see the full view total << data.gsub("\r", "") @@ -142,7 +174,7 @@ module VagrantPlugins total = "" yield 0 if block_given? - execute("snapshot", machine_id, "restore", snapshot_name) do |type, data| + execute("snapshot", machine_id, "restore", snapshot_name, retryable: true) do |type, data| if type == :stderr # Append the data so we can see the full view total << data.gsub("\r", "") @@ -165,7 +197,7 @@ module VagrantPlugins end def delete - execute("unregistervm", @uuid, "--delete") + execute("unregistervm", @uuid, "--delete", retryable: true) end def delete_unused_host_only_networks @@ -199,12 +231,12 @@ module VagrantPlugins raw("dhcpserver", "remove", "--ifname", name) # Delete the actual host only network interface. - execute("hostonlyif", "remove", name) + execute("hostonlyif", "remove", name, retryable: true) end end def discard_saved_state - execute("discardstate", @uuid) + execute("discardstate", @uuid, retryable: true) end def enable_adapters(adapters) @@ -237,7 +269,7 @@ module VagrantPlugins end end - execute("modifyvm", @uuid, *args) + execute("modifyvm", @uuid, *args, retryable: true) end def execute_command(command) @@ -245,11 +277,20 @@ module VagrantPlugins end def export(path) - execute("export", @uuid, "--output", path.to_s) + retryable(on: Vagrant::Errors::VBoxManageError, tries: 3, sleep: 1) do + begin + execute("export", @uuid, "--output", path.to_s) + rescue Vagrant::Errors::VBoxManageError => e + raise if !e.extra_data[:stderr].include?("VERR_E_FILE_ERROR") + + # If the file already exists we'll throw a custom error + raise Vagrant::Errors::VirtualBoxFileExists, + stderr: e.extra_data[:stderr] + end + end end def forward_ports(ports) - args = [] ports.each do |options| pf_builder = [options[:name], options[:protocol] || "tcp", @@ -258,11 +299,11 @@ module VagrantPlugins options[:guestip] || "", options[:guestport]] - args.concat(["--natpf#{options[:adapter] || 1}", - pf_builder.join(",")]) - end + args = ["--natpf#{options[:adapter] || 1}", + pf_builder.join(",")] - execute("modifyvm", @uuid, *args) if !args.empty? + execute("modifyvm", @uuid, *args, retryable: true) + end end def get_machine_id(machine_name) @@ -273,7 +314,7 @@ module VagrantPlugins end def halt - execute("controlvm", @uuid, "poweroff") + execute("controlvm", @uuid, "poweroff", retryable: true) end def import(ovf) @@ -322,7 +363,7 @@ module VagrantPlugins end end - execute("import", ovf , *name_params, *disk_params) do |type, data| + execute("import", ovf , *name_params, *disk_params, retryable: true) do |type, data| if type == :stdout # Keep track of the stdout so that we can get the VM name output << data @@ -604,26 +645,30 @@ module VagrantPlugins def reconfig_host_only(interface) execute("hostonlyif", "ipconfig", interface[:name], - "--ipv6", interface[:ipv6]) + "--ipv6", interface[:ipv6], retryable: true) end def remove_dhcp_server(network_name) - execute("dhcpserver", "remove", "--netname", network_name) + execute("dhcpserver", "remove", "--netname", network_name, retryable: true) end def set_mac_address(mac) - execute("modifyvm", @uuid, "--macaddress1", mac) + execute("modifyvm", @uuid, "--macaddress1", mac, retryable: true) end def set_name(name) - execute("modifyvm", @uuid, "--name", name, retryable: true) - rescue Vagrant::Errors::VBoxManageError => e - raise if !e.extra_data[:stderr].include?("VERR_ALREADY_EXISTS") + retryable(on: Vagrant::Errors::VBoxManageError, tries: 3, sleep: 1) do + begin + execute("modifyvm", @uuid, "--name", name) + rescue Vagrant::Errors::VBoxManageError => e + raise if !e.extra_data[:stderr].include?("VERR_ALREADY_EXISTS") - # We got VERR_ALREADY_EXISTS. This means that we're renaming to - # a VM name that already exists. Raise a custom error. - raise Vagrant::Errors::VirtualBoxNameExists, - stderr: e.extra_data[:stderr] + # We got VERR_ALREADY_EXISTS. This means that we're renaming to + # a VM name that already exists. Raise a custom error. + raise Vagrant::Errors::VirtualBoxNameExists, + stderr: e.extra_data[:stderr] + end + end end def share_folders(folders) @@ -647,10 +692,10 @@ module VagrantPlugins args << "--transient" if folder.key?(:transient) && folder[:transient] # Enable symlinks on the shared folder - execute("setextradata", @uuid, "VBoxInternal2/SharedFoldersEnableSymlinksCreate/#{folder[:name]}", "1") + execute("setextradata", @uuid, "VBoxInternal2/SharedFoldersEnableSymlinksCreate/#{folder[:name]}", "1", retryable: true) # Add the shared folder - execute("sharedfolder", "add", @uuid, *args) + execute("sharedfolder", "add", @uuid, *args, retryable: true) end end @@ -672,40 +717,40 @@ module VagrantPlugins def start(mode) command = ["startvm", @uuid, "--type", mode.to_s] - r = raw(*command) + retryable(on: Vagrant::Errors::VBoxManageError, tries: 3, sleep: 1) do + r = raw(*command) - if r.exit_code == 0 || r.stdout =~ /VM ".+?" has been successfully started/ - # Some systems return an exit code 1 for some reason. For that - # we depend on the output. - return true + if r.exit_code == 0 || r.stdout =~ /VM ".+?" has been successfully started/ + # Some systems return an exit code 1 for some reason. For that + # we depend on the output. + return true + end + + # If we reached this point then it didn't work out. + raise Vagrant::Errors::VBoxManageError, + command: command.inspect, + stderr: r.stderr end - - # If we reached this point then it didn't work out. - raise Vagrant::Errors::VBoxManageError, - command: command.inspect, - stderr: r.stderr end def suspend - execute("controlvm", @uuid, "savestate") + execute("controlvm", @uuid, "savestate", retryable: true) end def unshare_folders(names) names.each do |name| - begin - execute( - "sharedfolder", "remove", @uuid, - "--name", name, - "--transient") + retryable(on: Vagrant::Errors::VBoxManageError, tries: 3, sleep: 1) do + begin + execute( + "sharedfolder", "remove", @uuid, + "--name", name, + "--transient") - execute( - "setextradata", @uuid, - "VBoxInternal2/SharedFoldersEnableSymlinksCreate/#{name}") - rescue Vagrant::Errors::VBoxManageError => e - if e.extra_data[:stderr].include?("VBOX_E_FILE_ERROR") - # The folder doesn't exist. ignore. - else - raise + execute( + "setextradata", @uuid, + "VBoxInternal2/SharedFoldersEnableSymlinksCreate/#{name}") + rescue Vagrant::Errors::VBoxManageError => e + raise if !e.extra_data[:stderr].include?("VBOX_E_FILE_ERROR") end end end From 9b872c9e7e9827d7d40eebb7c5cf4ad96d8c69b6 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Wed, 6 Sep 2017 11:06:45 -0700 Subject: [PATCH 42/51] Cleanup virtualbox retryable commands Reverting the changes done in 7d2f7dab977d51a21381577a5345f96c22142711 because they don't work and or update vagrant to invoke running the vbox cli tool for every single forwarded port instead of forwarding them all in one command. --- .../virtualbox/driver/version_5_0.rb | 30 ++++--------------- 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/plugins/providers/virtualbox/driver/version_5_0.rb b/plugins/providers/virtualbox/driver/version_5_0.rb index 3e12c4665..3efb31f38 100644 --- a/plugins/providers/virtualbox/driver/version_5_0.rb +++ b/plugins/providers/virtualbox/driver/version_5_0.rb @@ -98,26 +98,7 @@ module VagrantPlugins end def create_snapshot(machine_id, snapshot_name) - execute("snapshot", machine_id, "take", snapshot_name, retryable: true) do |type, data| - if type == :stderr - # Append the data so we can see the full view - total << data.gsub("\r", "") - - # Break up the lines. We can't get the progress until we see an "OK" - lines = total.split("\n") - - # The progress of the import will be in the last line. Do a greedy - # regular expression to find what we're looking for. - match = /.+(\d{2})%/.match(lines.last) - if match - current = match[1].to_i - if current > last - last = current - yield current if block_given? - end - end - end - end + execute("snapshot", machine_id, "take", snapshot_name, retryable: true) end def delete_snapshot(machine_id, snapshot_name) @@ -291,6 +272,7 @@ module VagrantPlugins end def forward_ports(ports) + args = [] ports.each do |options| pf_builder = [options[:name], options[:protocol] || "tcp", @@ -299,11 +281,11 @@ module VagrantPlugins options[:guestip] || "", options[:guestport]] - args = ["--natpf#{options[:adapter] || 1}", - pf_builder.join(",")] - - execute("modifyvm", @uuid, *args, retryable: true) + args.concat(["--natpf#{options[:adapter] || 1}", + pf_builder.join(",")]) end + + execute("modifyvm", @uuid, *args, retryable: true) if !args.empty? end def get_machine_id(machine_name) From ac75e409a3470897d56a0841a575e981d60e2e3d Mon Sep 17 00:00:00 2001 From: Gilles Cornu Date: Wed, 6 Sep 2017 21:22:41 +0200 Subject: [PATCH 43/51] provisioners/ansible(both): Quote host_vars if needed This patch is based on @subimage's inputs in the related GitHub issue. Thanks again! Fix #8597 --- CHANGELOG.md | 1 + plugins/provisioners/ansible/provisioner/base.rb | 8 +++++++- .../unit/plugins/provisioners/ansible/provisioner_test.rb | 8 ++++++-- website/source/docs/provisioning/ansible_common.html.md | 2 +- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f4c4b648..8702b05fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ BUG FIXES: - guests/shell_expand_guest_path : Properly expand guest paths that include relative path alias [GH-8918] - hosts/linux: Remove duplicate export folders before writing /etc/exports [GH-8945] +- provisioners/ansible(both): Add single quotes to the inventory host variables, only when necessary [GH-8597] - provisioners/ansible(both): Add the "all:vars" section to the inventory when defined in `groups` option [GH-7730] - provisioners/ansible_local: Extra variables are no longer truncated when a dollar ($) character is present [GH-7735] - provisioners/file: Align file provisioner functionality on all platforms [GH-8939] diff --git a/plugins/provisioners/ansible/provisioner/base.rb b/plugins/provisioners/ansible/provisioner/base.rb index c2add455a..97bc04852 100644 --- a/plugins/provisioners/ansible/provisioner/base.rb +++ b/plugins/provisioners/ansible/provisioner/base.rb @@ -148,7 +148,13 @@ module VagrantPlugins end s = nil if vars.is_a?(Hash) - s = vars.each.collect{ |k, v| "#{k}=#{v}" }.join(" ") + s = vars.each.collect { + |k, v| + if v.is_a?(String) && v.include?(' ') && !v.match(/^('|")[^'"]+('|")$/) + v = %Q('#{v}') + end + "#{k}=#{v}" + }.join(" ") elsif vars.is_a?(Array) s = vars.join(" ") elsif vars.is_a?(String) diff --git a/test/unit/plugins/provisioners/ansible/provisioner_test.rb b/test/unit/plugins/provisioners/ansible/provisioner_test.rb index 861a4e1c6..c6b2d795c 100644 --- a/test/unit/plugins/provisioners/ansible/provisioner_test.rb +++ b/test/unit/plugins/provisioners/ansible/provisioner_test.rb @@ -291,11 +291,15 @@ VF it "adds host variables (given in Hash format) to the generated inventory" do config.host_vars = { - machine1: {"http_port" => 80, "comments" => "'some text with spaces'"} + machine1: { + "http_port" => 80, + "comments" => "'some text with spaces and quotes'", + "description" => "text with spaces but no quotes", + } } expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { inventory_content = File.read(generated_inventory_file) - expect(inventory_content).to match("^" + Regexp.quote(machine.name) + ".+http_port=80 comments='some text with spaces'$") + expect(inventory_content).to match("^" + Regexp.quote(machine.name) + ".+http_port=80 comments='some text with spaces and quotes' description='text with spaces but no quotes'") }.and_return(default_execute_result) end diff --git a/website/source/docs/provisioning/ansible_common.html.md b/website/source/docs/provisioning/ansible_common.html.md index f6fb84f07..a528f1a05 100644 --- a/website/source/docs/provisioning/ansible_common.html.md +++ b/website/source/docs/provisioning/ansible_common.html.md @@ -93,7 +93,7 @@ Some of these options are for advanced usage only and should not be used unless ansible.host_vars = { "host1" => {"http_port" => 80, "maxRequestsPerChild" => 808}, - "comments" => "'text with spaces'", + "comments" => "text with spaces", "host2" => {"http_port" => 303, "maxRequestsPerChild" => 909} } From 0363bd9c01c35da79ce1e628879a14951dbf619c Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Wed, 6 Sep 2017 13:05:49 -0700 Subject: [PATCH 44/51] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8702b05fb..4ba501d67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ IMPROVEMENTS: - commands/login: Add support for two-factor authentication [GH-8935] - commands/ssh-config: Properly display windows path if invoked from msys2 or cygwin [GH-8915] +- guests/kali: Fix file permissions on guest plugin ruby files [GH-8950] - providers/salt: Remove duplicate stdout, stderr output from salt [GH-8767] - providers/salt: Introduce salt_call_args and salt_args option for salt provisioner [GH-8927] From 90d1911367a86233d36e31078c3ff596e0290ab0 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Wed, 6 Sep 2017 13:31:12 -0700 Subject: [PATCH 45/51] Update CHANGELOG --- CHANGELOG.md | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ba501d67..251d1e5e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,21 +4,34 @@ FEATURES: IMPROVEMENTS: -- commands/login: Add support for two-factor authentication [GH-8935] -- commands/ssh-config: Properly display windows path if invoked from msys2 or cygwin [GH-8915] -- guests/kali: Fix file permissions on guest plugin ruby files [GH-8950] -- providers/salt: Remove duplicate stdout, stderr output from salt [GH-8767] -- providers/salt: Introduce salt_call_args and salt_args option for salt provisioner [GH-8927] + - commands/login: Add support for two-factor authentication [GH-8935] + - commands/ssh-config: Properly display windows path if invoked from msys2 or cygwin [GH-8915] + - guests/kali: Fix file permissions on guest plugin ruby files [GH-8950] + - providers/salt: Remove duplicate stdout, stderr output from salt [GH-8767] + - providers/salt: Introduce salt_call_args and salt_args option for salt provisioner [GH-8927] + - provisioners/ansible(both): Add the compatibility_mode option, with auto-detection enabled by default [GH-8913, GH-6570] + - provisioners/ansible: Add the version option to the host-based provisioner [GH-8913, GH-8914] + - provisioners/ansible(both): Add the become and become_user options with deprecation of sudo and sudo_user options [GH-8913, GH-6570] + - provisioners/ansible: Add the ask_become_pass option with deprecation of the ask_sudo_pass option [GH-8913, GH-6570] BUG FIXES: -- guests/shell_expand_guest_path : Properly expand guest paths that include relative path alias [GH-8918] -- hosts/linux: Remove duplicate export folders before writing /etc/exports [GH-8945] -- provisioners/ansible(both): Add single quotes to the inventory host variables, only when necessary [GH-8597] -- provisioners/ansible(both): Add the "all:vars" section to the inventory when defined in `groups` option [GH-7730] -- provisioners/ansible_local: Extra variables are no longer truncated when a dollar ($) character is present [GH-7735] -- provisioners/file: Align file provisioner functionality on all platforms [GH-8939] -- util/ssh: Properly quote key path for IdentityFile option to allow for spaces [GH-8924] + - guests/shell_expand_guest_path : Properly expand guest paths that include relative path alias [GH-8918] + - hosts/linux: Remove duplicate export folders before writing /etc/exports [GH-8945] + - provisioners/ansible(both): Add single quotes to the inventory host variables, only when necessary [GH-8597] + - provisioners/ansible(both): Add the "all:vars" section to the inventory when defined in `groups` option [GH-7730] + - provisioners/ansible_local: Extra variables are no longer truncated when a dollar ($) character is present [GH-7735] + - provisioners/file: Align file provisioner functionality on all platforms [GH-8939] + - util/ssh: Properly quote key path for IdentityFile option to allow for spaces [GH-8924] + +BREAKING CHANGES: + + - Both Ansible provisioners are now capable of automatically setting the compatibility_mode that + best fits with the Ansible version in use. You may encounter some compatibility issues when + upgrading. If you were using Ansible 2.x and referring to the _ssh-prefixed variables present + in the generated inventory (e.g. `ansible_ssh_host`). In this case, you can fix your Vagrant + setup by setting compatibility_mode = "1.8", or by migrating to the new variable names (e.g. + ansible_host). ## 1.9.8 (August 23, 2017) From 15405cfa9b764dd59c7109104b3c10f6299dba51 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Wed, 6 Sep 2017 14:52:32 -0700 Subject: [PATCH 46/51] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 251d1e5e3..909b1323a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ IMPROVEMENTS: - guests/kali: Fix file permissions on guest plugin ruby files [GH-8950] - providers/salt: Remove duplicate stdout, stderr output from salt [GH-8767] - providers/salt: Introduce salt_call_args and salt_args option for salt provisioner [GH-8927] + - providers/virtualbox: Improving resilience of some VirtualBox commands [GH-8951] - provisioners/ansible(both): Add the compatibility_mode option, with auto-detection enabled by default [GH-8913, GH-6570] - provisioners/ansible: Add the version option to the host-based provisioner [GH-8913, GH-8914] - provisioners/ansible(both): Add the become and become_user options with deprecation of sudo and sudo_user options [GH-8913, GH-6570] From 1964e401d269b0718d6183700cc2c3be0b6a5bdf Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Wed, 6 Sep 2017 17:02:37 -0700 Subject: [PATCH 47/51] Update CHANGELOG --- CHANGELOG.md | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 909b1323a..2b5cd4f4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,23 +5,35 @@ FEATURES: IMPROVEMENTS: - commands/login: Add support for two-factor authentication [GH-8935] - - commands/ssh-config: Properly display windows path if invoked from msys2 or cygwin [GH-8915] + - commands/ssh-config: Properly display windows path if invoked from msys2 or + cygwin [GH-8915] - guests/kali: Fix file permissions on guest plugin ruby files [GH-8950] + - hosts/linux: Provide common systemd detection for services interaction, fix NFS + host interactions [GH-8938] - providers/salt: Remove duplicate stdout, stderr output from salt [GH-8767] - - providers/salt: Introduce salt_call_args and salt_args option for salt provisioner [GH-8927] + - providers/salt: Introduce salt_call_args and salt_args option for salt provisioner + [GH-8927] - providers/virtualbox: Improving resilience of some VirtualBox commands [GH-8951] - - provisioners/ansible(both): Add the compatibility_mode option, with auto-detection enabled by default [GH-8913, GH-6570] - - provisioners/ansible: Add the version option to the host-based provisioner [GH-8913, GH-8914] - - provisioners/ansible(both): Add the become and become_user options with deprecation of sudo and sudo_user options [GH-8913, GH-6570] - - provisioners/ansible: Add the ask_become_pass option with deprecation of the ask_sudo_pass option [GH-8913, GH-6570] + - provisioners/ansible(both): Add the compatibility_mode option, with auto-detection + enabled by default [GH-8913, GH-6570] + - provisioners/ansible: Add the version option to the host-based provisioner + [GH-8913, GH-8914] + - provisioners/ansible(both): Add the become and become_user options with deprecation + of sudo and sudo_user options [GH-8913, GH-6570] + - provisioners/ansible: Add the ask_become_pass option with deprecation of the + ask_sudo_pass option [GH-8913, GH-6570] BUG FIXES: - - guests/shell_expand_guest_path : Properly expand guest paths that include relative path alias [GH-8918] + - guests/shell_expand_guest_path : Properly expand guest paths that include relative + path alias [GH-8918] - hosts/linux: Remove duplicate export folders before writing /etc/exports [GH-8945] - - provisioners/ansible(both): Add single quotes to the inventory host variables, only when necessary [GH-8597] - - provisioners/ansible(both): Add the "all:vars" section to the inventory when defined in `groups` option [GH-7730] - - provisioners/ansible_local: Extra variables are no longer truncated when a dollar ($) character is present [GH-7735] + - provisioners/ansible(both): Add single quotes to the inventory host variables, only + when necessary [GH-8597] + - provisioners/ansible(both): Add the "all:vars" section to the inventory when defined + in `groups` option [GH-7730] + - provisioners/ansible_local: Extra variables are no longer truncated when a dollar ($) + character is present [GH-7735] - provisioners/file: Align file provisioner functionality on all platforms [GH-8939] - util/ssh: Properly quote key path for IdentityFile option to allow for spaces [GH-8924] From 27ca217eae92d6c1f135ea2f8f9841d3ed782c38 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Wed, 6 Sep 2017 17:50:39 -0700 Subject: [PATCH 48/51] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b5cd4f4f..812b10878 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ IMPROVEMENTS: - commands/login: Add support for two-factor authentication [GH-8935] - commands/ssh-config: Properly display windows path if invoked from msys2 or cygwin [GH-8915] + - guests/alt: Add support for ALT Linux [GH-8746] - guests/kali: Fix file permissions on guest plugin ruby files [GH-8950] - hosts/linux: Provide common systemd detection for services interaction, fix NFS host interactions [GH-8938] From d1cf0f77e761289f0cc0b119067ba1903a2891c9 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Thu, 7 Sep 2017 05:43:26 -0700 Subject: [PATCH 49/51] Release v2.0.0 --- CHANGELOG.md | 4 +--- version.txt | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 812b10878..e1411af69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,4 @@ -## Next version (Unreleased) - -FEATURES: +## 2.0.0 (September 7, 2017) IMPROVEMENTS: diff --git a/version.txt b/version.txt index 2a64ecf1b..227cea215 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.9.9.dev +2.0.0 From 8610578d8d6af7bdcc40576599e732b6361273dc Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Thu, 7 Sep 2017 07:46:51 -0700 Subject: [PATCH 50/51] Update vagrant website version to 2.0.0 --- website/config.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/config.rb b/website/config.rb index a765ec2d7..b9042d601 100644 --- a/website/config.rb +++ b/website/config.rb @@ -2,7 +2,7 @@ set :base_url, "https://www.vagrantup.com/" activate :hashicorp do |h| h.name = "vagrant" - h.version = "1.9.8" + h.version = "2.0.0" h.github_slug = "mitchellh/vagrant" h.website_root = "website" end From a124f6635844848f4369e620e1607701d22ddcb9 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Thu, 7 Sep 2017 07:49:11 -0700 Subject: [PATCH 51/51] Update version for dev --- CHANGELOG.md | 8 ++++++++ version.txt | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1411af69..66d0c00d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## Next version (Unreleased) + +FEATURES: + +IMPROVEMENTS: + +BUG FIXES: + ## 2.0.0 (September 7, 2017) IMPROVEMENTS: diff --git a/version.txt b/version.txt index 227cea215..335195d0f 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.0.0 +2.0.1.dev