(#6656) Format windows paths for ssh_config command

Prior to this commit, if the ssh-config command was invoked within
cygwin or msys2, it would show a regular windows style path for private
keys rather than a path that could be used within msys2 or cygwin. This
commit updates that behavior by converting all of the private key paths
to the proper msys2 or cygwin path if the platform is windows and the
command was invoked from one of those two shells.
This commit is contained in:
Brian Cain 2017-08-22 16:43:20 -07:00
parent 4ddac2d023
commit b45ee4f455
4 changed files with 142 additions and 17 deletions

View File

@ -4,6 +4,7 @@ require "tmpdir"
require "vagrant/util/subprocess" require "vagrant/util/subprocess"
require "vagrant/util/powershell" require "vagrant/util/powershell"
require "vagrant/util/which"
module Vagrant module Vagrant
module Util module Util
@ -19,6 +20,15 @@ module Vagrant
@_cygwin @_cygwin
end end
def msys?
if !defined?(@_msys)
@_msys = ENV["VAGRANT_DETECTED_OS"].to_s.downcase.include?("msys") ||
platform.include?("msys") ||
ENV["OSTYPE"].to_s.downcase.include?("msys")
end
@_msys
end
def wsl? def wsl?
if !defined?(@_wsl) if !defined?(@_wsl)
@_wsl = false @_wsl = false
@ -93,15 +103,20 @@ module Vagrant
# @param [String] path # @param [String] path
# @return [String] # @return [String]
def cygwin_path(path) def cygwin_path(path)
if cygwin?
begin begin
# First try the real cygpath # We have to revert to the old env
process = Subprocess.execute("cygpath", "-u", "-a", path.to_s) # path here, otherwise it looks like
# msys2 ends up using the wrong cygpath
# binary and ends up with a `/cygdrive`
# when it doesn't exist in msys2
original_path_env = ENV['PATH']
ENV['PATH'] = ENV['VAGRANT_OLD_ENV_PATH']
cygpath = Vagrant::Util::Which.which("cygpath")
cygpath.gsub!("/", '\\')
process = Subprocess.execute(
cygpath, "-u", "-a", path.to_s)
return process.stdout.chomp return process.stdout.chomp
rescue Errors::CommandUnavailableWindows rescue Errors::CommandUnavailableWindows => e
end
end
# Sometimes cygpath isn't available (msys). Instead, do what we # Sometimes cygpath isn't available (msys). Instead, do what we
# can with bash tricks. # can with bash tricks.
process = Subprocess.execute( process = Subprocess.execute(
@ -110,7 +125,13 @@ module Vagrant
"--norc", "--norc",
"-c", "cd #{Shellwords.escape(path)} && pwd") "-c", "cd #{Shellwords.escape(path)} && pwd")
return process.stdout.chomp return process.stdout.chomp
ensure
ENV['PATH'] = original_path_env
end end
end
# Identical to cygwin_path for now
alias_method :msys_path, :cygwin_path
# This takes any path and converts it to a full-length Windows # This takes any path and converts it to a full-length Windows
# path on Windows machines in Cygwin. # path on Windows machines in Cygwin.
@ -264,6 +285,23 @@ module Vagrant
end end
end end
# Takes a windows path and formats it to the
# 'unix' style (i.e. `/cygdrive/c` or `/c/`)
#
# @param [Pathname, String] path Path to convert
# @param [Hash] hash of arguments
# @return [String]
def format_windows_path(path, *args)
path = cygwin_path(path) if cygwin?
path = msys_path(path) if msys?
path = wsl_to_windows_path(path) if wsl?
if windows? || wsl?
path = windows_unc_path(path) if !args.include?(:disable_unc)
end
path
end
# Automatically convert a given path to a Windows path. Will only # Automatically convert a given path to a Windows path. Will only
# be applied if running on a Windows host. If running on Windows # be applied if running on a Windows host. If running on Windows
# host within the WSL, the actual Windows path will be returned. # host within the WSL, the actual Windows path will be returned.

View File

@ -1,6 +1,7 @@
require 'optparse' require 'optparse'
require "vagrant/util/safe_puts" require "vagrant/util/safe_puts"
require "vagrant/util/platform"
module VagrantPlugins module VagrantPlugins
module CommandSSHConfig module CommandSSHConfig
@ -11,6 +12,10 @@ module VagrantPlugins
"outputs OpenSSH valid configuration to connect to the machine" "outputs OpenSSH valid configuration to connect to the machine"
end end
def convert_win_paths(paths)
paths.map! { |path| Vagrant::Util::Platform.format_windows_path(path, :disable_unc) }
end
def execute def execute
options = {} options = {}
@ -32,6 +37,10 @@ module VagrantPlugins
ssh_info = machine.ssh_info ssh_info = machine.ssh_info
raise Vagrant::Errors::SSHNotReady if ssh_info.nil? raise Vagrant::Errors::SSHNotReady if ssh_info.nil?
if Vagrant::Util::Platform.windows?
ssh_info[:private_key_path] = convert_win_paths(ssh_info[:private_key_path])
end
variables = { variables = {
host_key: options[:host] || machine.name || "vagrant", host_key: options[:host] || machine.name || "vagrant",
ssh_host: ssh_info[:host], ssh_host: ssh_info[:host],

View File

@ -24,7 +24,7 @@ describe VagrantPlugins::CommandSSHConfig::Command do
username: "testuser", username: "testuser",
keys_only: true, keys_only: true,
paranoid: false, paranoid: false,
private_key_path: [], private_key_path: ["/home/vagrant/.private/keys.key"],
forward_agent: false, forward_agent: false,
forward_x11: false forward_x11: false
}} }}
@ -53,6 +53,7 @@ Host #{machine.name}
UserKnownHostsFile /dev/null UserKnownHostsFile /dev/null
StrictHostKeyChecking no StrictHostKeyChecking no
PasswordAuthentication no PasswordAuthentication no
IdentityFile /home/vagrant/.private/keys.key
IdentitiesOnly yes IdentitiesOnly yes
LogLevel FATAL LogLevel FATAL
SSHCONFIG SSHCONFIG
@ -136,5 +137,19 @@ Host #{machine.name}
expect(output).not_to include('StrictHostKeyChecking ') expect(output).not_to include('StrictHostKeyChecking ')
expect(output).not_to include('UserKnownHostsFile ') expect(output).not_to include('UserKnownHostsFile ')
end end
it "formats windows paths if windows" do
allow(machine).to receive(:ssh_info) { ssh_info.merge(private_key_path: ["C:\\path\\to\\vagrant\\home.key"]) }
allow(Vagrant::Util::Platform).to receive(:format_windows_path).and_return("/home/vagrant/home.key")
allow(Vagrant::Util::Platform).to receive(:windows?).and_return(true)
output = ""
allow(subject).to receive(:safe_puts) do |data|
output += data if data
end
subject.execute
expect(output).to include('IdentityFile /home/vagrant/home.key')
end
end end
end end

