knife-vcenter 1.0.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.
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Author:: Chef Partner Engineering (<partnereng@chef.io>)
4
+ # Copyright:: Copyright (c) 2017 Chef Software, Inc.
5
+ # License:: Apache License, Version 2.0
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ require 'chef/knife'
21
+ require 'chef/knife/cloud/server/show_options'
22
+ require 'chef/knife/cloud/server/show_command'
23
+ require 'chef/knife/cloud/vcenter_service'
24
+ require 'chef/knife/cloud/vcenter_service_helpers'
25
+ require 'chef/knife/cloud/vcenter_service_options'
26
+
27
+ class Chef
28
+ class Knife
29
+ class Cloud
30
+ class VcenterVmShow < ServerShowCommand
31
+ include ServerShowOptions
32
+ include VcenterServiceOptions
33
+ include VcenterServiceHelpers
34
+
35
+ banner 'knife vcenter vm show NAME (options)'
36
+
37
+ # rubocop:disable Style/GuardClause
38
+ def validate_params!
39
+ if @name_args.empty?
40
+ ui.error('You must supply the name of the virtual machine to display.')
41
+ exit(1) if @name_args.empty?
42
+ end
43
+
44
+ if @name_args.size > 1
45
+ ui.error('You may only supply one virtual machine name')
46
+ exit 1
47
+ end
48
+
49
+ super
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Author:: Chef Partner Engineering (<partnereng@chef.io>)
4
+ # Copyright:: Copyright (c) 2017 Chef Software, Inc.
5
+ # License:: Apache License, Version 2.0
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ module KnifeVcenter
21
+ VERSION = '1.0.0'.freeze
22
+ end
@@ -0,0 +1,463 @@
1
+ # Copyright 2014-2017 VMware, Inc. All Rights Reserved.
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ require 'savon'
5
+ require 'nokogiri'
6
+ require 'base'
7
+ # require 'sample/framework/sample_base'
8
+
9
+ # Utility class that helps use the lookup service.
10
+ class LookupServiceHelper
11
+
12
+ attr_reader :sample, :wsdl_url, :soap_url
13
+ attr_reader :serviceRegistration
14
+
15
+ # Constructs a new instance.
16
+ # @param sample [SampleBase] the associated sample, which provides access
17
+ # to the configuration properties of the sample
18
+ def initialize(host)
19
+
20
+ @soap_url = format("https://%s/lookupservice/sdk", host)
21
+ @wsdl_url = format("https://%s/lookupservice/wsdl/lookup.wsdl", host)
22
+
23
+ end
24
+
25
+ # Connects to the lookup service.
26
+ def connect
27
+ rsc = RetrieveServiceContent.new(client).invoke()
28
+ @serviceRegistration = rsc.get_service_registration()
29
+ Base.log.info "service registration = #{serviceRegistration}"
30
+ end
31
+
32
+ # Finds the SSO service URL.
33
+ # In a MxN setup where there are more than one PSC nodes;
34
+ # This method returns the first SSO service endpoint URL
35
+ # as returned by the lookup service.
36
+ #
37
+ # @return [String] SSO Service endpoint URL.
38
+ def find_sso_url
39
+ result = find_service_url(product='com.vmware.cis',
40
+ service='cs.identity',
41
+ endpoint='com.vmware.cis.cs.identity.sso',
42
+ protocol='wsTrust')
43
+ raise 'SSO URL not found' unless result && result.size > 0
44
+ return result.values[0]
45
+ end
46
+
47
+ # Finds all the vAPI service endpoint URLs.
48
+ # In a MxN setup where there are more than one management node;
49
+ # this method returns more than one URL
50
+ #
51
+ # @return [Hash] vapi service endpoint URLs in a dictionary
52
+ # where the key is the node_id and the value is the service URL.
53
+ def find_vapi_urls
54
+ return find_service_url(product='com.vmware.cis',
55
+ service='cs.vapi',
56
+ endpoint='com.vmware.vapi.endpoint',
57
+ protocol='vapi.json.https.public')
58
+ end
59
+
60
+ # Finds the vapi service endpoint URL of a management node.
61
+ #
62
+ # @param node_id [String] The UUID of the management node.
63
+ # @return [String] vapi service endpoint URL of a management node or
64
+ # nil if no vapi endpoint is found.
65
+ def find_vapi_url(node_id)
66
+ raise 'node_id is required' if node_id.nil?
67
+ result = find_vapi_urls()
68
+ raise 'VAPI URLs not found' unless result && result.size > 0
69
+ return result[node_id]
70
+ end
71
+
72
+ # Finds all the vim service endpoint URLs
73
+ # In a MxN setup where there are more than one management node;
74
+ # this method returns more than one URL
75
+ #
76
+ # @return [Hash] vim service endpoint URLs in a dictionary where
77
+ # the key is the node_id and the value is the service URL.
78
+ def find_vim_urls
79
+ return find_service_url(product='com.vmware.cis',
80
+ service='vcenterserver',
81
+ endpoint='com.vmware.vim',
82
+ protocol='vmomi')
83
+ end
84
+
85
+ # Finds the vim service endpoint URL of a management node
86
+ #
87
+ # @param node_id [String] The UUID of the management node.
88
+ # @return [String] vim service endpoint URL of a management node or
89
+ # nil if no vim endpoint is found.
90
+ def find_vim_url(node_id)
91
+ raise 'node_id is required' if node_id.nil?
92
+ result = find_vim_urls()
93
+ raise 'VIM URLs not found' unless result && result.size > 0
94
+ return result[node_id]
95
+ end
96
+
97
+ # Finds all the spbm service endpoint URLs
98
+ # In a MxN setup where there are more than one management node;
99
+ # this method returns more than one URL
100
+ #
101
+ # @return [Hash] spbm service endpoint URLs in a dictionary where
102
+ # the key is the node_id and the value is the service URL.
103
+ def find_vim_pbm_urls
104
+ return find_service_url(product='com.vmware.vim.sms',
105
+ service='sms',
106
+ endpoint='com.vmware.vim.pbm',
107
+ protocol='https')
108
+ end
109
+
110
+ # Finds the spbm service endpoint URL of a management node
111
+ #
112
+ # @param node_id [String] The UUID of the management node.
113
+ # @return [String] spbm service endpoint URL of a management node or
114
+ # nil if no spbm endpoint is found.
115
+ def find_vim_pbm_url(node_id)
116
+ raise 'node_id is required' if node_id.nil?
117
+ result = find_vim_pbm_urls()
118
+ raise 'PBM URLs not found' unless result && result.size > 0
119
+ return result[node_id]
120
+ end
121
+
122
+ # Get the management node id from the instance name
123
+ #
124
+ # @param instance_name [String] The instance name of the management node
125
+ # @return [String] The UUID of the management node or
126
+ # nil is no management node is found by the given instance name
127
+ def get_mgmt_node_id(instance_name)
128
+ raise 'instance_name is required' if instance_name.nil?
129
+ result = find_mgmt_nodes()
130
+ raise 'Management nodes not found' unless result && result.size > 0
131
+ return result[instance_name]
132
+ end
133
+
134
+ def get_mgmt_node_instance_name(node_id)
135
+ raise 'node_id is required' if node_id.nil?
136
+ result = find_mgmt_nodes()
137
+ raise 'Management nodes not found' unless result && result.size > 0
138
+ result.each { |k, v| return k if v == node_id }
139
+ nil
140
+ end
141
+
142
+ # Finds the instance name and UUID of the management node for M1xN1 or
143
+ # when the PSC and management services all reside on a single node.
144
+ def get_default_mgmt_node
145
+ result = find_mgmt_nodes()
146
+ raise 'Management nodes not found' unless result && result.size > 0
147
+ #WHY: raise MultipleManagementNodeException.new if result.size > 1
148
+ return [result.keys[0], result.values[0]]
149
+ end
150
+
151
+ # Finds all the management nodes
152
+ #
153
+ # @return [Hash] management node instance name and node id (UUID) in a dictionary.
154
+ def find_mgmt_nodes
155
+ #assert self.serviceRegistration is not None
156
+ list = List.new(client, 'com.vmware.cis', 'vcenterserver',
157
+ 'vmomi', 'com.vmware.vim')
158
+
159
+ list.invoke()
160
+ list.get_instance_names()
161
+ end
162
+
163
+ private
164
+
165
+ # Finds a service URL with the given attributes.
166
+ def find_service_url(product, service, endpoint, protocol)
167
+ #assert serviceRegistration is not None
168
+ list = List.new(client, product, service, protocol, endpoint)
169
+
170
+ list.invoke()
171
+ list.get_service_endpoints()
172
+ end
173
+
174
+ # Gets or creates the Savon client instance.
175
+ def client
176
+ @client ||= Savon.client do |globals|
177
+ # see: http://savonrb.com/version2/globals.html
178
+ globals.wsdl wsdl_url
179
+ globals.endpoint soap_url
180
+
181
+ globals.strip_namespaces false
182
+ globals.env_namespace :S
183
+
184
+ # set like this so https connection does not fail
185
+ # TODO: find an acceptable solution for production
186
+ globals.ssl_verify_mode :none
187
+
188
+ # dev/debug settings
189
+ # globals.pretty_print_xml ENV['DEBUG_SOAP']
190
+ # globals.log ENV['DEBUG_SOAP']
191
+ end
192
+ end
193
+ end
194
+
195
+
196
+ # @abstract Base class for invocable service calls.
197
+ class Invocable
198
+
199
+ attr_reader :operation, :client, :response
200
+
201
+ # Constructs a new instance.
202
+ # @param operation [Symbol] the operation name
203
+ # @param client [Savon::Client] the client
204
+ def initialize(operation, client)
205
+ @operation = operation
206
+ @client = client
207
+ end
208
+
209
+ # Invokes the service call represented by this type.
210
+ def invoke
211
+ request = request_xml.to_s
212
+ Base.log.debug(request)
213
+ @response = client.call(operation, xml:request)
214
+ Base.log.debug(response)
215
+ self # for chaining with new
216
+ end
217
+
218
+ # Builds the request XML content.
219
+ def request_xml
220
+ builder = Builder::XmlMarkup.new()
221
+ builder.instruct!(:xml, encoding: "UTF-8")
222
+
223
+ builder.tag!("S:Envelope",
224
+ "xmlns:S" => "http://schemas.xmlsoap.org/soap/envelope/") do |envelope|
225
+ envelope.tag!("S:Body") do |body|
226
+ body_xml(body)
227
+ end
228
+ end
229
+ builder.target!
230
+ end
231
+
232
+ # Builds the body portion of the request XML content.
233
+ # Specific service operations must override this method.
234
+ def body_xml
235
+ raise 'abstract method not implemented!'
236
+ end
237
+
238
+ # Gets the response XML content.
239
+ def response_xml
240
+ raise 'illegal state: response not set yet' if response.nil?
241
+ @response_xml ||= Nokogiri::XML(response.to_xml)
242
+ end
243
+
244
+ def response_hash
245
+ @response_hash ||= response.to_hash
246
+ end
247
+ end
248
+
249
+ # Encapsulates the list operation of the lookup service.
250
+ class List < Invocable
251
+
252
+ # Constructs a new instance.
253
+ def initialize(client, product, service, protocol, endpoint)
254
+ super(:list, client)
255
+
256
+ @product = product
257
+ @service = service
258
+ @protocol = protocol
259
+ @endpoint = endpoint
260
+ end
261
+
262
+ =begin
263
+ <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
264
+ <S:Body>
265
+ <List xmlns="urn:lookup">
266
+ <_this type="LookupServiceRegistration">ServiceRegistration</_this>
267
+ <filterCriteria>
268
+ <serviceType>
269
+ <product>com.vmware.cis</product>
270
+ <type>cs.identity</type>
271
+ </serviceType>
272
+ <endpointType>
273
+ <protocol>wsTrust</protocol>
274
+ <type>com.vmware.cis.cs.identity.sso</type>
275
+ </endpointType>
276
+ </filterCriteria>
277
+ </List>
278
+ </S:Body>
279
+ </S:Envelope>
280
+ =end
281
+ def body_xml(body)
282
+ body.tag!("List", "xmlns" => "urn:lookup") do |list|
283
+ #TODO: use the copy that was retrieved on startup?
284
+ list.tag!("_this",
285
+ "type" => "LookupServiceRegistration") do |this|
286
+ this << "ServiceRegistration"
287
+ end
288
+ list.tag!("filterCriteria") do |criteria|
289
+ criteria.tag!("serviceType") do |stype|
290
+ stype.tag!("product") do |p|
291
+ p << @product
292
+ end
293
+ stype.tag!("type") do |t|
294
+ t << @service
295
+ end
296
+ end
297
+ criteria.tag!("endpointType") do |etype|
298
+ etype.tag!("protocol") do |p|
299
+ p << @protocol
300
+ end
301
+ etype.tag!("type") do |t|
302
+ t << @endpoint
303
+ end
304
+ end
305
+ end
306
+ end
307
+ end
308
+
309
+ # Gets the service endpoint information from the response.
310
+ # Support for MxN.
311
+ # @return [Hash] a hash where the key is NodeId and the Value is a Service URL
312
+ def get_service_endpoints
313
+ result = {}
314
+ =begin
315
+ <ListResponse xmlns="urn:lookup">
316
+ <returnval>
317
+ <serviceVersion>2.0</serviceVersion>
318
+ <vendorNameResourceKey/>
319
+ <vendorNameDefault/>
320
+ <vendorProductInfoResourceKey/>
321
+ <vendorProductInfoDefault/>
322
+ <serviceEndpoints>
323
+ <url>https://pa-rdinfra3-vm7-dhcp5583.eng.vmware.com/sts/STSService/vsphere.local</url>
324
+ <endpointType>
325
+ <protocol>wsTrust</protocol>
326
+ <type>com.vmware.cis.cs.identity.sso</type>
327
+ </endpointType>
328
+ <sslTrust>
329
+ ...
330
+ </sslTrust>
331
+ </serviceEndpoints>
332
+ <serviceNameResourceKey/>
333
+ <serviceNameDefault/>
334
+ <serviceDescriptionResourceKey/>
335
+ <serviceDescriptionDefault/>
336
+ <ownerId>pa-rdinfra3-vm7-dhcp5583.eng.vmware.com@vsphere.local</ownerId>
337
+ <serviceType>
338
+ <product>com.vmware.cis</product>
339
+ <type>cs.identity</type>
340
+ </serviceType>
341
+ <nodeId/>
342
+ <serviceId>6a8a5058-5d3d-4d42-bb5e-383b91c8732e</serviceId>
343
+ <siteId>default-first-site</siteId>
344
+ </returnval>
345
+ </ListResponse>
346
+ =end
347
+ Base.log.debug "List: response_hash = #{response_hash}"
348
+ return_val = response_hash[:list_response][:returnval]
349
+ return_val = [return_val] if return_val.is_a? Hash
350
+ return_val.each { |entry|
351
+ #FYI: the node_id is sometimes null, so use the service_id in this case
352
+ node_id = entry[:node_id] || entry[:service_id]
353
+ result[node_id] = entry[:service_endpoints][:url]
354
+ }
355
+ Base.log.debug "List: result = #{result}"
356
+ return result
357
+ end
358
+
359
+ def get_instance_names
360
+ result = {}
361
+ =begin
362
+ <serviceAttributes>
363
+ <key>com.vmware.cis.cm.GroupInternalId</key>
364
+ <value>com.vmware.vim.vcenter</value>
365
+ </serviceAttributes>
366
+ <serviceAttributes>
367
+ <key>com.vmware.cis.cm.ControlScript</key>
368
+ <value>vmware-vpxd.sh</value>
369
+ </serviceAttributes>
370
+ <serviceAttributes>
371
+ <key>com.vmware.cis.cm.HostId</key>
372
+ <value>906477a1-24c6-4d48-9e99-55ef962878f7</value>
373
+ </serviceAttributes>
374
+ <serviceAttributes>
375
+ <key>com.vmware.vim.vcenter.instanceName</key>
376
+ <value>pa-rdinfra3-vm7-dhcp5583.eng.vmware.com</value>
377
+ </serviceAttributes>
378
+ =end
379
+ Base.log.debug "List: response_hash = #{response_hash}"
380
+ return_val = response_hash[:list_response][:returnval]
381
+ return_val = [return_val] if return_val.is_a? Hash
382
+ return_val.each { |entry|
383
+ node_id = entry[:node_id]
384
+ #TODO: is it possible there be 0 or 1 attrs? if so, deal with it.
385
+ attrs = entry[:service_attributes]
386
+ Base.log.debug "List: attrs=#{attrs}"
387
+ attrs.each { |attr|
388
+ if attr[:key] == 'com.vmware.vim.vcenter.instanceName'
389
+ result[attr[:value]] = node_id
390
+ end
391
+ }
392
+ }
393
+ Base.log.debug "List: result = #{result}"
394
+ return result
395
+ end
396
+ end
397
+
398
+ # Encapsulates the RetrieveServiceContent operation of the lookup service.
399
+ class RetrieveServiceContent < Invocable
400
+
401
+ # Constructs a new instance.
402
+ def initialize(client)
403
+ super(:retrieve_service_content, client)
404
+ end
405
+
406
+ =begin
407
+ <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
408
+ <S:Body>
409
+ <RetrieveServiceContent xmlns="urn:lookup">
410
+ <_this type="LookupServiceInstance">ServiceInstance</_this>
411
+ </RetrieveServiceContent>
412
+ </S:Body>
413
+ </S:Envelope>
414
+ =end
415
+ def body_xml(body)
416
+ body.tag!("RetrieveServiceContent", "xmlns" => "urn:lookup") do |rsc|
417
+ rsc.tag!("_this", "type" => "LookupServiceInstance") do |this|
418
+ this << "ServiceInstance"
419
+ end
420
+ end
421
+ end
422
+
423
+ =begin
424
+ ...
425
+ <RetrieveServiceContentResponse xmlns="urn:lookup">
426
+ <returnval>
427
+ <lookupService type="LookupLookupService">lookupService</lookupService>
428
+ <serviceRegistration type="LookupServiceRegistration">ServiceRegistration</serviceRegistration>
429
+ <deploymentInformationService type="LookupDeploymentInformationService">deploymentInformationService</deploymentInformationService>
430
+ <l10n type="LookupL10n">l10n</l10n>
431
+ </returnval>
432
+ </RetrieveServiceContentResponse>
433
+ ...
434
+ =end
435
+ def get_service_registration
436
+ Base.log.debug "RetrieveServiceContent: response_hash = #{response_hash}"
437
+ return_val = response_hash[:retrieve_service_content_response][:returnval]
438
+ result = return_val[:service_registration]
439
+ Base.log.debug "RetrieveServiceContent: result = #{result}"
440
+ result
441
+ end
442
+ end
443
+
444
+ class MultipleManagementNodeException < Exception
445
+ end
446
+
447
+ # main: quick self tester
448
+ if __FILE__ == $0
449
+ Base.log.level = Logger::DEBUG if ENV['DEBUG']
450
+ sample = SelfTestSample.new
451
+ sample.ls_ip = ARGV[0] || '10.67.245.207'
452
+ #MXN: sample.ls_ip = '10.160.42.83'
453
+ #MXN: sample.ls_ip = '10.160.35.191'
454
+ #MAYBE: sample.main() # for arg parsing
455
+ ls_helper = LookupServiceHelper.new(sample)
456
+ ls_helper.connect()
457
+ puts '***************************************'
458
+ puts "SSO URL: #{ls_helper.find_sso_url()}"
459
+ puts "VAPI URL: #{ls_helper.find_vapi_urls()}"
460
+ puts "VIM URL: #{ls_helper.find_vim_urls()}"
461
+ puts "PBM URL: #{ls_helper.find_vim_pbm_urls()}"
462
+ puts "Mgmt Nodes: #{ls_helper.find_mgmt_nodes()}"
463
+ end