Class: Support::GuestOperations

Inherits:
Object
  • Object
show all
Defined in:
lib/train-vsphere-gom/guest_operations.rb

Overview

Encapsulate VMware Tools GOM interaction, originally inspired by github:dnuffer/raidopt

Constant Summary collapse

SHELL_TYPES =
{
  linux: {
    suffix: ".sh",
    cmd:    "/bin/sh",
    args:   '-c ". %<cmdfile>s" > %<outfile>s 2> %<errfile>s',
  },

  # BUG: Includes prompt
  cmd: {
    suffix: ".cmd",
    cmd:    "cmd.exe",
    args:   '/s /c "%<cmdfile>s" > %<outfile>s 2> %<errfile>s',
  },

  # Invoking PS via cmd seems the only way to get this to work
  powershell: {
    suffix: ".ps1",
    cmd:    "cmd.exe",
    args:   "/C powershell -NonInteractive -ExecutionPolicy Bypass -File %<cmdfile>s >%<outfile>s 2>%<errfile>s",
  },
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(vim, vm, username, password, ssl_verify: true, logger: nil, quick: false) ⇒ GuestOperations

Returns a new instance of GuestOperations.



31
32
33
34
35
36
37
38
39
# File 'lib/train-vsphere-gom/guest_operations.rb', line 31

def initialize(vim, vm, username, password, ssl_verify: true, logger: nil, quick: false)
  @vim = vim
  @vm = vm

  @guest_auth = RbVmomi::VIM::NamePasswordAuthentication(interactiveSession: false, username: username, password: password)

  @ssl_verify = ssl_verify
  @quick = quick
end

Instance Attribute Details

#gom=(value) ⇒ Object

Sets the attribute gom

Parameters:

  • value

    the value to set the attribute gom to.



29
30
31
# File 'lib/train-vsphere-gom/guest_operations.rb', line 29

def gom=(value)
  @gom = value
end

#logger=(value) ⇒ Object

Sets the attribute logger

Parameters:

  • value

    the value to set the attribute logger to.



29
30
31
# File 'lib/train-vsphere-gom/guest_operations.rb', line 29

def logger=(value)
  @logger = value
end

Instance Method Details

#delete_directory(remote_dir, recursive: true) ⇒ Object

Required privilege: VirtualMachine.GuestOperations.Modify



174
175
176
177
178
179
180
181
182
183
184
# File 'lib/train-vsphere-gom/guest_operations.rb', line 174

def delete_directory(remote_dir, recursive: true)
  logger.debug format("Deleting remote directory %s", remote_dir)

  gom.fileManager.DeleteDirectoryInGuest(vm: @vm, auth: @guest_auth, directoryPath: remote_dir, recursive: recursive)

  true
rescue RbVmomi::Fault => e
  raise if e.message.start_with? "NotADirectory:"

  false
end

#delete_file(remote_file) ⇒ Object

Required privilege: VirtualMachine.GuestOperations.Modify



161
162
163
164
165
166
167
168
169
170
171
# File 'lib/train-vsphere-gom/guest_operations.rb', line 161

def delete_file(remote_file)
  logger.debug format("Deleting remote file %s", remote_file)

  gom.fileManager.DeleteFileInGuest(vm: @vm, auth: @guest_auth, filePath: remote_file)

  true
rescue RbVmomi::Fault => e
  raise unless e.message.start_with? "FileNotFound:"

  false
end

#download_file(remote_file, local_file) ⇒ Object

Required privilege: VirtualMachine.GuestOperations.Modify



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/train-vsphere-gom/guest_operations.rb', line 144

def download_file(remote_file, local_file)
  logger.debug format("Downloading remote file %s to %s", local_file, remote_file)

  info = gom.fileManager.InitiateFileTransferFromGuest(vm: @vm, auth: @guest_auth, guestFilePath: remote_file)
  uri = URI.parse(info.url)

  request = Net::HTTP::Get.new(uri.request_uri)
  response = http_request(info.url, request)

  if response.body.size != info.size
    raise format("Downloaded file has different size than reported: %s (%d bytes instead of %d bytes)", remote_file, response.body.size, info.size)
  end

  local_file.nil? ? response.body : File.open(local_file, "w") { |file| file.write(response.body) }
end

#exist?(remote_file) ⇒ Boolean

Required privilege: VirtualMachine.GuestOperations.Query

Returns:

  • (Boolean)


83
84
85
86
87
88
89
90
91
# File 'lib/train-vsphere-gom/guest_operations.rb', line 83

def exist?(remote_file)
  logger.debug format("Checking for remote file %s", remote_file)

  gom.fileManager.ListFilesInGuest(vm: @vm, auth: @guest_auth, filePath: remote_file)

  true
rescue RbVmomi::Fault
  false
end

#read_file(remote_file) ⇒ Object



93
94
95
96
97
# File 'lib/train-vsphere-gom/guest_operations.rb', line 93

def read_file(remote_file)
  return "" unless @quick || exist?(remote_file)

  download_file(remote_file, nil) || ""
end

#run(command, shell_type: :auto, timeout: 60.0) ⇒ Object

Required privileges: VirtualMachine.GuestOperations.Execute, VirtualMachine.GuestOperations.Modify



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
# File 'lib/train-vsphere-gom/guest_operations.rb', line 42

def run(command, shell_type: :auto, timeout: 60.0)
  logger.debug format("Running `%s` remotely", command)

  if shell_type == :auto
    shell_type = :linux if linux?
    shell_type = :powershell if windows?
  end

  shell = SHELL_TYPES[shell_type]
  raise "Unsupported shell type #{shell_type}" unless shell

  logger.warn "Command execution on Windows is very slow" unless @warned || linux?
  @warned = true

  temp_file = write_temp_file(command, suffix: shell[:suffix] || "")
  temp_out  = "#{temp_file}-out.txt"
  temp_err  = "#{temp_file}-err.txt"

  begin
    args = format(shell[:args], cmdfile: temp_file, outfile: temp_out, errfile: temp_err)
    exit_code = run_program(shell[:cmd], args, timeout)
  rescue StandardError
    proc_err = read_file(temp_err)
    raise format("Error executing command %s. Exit code: %d. StdErr %s", command, exit_code || -1, proc_err)
  end

  stdout = read_file(temp_out)
  stdout = ascii_only(stdout) if bom?(stdout)

  stderr = read_file(temp_err) unless exit_code == 0 && @quick

  unless @quick
    delete_file(temp_file)
    delete_file(temp_out)
    delete_file(temp_err)
  end

  ::Train::Extras::CommandResult.new(stdout, stderr || "", exit_code)
end

#upload_file(local_file, remote_file) ⇒ Object

Required privilege: VirtualMachine.GuestOperations.Modify



137
138
139
140
141
# File 'lib/train-vsphere-gom/guest_operations.rb', line 137

def upload_file(local_file, remote_file)
  logger.debug format("Uploading %s to remote file %s", local_file, remote_file)

  write_file(remote_file, File.open(local_file, "rb").read)
end

#write_file(remote_file, contents) ⇒ Object

Required privilege: VirtualMachine.GuestOperations.Modify



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
# File 'lib/train-vsphere-gom/guest_operations.rb', line 100

def write_file(remote_file, contents)
  logger.debug format("Writing to remote file %s", remote_file)

  put_url = gom.fileManager.InitiateFileTransferToGuest(
    vm: @vm,
    auth: @guest_auth,
    guestFilePath: remote_file,
    fileAttributes: RbVmomi::VIM::GuestFileAttributes(),
    fileSize: contents.size,
    overwrite: true
  )

  # VCenter internal name might mismatch the external, so fix it
  put_url = put_url.gsub(%r{^https://\*:}, format("https://%s:%s", @vm._connection.host, put_url))
  uri = URI.parse(put_url)

  request = Net::HTTP::Put.new(uri.request_uri)
  request["Transfer-Encoding"] = "chunked"
  request["Content-Length"] = contents.size
  request.body = contents

  http_request(put_url, request)
rescue RbVmomi::Fault => e
  logger.error "Error during upload, check permissions on remote system: '" + e.message + "'"
end

#write_temp_file(contents, prefix: "", suffix: "") ⇒ Object

Required privilege: VirtualMachine.GuestOperations.Modify



127
128
129
130
131
132
133
134
# File 'lib/train-vsphere-gom/guest_operations.rb', line 127

def write_temp_file(contents, prefix: "", suffix: "")
  logger.debug format("Writing to temporary remote file")

  temp_name = gom.fileManager.CreateTemporaryFileInGuest(vm: @vm, auth: @guest_auth, prefix: prefix, suffix: suffix)
  write_file(temp_name, contents)

  temp_name
end