train-vsphere-gom 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []