From 5dd4a72a2d378bebc11f8fabd7f5714b9ffa54f2 Mon Sep 17 00:00:00 2001 From: GPT-5 mini Date: Sun, 5 Oct 2025 13:07:00 +0000 Subject: [PATCH] Quote IdentityFile paths with spaces in ANSIBLE_SSH_ARGS Fixes an issue where ANSIBLE_SSH_ARGS IdentityFile paths containing spaces were not quoted, causing Ansible/OpenSSH to misinterpret them as hostnames or options. Quote paths with spaces and continue to escape '%' characters. Fixes #9597. Powered-by: GitHub Copilot in VS Code Powered-by: GitHub Codespaces --- .../provisioners/ansible/provisioner/host.rb | 13 ++++++++++++- .../provisioners/ansible/provisioner_test.rb | 17 +++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/plugins/provisioners/ansible/provisioner/host.rb b/plugins/provisioners/ansible/provisioner/host.rb index 9f9dc5d0d..6c989d527 100644 --- a/plugins/provisioners/ansible/provisioner/host.rb +++ b/plugins/provisioners/ansible/provisioner/host.rb @@ -302,7 +302,18 @@ module VagrantPlugins # Multiple Private Keys unless !config.inventory_path && @ssh_info[:private_key_path].size == 1 @ssh_info[:private_key_path].each do |key| - ssh_options += ["-o", "IdentityFile=%s" % [key.gsub('%', '%%')]] + # Escape any '%' characters to avoid formatting issues when + # building the final command string for Ansible. If the path + # contains spaces, wrap it in double quotes so the OpenSSH + # client invoked by Ansible treats the whole path as a single + # argument (otherwise the path would be split on spaces and + # could be interpreted as a hostname or separate option). + escaped = key.gsub('%', '%%') + if escaped.include?(' ') + ssh_options += ["-o", "IdentityFile=\"%s\"" % [escaped]] + else + ssh_options += ["-o", "IdentityFile=%s" % [escaped]] + end end end diff --git a/test/unit/plugins/provisioners/ansible/provisioner_test.rb b/test/unit/plugins/provisioners/ansible/provisioner_test.rb index 4c8b77f4c..c96d2639a 100644 --- a/test/unit/plugins/provisioners/ansible/provisioner_test.rb +++ b/test/unit/plugins/provisioners/ansible/provisioner_test.rb @@ -856,6 +856,23 @@ VF end end + describe "with an identity file path containing spaces and a custom inventory_path" do + before do + ssh_info[:private_key_path] = ['/path/with a space/key'] + # When inventory_path is provided, the provisioner will add IdentityFile + # entries to ANSIBLE_SSH_ARGS even if there's a single key. Use a value + # containing spaces to reproduce the problematic scenario. + config.inventory_path = '/some inventory/with spaces/inv' + end + + it "wraps the IdentityFile path in double quotes inside ANSIBLE_SSH_ARGS" do + 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=\"/path/with a space/key\"") + }.and_return(default_execute_result) + end + end + describe "with an identity file containing `%`" do before do ssh_info[:private_key_path] = ['/foo%bar/key', '/bar%%buz/key']