From f0718d620d49141d8d6f9f71a31c291f2b4fab55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Pab=C3=B3n?= Date: Sat, 20 Dec 2014 23:44:23 -0500 Subject: [PATCH 1/5] provisioners/ansible: Put ssh key in the inventory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vagrant 1.7.1 creates and injects new ssh keys for each virtual machine. When it started ansible with the "parallel provisioning trick", it would only send the ssh key of the targeted virtual machine. With this change, vagrant now stores the ssh key for each virtual machines directly in the generated ansible inventory, and thus allow ansible parallelism. Note that this change is not sufficient, as it would break vagrant configuration based on a custom inventory (file or script). This issue will be addressed in a next commit. Signed-off-by: Luis Pabón --- plugins/provisioners/ansible/provisioner.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/provisioners/ansible/provisioner.rb b/plugins/provisioners/ansible/provisioner.rb index 3d699ac57..022d02473 100644 --- a/plugins/provisioners/ansible/provisioner.rb +++ b/plugins/provisioners/ansible/provisioner.rb @@ -20,8 +20,8 @@ module VagrantPlugins # Ansible provisioner options # - # Connect with Vagrant SSH identity - options = %W[--private-key=#{@ssh_info[:private_key_path][0]} --user=#{@ssh_info[:username]}] + # By default, connect with Vagrant SSH username + options = %W[--user=#{@ssh_info[:username]}] # Connect with native OpenSSH client # Other modes (e.g. paramiko) are not officially supported, @@ -127,7 +127,7 @@ module VagrantPlugins m = @machine.env.machine(*am) m_ssh_info = m.ssh_info if !m_ssh_info.nil? - inventory += "#{m.name} ansible_ssh_host=#{m_ssh_info[:host]} ansible_ssh_port=#{m_ssh_info[:port]}\n" + inventory += "#{m.name} ansible_ssh_host=#{m_ssh_info[:host]} ansible_ssh_port=#{m_ssh_info[:port]} ansible_ssh_private_key_file=#{m_ssh_info[:private_key_path][0]}\n" inventory_machines[m.name] = m else @logger.error("Auto-generated inventory: Impossible to get SSH information for machine '#{m.name} (#{m.provider_name})'. This machine should be recreated.") From 76651a413d4c187f5e0d414e27772d55d1bf27e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Pab=C3=B3n?= Date: Sun, 21 Dec 2014 23:13:14 -0500 Subject: [PATCH 2/5] provisioners/ansible: Update unit tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Luis Pabón --- .../provisioners/ansible/provisioner_test.rb | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/test/unit/plugins/provisioners/ansible/provisioner_test.rb b/test/unit/plugins/provisioners/ansible/provisioner_test.rb index 5600a1753..5405dc265 100644 --- a/test/unit/plugins/provisioners/ansible/provisioner_test.rb +++ b/test/unit/plugins/provisioners/ansible/provisioner_test.rb @@ -67,16 +67,15 @@ VF # def self.it_should_set_arguments_and_environment_variables( - expected_args_count = 7, expected_vars_count = 4, expected_host_key_checking = false, expected_transport_mode = "ssh") + expected_args_count = 6, expected_vars_count = 4, expected_host_key_checking = false, expected_transport_mode = "ssh") it "sets implicit arguments in a specific order" do expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| expect(args[0]).to eq("ansible-playbook") - 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[3]).to eq("--connection=ssh") - expect(args[4]).to eq("--timeout=30") + expect(args[1]).to eq("--user=#{machine.ssh_info[:username]}") + expect(args[2]).to eq("--connection=ssh") + expect(args[3]).to eq("--timeout=30") inventory_count = args.count { |x| x =~ /^--inventory-file=.+$/ } expect(inventory_count).to be > 0 @@ -169,7 +168,7 @@ VF expect(config.inventory_path).to be_nil expect(File.exists?(generated_inventory_file)).to be_true inventory_content = File.read(generated_inventory_file) - expect(inventory_content).to include("#{machine.name} ansible_ssh_host=#{machine.ssh_info[:host]} ansible_ssh_port=#{machine.ssh_info[:port]}\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") expect(inventory_content).to include("# MISSING: '#{iso_env.machine_names[1]}' machine was probably removed without using Vagrant. This machine should be recreated.\n") } end @@ -277,7 +276,7 @@ VF config.host_key_checking = true end - it_should_set_arguments_and_environment_variables 7, 4, true + it_should_set_arguments_and_environment_variables 6, 4, true end describe "with boolean (flag) options disabled" do @@ -289,7 +288,7 @@ VF config.sudo_user = 'root' end - it_should_set_arguments_and_environment_variables 8 + it_should_set_arguments_and_environment_variables 7 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 @@ -318,7 +317,7 @@ VF "--new-arg=yeah"] end - it_should_set_arguments_and_environment_variables 18, 4, false, "paramiko" + 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 { |*args| @@ -374,7 +373,7 @@ VF config.ask_vault_pass = true end - it_should_set_arguments_and_environment_variables 8 + it_should_set_arguments_and_environment_variables 7 it "should ask the vault password" do expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| @@ -388,7 +387,7 @@ VF config.vault_password_file = existing_file end - it_should_set_arguments_and_environment_variables 8 + it_should_set_arguments_and_environment_variables 7 it "uses the given vault password file" do expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| @@ -402,7 +401,7 @@ VF config.raw_ssh_args = ['-o ControlMaster=no', '-o ForwardAgent=no'] end - it_should_set_arguments_and_environment_variables 7, 4 + it_should_set_arguments_and_environment_variables 6, 4 it_should_explicitly_enable_ansible_ssh_control_persist_defaults it "passes custom SSH options via ANSIBLE_SSH_ARGS with the highest priority" do @@ -436,7 +435,7 @@ VF ssh_info[:private_key_path] = ['/path/to/my/key', '/an/other/identity', '/yet/an/other/key'] end - it_should_set_arguments_and_environment_variables 7, 4 + it_should_set_arguments_and_environment_variables 6, 4 it_should_explicitly_enable_ansible_ssh_control_persist_defaults it "passes additional Identity Files via ANSIBLE_SSH_ARGS" do @@ -453,7 +452,7 @@ VF ssh_info[:forward_agent] = true end - it_should_set_arguments_and_environment_variables 7, 4 + it_should_set_arguments_and_environment_variables 6, 4 it_should_explicitly_enable_ansible_ssh_control_persist_defaults it "enables SSH-Forwarding via ANSIBLE_SSH_ARGS" do @@ -469,12 +468,12 @@ VF config.verbose = 'v' end - it_should_set_arguments_and_environment_variables 8 + it_should_set_arguments_and_environment_variables 7 it_should_set_optional_arguments({ "verbose" => "-v" }) it "shows the ansible-playbook command" do expect(machine.env.ui).to receive(:detail).with { |full_command| - expect(full_command).to eq("PYTHONUNBUFFERED=1 ANSIBLE_HOST_KEY_CHECKING=false ANSIBLE_FORCE_COLOR=true ANSIBLE_SSH_ARGS='-o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --private-key=/path/to/my/key --user=testuser --connection=ssh --timeout=30 --limit='machine1' --inventory-file=#{generated_inventory_dir} -v playbook.yml") + expect(full_command).to eq("PYTHONUNBUFFERED=1 ANSIBLE_HOST_KEY_CHECKING=false ANSIBLE_FORCE_COLOR=true ANSIBLE_SSH_ARGS='-o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --user=testuser --connection=ssh --timeout=30 --limit='machine1' --inventory-file=#{generated_inventory_dir} -v playbook.yml") } end end @@ -521,7 +520,7 @@ VF config.raw_ssh_args = ['-o ControlMaster=no'] end - it_should_set_arguments_and_environment_variables 21, 4, true + it_should_set_arguments_and_environment_variables 20, 4, true it_should_explicitly_enable_ansible_ssh_control_persist_defaults it_should_set_optional_arguments({ "extra_vars" => "--extra-vars=@#{File.expand_path(__FILE__)}", "sudo" => "--sudo", @@ -546,7 +545,7 @@ VF it "shows the ansible-playbook command, with additional quotes when required" do expect(machine.env.ui).to receive(:detail).with { |full_command| - expect(full_command).to eq("PYTHONUNBUFFERED=1 ANSIBLE_HOST_KEY_CHECKING=true ANSIBLE_FORCE_COLOR=true ANSIBLE_SSH_ARGS='-o IdentitiesOnly=yes -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 --timeout=30 --limit='machine*:&vagrant:!that_one' --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 --start-at-task='an awesome task' --why-not --su-user=foot --ask-su-pass --limit='all' playbook.yml") + expect(full_command).to eq("PYTHONUNBUFFERED=1 ANSIBLE_HOST_KEY_CHECKING=true ANSIBLE_FORCE_COLOR=true ANSIBLE_SSH_ARGS='-o IdentitiesOnly=yes -o IdentityFile=/my/key2 -o ForwardAgent=yes -o ControlMaster=no -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --user=testuser --connection=ssh --timeout=30 --limit='machine*:&vagrant:!that_one' --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 --start-at-task='an awesome task' --why-not --su-user=foot --ask-su-pass --limit='all' playbook.yml") } end end From 3842a1f710b493a534859f2341cc4ee61547bfd3 Mon Sep 17 00:00:00 2001 From: Gilles Cornu Date: Fri, 29 May 2015 10:18:21 +0200 Subject: [PATCH 3/5] provisioners/ansible: provide ssh identities via ANSIBLE_SSH_ARGS (when necessary) When provisioning multiple machines in sequence (the default vagrant behaviour), it doesn't make sense to require to provide the private ssh key(s) via the custom ansible inventory script/file. To align with the handling of multiple ssh keys per machine, we won't rely any longer on `--private-key` command line argument, but only pass the keys via `ANSIBLE_SSH_ARGS` environment variable. Note that when vagrant generates the ansible inventory and that only one key is associated to a VM, this step would be redundant, and therefore won't be applied. This change fixes the breaking change introduced by 3d62a91. --- plugins/provisioners/ansible/provisioner.rb | 6 ++++-- .../plugins/provisioners/ansible/provisioner_test.rb | 10 ++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/plugins/provisioners/ansible/provisioner.rb b/plugins/provisioners/ansible/provisioner.rb index 022d02473..6000621af 100644 --- a/plugins/provisioners/ansible/provisioner.rb +++ b/plugins/provisioners/ansible/provisioner.rb @@ -242,8 +242,10 @@ module VagrantPlugins ssh_options << "-o IdentitiesOnly=yes" unless Vagrant::Util::Platform.solaris? # Multiple Private Keys - @ssh_info[:private_key_path].drop(1).each do |key| - ssh_options << "-o IdentityFile=#{key}" + unless !config.inventory_path && @ssh_info[:private_key_path].size == 1 + @ssh_info[:private_key_path].each do |key| + ssh_options << "-o IdentityFile=#{key}" + end end # SSH Forwarding diff --git a/test/unit/plugins/provisioners/ansible/provisioner_test.rb b/test/unit/plugins/provisioners/ansible/provisioner_test.rb index 5405dc265..ac1a80f97 100644 --- a/test/unit/plugins/provisioners/ansible/provisioner_test.rb +++ b/test/unit/plugins/provisioners/ansible/provisioner_test.rb @@ -513,14 +513,14 @@ VF config.skip_tags = %w(foo bar) config.limit = 'machine*:&vagrant:!that_one' config.start_at_task = 'an awesome task' - config.raw_arguments = ["--why-not", "--su-user=foot", "--ask-su-pass", "--limit=all"] + config.raw_arguments = ["--why-not", "--su-user=foot", "--ask-su-pass", "--limit=all", "--private-key=./myself.key"] # environment variables config.host_key_checking = true config.raw_ssh_args = ['-o ControlMaster=no'] end - it_should_set_arguments_and_environment_variables 20, 4, true + it_should_set_arguments_and_environment_variables 21, 4, true it_should_explicitly_enable_ansible_ssh_control_persist_defaults it_should_set_optional_arguments({ "extra_vars" => "--extra-vars=@#{File.expand_path(__FILE__)}", "sudo" => "--sudo", @@ -537,15 +537,17 @@ VF it "also includes given raw arguments" do expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| + expect(args).to include("--why-not") expect(args).to include("--su-user=foot") expect(args).to include("--ask-su-pass") - expect(args).to include("--why-not") + expect(args).to include("--limit=all") + expect(args).to include("--private-key=./myself.key") } end it "shows the ansible-playbook command, with additional quotes when required" do expect(machine.env.ui).to receive(:detail).with { |full_command| - expect(full_command).to eq("PYTHONUNBUFFERED=1 ANSIBLE_HOST_KEY_CHECKING=true ANSIBLE_FORCE_COLOR=true ANSIBLE_SSH_ARGS='-o IdentitiesOnly=yes -o IdentityFile=/my/key2 -o ForwardAgent=yes -o ControlMaster=no -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --user=testuser --connection=ssh --timeout=30 --limit='machine*:&vagrant:!that_one' --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 --start-at-task='an awesome task' --why-not --su-user=foot --ask-su-pass --limit='all' playbook.yml") + expect(full_command).to eq("PYTHONUNBUFFERED=1 ANSIBLE_HOST_KEY_CHECKING=true ANSIBLE_FORCE_COLOR=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 --user=testuser --connection=ssh --timeout=30 --limit='machine*:&vagrant:!that_one' --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 --start-at-task='an awesome task' --why-not --su-user=foot --ask-su-pass --limit='all' --private-key=./myself.key playbook.yml") } end end From df4b74ee546f8d4b54112dcef56e18a600f333df Mon Sep 17 00:00:00 2001 From: Gilles Cornu Date: Sun, 7 Jun 2015 17:59:33 +0200 Subject: [PATCH 4/5] provisioners/ansible: update doc for GH-5765 (wip) --- .../source/v2/provisioning/ansible.html.md | 77 ++++++++++++++----- 1 file changed, 59 insertions(+), 18 deletions(-) diff --git a/website/docs/source/v2/provisioning/ansible.html.md b/website/docs/source/v2/provisioning/ansible.html.md index 21d469a97..48e392210 100644 --- a/website/docs/source/v2/provisioning/ansible.html.md +++ b/website/docs/source/v2/provisioning/ansible.html.md @@ -61,8 +61,8 @@ Vagrant would generate an inventory file that might look like: ``` # Generated by Vagrant -machine1 ansible_ssh_host=127.0.0.1 ansible_ssh_port=2200 -machine2 ansible_ssh_host=127.0.0.1 ansible_ssh_port=2201 +machine1 ansible_ssh_host=127.0.0.1 ansible_ssh_port=2200 ansible_ssh_private_key_file=/home/.../.vagrant/machines/machine1/virtualbox/private_key +machine2 ansible_ssh_host=127.0.0.1 ansible_ssh_port=2201 ansible_ssh_private_key_file=/home/.../.vagrant/machines/machine2/virtualbox/private_key [group1] machine1 @@ -79,6 +79,7 @@ group2 * The generation of group variables blocks (e.g. `[group1:vars]`) are intentionally not supported, as it is [not recommended to store group variables in the main inventory file](http://docs.ansible.com/intro_inventory.html#splitting-out-host-and-group-specific-data). A good practice is to store these group (or host) variables in `YAML` files stored in `group_vars/` or `host_vars/` directories in the playbook (or inventory) directory. * Unmanaged machines and undefined groups are not added to the inventory, to avoid useless Ansible errors (e.g. *unreachable host* or *undefined child group*) + * Prior to Vagrant 1.7.3, the `ansible_ssh_private_key_file` variable was not set in generated inventory, but passed as command line argument to `ansible-playbook` command. For example, `machine3`, `group3` and `group1:vars` in the example below would not be added to the generated inventory file: @@ -227,30 +228,47 @@ by the sudo command. Vagrant is designed to provision [multi-machine environments](/v2/multi-machine) in sequence, but the following configuration pattern can be used to take advantage of Ansible parallelism: ``` - # By default, Vagrant 1.7+ automatically inserts a different - # insecure keypair for each new VM created. The easiest way - # to use the same keypair for all the machines is to disable - # this feature and rely on the legacy insecure key. - config.ssh.insert_key = false +# Vagrant 1.7+ automatically inserts a different +# insecure keypair for each new VM created. The easiest way +# to use the same keypair for all the machines is to disable +# this feature and rely on the legacy insecure key. +# config.ssh.insert_key = false +# +# Note: +# As of Vagrant 1.7.3, it is no longer necessary to disable +# the keypair creation when using the auto-generated inventory. - config.vm.define 'machine2' do |machine| - machine.vm.hostname = 'machine2' - machine.vm.network "private_network", ip: "192.168.77.22" - end +N = 3 +(1..N).each do |machine_id| + config.vm.define "machine#{machine_id}" do |machine| - config.vm.define 'machine1' do |machine| - machine.vm.hostname = 'machine1' - machine.vm.network "private_network", ip: "192.168.77.21" + machine.vm.hostname = "machine#{machine_id}" + machine.vm.network "private_network", ip: "192.168.77.#{20+machine_id}" - machine.vm.provision :ansible do |ansible| - ansible.playbook = "playbook.yml" + if machine_id == N - # Disable default limit (required with Vagrant 1.5+) - ansible.limit = 'all' + # TODO add a comment + machine.vm.provision :ansible do |ansible| + ansible.playbook = "playbook.yml" + + # Disable default limit (required with Vagrant 1.5+) + ansible.limit = 'all' + end end + end +end ``` +**Caveats:** + +- Vagrant will only + + +TODO: add a note about mixing parallel and custom inventory +TODO: multiple keys are not supported with parallelism, except if you pass them as raw_ssh_args! + + ### Provide a local `ansible.cfg` file Certain settings in Ansible are (only) adjustable via a [configuration file](http://docs.ansible.com/intro_configuration.html), and you might want to ship such a file in your Vagrant project. @@ -283,3 +301,26 @@ In a situation like the above, to override the `remote_user` specified in a play ``` ansible.extra_vars = { ansible_ssh_user: 'vagrant' } ``` + +### Force Paramiko Connection Mode + +The Ansible provisioner is implemented with native OpenSSH support in mind, and there is no official support for [paramiko](https://github.com/paramiko/paramiko/) (A native Python SSHv2 protocol library). + +If you really need to use this connection mode, it is though possible to enable paramiko as illustrated in the following configuration examples: + + +With auto-generated inventory: + +``` +ansible.raw_arguments = ["--connection=paramiko"] +``` + +With a custom inventory, the private key must be specified (e.g. via an `ansible.cfg` configuration file, `--private-key` argument, or as part of your inventory file): + +``` +ansible.inventory_path = "./my-inventory" +ansible.raw_arguments = [ + "--connection=paramiko", + "--private-key=/home/.../.vagrant/machines/.../private_key" +] +``` \ No newline at end of file From e932bc405ea09d7cc8f0c499efc4b5aa5c041ba9 Mon Sep 17 00:00:00 2001 From: Gilles Cornu Date: Fri, 10 Jul 2015 08:31:58 +0200 Subject: [PATCH 5/5] Update CHANGELOG [GH-5765] [ci skip] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa202fd19..d69c21ef1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ IMPROVEMENTS: - guests/solaris,solaris11: support inserting generated key [GH-5182] [GH-5290] - providers/docker: images are pulled prior to starting [GH-5249] + - provisioners/ansible: store the first ssh private key in the auto-generated inventory [GH-5765] - provisioners/chef: add capability for checking if Chef is installed on Windows [GH-5669] - provisioners/puppet: add support for Puppet 4 and configuration options [GH-5601] - provisioners/puppet: add support for `synced_folder_args` in apply [GH-5359]