View File

@ -5,8 +5,27 @@ require "vagrant/util/platform"
describe Vagrant::Util::Platform do describe Vagrant::Util::Platform do
include_context "unit" include_context "unit"
subject { described_class } subject { described_class }
describe "#cygwin_path" do
let(:path) { "C:\\msys2\\home\\vagrant" }
let(:updated_path) { "/home/vagrant" }
let(:subprocess_result) do
double("subprocess_result").tap do |result|
allow(result).to receive(:exit_code).and_return(0)
allow(result).to receive(:stdout).and_return(updated_path)
end
end
it "takes a windows path and returns a formatted path" do
allow(Vagrant::Util::Which).to receive(:which).and_return("C:/msys2/cygpath")
allow(Vagrant::Util::Subprocess).to receive(:execute).and_return(subprocess_result)
expect(subject.cygwin_path(path)).to eq("/home/vagrant")
end
end
describe "#cygwin?" do describe "#cygwin?" do
before do before do
allow(subject).to receive(:platform).and_return("test") allow(subject).to receive(:platform).and_return("test")
@ -51,6 +70,50 @@ describe Vagrant::Util::Platform do
end end
end end
describe "#msys?" do
before do
allow(subject).to receive(:platform).and_return("test")
described_class.reset!
end
after do
described_class.reset!
end
around do |example|
with_temp_env(VAGRANT_DETECTED_OS: "nope", PATH: "") do
example.run
end
end
it "returns true if VAGRANT_DETECTED_OS includes msys" do
with_temp_env(VAGRANT_DETECTED_OS: "msys") do
expect(subject).to be_msys
end
end
it "returns true if OSTYPE includes msys" do
with_temp_env(OSTYPE: "msys") do
expect(subject).to be_msys
end
end
it "returns true if platform has msys" do
allow(subject).to receive(:platform).and_return("msys")
expect(subject).to be_msys
end
it "returns false if the PATH contains msys" do
with_temp_env(PATH: "C:/msys") do
expect(subject).to_not be_msys
end
end
it "returns false if nothing is available" do
expect(subject).to_not be_msys
end
end
describe "#fs_real_path" do describe "#fs_real_path" do
it "fixes drive letters on Windows", :windows do it "fixes drive letters on Windows", :windows do
expect(described_class.fs_real_path("c:/foo").to_s).to eql("C:/foo") expect(described_class.fs_real_path("c:/foo").to_s).to eql("C:/foo")