Class: Support::GuestOperations

Inherits:
Object
  • Object
show all
Defined in:
lib/support/guest_operations.rb

Overview

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(vim, vm, guest_auth, ssl_verify = true) ⇒ GuestOperations


9
10
11
12
13
14
# File 'lib/support/guest_operations.rb', line 9

def initialize(vim, vm, guest_auth, ssl_verify = true)
  @gom = vim.serviceContent.guestOperationsManager
  @vm = vm
  @guest_auth = guest_auth
  @ssl_verify = ssl_verify
end

Instance Attribute Details

#gomObject (readonly)

Returns the value of attribute gom


7
8
9
# File 'lib/support/guest_operations.rb', line 7

def gom
  @gom
end

#guest_authObject (readonly)

Returns the value of attribute guest_auth


7
8
9
# File 'lib/support/guest_operations.rb', line 7

def guest_auth
  @guest_auth
end

#ssl_verifyObject (readonly)

Returns the value of attribute ssl_verify


7
8
9
# File 'lib/support/guest_operations.rb', line 7

def ssl_verify
  @ssl_verify
end

#vmObject (readonly)

Returns the value of attribute vm


7
8
9
# File 'lib/support/guest_operations.rb', line 7

def vm
  @vm
end

Instance Method Details

#delete_dir(dir) ⇒ Object


31
32
33
# File 'lib/support/guest_operations.rb', line 31

def delete_dir(dir)
  gom.fileManager.DeleteDirectoryInGuest(vm: vm, auth: guest_auth, directoryPath: dir, recursive: true)
end

#download_file(remote_file, local_file) ⇒ Object


134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/support/guest_operations.rb', line 134

def download_file(remote_file, local_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)
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = (uri.scheme == "https")
  http.verify_mode = ssl_verify ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
  response = http.request(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

#linux?Boolean


23
24
25
# File 'lib/support/guest_operations.rb', line 23

def linux?
  os_family == :linux
end

#os_familyObject


16
17
18
19
20
21
# File 'lib/support/guest_operations.rb', line 16

def os_family
  return vm.guest.guestFamily == "windowsGuest" ? :windows : :linux if vm.guest.guestFamily

  # VMware tools are not initialized or missing, infer from Guest Id
  vm.config&.guestId&.match(/^win/) ? :windows : :linux
end

#process_exit_code(pid) ⇒ Object


40
41
42
# File 'lib/support/guest_operations.rb', line 40

def process_exit_code(pid)
  gom.processManager.ListProcessesInGuest(vm: vm, auth: guest_auth, pids: [pid])&.first&.exitCode
end

#process_is_running(pid) ⇒ Object


35
36
37
38
# File 'lib/support/guest_operations.rb', line 35

def process_is_running(pid)
  procs = gom.processManager.ListProcessesInGuest(vm: vm, auth: guest_auth, pids: [pid])
  procs.empty? || procs.any? { |gpi| gpi.exitCode.nil? }
end

#read_file(remote_file) ⇒ Object


125
126
127
# File 'lib/support/guest_operations.rb', line 125

def read_file(remote_file)
  download_file(remote_file, nil)
end

#run_program(path, args = "", timeout = 60.0) ⇒ Object


56
57
58
59
60
61
62
63
64
65
66
# File 'lib/support/guest_operations.rb', line 56

def run_program(path, args = "", timeout = 60.0)
  Kitchen.logger.debug format("Running %s %s", path, args)

  pid = gom.processManager.StartProgramInGuest(vm: vm, auth: guest_auth, spec: RbVmomi::VIM::GuestProgramSpec.new(programPath: path, arguments: args))
  wait_for_process_exit(pid, timeout)

  exit_code = process_exit_code(pid)
  raise format("Failed to run '%s %s'. Exit code: %d", path, args, exit_code) if exit_code != 0

  exit_code
end

#run_shell_capture_output(command, shell = :auto, timeout = 60.0) ⇒ Object


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
# File 'lib/support/guest_operations.rb', line 68

def run_shell_capture_output(command, shell = :auto, timeout = 60.0)
  if shell == :auto
    shell = :linux if linux?
    shell = :cmd if windows?
  end

  if shell == :linux
    tmp_out_fname = format("/tmp/vm_utils_run_out_%s", Random.rand)
    tmp_err_fname = format("/tmp/vm_utils_run_err_%s", Random.rand)
    shell = "/bin/sh"
    args = format("-c '(%s) > %s 2> %s'", command.gsub("'", %q{\\\'}), tmp_out_fname, tmp_err_fname)
  elsif shell == :cmd
    tmp_out_fname = format('C:\Windows\TEMP\vm_utils_run_out_%s', Random.rand)
    tmp_err_fname = format('C:\Windows\TEMP\vm_utils_run_err_%s', Random.rand)
    shell = "cmd.exe"
    args = format('/c "%s > %s 2> %s"', command.gsub("\"", %q{\\\"}), tmp_out_fname, tmp_err_fname)
  elsif shell == :powershell
    tmp_out_fname = format('C:\Windows\TEMP\vm_utils_run_out_%s', Random.rand)
    tmp_err_fname = format('C:\Windows\TEMP\vm_utils_run_err_%s', Random.rand)
    shell = 'C:\Windows\System32\WindowsPowershell\v1.0\powershell.exe'
    args = format('-Command "%s > %s 2> %s"', command.gsub("\"", %q{\\\"}), tmp_out_fname, tmp_err_fname)
  end

  begin
    exit_code = run_program(shell, args, timeout)
  rescue StandardError
    proc_err = "" # read_file(tmp_err_fname)
    raise format("Error executing command %s. Exit code: %d. StdErr %s", command, exit_code, proc_err)
  end

  read_file(tmp_out_fname)
end

#upload_file(local_file, remote_file) ⇒ Object


129
130
131
132
# File 'lib/support/guest_operations.rb', line 129

def upload_file(local_file, remote_file)
  Kitchen.logger.debug format("Copy %s to %s", local_file, remote_file)
  write_file(remote_file, File.open(local_file, "rb").read)
end

#wait_for_process_exit(pid, timeout = 60.0, interval = 1.0) ⇒ Object


44
45
46
47
48
49
50
51
52
53
54
# File 'lib/support/guest_operations.rb', line 44

def wait_for_process_exit(pid, timeout = 60.0, interval = 1.0)
  start = Time.new

  loop do
    return unless process_is_running(pid)
    break if (Time.new - start) >= timeout
    sleep interval
  end

  raise format("Timeout waiting for process %d to exit after %d seconds", pid, timeout) if (Time.new - start) >= timeout
end

#windows?Boolean


27
28
29
# File 'lib/support/guest_operations.rb', line 27

def windows?
  os_family == :windows
end

#write_file(remote_file, contents) ⇒ Object


101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/support/guest_operations.rb', line 101

def write_file(remote_file, contents)
  # Required privilege: VirtualMachine.GuestOperations.Modify
  put_url = gom.fileManager.InitiateFileTransferToGuest(
    vm: vm,
    auth: guest_auth,
    guestFilePath: remote_file,
    fileAttributes: RbVmomi::VIM::GuestFileAttributes(),
    fileSize: contents.size,
    overwrite: true
  )
  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 = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = (uri.scheme == "https")
  http.verify_mode = ssl_verify ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
  http.request(request)
end