Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion plugins/provisioners/ansible/provisioner/host.rb
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,18 @@ def prepare_ansible_ssh_args
# 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

Expand Down
17 changes: 17 additions & 0 deletions test/unit/plugins/provisioners/ansible/provisioner_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -856,6 +856,23 @@ def ensure_that_config_is_valid
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']
Expand Down