knife-vcenter 2.0.1 → 2.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|