knife-vcenter 2.0.1 → 2.0.2
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 +4 -4
- data/lib/chef/knife/cloud/vcenter_service.rb +83 -102
- data/lib/chef/knife/vcenter_host_list.rb +2 -3
- data/lib/chef/knife/vcenter_vm_list.rb +2 -3
- data/lib/knife-vcenter/version.rb +1 -1
- metadata +42 -17
- data/lib/base.rb +0 -40
- data/lib/lookup_service_helper.rb +0 -457
- data/lib/sso.rb +0 -264
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f5b64c8898d3f062d865d4d8ec26728caca8e5477cd60b4bb94812ddb6631167
|
4
|
+
data.tar.gz: 76f8c566aa4eb7d027490f7d6d70fffd07a45a2944cc7d3d09b357be600e856f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cadd9cf60dab6043f285e45d19042de4e34409e71f904bcfe1791e3ed2a3e9619da7b2df5bf036eb2c1895dbc2de849cf421a40c685fff398c9f3add337072c0
|
7
|
+
data.tar.gz: a742b7f205341ae1f6e3d3fd5702fa83215f5c5e588f2fdf6d660bce5becd5a7fe89972bc82aa4bc13249235fdf42cf92e916102d3bf8b9223ebc39a4f8d2cd2
|
@@ -21,19 +21,12 @@ require "chef/knife/cloud/exceptions"
|
|
21
21
|
require "chef/knife/cloud/service"
|
22
22
|
require "chef/knife/cloud/helpers"
|
23
23
|
require "chef/knife/cloud/vcenter_service_helpers"
|
24
|
-
require "net/http"
|
25
24
|
require "uri"
|
26
25
|
require "json"
|
27
26
|
require "ostruct"
|
28
|
-
require "
|
29
|
-
require "
|
30
|
-
require "com/vmware/cis"
|
31
|
-
require "com/vmware/vcenter"
|
32
|
-
require "com/vmware/vcenter/vm"
|
33
|
-
require "sso"
|
34
|
-
require "base"
|
27
|
+
require "vsphere-automation-cis"
|
28
|
+
require "vsphere-automation-vcenter"
|
35
29
|
require "set"
|
36
|
-
require "support/clone_vm"
|
37
30
|
|
38
31
|
class Chef
|
39
32
|
# The main knife class
|
@@ -44,43 +37,30 @@ class Chef
|
|
44
37
|
class VcenterService < Service
|
45
38
|
include VcenterServiceHelpers
|
46
39
|
|
47
|
-
attr_reader :
|
40
|
+
attr_reader :api_client, :session_api, :session_id
|
48
41
|
attr_reader :connection_options, :ipaddress
|
49
42
|
|
50
43
|
def initialize(options = {})
|
51
44
|
super(options)
|
52
45
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
sso_url = lookup_service_helper.find_sso_url
|
72
|
-
sso = SSO::Connection.new(sso_url).login(options[:username], options[:password])
|
73
|
-
token = sso.request_bearer_token
|
74
|
-
vapi_config.set_security_context(
|
75
|
-
VAPI::Security.create_saml_bearer_security_context(token.to_s)
|
76
|
-
)
|
77
|
-
|
78
|
-
# Login and get the session information
|
79
|
-
@session_svc = Com::Vmware::Cis::Session.new(vapi_config)
|
80
|
-
@session_id = session_svc.create
|
81
|
-
vapi_config.set_security_context(
|
82
|
-
VAPI::Security.create_session_security_context(session_id)
|
83
|
-
)
|
46
|
+
configuration = VSphereAutomation::Configuration.new.tap do |c|
|
47
|
+
c.host = options[:host]
|
48
|
+
c.username = options[:username]
|
49
|
+
c.password = options[:password]
|
50
|
+
c.scheme = "https"
|
51
|
+
c.verify_ssl = options[:verify_ssl]
|
52
|
+
c.verify_ssl_host = options[:verify_ssl]
|
53
|
+
end
|
54
|
+
|
55
|
+
Base.log.warn("SSL Verification is turned OFF") if options[:logs] && !options[:verify_ssl]
|
56
|
+
|
57
|
+
@api_client = VSphereAutomation::ApiClient.new(configuration)
|
58
|
+
api_client.default_headers["Authorization"] = configuration.basic_auth_token
|
59
|
+
|
60
|
+
session_api = VSphereAutomation::CIS::SessionApi.new(api_client)
|
61
|
+
session_id = session_api.create("").value
|
62
|
+
|
63
|
+
api_client.default_headers["vmware-api-session-id"] = session_id
|
84
64
|
|
85
65
|
# Set the class properties for the rbvmomi connections
|
86
66
|
@connection_options = {
|
@@ -96,7 +76,7 @@ class Chef
|
|
96
76
|
# @param [Object] options to override anything you need to do
|
97
77
|
def create_server(options = {})
|
98
78
|
# Create the vm object
|
99
|
-
|
79
|
+
vm_api = VSphereAutomation::VCenter::VMApi.new(api_client)
|
100
80
|
|
101
81
|
# Use the option to determine now a new machine is being created
|
102
82
|
case options[:type]
|
@@ -128,24 +108,24 @@ class Chef
|
|
128
108
|
when "create"
|
129
109
|
|
130
110
|
# Create the placement object
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
111
|
+
placement_spec = VSphereAutomation::VCenter::VcenterVMPlacementSpec.new( ###
|
112
|
+
folder: get_folder(options[:folder]),
|
113
|
+
host: get_host(options[:targethost]).host,
|
114
|
+
datastore: get_datastore(options[:datastore]),
|
115
|
+
resource_pool: get_resourcepool(options[:resource_pool])
|
116
|
+
)
|
136
117
|
|
137
118
|
# Create the CreateSpec object
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
puts "setting the placement"
|
144
|
-
createspec.placement = placementspec
|
119
|
+
create_spec = VSphereAutomation::VCenter::VcenterVMCreateSpec.new(
|
120
|
+
name: options[:name],
|
121
|
+
guest_OS: VSphereAutomation::VCenter::VcenterVmGuestOS::OTHER,
|
122
|
+
placement: placement_spec
|
123
|
+
)
|
145
124
|
|
146
125
|
# Create the new machine
|
147
126
|
begin
|
148
|
-
|
127
|
+
create_model = VSphereAutomation::VCenter::VcenterVMCreate.new(spec: create_spec)
|
128
|
+
vm = vm_api.create(create_model).value
|
149
129
|
rescue StandardError => e
|
150
130
|
puts e.message
|
151
131
|
end
|
@@ -155,111 +135,112 @@ class Chef
|
|
155
135
|
# Get a list of vms from the API
|
156
136
|
#
|
157
137
|
def list_servers
|
158
|
-
|
138
|
+
vms = VSphereAutomation::VCenter::VMApi.new(api_client).list.value
|
139
|
+
|
140
|
+
# list_resource_command uses .send(:name) syntax, so convert to OpenStruct to keep it compatible
|
141
|
+
vms.map { |vmsummary| OpenStruct.new(vmsummary.to_hash) }
|
159
142
|
end
|
160
143
|
|
161
144
|
# Return a list of the hosts in the vCenter
|
162
145
|
#
|
163
146
|
def list_hosts
|
164
|
-
|
147
|
+
VSphereAutomation::VCenter::HostApi.new(api_client).list.value
|
165
148
|
end
|
166
149
|
|
167
150
|
# Return a list of the datacenters in the vCenter
|
168
151
|
#
|
169
152
|
def list_datacenters
|
170
|
-
|
153
|
+
VSphereAutomation::VCenter::DatacenterApi.new(api_client).list.value
|
171
154
|
end
|
172
155
|
|
173
156
|
# Return a list of the clusters in the vCenter
|
174
157
|
#
|
175
158
|
def list_clusters
|
176
|
-
|
159
|
+
VSphereAutomation::VCenter::ClusterApi.new(api_client).list.value
|
177
160
|
end
|
178
161
|
|
179
162
|
# Checks to see if the datacenter exists in the vCenter
|
180
163
|
#
|
181
164
|
# @param [String] name is the name of the datacenter
|
182
165
|
def datacenter_exists?(name)
|
183
|
-
|
184
|
-
|
185
|
-
dc = dc_obj.list(filter)
|
166
|
+
dc_api = VSphereAutomation::VCenter::DatacenterApi.new(api_client)
|
167
|
+
dcs = dc_api.list({ filter_names: name }).value
|
186
168
|
|
187
|
-
raise format("Unable to find data center: %s", name) if
|
169
|
+
raise format("Unable to find data center: %s", name) if dcs.empty?
|
188
170
|
end
|
189
171
|
|
190
172
|
# Gets the folder
|
191
173
|
#
|
192
174
|
# @param [String] name is the folder of the datacenter
|
193
175
|
def get_folder(name)
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
folder = folder_obj.list(filter)
|
176
|
+
folder_api = VSphereAutomation::VCenter::FolderApi.new(api_client)
|
177
|
+
folders = folder_api.list({ filter_names: name }).value
|
178
|
+
|
179
|
+
raise format("Unable to find folder: %s", name) if folders.empty?
|
199
180
|
|
200
|
-
|
181
|
+
folders.first.folder
|
201
182
|
end
|
202
183
|
|
203
184
|
# Gets the host
|
204
185
|
#
|
205
186
|
# @param [String] name is the host of the datacenter
|
206
187
|
def get_host(name)
|
207
|
-
|
188
|
+
# create a host object to work with
|
189
|
+
host_api = VSphereAutomation::VCenter::HostApi.new(api_client)
|
208
190
|
|
209
191
|
if name.nil?
|
210
|
-
|
192
|
+
hosts = host_api.list.value
|
211
193
|
else
|
212
|
-
|
213
|
-
host = host_obj.list(filter)
|
194
|
+
hosts = host_api.list({ filter_names: name }).value
|
214
195
|
end
|
215
196
|
|
216
|
-
host
|
197
|
+
raise format("Unable to find target host: %s", name) if hosts.empty?
|
198
|
+
|
199
|
+
hosts.first
|
217
200
|
end
|
218
201
|
|
219
202
|
# Gets the datastore
|
220
203
|
#
|
221
204
|
# @param [String] name is the datastore of the datacenter
|
222
205
|
def get_datastore(name)
|
223
|
-
|
206
|
+
ds_api = VSphereAutomation::VCenter::DatastoreApi.new(api_client)
|
207
|
+
ds = ds_api.list({ filter_names: name }).value
|
224
208
|
|
225
|
-
if
|
226
|
-
|
227
|
-
else
|
228
|
-
filter = Com::Vmware::Vcenter::Datastore::FilterSpec.new(names: Set.new([name]))
|
229
|
-
datastore = datastore_obj.list(filter)
|
230
|
-
end
|
231
|
-
|
232
|
-
datastore[0].datastore
|
209
|
+
raise format("Unable to find data store: %s", name) if ds.empty?
|
210
|
+
ds.first.datastore
|
233
211
|
end
|
234
212
|
|
235
213
|
# Gets the resource_pool
|
236
214
|
#
|
237
215
|
# @param [String] name is the resource_pool of the datacenter
|
238
|
-
def
|
239
|
-
|
240
|
-
|
216
|
+
def get_resourcepool(name)
|
217
|
+
### verify
|
218
|
+
rp_api = VSphereAutomation::VCenter::ResourcePoolApi.new(api_client)
|
241
219
|
|
242
|
-
# If a name has been set then try to find it, otherwise use the first
|
243
|
-
# resource pool that can be found
|
244
220
|
if name.nil?
|
245
|
-
|
221
|
+
# Remove default pool for first pass (<= 1.2.1 behaviour to pick first user-defined pool found)
|
222
|
+
resource_pools = rp_api.list.value.delete_if { |pool| pool.name == "Resources" }
|
223
|
+
puts "Search of all resource pools found: " + resource_pools.map { |pool| pool.name }.to_s
|
224
|
+
|
225
|
+
# Revert to default pool, if no user-defined pool found (> 1.2.1 behaviour)
|
226
|
+
# (This one might not be found under some circumstances by the statement above)
|
227
|
+
return get_resource_pool("Resources") if resource_pools.empty?
|
246
228
|
else
|
247
|
-
|
248
|
-
|
249
|
-
resource_pool = rp_obj.list(filter)
|
250
|
-
raise format("Unable to find Resource Pool: %s", name) if resource_pool.nil?
|
229
|
+
resource_pools = rp_api.list({ filter_names: name }).value
|
230
|
+
puts "Search for resource pools found: " + resource_pools.map { |pool| pool.name }.to_s
|
251
231
|
end
|
252
232
|
|
253
|
-
|
233
|
+
raise format("Unable to find Resource Pool: %s", name) if resource_pools.empty?
|
234
|
+
|
235
|
+
resource_pools.first.resource_pool
|
254
236
|
end
|
255
237
|
|
256
238
|
# Gets the server
|
257
239
|
#
|
258
240
|
# @param [String] name is the server of the datacenter
|
259
241
|
def get_server(name)
|
260
|
-
|
261
|
-
|
262
|
-
vm_obj.list(filter)[0]
|
242
|
+
vm_api = VSphereAutomation::VCenter::VMApi.new(api_client)
|
243
|
+
vm_api.list({ filter_names: name }).value.first
|
263
244
|
end
|
264
245
|
|
265
246
|
# Deletes the VM
|
@@ -272,16 +253,16 @@ class Chef
|
|
272
253
|
|
273
254
|
ui.confirm("Do you really want to be delete this virtual machine")
|
274
255
|
|
275
|
-
|
256
|
+
vm_api = VSphereAutomation::VCenter::VMApi.new(api_client)
|
276
257
|
|
277
258
|
# check the power state of the machine, if it is powered on turn it off
|
278
|
-
if vm.power_state
|
279
|
-
|
259
|
+
if vm.power_state == "POWERED_ON"
|
260
|
+
power_api = VSphereAutomation::VCenter::VmPowerApi.new(api_client)
|
280
261
|
ui.msg("Shutting down machine")
|
281
|
-
|
262
|
+
power_api.stop(vm.vm)
|
282
263
|
end
|
283
264
|
|
284
|
-
|
265
|
+
vm_api.delete(vm.vm)
|
285
266
|
end
|
286
267
|
|
287
268
|
# Gets some server information
|
@@ -58,8 +58,7 @@ class Chef
|
|
58
58
|
#
|
59
59
|
# @param [Object] status takes the number and formats it how you need it to
|
60
60
|
def format_power_status(status)
|
61
|
-
|
62
|
-
status_color = case status_check
|
61
|
+
status_color = case status
|
63
62
|
when "POWERED_OFF"
|
64
63
|
:red
|
65
64
|
when "POWERED_ON"
|
@@ -68,7 +67,7 @@ class Chef
|
|
68
67
|
:yellow
|
69
68
|
end
|
70
69
|
|
71
|
-
ui.color(status
|
70
|
+
ui.color(status, status_color)
|
72
71
|
end
|
73
72
|
end
|
74
73
|
end
|
@@ -53,8 +53,7 @@ class Chef
|
|
53
53
|
# Sets the color for the different status of the machines
|
54
54
|
#
|
55
55
|
def format_power_status(status)
|
56
|
-
|
57
|
-
status_color = case status_check
|
56
|
+
status_color = case status
|
58
57
|
when "POWERED_OFF"
|
59
58
|
:red
|
60
59
|
when "POWERED_ON"
|
@@ -63,7 +62,7 @@ class Chef
|
|
63
62
|
:yellow
|
64
63
|
end
|
65
64
|
|
66
|
-
ui.color(status
|
65
|
+
ui.color(status, status_color)
|
67
66
|
end
|
68
67
|
|
69
68
|
# Formats the memory value
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: knife-vcenter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
4
|
+
version: 2.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chef Partner Engineering
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-02-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: knife-cloud
|
@@ -72,30 +72,30 @@ dependencies:
|
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
75
|
+
version: '0.1'
|
76
76
|
type: :runtime
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
82
|
+
version: '0.1'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: bundler
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
|
-
- - "
|
87
|
+
- - ">="
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: '
|
89
|
+
version: '0'
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
|
-
- - "
|
94
|
+
- - ">="
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: '
|
96
|
+
version: '0'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
|
-
name:
|
98
|
+
name: pry
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
101
|
- - ">="
|
@@ -109,7 +109,21 @@ dependencies:
|
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0'
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
|
-
name:
|
112
|
+
name: ruby-debug-ide
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 0.6.0
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 0.6.0
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rake
|
113
127
|
requirement: !ruby/object:Gem::Requirement
|
114
128
|
requirements:
|
115
129
|
- - ">="
|
@@ -123,19 +137,33 @@ dependencies:
|
|
123
137
|
- !ruby/object:Gem::Version
|
124
138
|
version: '0'
|
125
139
|
- !ruby/object:Gem::Dependency
|
126
|
-
name:
|
140
|
+
name: rspec
|
127
141
|
requirement: !ruby/object:Gem::Requirement
|
128
142
|
requirements:
|
129
143
|
- - "~>"
|
130
144
|
- !ruby/object:Gem::Version
|
131
|
-
version:
|
145
|
+
version: '3.7'
|
132
146
|
type: :development
|
133
147
|
prerelease: false
|
134
148
|
version_requirements: !ruby/object:Gem::Requirement
|
135
149
|
requirements:
|
136
150
|
- - "~>"
|
137
151
|
- !ruby/object:Gem::Version
|
138
|
-
version:
|
152
|
+
version: '3.7'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: rubocop-rspec
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - "~>"
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '1.18'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - "~>"
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '1.18'
|
139
167
|
description: Knife plugin to VMware vCenter.
|
140
168
|
email:
|
141
169
|
- partnereng@chef.io
|
@@ -144,7 +172,6 @@ extensions: []
|
|
144
172
|
extra_rdoc_files: []
|
145
173
|
files:
|
146
174
|
- LICENSE
|
147
|
-
- lib/base.rb
|
148
175
|
- lib/chef/knife/cloud/vcenter_service.rb
|
149
176
|
- lib/chef/knife/cloud/vcenter_service_helpers.rb
|
150
177
|
- lib/chef/knife/cloud/vcenter_service_options.rb
|
@@ -157,8 +184,6 @@ files:
|
|
157
184
|
- lib/chef/knife/vcenter_vm_list.rb
|
158
185
|
- lib/chef/knife/vcenter_vm_show.rb
|
159
186
|
- lib/knife-vcenter/version.rb
|
160
|
-
- lib/lookup_service_helper.rb
|
161
|
-
- lib/sso.rb
|
162
187
|
- lib/support/clone_vm.rb
|
163
188
|
- spec/spec_helper.rb
|
164
189
|
- spec/unit/vcenter_vm_list_spec.rb
|
@@ -174,7 +199,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
174
199
|
requirements:
|
175
200
|
- - ">="
|
176
201
|
- !ruby/object:Gem::Version
|
177
|
-
version: '
|
202
|
+
version: '2.3'
|
178
203
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
179
204
|
requirements:
|
180
205
|
- - ">="
|
data/lib/base.rb
DELETED
@@ -1,40 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
#
|
3
|
-
# Author:: Chef Partner Engineering (<partnereng@chef.io>)
|
4
|
-
# Copyright:: Copyright (c) 2017-2018 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 "logger"
|
21
|
-
|
22
|
-
# Base module for vcenter knife commands
|
23
|
-
module Base
|
24
|
-
attr_accessor :log
|
25
|
-
|
26
|
-
# Creates the @log variable
|
27
|
-
#
|
28
|
-
def self.log
|
29
|
-
@log ||= init_logger
|
30
|
-
end
|
31
|
-
|
32
|
-
# Set the logger level by default
|
33
|
-
#
|
34
|
-
def self.init_logger
|
35
|
-
log = Logger.new(STDOUT)
|
36
|
-
log.progname = "Knife VCenter"
|
37
|
-
log.level = Logger::INFO
|
38
|
-
log
|
39
|
-
end
|
40
|
-
end
|
@@ -1,457 +0,0 @@
|
|
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
|
-
attr_reader :sample, :wsdl_url, :soap_url
|
12
|
-
attr_reader :serviceRegistration
|
13
|
-
|
14
|
-
# Constructs a new instance.
|
15
|
-
# @param [Object] host the associated sample, which provides access
|
16
|
-
# to the configuration properties of the sample
|
17
|
-
def initialize(host)
|
18
|
-
@soap_url = format("https://%s/lookupservice/sdk", host)
|
19
|
-
@wsdl_url = format("https://%s/lookupservice/wsdl/lookup.wsdl", host)
|
20
|
-
end
|
21
|
-
|
22
|
-
# Connects to the lookup service.
|
23
|
-
def connect
|
24
|
-
rsc = RetrieveServiceContent.new(client).invoke
|
25
|
-
@serviceRegistration = rsc.get_service_registration
|
26
|
-
Base.log.info "service registration = #{serviceRegistration}"
|
27
|
-
end
|
28
|
-
|
29
|
-
# Finds the SSO service URL.
|
30
|
-
# In a MxN setup where there are more than one PSC nodes;
|
31
|
-
# This method returns the first SSO service endpoint URL
|
32
|
-
# as returned by the lookup service.
|
33
|
-
#
|
34
|
-
# @return [String] SSO Service endpoint URL.
|
35
|
-
def find_sso_url
|
36
|
-
result = find_service_url(product = "com.vmware.cis",
|
37
|
-
service = "cs.identity",
|
38
|
-
endpoint = "com.vmware.cis.cs.identity.sso",
|
39
|
-
protocol = "wsTrust")
|
40
|
-
raise "SSO URL not found" unless result && !result.empty?
|
41
|
-
|
42
|
-
result.values[0]
|
43
|
-
end
|
44
|
-
|
45
|
-
# Finds all the vAPI service endpoint URLs.
|
46
|
-
# In a MxN setup where there are more than one management node;
|
47
|
-
# this method returns more than one URL
|
48
|
-
#
|
49
|
-
# @return [Hash] vapi service endpoint URLs in a dictionary
|
50
|
-
# where the key is the node_id and the value is the service URL.
|
51
|
-
def find_vapi_urls
|
52
|
-
find_service_url(product = "com.vmware.cis",
|
53
|
-
service = "cs.vapi",
|
54
|
-
endpoint = "com.vmware.vapi.endpoint",
|
55
|
-
protocol = "vapi.json.https.public")
|
56
|
-
end
|
57
|
-
|
58
|
-
# Finds the vapi service endpoint URL of a management node.
|
59
|
-
#
|
60
|
-
# @param node_id [String] The UUID of the management node.
|
61
|
-
# @return [String] vapi service endpoint URL of a management node or
|
62
|
-
# nil if no vapi endpoint is found.
|
63
|
-
def find_vapi_url(node_id)
|
64
|
-
raise "node_id is required" if node_id.nil?
|
65
|
-
|
66
|
-
result = find_vapi_urls
|
67
|
-
raise "VAPI URLs not found" unless result && !result.empty?
|
68
|
-
|
69
|
-
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
|
-
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
|
-
|
93
|
-
result = find_vim_urls
|
94
|
-
raise "VIM URLs not found" unless result && !result.empty?
|
95
|
-
|
96
|
-
result[node_id]
|
97
|
-
end
|
98
|
-
|
99
|
-
# Finds all the spbm service endpoint URLs
|
100
|
-
# In a MxN setup where there are more than one management node;
|
101
|
-
# this method returns more than one URL
|
102
|
-
#
|
103
|
-
# @return [Hash] spbm service endpoint URLs in a dictionary where
|
104
|
-
# the key is the node_id and the value is the service URL.
|
105
|
-
def find_vim_pbm_urls
|
106
|
-
find_service_url(product = "com.vmware.vim.sms",
|
107
|
-
service = "sms",
|
108
|
-
endpoint = "com.vmware.vim.pbm",
|
109
|
-
protocol = "https")
|
110
|
-
end
|
111
|
-
|
112
|
-
# Finds the spbm service endpoint URL of a management node
|
113
|
-
#
|
114
|
-
# @param node_id [String] The UUID of the management node.
|
115
|
-
# @return [String] spbm service endpoint URL of a management node or
|
116
|
-
# nil if no spbm endpoint is found.
|
117
|
-
def find_vim_pbm_url(node_id)
|
118
|
-
raise "node_id is required" if node_id.nil?
|
119
|
-
|
120
|
-
result = find_vim_pbm_urls
|
121
|
-
raise "PBM URLs not found" unless result && !result.empty?
|
122
|
-
|
123
|
-
result[node_id]
|
124
|
-
end
|
125
|
-
|
126
|
-
# Get the management node id from the instance name
|
127
|
-
#
|
128
|
-
# @param instance_name [String] The instance name of the management node
|
129
|
-
# @return [String] The UUID of the management node or
|
130
|
-
# nil is no management node is found by the given instance name
|
131
|
-
def get_mgmt_node_id(instance_name)
|
132
|
-
raise "instance_name is required" if instance_name.nil?
|
133
|
-
result = find_mgmt_nodes()
|
134
|
-
raise "Management nodes not found" unless result && result.size > 0
|
135
|
-
result[instance_name]
|
136
|
-
end
|
137
|
-
|
138
|
-
def get_mgmt_node_instance_name(node_id)
|
139
|
-
raise "node_id is required" if node_id.nil?
|
140
|
-
|
141
|
-
result = find_mgmt_nodes
|
142
|
-
raise "Management nodes not found" unless result && !result.empty?
|
143
|
-
|
144
|
-
result.each { |k, v| return k if v == node_id }
|
145
|
-
nil
|
146
|
-
end
|
147
|
-
|
148
|
-
# Finds the instance name and UUID of the management node for M1xN1 or
|
149
|
-
# when the PSC and management services all reside on a single node.
|
150
|
-
def get_default_mgmt_node
|
151
|
-
result = find_mgmt_nodes
|
152
|
-
raise "Management nodes not found" unless result && !result.empty?
|
153
|
-
|
154
|
-
# WHY: raise MultipleManagementNodeException.new if result.size > 1
|
155
|
-
[result.keys[0], result.values[0]]
|
156
|
-
end
|
157
|
-
|
158
|
-
# Finds all the management nodes
|
159
|
-
#
|
160
|
-
# @return [Hash] management node instance name and node id (UUID) in a dictionary.
|
161
|
-
def find_mgmt_nodes
|
162
|
-
# assert self.serviceRegistration is not None
|
163
|
-
list = List.new(client, "com.vmware.cis", "vcenterserver",
|
164
|
-
"vmomi", "com.vmware.vim")
|
165
|
-
|
166
|
-
list.invoke
|
167
|
-
list.get_instance_names
|
168
|
-
end
|
169
|
-
|
170
|
-
private
|
171
|
-
|
172
|
-
# Finds a service URL with the given attributes.
|
173
|
-
def find_service_url(product, service, endpoint, protocol)
|
174
|
-
# assert serviceRegistration is not None
|
175
|
-
list = List.new(client, product, service, protocol, endpoint)
|
176
|
-
|
177
|
-
list.invoke
|
178
|
-
list.get_service_endpoints
|
179
|
-
end
|
180
|
-
|
181
|
-
# Gets or creates the Savon client instance.
|
182
|
-
def client
|
183
|
-
@client ||= Savon.client do |globals|
|
184
|
-
# see: http://savonrb.com/version2/globals.html
|
185
|
-
globals.wsdl wsdl_url
|
186
|
-
globals.endpoint soap_url
|
187
|
-
|
188
|
-
globals.strip_namespaces false
|
189
|
-
globals.env_namespace :S
|
190
|
-
|
191
|
-
# set like this so https connection does not fail
|
192
|
-
# TODO: find an acceptable solution for production
|
193
|
-
globals.ssl_verify_mode :none
|
194
|
-
|
195
|
-
# dev/debug settings
|
196
|
-
# globals.pretty_print_xml ENV['DEBUG_SOAP']
|
197
|
-
# globals.log ENV['DEBUG_SOAP']
|
198
|
-
end
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
|
-
# @abstract Base class for invocable service calls.
|
203
|
-
class Invocable
|
204
|
-
attr_reader :operation, :client, :response
|
205
|
-
|
206
|
-
# Constructs a new instance.
|
207
|
-
# @param operation [Symbol] the operation name
|
208
|
-
# @param client [Savon::Client] the client
|
209
|
-
def initialize(operation, client)
|
210
|
-
@operation = operation
|
211
|
-
@client = client
|
212
|
-
end
|
213
|
-
|
214
|
-
# Invokes the service call represented by this type.
|
215
|
-
def invoke
|
216
|
-
request = request_xml.to_s
|
217
|
-
Base.log.debug(request)
|
218
|
-
@response = client.call(operation, xml: request)
|
219
|
-
Base.log.debug(response)
|
220
|
-
self # for chaining with new
|
221
|
-
end
|
222
|
-
|
223
|
-
# Builds the request XML content.
|
224
|
-
def request_xml
|
225
|
-
builder = Builder::XmlMarkup.new
|
226
|
-
builder.instruct!(:xml, encoding: "UTF-8")
|
227
|
-
|
228
|
-
builder.tag!("S:Envelope",
|
229
|
-
"xmlns:S" => "http://schemas.xmlsoap.org/soap/envelope/") do |envelope|
|
230
|
-
envelope.tag!("S:Body") do |body|
|
231
|
-
body_xml(body)
|
232
|
-
end
|
233
|
-
end
|
234
|
-
builder.target!
|
235
|
-
end
|
236
|
-
|
237
|
-
# Builds the body portion of the request XML content.
|
238
|
-
# Specific service operations must override this method.
|
239
|
-
def body_xml
|
240
|
-
raise "abstract method not implemented!"
|
241
|
-
end
|
242
|
-
|
243
|
-
# Gets the response XML content.
|
244
|
-
def response_xml
|
245
|
-
raise "illegal state: response not set yet" if response.nil?
|
246
|
-
|
247
|
-
@response_xml ||= Nokogiri::XML(response.to_xml)
|
248
|
-
end
|
249
|
-
|
250
|
-
def response_hash
|
251
|
-
@response_hash ||= response.to_hash
|
252
|
-
end
|
253
|
-
end
|
254
|
-
|
255
|
-
# Encapsulates the list operation of the lookup service.
|
256
|
-
class List < Invocable
|
257
|
-
# Constructs a new instance.
|
258
|
-
def initialize(client, product, service, protocol, endpoint)
|
259
|
-
super(:list, client)
|
260
|
-
|
261
|
-
@product = product
|
262
|
-
@service = service
|
263
|
-
@protocol = protocol
|
264
|
-
@endpoint = endpoint
|
265
|
-
end
|
266
|
-
|
267
|
-
# <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
|
268
|
-
# <S:Body>
|
269
|
-
# <List xmlns="urn:lookup">
|
270
|
-
# <_this type="LookupServiceRegistration">ServiceRegistration</_this>
|
271
|
-
# <filterCriteria>
|
272
|
-
# <serviceType>
|
273
|
-
# <product>com.vmware.cis</product>
|
274
|
-
# <type>cs.identity</type>
|
275
|
-
# </serviceType>
|
276
|
-
# <endpointType>
|
277
|
-
# <protocol>wsTrust</protocol>
|
278
|
-
# <type>com.vmware.cis.cs.identity.sso</type>
|
279
|
-
# </endpointType>
|
280
|
-
# </filterCriteria>
|
281
|
-
# </List>
|
282
|
-
# </S:Body>
|
283
|
-
# </S:Envelope>
|
284
|
-
def body_xml(body)
|
285
|
-
body.tag!("List", "xmlns" => "urn:lookup") do |list|
|
286
|
-
# TODO: use the copy that was retrieved on startup?
|
287
|
-
list.tag!("_this",
|
288
|
-
"type" => "LookupServiceRegistration") do |this|
|
289
|
-
this << "ServiceRegistration"
|
290
|
-
end
|
291
|
-
list.tag!("filterCriteria") do |criteria|
|
292
|
-
criteria.tag!("serviceType") do |stype|
|
293
|
-
stype.tag!("product") do |p|
|
294
|
-
p << @product
|
295
|
-
end
|
296
|
-
stype.tag!("type") do |t|
|
297
|
-
t << @service
|
298
|
-
end
|
299
|
-
end
|
300
|
-
criteria.tag!("endpointType") do |etype|
|
301
|
-
etype.tag!("protocol") do |p|
|
302
|
-
p << @protocol
|
303
|
-
end
|
304
|
-
etype.tag!("type") do |t|
|
305
|
-
t << @endpoint
|
306
|
-
end
|
307
|
-
end
|
308
|
-
end
|
309
|
-
end
|
310
|
-
end
|
311
|
-
|
312
|
-
# Gets the service endpoint information from the response.
|
313
|
-
# Support for MxN.
|
314
|
-
# @return [Hash] a hash where the key is NodeId and the Value is a Service URL
|
315
|
-
def get_service_endpoints
|
316
|
-
result = {}
|
317
|
-
# <ListResponse xmlns="urn:lookup">
|
318
|
-
# <returnval>
|
319
|
-
# <serviceVersion>2.0</serviceVersion>
|
320
|
-
# <vendorNameResourceKey/>
|
321
|
-
# <vendorNameDefault/>
|
322
|
-
# <vendorProductInfoResourceKey/>
|
323
|
-
# <vendorProductInfoDefault/>
|
324
|
-
# <serviceEndpoints>
|
325
|
-
# <url>https://pa-rdinfra3-vm7-dhcp5583.eng.vmware.com/sts/STSService/vsphere.local</url>
|
326
|
-
# <endpointType>
|
327
|
-
# <protocol>wsTrust</protocol>
|
328
|
-
# <type>com.vmware.cis.cs.identity.sso</type>
|
329
|
-
# </endpointType>
|
330
|
-
# <sslTrust>
|
331
|
-
# ...
|
332
|
-
# </sslTrust>
|
333
|
-
# </serviceEndpoints>
|
334
|
-
# <serviceNameResourceKey/>
|
335
|
-
# <serviceNameDefault/>
|
336
|
-
# <serviceDescriptionResourceKey/>
|
337
|
-
# <serviceDescriptionDefault/>
|
338
|
-
# <ownerId>pa-rdinfra3-vm7-dhcp5583.eng.vmware.com@vsphere.local</ownerId>
|
339
|
-
# <serviceType>
|
340
|
-
# <product>com.vmware.cis</product>
|
341
|
-
# <type>cs.identity</type>
|
342
|
-
# </serviceType>
|
343
|
-
# <nodeId/>
|
344
|
-
# <serviceId>6a8a5058-5d3d-4d42-bb5e-383b91c8732e</serviceId>
|
345
|
-
# <siteId>default-first-site</siteId>
|
346
|
-
# </returnval>
|
347
|
-
# </ListResponse>
|
348
|
-
Base.log.debug "List: response_hash = #{response_hash}"
|
349
|
-
return_val = response_hash[:list_response][:returnval]
|
350
|
-
return_val = [return_val] if return_val.is_a? Hash
|
351
|
-
return_val.each do |entry|
|
352
|
-
# FYI: the node_id is sometimes null, so use the service_id in this case
|
353
|
-
node_id = entry[:node_id] || entry[:service_id]
|
354
|
-
result[node_id] = entry[:service_endpoints][:url]
|
355
|
-
end
|
356
|
-
Base.log.debug "List: result = #{result}"
|
357
|
-
result
|
358
|
-
end
|
359
|
-
|
360
|
-
def get_instance_names
|
361
|
-
result = {}
|
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
|
-
Base.log.debug "List: response_hash = #{response_hash}"
|
379
|
-
return_val = response_hash[:list_response][:returnval]
|
380
|
-
return_val = [return_val] if return_val.is_a? Hash
|
381
|
-
return_val.each do |entry|
|
382
|
-
node_id = entry[:node_id]
|
383
|
-
# TODO: is it possible there be 0 or 1 attrs? if so, deal with it.
|
384
|
-
attrs = entry[:service_attributes]
|
385
|
-
Base.log.debug "List: attrs=#{attrs}"
|
386
|
-
attrs.each do |attr|
|
387
|
-
if attr[:key] == "com.vmware.vim.vcenter.instanceName"
|
388
|
-
result[attr[:value]] = node_id
|
389
|
-
end
|
390
|
-
end
|
391
|
-
end
|
392
|
-
Base.log.debug "List: result = #{result}"
|
393
|
-
result
|
394
|
-
end
|
395
|
-
end
|
396
|
-
|
397
|
-
# Encapsulates the RetrieveServiceContent operation of the lookup service.
|
398
|
-
class RetrieveServiceContent < Invocable
|
399
|
-
# Constructs a new instance.
|
400
|
-
def initialize(client)
|
401
|
-
super(:retrieve_service_content, client)
|
402
|
-
end
|
403
|
-
|
404
|
-
# <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
|
405
|
-
# <S:Body>
|
406
|
-
# <RetrieveServiceContent xmlns="urn:lookup">
|
407
|
-
# <_this type="LookupServiceInstance">ServiceInstance</_this>
|
408
|
-
# </RetrieveServiceContent>
|
409
|
-
# </S:Body>
|
410
|
-
# </S:Envelope>
|
411
|
-
def body_xml(body)
|
412
|
-
body.tag!("RetrieveServiceContent", "xmlns" => "urn:lookup") do |rsc|
|
413
|
-
rsc.tag!("_this", "type" => "LookupServiceInstance") do |this|
|
414
|
-
this << "ServiceInstance"
|
415
|
-
end
|
416
|
-
end
|
417
|
-
end
|
418
|
-
|
419
|
-
# ...
|
420
|
-
# <RetrieveServiceContentResponse xmlns="urn:lookup">
|
421
|
-
# <returnval>
|
422
|
-
# <lookupService type="LookupLookupService">lookupService</lookupService>
|
423
|
-
# <serviceRegistration type="LookupServiceRegistration">ServiceRegistration</serviceRegistration>
|
424
|
-
# <deploymentInformationService type="LookupDeploymentInformationService">deploymentInformationService</deploymentInformationService>
|
425
|
-
# <l10n type="LookupL10n">l10n</l10n>
|
426
|
-
# </returnval>
|
427
|
-
# </RetrieveServiceContentResponse>
|
428
|
-
# ...
|
429
|
-
def get_service_registration
|
430
|
-
Base.log.debug "RetrieveServiceContent: response_hash = #{response_hash}"
|
431
|
-
return_val = response_hash[:retrieve_service_content_response][:returnval]
|
432
|
-
result = return_val[:service_registration]
|
433
|
-
Base.log.debug "RetrieveServiceContent: result = #{result}"
|
434
|
-
result
|
435
|
-
end
|
436
|
-
end
|
437
|
-
|
438
|
-
class MultipleManagementNodeException < RuntimeError
|
439
|
-
end
|
440
|
-
|
441
|
-
# main: quick self tester
|
442
|
-
if $PROGRAM_NAME == __FILE__
|
443
|
-
Base.log.level = Logger::DEBUG if ENV["DEBUG"]
|
444
|
-
sample = SelfTestSample.new
|
445
|
-
sample.ls_ip = ARGV[0] || "10.67.245.207"
|
446
|
-
# MXN: sample.ls_ip = '10.160.42.83'
|
447
|
-
# MXN: sample.ls_ip = '10.160.35.191'
|
448
|
-
# MAYBE: sample.main() # for arg parsing
|
449
|
-
ls_helper = LookupServiceHelper.new(sample)
|
450
|
-
ls_helper.connect
|
451
|
-
puts "***************************************"
|
452
|
-
puts "SSO URL: #{ls_helper.find_sso_url}"
|
453
|
-
puts "VAPI URL: #{ls_helper.find_vapi_urls}"
|
454
|
-
puts "VIM URL: #{ls_helper.find_vim_urls}"
|
455
|
-
puts "PBM URL: #{ls_helper.find_vim_pbm_urls}"
|
456
|
-
puts "Mgmt Nodes: #{ls_helper.find_mgmt_nodes}"
|
457
|
-
end
|
data/lib/sso.rb
DELETED
@@ -1,264 +0,0 @@
|
|
1
|
-
# Copyright 2014-2017 VMware, Inc. All Rights Reserved.
|
2
|
-
# SPDX-License-Identifier: MIT
|
3
|
-
|
4
|
-
require "savon"
|
5
|
-
require "nokogiri"
|
6
|
-
require "date"
|
7
|
-
require "securerandom"
|
8
|
-
|
9
|
-
# A little utility library for VMware SSO.
|
10
|
-
# For now, this is not a general purpose library that covers all
|
11
|
-
# the interfaces of the SSO service.
|
12
|
-
# Specifically, the support is limited to the following:
|
13
|
-
# * request bearer token.
|
14
|
-
module SSO
|
15
|
-
# The XML date format.
|
16
|
-
DATE_FORMAT = "%FT%T.%LZ".freeze
|
17
|
-
|
18
|
-
# The XML namespaces that are required: SOAP, WSDL, et al.
|
19
|
-
NAMESPACES = {
|
20
|
-
"xmlns:S" => "http://schemas.xmlsoap.org/soap/envelope/",
|
21
|
-
"xmlns:wst" => "http://docs.oasis-open.org/ws-sx/ws-trust/200512",
|
22
|
-
"xmlns:u" => "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd",
|
23
|
-
"xmlns:x" => "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
|
24
|
-
}.freeze
|
25
|
-
|
26
|
-
# Provides the connection details for the SSO service.
|
27
|
-
class Connection
|
28
|
-
attr_accessor :sso_url, :wsdl_url, :username, :password
|
29
|
-
|
30
|
-
# Creates a new instance.
|
31
|
-
def initialize(sso_url, wsdl_url = nil)
|
32
|
-
self.sso_url = sso_url
|
33
|
-
self.wsdl_url = wsdl_url || "#{sso_url}?wsdl"
|
34
|
-
end
|
35
|
-
|
36
|
-
# Login with the given credentials.
|
37
|
-
# Note: this does not invoke a login action, but rather stores the
|
38
|
-
# credentials for use later.
|
39
|
-
def login(username, password)
|
40
|
-
self.username = username
|
41
|
-
self.password = password
|
42
|
-
self # enable builder pattern
|
43
|
-
end
|
44
|
-
|
45
|
-
# Gets (or creates) the Savon client instance.
|
46
|
-
def client
|
47
|
-
# construct and init the client proxy
|
48
|
-
@client ||= Savon.client do |globals|
|
49
|
-
# see: http://savonrb.com/version2/globals.html
|
50
|
-
globals.wsdl wsdl_url
|
51
|
-
globals.endpoint sso_url
|
52
|
-
|
53
|
-
globals.strip_namespaces false
|
54
|
-
globals.env_namespace :S
|
55
|
-
|
56
|
-
# set like this so https connection does not fail
|
57
|
-
# TODO: find an acceptable solution for production
|
58
|
-
globals.ssl_verify_mode :none
|
59
|
-
|
60
|
-
# dev/debug settings
|
61
|
-
# globals.pretty_print_xml ENV['DEBUG_SOAP']
|
62
|
-
# globals.log ENV['DEBUG_SOAP']
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
# Invokes the request bearer token operation.
|
67
|
-
# @return [SamlToken]
|
68
|
-
def request_bearer_token
|
69
|
-
rst = RequestSecurityToken.new(client, username, password)
|
70
|
-
rst.invoke
|
71
|
-
rst.saml_token
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
# @abstract Base class for invocable service calls.
|
76
|
-
class SoapInvocable
|
77
|
-
attr_reader :operation, :client, :response
|
78
|
-
|
79
|
-
# Constructs a new instance.
|
80
|
-
# @param operation [Symbol] the SOAP operation name (in Symbol form)
|
81
|
-
# @param client [Savon::Client] the client
|
82
|
-
def initialize(operation, client)
|
83
|
-
@operation = operation
|
84
|
-
@client = client
|
85
|
-
end
|
86
|
-
|
87
|
-
# Invokes the service call represented by this type.
|
88
|
-
def invoke
|
89
|
-
request = request_xml.to_s
|
90
|
-
puts "request = #{request}" if ENV["DEBUG"]
|
91
|
-
@response = client.call(operation, xml: request)
|
92
|
-
puts "response = #{response}" if ENV["DEBUG"]
|
93
|
-
self # for chaining with new
|
94
|
-
end
|
95
|
-
|
96
|
-
# Builds the request XML content.
|
97
|
-
def request_xml
|
98
|
-
builder = Builder::XmlMarkup.new
|
99
|
-
builder.instruct!(:xml, encoding: "UTF-8")
|
100
|
-
|
101
|
-
builder.tag!("S:Envelope", NAMESPACES) do |envelope|
|
102
|
-
if has_header?
|
103
|
-
envelope.tag!("S:Header") do |header|
|
104
|
-
header_xml(header)
|
105
|
-
end
|
106
|
-
end
|
107
|
-
envelope.tag!("S:Body") do |body|
|
108
|
-
body_xml(body)
|
109
|
-
end
|
110
|
-
end
|
111
|
-
builder.target!
|
112
|
-
end
|
113
|
-
|
114
|
-
def has_header?
|
115
|
-
true
|
116
|
-
end
|
117
|
-
|
118
|
-
# Builds the header portion of the SOAP request.
|
119
|
-
# Specific service operations must override this method.
|
120
|
-
def header_xml(_header)
|
121
|
-
raise "abstract method not implemented!"
|
122
|
-
end
|
123
|
-
|
124
|
-
# Builds the body portion of the SOAP request.
|
125
|
-
# Specific service operations must override this method.
|
126
|
-
def body_xml(_body)
|
127
|
-
raise "abstract method not implemented!"
|
128
|
-
end
|
129
|
-
|
130
|
-
# Gets the response XML content.
|
131
|
-
def response_xml
|
132
|
-
raise "illegal state: response not set yet" if response.nil?
|
133
|
-
|
134
|
-
@response_xml ||= Nokogiri::XML(response.to_xml)
|
135
|
-
end
|
136
|
-
|
137
|
-
def response_hash
|
138
|
-
@response_hash ||= response.to_hash
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
# Encapsulates an issue operation that requests a security token
|
143
|
-
# from the SSO service.
|
144
|
-
class RequestSecurityToken < SoapInvocable
|
145
|
-
attr_accessor :request_type, :delegatable
|
146
|
-
|
147
|
-
# Constructs a new instance.
|
148
|
-
def initialize(client, username, password, hours = 2)
|
149
|
-
super(:issue, client)
|
150
|
-
|
151
|
-
@username = username
|
152
|
-
@password = password
|
153
|
-
@hours = hours
|
154
|
-
|
155
|
-
# TODO: these things should be configurable, so we can get
|
156
|
-
# non-delegatable tokens, HoK tokens, etc.
|
157
|
-
@request_type = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue"
|
158
|
-
@delegatable = true
|
159
|
-
end
|
160
|
-
|
161
|
-
def now
|
162
|
-
@now ||= Time.now.utc.to_datetime
|
163
|
-
end
|
164
|
-
|
165
|
-
def created
|
166
|
-
@created ||= now.strftime(DATE_FORMAT)
|
167
|
-
end
|
168
|
-
|
169
|
-
def future
|
170
|
-
@future ||= now + (2 / 24.0) # days (for DateTime math)
|
171
|
-
end
|
172
|
-
|
173
|
-
def expires
|
174
|
-
@expires ||= future.strftime(DATE_FORMAT)
|
175
|
-
end
|
176
|
-
|
177
|
-
# Builds the header XML for the SOAP request.
|
178
|
-
def header_xml(header)
|
179
|
-
id = "uuid-" + SecureRandom.uuid
|
180
|
-
|
181
|
-
# header.tag!("x:Security", "x:mustUnderstand" => "1") do |security|
|
182
|
-
header.tag!("x:Security") do |security|
|
183
|
-
security.tag!("u:Timestamp", "u:Id" => "_0") do |timestamp|
|
184
|
-
timestamp.tag!("u:Created") do |element|
|
185
|
-
element << created
|
186
|
-
end
|
187
|
-
timestamp.tag!("u:Expires") do |element|
|
188
|
-
element << expires
|
189
|
-
end
|
190
|
-
end
|
191
|
-
security.tag!("x:UsernameToken", "u:Id" => id) do |utoken|
|
192
|
-
utoken.tag!("x:Username") do |element|
|
193
|
-
element << @username
|
194
|
-
end
|
195
|
-
utoken.tag!("x:Password") do |element|
|
196
|
-
element << @password
|
197
|
-
end
|
198
|
-
end
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
|
-
# Builds the body XML for the SOAP request.
|
203
|
-
def body_xml(body)
|
204
|
-
body.tag!("wst:RequestSecurityToken") do |rst|
|
205
|
-
rst.tag!("wst:RequestType") do |element|
|
206
|
-
element << request_type
|
207
|
-
end
|
208
|
-
rst.tag!("wst:Delegatable") do |element|
|
209
|
-
element << delegatable.to_s
|
210
|
-
end
|
211
|
-
# #TODO: we don't seem to need this, but I'm leaving this
|
212
|
-
# #here for now as a reminder.
|
213
|
-
# rst.tag!("wst:Lifetime") do |lifetime|
|
214
|
-
# lifetime.tag!("u:Created") do |element|
|
215
|
-
# element << created
|
216
|
-
# end
|
217
|
-
# lifetime.tag!("u:Expires") do |element|
|
218
|
-
# element << expires
|
219
|
-
# end
|
220
|
-
# end
|
221
|
-
end
|
222
|
-
end
|
223
|
-
|
224
|
-
# Gets the saml_token from the SOAP response body.
|
225
|
-
# @return [SamlToken] the requested SAML token
|
226
|
-
def saml_token
|
227
|
-
assertion = response_xml.at_xpath("//saml2:Assertion",
|
228
|
-
"saml2" => "urn:oasis:names:tc:SAML:2.0:assertion")
|
229
|
-
SamlToken.new(assertion)
|
230
|
-
end
|
231
|
-
end
|
232
|
-
|
233
|
-
# Holds a SAML token.
|
234
|
-
class SamlToken
|
235
|
-
attr_reader :xml
|
236
|
-
|
237
|
-
# Creates a new instance.
|
238
|
-
def initialize(xml)
|
239
|
-
@xml = xml
|
240
|
-
end
|
241
|
-
|
242
|
-
# TODO: add some getters for interesting content
|
243
|
-
|
244
|
-
def to_s
|
245
|
-
esc_token = xml.to_xml(indent: 0, encoding: "UTF-8")
|
246
|
-
esc_token = esc_token.delete("\n")
|
247
|
-
esc_token
|
248
|
-
end
|
249
|
-
end
|
250
|
-
end
|
251
|
-
|
252
|
-
# main: quick self tester
|
253
|
-
if $PROGRAM_NAME == __FILE__
|
254
|
-
cloudvm_ip = ARGV[0]
|
255
|
-
cloudvm_ip ||= "10.20.17.0"
|
256
|
-
# cloudvm_ip ||= "10.67.245.207"
|
257
|
-
sso_url = "https://#{cloudvm_ip}/sts/STSService/vsphere.local"
|
258
|
-
wsdl_url = "#{sso_url}?wsdl"
|
259
|
-
sso = SSO::Connection.new(sso_url, wsdl_url)
|
260
|
-
# sso.login("administrator@vsphere.local", "Admin!23")
|
261
|
-
sso.login("root", "vmware")
|
262
|
-
token = sso.request_bearer_token
|
263
|
-
puts token.to_s
|
264
|
-
end
|