1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
|
# encoding: utf-8
require "train/plugins"
require "open3"
require "ostruct"
require "json"
require "mkmf"
module Train::Transports
class VMware < Train.plugin(1)
name "vmware"
option :viserver, default: proc { ENV["VISERVER"] }
option :username, default: proc { ENV["VISERVER_USERNAME"] }
option :password, default: proc { ENV["VISERVER_PASSWORD"] }
option :insecure, default: false
def connection(_ = nil)
@connection ||= Connection.new(@options)
end
class Connection < BaseConnection # rubocop:disable ClassLength
POWERSHELL_PROMPT_REGEX = /PS\s.*> $/.freeze
def initialize(options)
super(options)
options[:viserver] = options[:viserver] || options[:host]
options[:username] = options[:username] || options[:user]
@username = options[:username]
@viserver = options[:viserver]
@session = nil
@stdout_buffer = ""
@stderr_buffer = ""
@powershell_binary = detect_powershell_binary
if @powershell_binary == :powershell
require_relative "local"
@powershell = Train::Transports::Local::Connection.new(options)
end
if options[:insecure] == true
run_command_via_connection("Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Scope Session -Confirm:$False")
end
@platform_details = {
release: "vmware-powercli-#{powercli_version}",
}
connect
end
def connect
login_command = "Connect-VIServer #{options[:viserver]} -User #{options[:username]} -Password #{options[:password]} | Out-Null"
result = run_command_via_connection(login_command)
if result.exit_status != 0
message = "Unable to connect to VIServer at #{options[:viserver]}. "
case result.stderr
when /Invalid server certificate/
message += "Certification verification failed. Please use `--insecure` or set `Set-PowerCLIConfiguration -InvalidCertificateAction Ignore` in PowerShell"
when /incorrect user name or password/
message += "Incorrect username or password"
else
message += result.stderr.gsub(/-Password .*\s/, "-Password REDACTED")
end
raise message
end
end
def platform
force_platform!("vmware", @platform_details)
end
def run_command_via_connection(cmd, &_data_handler)
if @powershell_binary == :pwsh
result = parse_pwsh_output(cmd)
# Attach exit status to result
exit_status = parse_pwsh_output("echo $?").stdout.chomp
result.exit_status = exit_status == "True" ? 0 : 1
result
else
@powershell.run_command(cmd)
end
end
def unique_identifier
uuid_command = "(Get-VMHost | Get-View).hardware.systeminfo.uuid"
run_command_via_connection(uuid_command).stdout.chomp
end
def uri
"vmware://#{@username}@#{@viserver}"
end
private
def detect_powershell_binary
if find_executable0("pwsh")
:pwsh
elsif find_executable0("powershell")
:powershell
else
raise "Cannot find PowerShell binary, is `pwsh` installed?"
end
end
# Read from stdout pipe until prompt is received
def flush_stdout(pipe)
@stdout_buffer += pipe.read_nonblock(1) while @stdout_buffer !~ POWERSHELL_PROMPT_REGEX
@stdout_buffer
rescue IO::EAGAINWaitReadable
# We cannot know when the stdout pipe is finished so we keep reading
retry
ensure
@stdout_buffer = ""
end
# This must be called after `flush_stdout` to ensure buffer is full
def flush_stderr(pipe)
loop do
@stderr_buffer += pipe.read_nonblock(1)
end
rescue IO::EAGAINWaitReadable
# If `flush_stderr` is ran after reading stdout we know that all of
# stderr is in the pipe. Thus, we can return the buffer once the pipe
# is unreadable.
@stderr_buffer
ensure
@stderr_buffer = ""
end
def parse_pwsh_output(cmd)
session.stdin.puts(cmd)
stdout = flush_stdout(session.stdout)
# Remove stdin from stdout (including trailing newline)
stdout.slice!(0, cmd.length + 1)
# Remove prompt from stdout
stdout.gsub!(POWERSHELL_PROMPT_REGEX, "")
# Grab stderr
stderr = flush_stderr(session.stderr)
CommandResult.new(
stdout,
stderr,
nil # exit_status is attached in `run_command_via_connection`
)
end
def powercli_version
version_command = "[string](Get-Module -Name VMware.PowerCLI -ListAvailable | Select -ExpandProperty Version)"
result = run_command_via_connection(version_command)
if result.stdout.empty? || result.exit_status != 0
raise "Unable to determine PowerCLI Module version, is it installed?"
end
result.stdout.chomp
end
def session
return @session unless @session.nil?
stdin, stdout, stderr = Open3.popen3("pwsh")
# Remove leading prompt and intro text
flush_stdout(stdout)
@session = OpenStruct.new
@session.stdin = stdin
@session.stdout = stdout
@session.stderr = stderr
@session
end
end
end
end
|