provisioners/ansible: force --connection=ssh
When `--connection` argument is not specified, Ansible will use the 'smart' mode, which can either use `ssh` or `paramiko` transports, depending of the version of OpenSSH available. If OpenSSH version is new enough to support ControlPersist technology, `ssh` will be used. See also http://docs.ansible.com/intro_configuration.html#transport. In order to support some advanced features of Vagrant (e.g. multiple ssh private key identities or ssh forwarding), the Ansible provisioner already must force `ssh` connection mode. Having to deal with the possible fallback to `paramiko` increase the burden of special cases that Ansible provisioner must handle, without any added value, as Vagrant is based on OpenSSH and its users are usually using modern operating systems. With this change, the Ansible provisioner will officially only support `ssh`. It will still be possible to switch to another connection mode via `raw_arguments`, but it will breach the "contract", and no (community) support can be expected in such use case. ref #3900, #3396
This commit is contained in:
parent
1c884fa4e5
commit
306c4f7eda
|
@ -88,6 +88,7 @@ BUG FIXES:
|
||||||
IP address and don't allow it. [GH-4671]
|
IP address and don't allow it. [GH-4671]
|
||||||
- providers/virtualbox: Show more descriptive error if VirtualBox is
|
- providers/virtualbox: Show more descriptive error if VirtualBox is
|
||||||
reporting an empty version. [GH-4657]
|
reporting an empty version. [GH-4657]
|
||||||
|
- provisioners/ansible: Force `ssh` (OpenSSH) connection by default [GH-3396]
|
||||||
- provisioners/docker: Get GPG key over SSL. [GH-4597]
|
- provisioners/docker: Get GPG key over SSL. [GH-4597]
|
||||||
- provisioners/docker: Search for docker binary in multiple places. [GH-4580]
|
- provisioners/docker: Search for docker binary in multiple places. [GH-4580]
|
||||||
- provisioners/salt: Highstate works properly with a master. [GH-4471]
|
- provisioners/salt: Highstate works properly with a master. [GH-4471]
|
||||||
|
|
|
@ -18,11 +18,10 @@ module VagrantPlugins
|
||||||
# Connect with Vagrant SSH identity
|
# Connect with Vagrant SSH identity
|
||||||
options = %W[--private-key=#{@ssh_info[:private_key_path][0]} --user=#{@ssh_info[:username]}]
|
options = %W[--private-key=#{@ssh_info[:private_key_path][0]} --user=#{@ssh_info[:username]}]
|
||||||
|
|
||||||
# Multiple SSH keys and/or SSH forwarding can be passed via
|
# Connect with native OpenSSH client
|
||||||
# ANSIBLE_SSH_ARGS environment variable, which requires 'ssh' mode.
|
# Other modes (e.g. paramiko) are not officially supported,
|
||||||
# Note that multiple keys and ssh-forwarding settings are not supported
|
# but can be enabled via raw_arguments option.
|
||||||
# by deprecated 'paramiko' mode.
|
options << "--connection=ssh"
|
||||||
options << "--connection=ssh" unless ansible_ssh_args.empty?
|
|
||||||
|
|
||||||
# By default we limit by the current machine.
|
# By default we limit by the current machine.
|
||||||
# This can be overridden by the limit config option.
|
# This can be overridden by the limit config option.
|
||||||
|
@ -54,16 +53,17 @@ module VagrantPlugins
|
||||||
# Assemble the full ansible-playbook command
|
# Assemble the full ansible-playbook command
|
||||||
command = (%w(ansible-playbook) << options << config.playbook).flatten
|
command = (%w(ansible-playbook) << options << config.playbook).flatten
|
||||||
|
|
||||||
# Some Ansible options must be passed as environment variables
|
|
||||||
env = {
|
env = {
|
||||||
"ANSIBLE_FORCE_COLOR" => "true",
|
|
||||||
"ANSIBLE_HOST_KEY_CHECKING" => "#{config.host_key_checking}",
|
|
||||||
|
|
||||||
# Ensure Ansible output isn't buffered so that we receive output
|
# Ensure Ansible output isn't buffered so that we receive output
|
||||||
# on a task-by-task basis.
|
# on a task-by-task basis.
|
||||||
"PYTHONUNBUFFERED" => 1
|
"PYTHONUNBUFFERED" => 1,
|
||||||
|
|
||||||
|
# Some Ansible options must be passed as environment variables,
|
||||||
|
# as there is no equivalent command line arguments
|
||||||
|
"ANSIBLE_FORCE_COLOR" => "true",
|
||||||
|
"ANSIBLE_HOST_KEY_CHECKING" => "#{config.host_key_checking}",
|
||||||
}
|
}
|
||||||
# Support Multiple SSH keys and SSH forwarding:
|
# ANSIBLE_SSH_ARGS is required for Multiple SSH keys, SSH forwarding and custom SSH settings
|
||||||
env["ANSIBLE_SSH_ARGS"] = ansible_ssh_args unless ansible_ssh_args.empty?
|
env["ANSIBLE_SSH_ARGS"] = ansible_ssh_args unless ansible_ssh_args.empty?
|
||||||
|
|
||||||
show_ansible_playbook_command(env, command) if (config.verbose || @logger.debug?)
|
show_ansible_playbook_command(env, command) if (config.verbose || @logger.debug?)
|
||||||
|
|
|
@ -62,13 +62,16 @@ VF
|
||||||
# Class methods for code reuse across examples
|
# Class methods for code reuse across examples
|
||||||
#
|
#
|
||||||
|
|
||||||
def self.it_should_set_arguments_and_environment_variables(expected_args_count = 5, expected_vars_count = 3, expected_host_key_checking = false)
|
def self.it_should_set_arguments_and_environment_variables(
|
||||||
|
expected_args_count = 6, expected_vars_count = 3, expected_host_key_checking = false, expected_transport_mode = "ssh")
|
||||||
|
|
||||||
it "sets implicit arguments in a specific order" do
|
it "sets implicit arguments in a specific order" do
|
||||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args|
|
expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args|
|
||||||
|
|
||||||
expect(args[0]).to eq("ansible-playbook")
|
expect(args[0]).to eq("ansible-playbook")
|
||||||
expect(args[1]).to eq("--private-key=#{machine.ssh_info[:private_key_path][0]}")
|
expect(args[1]).to eq("--private-key=#{machine.ssh_info[:private_key_path][0]}")
|
||||||
expect(args[2]).to eq("--user=#{machine.ssh_info[:username]}")
|
expect(args[2]).to eq("--user=#{machine.ssh_info[:username]}")
|
||||||
|
expect(args[3]).to eq("--connection=ssh")
|
||||||
|
|
||||||
inventory_count = args.count { |x| x =~ /^--inventory-file=.+$/ }
|
inventory_count = args.count { |x| x =~ /^--inventory-file=.+$/ }
|
||||||
expect(inventory_count).to be > 0
|
expect(inventory_count).to be > 0
|
||||||
|
@ -111,6 +114,15 @@ VF
|
||||||
expect(args.last[:env].length).to eq(expected_vars_count)
|
expect(args.last[:env].length).to eq(expected_vars_count)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "enables '#{expected_transport_mode}' transport mode" do
|
||||||
|
expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*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
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.it_should_set_optional_arguments(arg_map)
|
def self.it_should_set_optional_arguments(arg_map)
|
||||||
|
@ -128,35 +140,7 @@ VF
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.it_should_use_smart_transport_mode
|
def self.it_should_explicitly_enable_ansible_ssh_control_persist_defaults
|
||||||
it "does not export ANSIBLE_SSH_ARGS" do
|
|
||||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args|
|
|
||||||
cmd_opts = args.last
|
|
||||||
expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to be_nil
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
it "does not force any transport mode" do
|
|
||||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args|
|
|
||||||
total = args.count { |x| x =~ /^--connection=\w+$/ }
|
|
||||||
expect(total).to eql(0)
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.it_should_use_transport_mode(transport_mode)
|
|
||||||
it "enables '#{transport_mode}' transport mode" do
|
|
||||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args|
|
|
||||||
index = args.rindex("--connection=#{transport_mode}")
|
|
||||||
expect(index).to be > 0
|
|
||||||
expect(find_last_argument_after(index, args, /--connection=\w+/)).to be_false
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.it_should_force_ssh_transport_mode
|
|
||||||
it_should_use_transport_mode('ssh')
|
|
||||||
|
|
||||||
it "configures ControlPersist (like Ansible defaults) via ANSIBLE_SSH_ARGS" do
|
it "configures ControlPersist (like Ansible defaults) via ANSIBLE_SSH_ARGS" do
|
||||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args|
|
expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args|
|
||||||
cmd_opts = args.last
|
cmd_opts = args.last
|
||||||
|
@ -212,7 +196,6 @@ VF
|
||||||
|
|
||||||
describe "with default options" do
|
describe "with default options" do
|
||||||
it_should_set_arguments_and_environment_variables
|
it_should_set_arguments_and_environment_variables
|
||||||
it_should_use_smart_transport_mode
|
|
||||||
it_should_create_and_use_generated_inventory
|
it_should_create_and_use_generated_inventory
|
||||||
|
|
||||||
it "does not add any group section to the generated inventory" do
|
it "does not add any group section to the generated inventory" do
|
||||||
|
@ -281,8 +264,7 @@ VF
|
||||||
config.host_key_checking = true
|
config.host_key_checking = true
|
||||||
end
|
end
|
||||||
|
|
||||||
it_should_set_arguments_and_environment_variables 5, 3, true
|
it_should_set_arguments_and_environment_variables 6, 3, true
|
||||||
it_should_use_smart_transport_mode
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "with boolean (flag) options disabled" do
|
describe "with boolean (flag) options disabled" do
|
||||||
|
@ -294,7 +276,7 @@ VF
|
||||||
config.sudo_user = 'root'
|
config.sudo_user = 'root'
|
||||||
end
|
end
|
||||||
|
|
||||||
it_should_set_arguments_and_environment_variables 6
|
it_should_set_arguments_and_environment_variables 7
|
||||||
it_should_set_optional_arguments({ "sudo_user" => "--sudo-user=root" })
|
it_should_set_optional_arguments({ "sudo_user" => "--sudo-user=root" })
|
||||||
|
|
||||||
it "it does not set boolean flag when corresponding option is set to false" do
|
it "it does not set boolean flag when corresponding option is set to false" do
|
||||||
|
@ -321,9 +303,8 @@ VF
|
||||||
"--new-arg=yeah"]
|
"--new-arg=yeah"]
|
||||||
end
|
end
|
||||||
|
|
||||||
it_should_set_arguments_and_environment_variables 15
|
it_should_set_arguments_and_environment_variables 16, 3, false, "paramiko"
|
||||||
it_should_create_and_use_generated_inventory
|
it_should_create_and_use_generated_inventory
|
||||||
it_should_use_transport_mode('paramiko')
|
|
||||||
|
|
||||||
it "sets all raw arguments" do
|
it "sets all raw arguments" do
|
||||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args|
|
expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args|
|
||||||
|
@ -361,7 +342,6 @@ VF
|
||||||
end
|
end
|
||||||
|
|
||||||
it_should_set_arguments_and_environment_variables
|
it_should_set_arguments_and_environment_variables
|
||||||
it_should_use_smart_transport_mode
|
|
||||||
|
|
||||||
it "does not generate the inventory and uses given inventory path instead" do
|
it "does not generate the inventory and uses given inventory path instead" do
|
||||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args|
|
expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args|
|
||||||
|
@ -377,7 +357,7 @@ VF
|
||||||
config.ask_vault_pass = true
|
config.ask_vault_pass = true
|
||||||
end
|
end
|
||||||
|
|
||||||
it_should_set_arguments_and_environment_variables 6
|
it_should_set_arguments_and_environment_variables 7
|
||||||
|
|
||||||
it "should ask the vault password" do
|
it "should ask the vault password" do
|
||||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args|
|
expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args|
|
||||||
|
@ -391,7 +371,7 @@ VF
|
||||||
config.vault_password_file = existing_file
|
config.vault_password_file = existing_file
|
||||||
end
|
end
|
||||||
|
|
||||||
it_should_set_arguments_and_environment_variables 6
|
it_should_set_arguments_and_environment_variables 7
|
||||||
|
|
||||||
it "uses the given vault password file" do
|
it "uses the given vault password file" do
|
||||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args|
|
expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args|
|
||||||
|
@ -406,7 +386,7 @@ VF
|
||||||
end
|
end
|
||||||
|
|
||||||
it_should_set_arguments_and_environment_variables 6, 4
|
it_should_set_arguments_and_environment_variables 6, 4
|
||||||
it_should_force_ssh_transport_mode
|
it_should_explicitly_enable_ansible_ssh_control_persist_defaults
|
||||||
|
|
||||||
it "passes custom SSH options via ANSIBLE_SSH_ARGS with the highest priority" do
|
it "passes custom SSH options via ANSIBLE_SSH_ARGS with the highest priority" do
|
||||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args|
|
expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args|
|
||||||
|
@ -440,7 +420,7 @@ VF
|
||||||
end
|
end
|
||||||
|
|
||||||
it_should_set_arguments_and_environment_variables 6, 4
|
it_should_set_arguments_and_environment_variables 6, 4
|
||||||
it_should_force_ssh_transport_mode
|
it_should_explicitly_enable_ansible_ssh_control_persist_defaults
|
||||||
|
|
||||||
it "passes additional Identity Files via ANSIBLE_SSH_ARGS" do
|
it "passes additional Identity Files via ANSIBLE_SSH_ARGS" do
|
||||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args|
|
expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args|
|
||||||
|
@ -457,7 +437,7 @@ VF
|
||||||
end
|
end
|
||||||
|
|
||||||
it_should_set_arguments_and_environment_variables 6, 4
|
it_should_set_arguments_and_environment_variables 6, 4
|
||||||
it_should_force_ssh_transport_mode
|
it_should_explicitly_enable_ansible_ssh_control_persist_defaults
|
||||||
|
|
||||||
it "enables SSH-Forwarding via ANSIBLE_SSH_ARGS" do
|
it "enables SSH-Forwarding via ANSIBLE_SSH_ARGS" do
|
||||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args|
|
expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args|
|
||||||
|
@ -475,7 +455,7 @@ VF
|
||||||
|
|
||||||
it "shows the ansible-playbook command" do
|
it "shows the ansible-playbook command" do
|
||||||
expect(machine.env.ui).to receive(:detail).with { |full_command|
|
expect(machine.env.ui).to receive(:detail).with { |full_command|
|
||||||
expect(full_command).to eq("ANSIBLE_FORCE_COLOR=true ANSIBLE_HOST_KEY_CHECKING=false PYTHONUNBUFFERED=1 ansible-playbook --private-key=/path/to/my/key --user=testuser --limit='machine1' --inventory-file=#{generated_inventory_dir} playbook.yml")
|
expect(full_command).to eq("PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ANSIBLE_HOST_KEY_CHECKING=false ansible-playbook --private-key=/path/to/my/key --user=testuser --connection=ssh --limit='machine1' --inventory-file=#{generated_inventory_dir} playbook.yml")
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -485,12 +465,12 @@ VF
|
||||||
config.verbose = 'v'
|
config.verbose = 'v'
|
||||||
end
|
end
|
||||||
|
|
||||||
it_should_set_arguments_and_environment_variables 6
|
it_should_set_arguments_and_environment_variables 7
|
||||||
it_should_set_optional_arguments({ "verbose" => "-v" })
|
it_should_set_optional_arguments({ "verbose" => "-v" })
|
||||||
|
|
||||||
it "shows the ansible-playbook command" do
|
it "shows the ansible-playbook command" do
|
||||||
expect(machine.env.ui).to receive(:detail).with { |full_command|
|
expect(machine.env.ui).to receive(:detail).with { |full_command|
|
||||||
expect(full_command).to eq("ANSIBLE_FORCE_COLOR=true ANSIBLE_HOST_KEY_CHECKING=false PYTHONUNBUFFERED=1 ansible-playbook --private-key=/path/to/my/key --user=testuser --limit='machine1' --inventory-file=#{generated_inventory_dir} -v playbook.yml")
|
expect(full_command).to eq("PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ANSIBLE_HOST_KEY_CHECKING=false ansible-playbook --private-key=/path/to/my/key --user=testuser --connection=ssh --limit='machine1' --inventory-file=#{generated_inventory_dir} -v playbook.yml")
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -524,7 +504,7 @@ VF
|
||||||
end
|
end
|
||||||
|
|
||||||
it_should_set_arguments_and_environment_variables 20, 4, true
|
it_should_set_arguments_and_environment_variables 20, 4, true
|
||||||
it_should_force_ssh_transport_mode
|
it_should_explicitly_enable_ansible_ssh_control_persist_defaults
|
||||||
it_should_set_optional_arguments({ "extra_vars" => "--extra-vars=@#{File.expand_path(__FILE__)}",
|
it_should_set_optional_arguments({ "extra_vars" => "--extra-vars=@#{File.expand_path(__FILE__)}",
|
||||||
"sudo" => "--sudo",
|
"sudo" => "--sudo",
|
||||||
"sudo_user" => "--sudo-user=deployer",
|
"sudo_user" => "--sudo-user=deployer",
|
||||||
|
@ -537,7 +517,7 @@ VF
|
||||||
"limit" => "--limit=machine*:&vagrant:!that_one",
|
"limit" => "--limit=machine*:&vagrant:!that_one",
|
||||||
"start_at_task" => "--start-at-task=an awesome task",
|
"start_at_task" => "--start-at-task=an awesome task",
|
||||||
})
|
})
|
||||||
|
|
||||||
it "also includes given raw arguments" do
|
it "also includes given raw arguments" do
|
||||||
expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args|
|
expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args|
|
||||||
expect(args).to include("--su-user=foot")
|
expect(args).to include("--su-user=foot")
|
||||||
|
@ -548,7 +528,7 @@ VF
|
||||||
|
|
||||||
it "shows the ansible-playbook command, with additional quotes when required" do
|
it "shows the ansible-playbook command, with additional quotes when required" do
|
||||||
expect(machine.env.ui).to receive(:detail).with { |full_command|
|
expect(machine.env.ui).to receive(:detail).with { |full_command|
|
||||||
expect(full_command).to eq("ANSIBLE_FORCE_COLOR=true ANSIBLE_HOST_KEY_CHECKING=true PYTHONUNBUFFERED=1 ANSIBLE_SSH_ARGS='-o IdentityFile=/my/key2 -o ForwardAgent=yes -o ControlMaster=no -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --private-key=/my/key1 --user=testuser --connection=ssh --why-not --su-user=foot --ask-su-pass --limit='all' --inventory-file=#{generated_inventory_dir} --extra-vars=@#{File.expand_path(__FILE__)} --sudo --sudo-user=deployer -vvv --ask-sudo-pass --ask-vault-pass --vault-password-file=#{File.expand_path(__FILE__)} --tags=db,www --skip-tags=foo,bar --limit='machine*:&vagrant:!that_one' --start-at-task='an awesome task' playbook.yml")
|
expect(full_command).to eq("PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ANSIBLE_HOST_KEY_CHECKING=true ANSIBLE_SSH_ARGS='-o IdentityFile=/my/key2 -o ForwardAgent=yes -o ControlMaster=no -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --private-key=/my/key1 --user=testuser --connection=ssh --why-not --su-user=foot --ask-su-pass --limit='all' --inventory-file=#{generated_inventory_dir} --extra-vars=@#{File.expand_path(__FILE__)} --sudo --sudo-user=deployer -vvv --ask-sudo-pass --ask-vault-pass --vault-password-file=#{File.expand_path(__FILE__)} --tags=db,www --skip-tags=foo,bar --limit='machine*:&vagrant:!that_one' --start-at-task='an awesome task' playbook.yml")
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,7 +8,7 @@ sidebar_current: "provisioning-ansible"
|
||||||
**Provisioner name: `"ansible"`**
|
**Provisioner name: `"ansible"`**
|
||||||
|
|
||||||
The ansible provisioner allows you to provision the guest using
|
The ansible provisioner allows you to provision the guest using
|
||||||
[Ansible](http://ansible.com) playbooks.
|
[Ansible](http://ansible.com) playbooks by executing `ansible-playbook` from the Vagrant host.
|
||||||
|
|
||||||
Ansible playbooks are [YAML](http://en.wikipedia.org/wiki/YAML) documents that
|
Ansible playbooks are [YAML](http://en.wikipedia.org/wiki/YAML) documents that
|
||||||
comprise the set of steps to be orchestrated on one or more machines. This documentation
|
comprise the set of steps to be orchestrated on one or more machines. This documentation
|
||||||
|
@ -25,6 +25,11 @@ a single page of documentation.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
## Setup Requirements
|
||||||
|
|
||||||
|
* [Install Ansible](http://docs.ansible.com/intro_installation.html#installing-the-control-machine) on your Vagrant host.
|
||||||
|
* Your Vagrant host should ideally provide a recent version of OpenSSH that [supports ControlPersist](http://docs.ansible.com/faq.html#how-do-i-get-ansible-to-reuse-connections-enable-kerberized-ssh-or-have-ansible-pay-attention-to-my-local-ssh-config-file)
|
||||||
|
|
||||||
## Inventory File
|
## Inventory File
|
||||||
|
|
||||||
When using Ansible, it needs to know on which machines a given playbook should run. It does
|
When using Ansible, it needs to know on which machines a given playbook should run. It does
|
||||||
|
|
Loading…
Reference in New Issue