train-vsphere-gom 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4836ba3be5bd955bdb834325a38d79d6dd05c0f9fee8e735cd7a2de1de9c7c94
4
+ data.tar.gz: a76b3ec3cbcb66e743989c03a072095a6fd191c7e1d9f487530b4b00ba62c03c
5
+ SHA512:
6
+ metadata.gz: c6b9c54a5e7939c40a1c20cf18e6fe6a92f3904968f3f0361dd0300b4ed9e1b86a7f6505ae4f85f6308dd05f5592da3bab6c0d28df1e823a78a098038de23df3
7
+ data.tar.gz: 13430dd8a4d44f36e076cb54c2c8de94f05bd4bd7facd1ea09f985d85c4c66697936b376079cb7a14694541e8a60185cb982ccc1e37c2784c2386aa25624a028
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem "bump", "~> 0.10"
7
+ gem "bundler-audit", "~> 0.7"
8
+ gem "chefstyle", "~> 1.7"
9
+ gem "guard", "~> 2.16"
10
+ gem "mdl", "~> 0.11"
11
+ gem "pry", "~> 0.14"
12
+ gem "rake", "~> 13.0"
13
+ gem "train", "~> 3.5"
14
+ gem "yard", "~> 0.9"
15
+ end
data/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # Train-vsphere-gom
2
+
3
+ `train-vsphere-gom` is a Train plugin and is used as a Train OS Transport to
4
+ connect to virtual machines via VMware Tools.
5
+
6
+ This allows working with machines in different network segments, without
7
+ adding routes, NAT or firewall routes.
8
+
9
+ vSphere Guest Operations Manager covers various use cases within a target VM,
10
+ including file transfers, Windows registry access and command execution. It is
11
+ used by various 3rd party tools to monitor and manage VMs.
12
+
13
+ ## Requirements
14
+
15
+ - VMware vSphere 5.0 or higher
16
+
17
+ ## Permissions
18
+
19
+ Needs login credentials for vCenter (API) as well as the target machine (OS).
20
+
21
+ Mandatory privileges in vCenter:
22
+
23
+ - VirtualMachine.GuestOperations.Query
24
+ - VirtualMachine.GuestOperations.Execute
25
+ - VirtualMachine.GuestOperations.Modify (for file uploads)
26
+
27
+ ## Installation
28
+
29
+ Install this Gem from rubygems.org:
30
+
31
+ ```bash
32
+ gem install train-vsphere-gom
33
+ ```
34
+
35
+ ## Transport parameters
36
+
37
+ | Option | Explanation | Default | Env. Vars |
38
+ |--------------------|--------------------------------------------------------|------------|---------------|
39
+ | `host` | VM to connect (`host` for Train compatibility) | _required_ | `VI_VM` |
40
+ | `user` | VM username for login | _required_ | |
41
+ | `password` | VM password for login | _required_ | |
42
+ | `vcenter_server` | VCenter server | _required_ | `VI_SERVER` |
43
+ | `vcenter_username` | VCenter username | _required_ | `VI_USERNAME` |
44
+ | `vcenter_password` | VCenter password | _required_ | `VI_PASSWORD` |
45
+ | `vcenter_insecure` | Allow connections when SSL certificate is not matching | `false` | |
46
+ | `logger` | Logger instance | $stdout, info | |
47
+ | `quick` | Enable quick mode | `false` | |
48
+ | `shell_type` | Shell type ("auto", "linux", "cmd", "powershell") | `auto` | |
49
+ | `timeout` | Timeout in seconds | `60` | |
50
+
51
+ By design, Train VSphere GOM requires two authentication steps: One to get access to the VCenter API and one
52
+ to get access to the guest VM with local credentials.
53
+
54
+ VMs can be searched by their IP address, their UUID, their VMware inventory path or by DNS name.
55
+
56
+ The environment variables are aligned to those from VMware SDK and ESXCLI.
57
+
58
+ ## Quick Mode
59
+
60
+ In quick mode, non-essential operations are omitted to save time over slow connections.
61
+
62
+ - no deletion of temporary files (in system temp directory)
63
+ - no checking for file existance before trying to read
64
+ - only reading standard error, if exit code was not zero
65
+
66
+ ## Limitations
67
+
68
+ - SSPI based guest VM logins are not supported yet
69
+ - using PowerShell via GOM is very slow (7-10 seconds roundtrip)
70
+
71
+ ## Example use
72
+
73
+ ```ruby
74
+ require 'train-vsphere-gom'
75
+
76
+ train = Train.create('vsphere-gom', {
77
+ # Relying on VI_* variables for VCenter configuration
78
+ host: '10.20.30.40'
79
+ username: 'Administrator',
80
+ password: 'Password'
81
+ })
82
+ conn = train.connection
83
+ conn.run_command("Write-Host 'Inside the VM'")
84
+ ```
85
+
86
+ ## References
87
+
88
+ - [vSphere Web Services: GOM](https://code.vmware.com/docs/5722/vsphere-web-services-api-reference/doc/vim.vm.guest.GuestOperationsManager.html)
@@ -0,0 +1,3 @@
1
+ require "train-vsphere-gom/version"
2
+ require "train-vsphere-gom/transport"
3
+ require "train-vsphere-gom/connection"
@@ -0,0 +1,191 @@
1
+ require "resolv" unless defined?(Resolv)
2
+ require "rbvmomi" unless defined?(RbVmomi)
3
+
4
+ require_relative "guest_operations"
5
+
6
+ module TrainPlugins
7
+ module VsphereGom
8
+ class Connection < Train::Plugins::Transport::BaseConnection
9
+ attr_reader :options, :config
10
+
11
+ attr_writer :logger, :vim, :vm, :vm_guest
12
+
13
+ def initialize(config = {})
14
+ @config = config
15
+
16
+ unless vm
17
+ logger.error format("[VSphere-GOM] Could not find VM for '%<id>s'. Check power status, if searched via IP", id: config[:host])
18
+ return
19
+ end
20
+
21
+ logger.debug format("[VSphere-GOM] Found %<id>s for %<search_type>s %<search>s",
22
+ id: @vm._ref,
23
+ search: config[:host],
24
+ search_type: search_type(config[:host]))
25
+
26
+ super(config)
27
+ end
28
+
29
+ def close
30
+ return unless vim
31
+
32
+ vim.close
33
+ logger.info format("[VSphere-GOM] Closed connection to %<vm>s (VCenter %<vcenter>s)",
34
+ vm: options[:host],
35
+ vcenter: options[:vcenter_server])
36
+ end
37
+
38
+ def uri
39
+ "vsphere-gom://#{options[:user]}@#{options[:vcenter_server]}/#{options[:host]}"
40
+ end
41
+
42
+ def file_via_connection(path, *args)
43
+ if windows?
44
+ Train::File::Remote::Windows.new(self, path, *args)
45
+ else
46
+ Train::File::Remote::Unix.new(self, path, *args)
47
+ end
48
+ end
49
+
50
+ def upload(locals, remote)
51
+ logger.debug format("[VSphere-GOM] Copy %<locals>s to %<remote>s",
52
+ locals: Array(locals).join(", "),
53
+ remote: remote)
54
+
55
+ # Collect recursive list of directories and files
56
+ all = Array(locals).map do |local|
57
+ File.directory?(local) ? Dir.glob("#{local}/**/*") : local
58
+ end.flatten
59
+
60
+ dirs = all.select { |obj| File.directory? obj }
61
+ dirs.each { |dir| tools.create_dir(dir) }
62
+
63
+ # Then upload all files
64
+ files = all.select { |obj| File.file? obj }
65
+ files.each do |local|
66
+ remotefile = path(remote, File.basename(local))
67
+ vm_guest.upload_file(local, remotefile)
68
+ end
69
+ end
70
+
71
+ def download(remotes, local)
72
+ logger.debug format("[VSphere-GOM] Download %<remotes>s to %<local>s",
73
+ remotes: Array(Remotes).join(","),
74
+ local: local)
75
+
76
+ Array(remotes).each do |remote|
77
+ localfile = File.join(local, File.basename(remote))
78
+ vm_guest.download_file(remote, localfile)
79
+ end
80
+ end
81
+
82
+ def run_command_via_connection(command)
83
+ logger.debug format("[VSphere-GOM] Sending command to %<vm>s (VCenter %<vcenter>s)",
84
+ vm: options[:host],
85
+ vcenter: options[:vcenter_server])
86
+
87
+ result = vm_guest.run(command, shell_type: config[:shell_type].to_sym, timeout: config[:timeout].to_i)
88
+
89
+ if windows? && result.exit_status != 0
90
+ logger.debug format("[VSphere-GOM] Received Windows exit code: %<hexcode>s",
91
+ hexcode: hexit_code(result.exit_status))
92
+ end
93
+
94
+ result
95
+ end
96
+
97
+ private
98
+
99
+ def vim
100
+ @vim ||= RbVmomi::VIM.connect(
101
+ user: config[:vcenter_username],
102
+ password: config[:vcenter_password],
103
+ insecure: config[:vcenter_insecure],
104
+ host: config[:vcenter_server]
105
+ )
106
+ end
107
+
108
+ def vm
109
+ @vm ||= find_vm(config[:host])
110
+ end
111
+
112
+ def logger
113
+ return @logger if @logger
114
+
115
+ @logger = config[:logger] || Logger.new($stdout, level: :info)
116
+ end
117
+
118
+ def vm_guest
119
+ return @vm_guest if @vm_guest
120
+
121
+ username = config[:user] || config[:username]
122
+ password = config[:password]
123
+ ssl_verify = !config[:vcenter_insecure]
124
+ quick = config[:quick]
125
+
126
+ @vm_guest = Support::GuestOperations.new(vim, vm, username, password, ssl_verify: ssl_verify, quick: quick)
127
+ @vm_guest.logger = logger
128
+ @vm_guest
129
+ end
130
+
131
+ def os_family
132
+ return vm.guest.guestFamily == "windowsGuest" ? :windows : :linux if vm.guest.guestFamily
133
+
134
+ # VMware tools are not initialized or missing, infer from Guest Id
135
+ vm.config&.guestId =~ /^[Ww]in/ ? :windows : :linux
136
+ end
137
+
138
+ def linux?
139
+ os_family == :linux
140
+ end
141
+
142
+ def windows?
143
+ os_family == :windows
144
+ end
145
+
146
+ def hexit_code(exit_code)
147
+ exit_code += 2.pow(32) if exit_code < 0
148
+
149
+ "0x" + exit_code.to_s(16).upcase
150
+ end
151
+
152
+ def find_vm(needle)
153
+ root_folder = vim.serviceInstance.content.rootFolder
154
+
155
+ if ip?(needle)
156
+ root_folder.findByIp(config[:host])
157
+ elsif uuid?(needle)
158
+ root_folder.findByUuid(config[:host])
159
+ elsif inventory_path?(needle)
160
+ root_folder.findByInventoryPath(config[:host])
161
+ else
162
+ root_folder.findByDnsName(config[:host])
163
+ end
164
+ end
165
+
166
+ def ip?(ip_string)
167
+ ip_string.match? Resolv::IPv4::Regex
168
+ end
169
+
170
+ def uuid?(uuid)
171
+ uuid.downcase.match?(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/)
172
+ end
173
+
174
+ def inventory_path?(path)
175
+ path.include? "/"
176
+ end
177
+
178
+ def moref?(ref)
179
+ ref.match?(/vm-[0-9]*/)
180
+ end
181
+
182
+ def search_type(needle)
183
+ return "IP" if ip?(needle)
184
+ return "UUID" if uuid?(needle)
185
+ return "PATH" if inventory_path?(needle)
186
+
187
+ "DNS"
188
+ end
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,261 @@
1
+ require "rbvmomi" unless defined?(RbVmomi)
2
+ require "net/http" unless defined?(Net::HTTP)
3
+
4
+ class Support
5
+ # Encapsulate VMware Tools GOM interaction, originally inspired by github:dnuffer/raidopt
6
+ class GuestOperations
7
+ SHELL_TYPES = {
8
+ linux: {
9
+ suffix: ".sh",
10
+ cmd: "/bin/sh",
11
+ args: '-c ". %<cmdfile>s" > %<outfile>s 2> %<errfile>s',
12
+ },
13
+
14
+ # BUG: Includes prompt
15
+ cmd: {
16
+ suffix: ".cmd",
17
+ cmd: "cmd.exe",
18
+ args: '/s /c "%<cmdfile>s" > %<outfile>s 2> %<errfile>s',
19
+ },
20
+
21
+ # Invoking PS via cmd seems the only way to get this to work
22
+ powershell: {
23
+ suffix: ".ps1",
24
+ cmd: "cmd.exe",
25
+ args: "/C powershell -NonInteractive -ExecutionPolicy Bypass -File %<cmdfile>s >%<outfile>s 2>%<errfile>s",
26
+ },
27
+ }.freeze
28
+
29
+ attr_writer :gom, :logger
30
+
31
+ def initialize(vim, vm, username, password, ssl_verify: true, logger: nil, quick: false)
32
+ @vim = vim
33
+ @vm = vm
34
+
35
+ @guest_auth = RbVmomi::VIM::NamePasswordAuthentication(interactiveSession: false, username: username, password: password)
36
+
37
+ @ssl_verify = ssl_verify
38
+ @quick = quick
39
+ end
40
+
41
+ # Required privileges: VirtualMachine.GuestOperations.Execute, VirtualMachine.GuestOperations.Modify
42
+ def run(command, shell_type: :auto, timeout: 60.0)
43
+ logger.debug format("Running `%s` remotely", command)
44
+
45
+ if shell_type == :auto
46
+ shell_type = :linux if linux?
47
+ shell_type = :powershell if windows?
48
+ end
49
+
50
+ shell = SHELL_TYPES[shell_type]
51
+ raise "Unsupported shell type #{shell_type}" unless shell
52
+
53
+ logger.warn "Command execution on Windows is very slow" unless @warned || linux?
54
+ @warned = true
55
+
56
+ temp_file = write_temp_file(command, suffix: shell[:suffix] || "")
57
+ temp_out = "#{temp_file}-out.txt"
58
+ temp_err = "#{temp_file}-err.txt"
59
+
60
+ begin
61
+ args = format(shell[:args], cmdfile: temp_file, outfile: temp_out, errfile: temp_err)
62
+ exit_code = run_program(shell[:cmd], args, timeout)
63
+ rescue StandardError
64
+ proc_err = read_file(temp_err)
65
+ raise format("Error executing command %s. Exit code: %d. StdErr %s", command, exit_code || -1, proc_err)
66
+ end
67
+
68
+ stdout = read_file(temp_out)
69
+ stdout = ascii_only(stdout) if bom?(stdout)
70
+
71
+ stderr = read_file(temp_err) unless exit_code == 0 && @quick
72
+
73
+ unless @quick
74
+ delete_file(temp_file)
75
+ delete_file(temp_out)
76
+ delete_file(temp_err)
77
+ end
78
+
79
+ ::Train::Extras::CommandResult.new(stdout, stderr || "", exit_code)
80
+ end
81
+
82
+ # Required privilege: VirtualMachine.GuestOperations.Query
83
+ def exist?(remote_file)
84
+ logger.debug format("Checking for remote file %s", remote_file)
85
+
86
+ gom.fileManager.ListFilesInGuest(vm: @vm, auth: @guest_auth, filePath: remote_file)
87
+
88
+ true
89
+ rescue RbVmomi::Fault
90
+ false
91
+ end
92
+
93
+ def read_file(remote_file)
94
+ return "" unless @quick || exist?(remote_file)
95
+
96
+ download_file(remote_file, nil) || ""
97
+ end
98
+
99
+ # Required privilege: VirtualMachine.GuestOperations.Modify
100
+ def write_file(remote_file, contents)
101
+ logger.debug format("Writing to remote file %s", remote_file)
102
+
103
+ put_url = gom.fileManager.InitiateFileTransferToGuest(
104
+ vm: @vm,
105
+ auth: @guest_auth,
106
+ guestFilePath: remote_file,
107
+ fileAttributes: RbVmomi::VIM::GuestFileAttributes(),
108
+ fileSize: contents.size,
109
+ overwrite: true
110
+ )
111
+
112
+ # VCenter internal name might mismatch the external, so fix it
113
+ put_url = put_url.gsub(%r{^https://\*:}, format("https://%s:%s", @vm._connection.host, put_url))
114
+ uri = URI.parse(put_url)
115
+
116
+ request = Net::HTTP::Put.new(uri.request_uri)
117
+ request["Transfer-Encoding"] = "chunked"
118
+ request["Content-Length"] = contents.size
119
+ request.body = contents
120
+
121
+ http_request(put_url, request)
122
+ rescue RbVmomi::Fault => e
123
+ logger.error "Error during upload, check permissions on remote system: '" + e.message + "'"
124
+ end
125
+
126
+ # Required privilege: VirtualMachine.GuestOperations.Modify
127
+ def write_temp_file(contents, prefix: "", suffix: "")
128
+ logger.debug format("Writing to temporary remote file")
129
+
130
+ temp_name = gom.fileManager.CreateTemporaryFileInGuest(vm: @vm, auth: @guest_auth, prefix: prefix, suffix: suffix)
131
+ write_file(temp_name, contents)
132
+
133
+ temp_name
134
+ end
135
+
136
+ # Required privilege: VirtualMachine.GuestOperations.Modify
137
+ def upload_file(local_file, remote_file)
138
+ logger.debug format("Uploading %s to remote file %s", local_file, remote_file)
139
+
140
+ write_file(remote_file, File.open(local_file, "rb").read)
141
+ end
142
+
143
+ # Required privilege: VirtualMachine.GuestOperations.Modify
144
+ def download_file(remote_file, local_file)
145
+ logger.debug format("Downloading remote file %s to %s", local_file, remote_file)
146
+
147
+ info = gom.fileManager.InitiateFileTransferFromGuest(vm: @vm, auth: @guest_auth, guestFilePath: remote_file)
148
+ uri = URI.parse(info.url)
149
+
150
+ request = Net::HTTP::Get.new(uri.request_uri)
151
+ response = http_request(info.url, request)
152
+
153
+ if response.body.size != info.size
154
+ raise format("Downloaded file has different size than reported: %s (%d bytes instead of %d bytes)", remote_file, response.body.size, info.size)
155
+ end
156
+
157
+ local_file.nil? ? response.body : File.open(local_file, "w") { |file| file.write(response.body) }
158
+ end
159
+
160
+ # Required privilege: VirtualMachine.GuestOperations.Modify
161
+ def delete_file(remote_file)
162
+ logger.debug format("Deleting remote file %s", remote_file)
163
+
164
+ gom.fileManager.DeleteFileInGuest(vm: @vm, auth: @guest_auth, filePath: remote_file)
165
+
166
+ true
167
+ rescue RbVmomi::Fault => e
168
+ raise unless e.message.start_with? "FileNotFound:"
169
+
170
+ false
171
+ end
172
+
173
+ # Required privilege: VirtualMachine.GuestOperations.Modify
174
+ def delete_directory(remote_dir, recursive: true)
175
+ logger.debug format("Deleting remote directory %s", remote_dir)
176
+
177
+ gom.fileManager.DeleteDirectoryInGuest(vm: @vm, auth: @guest_auth, directoryPath: remote_dir, recursive: recursive)
178
+
179
+ true
180
+ rescue RbVmomi::Fault => e
181
+ raise if e.message.start_with? "NotADirectory:"
182
+
183
+ false
184
+ end
185
+
186
+ private
187
+
188
+ def gom
189
+ @gom ||= @vim.serviceContent.guestOperationsManager
190
+ end
191
+
192
+ def logger
193
+ @logger ||= Logger.new($stdout, level: :info)
194
+ end
195
+
196
+ def ascii_only(string)
197
+ string.bytes[2..].map(&:chr).join.delete("\000")
198
+ end
199
+
200
+ def bom?(string)
201
+ string.bytes[0..1] == [0xFF, 0xFE]
202
+ end
203
+
204
+ def http_request(url, request_data)
205
+ uri = URI.parse(url)
206
+
207
+ http = Net::HTTP.new(uri.host, uri.port)
208
+ http.use_ssl = (uri.scheme == "https")
209
+ http.verify_mode = @ssl_verify ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
210
+
211
+ http.request(request_data)
212
+ end
213
+
214
+ def linux?
215
+ os_family == :linux
216
+ end
217
+
218
+ def windows?
219
+ os_family == :windows
220
+ end
221
+
222
+ def os_family
223
+ return @vm.guest.guestFamily == "windowsGuest" ? :windows : :linux if @vm.guest.guestFamily
224
+
225
+ # VMware tools are not initialized or missing, infer from Guest ID
226
+ @vm.config&.guestId&.match(/^win/) ? :windows : :linux
227
+ end
228
+
229
+ def run_program(path, args = "", timeout = 60.0)
230
+ logger.debug format("Running %s %s", path, args)
231
+
232
+ gp_spec = RbVmomi::VIM::GuestProgramSpec.new(programPath: path, arguments: args)
233
+ pid = gom.processManager.StartProgramInGuest(vm: @vm, auth: @guest_auth, spec: gp_spec)
234
+
235
+ wait_for_process_exit(pid, timeout)
236
+ process_exit_code(pid)
237
+ end
238
+
239
+ def wait_for_process_exit(pid, timeout = 60.0, interval = 1.0)
240
+ start = Time.new
241
+
242
+ loop do
243
+ return unless process_running?(pid)
244
+ break if (Time.new - start) >= timeout
245
+
246
+ sleep interval
247
+ end
248
+
249
+ raise format("Timeout waiting for process %d to exit after %d seconds", pid, timeout) if (Time.new - start) >= timeout
250
+ end
251
+
252
+ def process_running?(pid)
253
+ procs = gom.processManager.ListProcessesInGuest(vm: @vm, auth: @guest_auth, pids: [pid])
254
+ procs.empty? || procs.any? { |gpi| gpi.exitCode.nil? }
255
+ end
256
+
257
+ def process_exit_code(pid)
258
+ gom.processManager.ListProcessesInGuest(vm: @vm, auth: @guest_auth, pids: [pid])&.first&.exitCode
259
+ end
260
+ end
261
+ end
@@ -0,0 +1,29 @@
1
+ require "train"
2
+ require "train/plugins"
3
+ require "train-vsphere-gom/connection"
4
+
5
+ module TrainPlugins
6
+ module VsphereGom
7
+ class Transport < Train.plugin(1)
8
+ name "vsphere-gom"
9
+
10
+ option :vcenter_server, required: true, default: ENV["VI_SERVER"]
11
+ option :vcenter_username, required: true, default: ENV["VI_USERNAME"]
12
+ option :vcenter_password, required: true, default: ENV["VI_PASSWORD"]
13
+ option :vcenter_insecure, required: false, default: true
14
+
15
+ option :host, default: ENV["VI_VM"], required: true
16
+ option :user, required: true
17
+ option :password, required: true
18
+
19
+ option :quick, default: false
20
+ option :shell_type, default: :auto
21
+ option :timeout, default: 60
22
+
23
+ # inspec -t vsphere-gom://
24
+ def connection(_instance_opts = nil)
25
+ @connection ||= TrainPlugins::VsphereGom::Connection.new(@options)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,5 @@
1
+ module TrainPlugins
2
+ module VsphereGom
3
+ VERSION = "0.1.0".freeze
4
+ end
5
+ end
@@ -0,0 +1,30 @@
1
+ lib = File.expand_path("lib", __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "train-vsphere-gom"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "train-vsphere-gom"
7
+ spec.version = TrainPlugins::VsphereGom::VERSION
8
+ spec.authors = ["Thomas Heinen"]
9
+ spec.email = ["theinen@tecracer.de"]
10
+ spec.summary = "Train transport for vSphere GOM"
11
+ spec.description = "Execute commands via VMware Tools (without need for network)"
12
+ spec.homepage = "https://github.com/tecracer-chef/train-vsphere-gom"
13
+ spec.license = "Apache-2.0"
14
+
15
+ spec.files = %w{
16
+ README.md train-vsphere-gom.gemspec Gemfile
17
+ } + Dir.glob(
18
+ "lib/**/*", File::FNM_DOTMATCH
19
+ ).reject { |f| File.directory?(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.required_ruby_version = "~> 2.6"
23
+
24
+ # If you only need certain gems during development or testing, list
25
+ # them in Gemfile, not here.
26
+
27
+ # Do not list inspec as a dependency of a train plugin.
28
+ # Do not list train or train-core as a dependency of a train plugin.
29
+ spec.add_dependency "rbvmomi", "~> 3.0"
30
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: train-vsphere-gom
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Thomas Heinen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-02-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rbvmomi
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
27
+ description: Execute commands via VMware Tools (without need for network)
28
+ email:
29
+ - theinen@tecracer.de
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - Gemfile
35
+ - README.md
36
+ - lib/train-vsphere-gom.rb
37
+ - lib/train-vsphere-gom/connection.rb
38
+ - lib/train-vsphere-gom/guest_operations.rb
39
+ - lib/train-vsphere-gom/transport.rb
40
+ - lib/train-vsphere-gom/version.rb
41
+ - train-vsphere-gom.gemspec
42
+ homepage: https://github.com/tecracer-chef/train-vsphere-gom
43
+ licenses:
44
+ - Apache-2.0
45
+ metadata: {}
46
+ post_install_message:
47
+ rdoc_options: []
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.6'
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ requirements: []
61
+ rubygems_version: 3.0.3
62
+ signing_key:
63
+ specification_version: 4
64
+ summary: Train transport for vSphere GOM
65
+ test_files: []