kitchen-oci 1.15.1 → 1.16.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/kitchen/driver/oci/api.rb +95 -0
- data/lib/kitchen/driver/oci/blockstorage.rb +127 -0
- data/lib/kitchen/driver/oci/config.rb +92 -0
- data/lib/kitchen/driver/oci/instance.rb +146 -0
- data/lib/kitchen/driver/oci/models/compute.rb +179 -0
- data/lib/kitchen/driver/oci/models/dbaas.rb +221 -0
- data/lib/kitchen/driver/oci/models/iscsi.rb +53 -0
- data/lib/kitchen/driver/oci/models/paravirtual.rb +50 -0
- data/lib/kitchen/driver/oci/models.rb +48 -0
- data/lib/kitchen/driver/oci.rb +86 -632
- data/lib/kitchen/driver/oci_version.rb +1 -1
- metadata +17 -8
data/lib/kitchen/driver/oci.rb
CHANGED
@@ -17,17 +17,15 @@
|
|
17
17
|
# See the License for the specific language governing permissions and
|
18
18
|
# limitations under the License.
|
19
19
|
|
20
|
-
# rubocop:disable Metrics/AbcSize
|
21
|
-
|
22
20
|
# This require fixes bug in ChefDK 4.0.60-1 on Linux.
|
23
|
-
require
|
24
|
-
require
|
25
|
-
require
|
26
|
-
require
|
27
|
-
require
|
28
|
-
require
|
29
|
-
require
|
30
|
-
require
|
21
|
+
require "forwardable" unless defined?(Forwardable)
|
22
|
+
require "base64" unless defined?(Base64)
|
23
|
+
require "erb" unless defined?(Erb)
|
24
|
+
require "kitchen"
|
25
|
+
require "oci"
|
26
|
+
require "openssl" unless defined?(OpenSSL)
|
27
|
+
require "uri" unless defined?(URI)
|
28
|
+
require "zlib" unless defined?(Zlib)
|
31
29
|
|
32
30
|
module Kitchen
|
33
31
|
module Driver
|
@@ -35,677 +33,133 @@ module Kitchen
|
|
35
33
|
#
|
36
34
|
# @author Stephen Pearson <stephen.pearson@oracle.com>
|
37
35
|
class Oci < Kitchen::Driver::Base # rubocop:disable Metrics/ClassLength
|
36
|
+
require_relative "oci_version"
|
37
|
+
require_relative "oci/models"
|
38
|
+
|
39
|
+
plugin_version Kitchen::Driver::OCI_VERSION
|
40
|
+
|
38
41
|
# required config items
|
39
42
|
required_config :availability_domain
|
40
43
|
required_config :shape
|
41
44
|
required_config :subnet_id
|
42
45
|
|
43
46
|
# common config items
|
47
|
+
default_config :oci_config, {}
|
48
|
+
default_config :oci_config_file, nil
|
49
|
+
default_config :oci_profile_name, nil
|
44
50
|
default_config :compartment_id, nil
|
45
51
|
default_config :compartment_name, nil
|
46
|
-
default_config :instance_type,
|
47
|
-
default_config :
|
48
|
-
|
52
|
+
default_config :instance_type, "compute"
|
53
|
+
default_config :image_id
|
54
|
+
default_config :hostname_prefix do |hnp|
|
55
|
+
hnp.instance.name
|
56
|
+
end
|
57
|
+
default_keypath = File.expand_path(File.join(%w{~ .ssh id_rsa.pub}))
|
49
58
|
default_config :ssh_keypath, default_keypath
|
50
59
|
default_config :post_create_script, nil
|
51
60
|
default_config :proxy_url, nil
|
52
61
|
default_config :user_data, nil
|
53
62
|
default_config :freeform_tags, {}
|
54
63
|
default_config :defined_tags, {}
|
55
|
-
|
56
|
-
# compute config items
|
57
|
-
default_config :image_id
|
58
|
-
default_config :boot_volume_size_in_gbs, nil
|
59
|
-
default_config :use_private_ip, false
|
60
|
-
default_config :oci_config, {}
|
61
|
-
default_config :oci_config_file, nil
|
62
|
-
default_config :oci_profile_name, nil
|
63
|
-
default_config :setup_winrm, false
|
64
|
-
default_config :winrm_user, 'opc'
|
65
|
-
default_config :winrm_password, nil
|
64
|
+
default_config :custom_metadata, {}
|
66
65
|
default_config :use_instance_principals, false
|
67
66
|
default_config :use_token_auth, false
|
68
|
-
default_config :preemptible_instance, false
|
69
67
|
default_config :shape_config, {}
|
70
|
-
default_config :
|
71
|
-
|
72
|
-
# dbaas config items
|
73
|
-
default_config :dbaas, {}
|
68
|
+
default_config :nsg_ids, []
|
74
69
|
|
75
|
-
#
|
70
|
+
# compute only configs
|
71
|
+
default_config :setup_winrm, false
|
72
|
+
default_config :winrm_user, "opc"
|
73
|
+
default_config :winrm_password, nil
|
74
|
+
default_config :preemptible_instance, false
|
75
|
+
default_config :boot_volume_size_in_gbs, nil
|
76
|
+
default_config :use_private_ip, false
|
76
77
|
default_config :volumes, {}
|
77
78
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
state = process_windows_options(state)
|
82
|
-
|
83
|
-
instance_id = launch_instance(state)
|
84
|
-
|
85
|
-
state[:server_id] = instance_id
|
86
|
-
state[:hostname] = instance_ip(instance_id)
|
87
|
-
|
88
|
-
instance.transport.connection(state).wait_until_ready
|
89
|
-
|
90
|
-
state[:volumes] = process_volumes_list(state)
|
91
|
-
state[:volume_attachments] = process_volume_attachments(state)
|
92
|
-
|
93
|
-
return unless config[:post_create_script]
|
94
|
-
|
95
|
-
info('Running post create script')
|
96
|
-
script = config[:post_create_script]
|
97
|
-
instance.transport.connection(state).execute(script)
|
98
|
-
end
|
99
|
-
|
100
|
-
def destroy(state)
|
101
|
-
return unless state[:server_id]
|
102
|
-
|
103
|
-
instance.transport.connection(state).close
|
104
|
-
|
105
|
-
if state[:volume_attachments]
|
106
|
-
state[:volume_attachments].each do |attachment|
|
107
|
-
volume_detach(attachment)
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
if state[:volumes]
|
112
|
-
state[:volumes].each do |vol|
|
113
|
-
volume_delete(vol[:id])
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
if instance_type == 'compute'
|
118
|
-
comp_api.terminate_instance(state[:server_id])
|
119
|
-
elsif instance_type == 'dbaas'
|
120
|
-
dbaas_api.terminate_db_system(state[:server_id])
|
121
|
-
end
|
122
|
-
|
123
|
-
state.delete(:server_id)
|
124
|
-
state.delete(:hostname)
|
125
|
-
end
|
126
|
-
|
127
|
-
def process_freeform_tags(freeform_tags)
|
128
|
-
prov = instance.provisioner.instance_variable_get(:@config)
|
129
|
-
tags = %w[run_list policyfile]
|
130
|
-
tags.each do |tag|
|
131
|
-
freeform_tags[tag] = prov[tag.to_sym].join(',') unless prov[tag.to_sym].nil? || prov[tag.to_sym].empty?
|
132
|
-
end
|
133
|
-
freeform_tags[:kitchen] = true
|
134
|
-
freeform_tags
|
135
|
-
end
|
136
|
-
|
137
|
-
def process_windows_options(state)
|
138
|
-
state[:username] = config[:winrm_user] if config[:setup_winrm]
|
139
|
-
if config[:setup_winrm] == true &&
|
140
|
-
config[:password].nil? &&
|
141
|
-
state[:password].nil?
|
142
|
-
state[:password] = config[:winrm_password] || random_password
|
143
|
-
end
|
144
|
-
state
|
145
|
-
end
|
146
|
-
|
147
|
-
private
|
148
|
-
|
149
|
-
def compartment_id
|
150
|
-
return config[:compartment_id] if config[:compartment_id]
|
151
|
-
raise 'must specify either compartment_id or compartment_name' unless config[:compartment_name]
|
152
|
-
ident_api.list_compartments(tenancy).data.find do |item|
|
153
|
-
return item.id if item.name == config[:compartment_name]
|
154
|
-
end
|
155
|
-
raise 'compartment not found'
|
156
|
-
end
|
157
|
-
|
158
|
-
def tenancy
|
159
|
-
if config[:use_instance_principals]
|
160
|
-
sign = OCI::Auth::Signers::InstancePrincipalsSecurityTokenSigner.new
|
161
|
-
sign.instance_variable_get '@tenancy_id'
|
162
|
-
else
|
163
|
-
oci_config.tenancy
|
164
|
-
end
|
165
|
-
end
|
166
|
-
|
167
|
-
def instance_type
|
168
|
-
raise 'instance_type must be either compute or dbaas!' unless %w[compute dbaas].include?(config[:instance_type].downcase)
|
169
|
-
|
170
|
-
config[:instance_type].downcase
|
171
|
-
end
|
172
|
-
|
173
|
-
####################
|
174
|
-
# OCI config setup #
|
175
|
-
####################
|
176
|
-
def oci_config
|
177
|
-
# OCI::Config is missing this
|
178
|
-
OCI::Config.class_eval { attr_accessor :security_token_file } if config[:use_token_auth]
|
179
|
-
|
180
|
-
opts = {}
|
181
|
-
opts[:config_file_location] = config[:oci_config_file] if config[:oci_config_file]
|
182
|
-
opts[:profile_name] = config[:oci_profile_name] if config[:oci_profile_name]
|
183
|
-
|
184
|
-
oci_config = begin
|
185
|
-
OCI::ConfigFileLoader.load_config(**opts)
|
186
|
-
rescue OCI::ConfigFileLoader::Errors::ConfigFileNotFoundError
|
187
|
-
OCI::Config.new
|
188
|
-
end
|
189
|
-
|
190
|
-
config[:oci_config].each do |key, value|
|
191
|
-
oci_config.send("#{key}=", value) unless value.nil? || value.empty?
|
192
|
-
end
|
193
|
-
oci_config
|
194
|
-
end
|
195
|
-
|
196
|
-
def proxy_config
|
197
|
-
if config[:proxy_url]
|
198
|
-
URI.parse(config[:proxy_url])
|
199
|
-
else
|
200
|
-
URI.parse('http://').find_proxy
|
201
|
-
end
|
202
|
-
end
|
203
|
-
|
204
|
-
def api_proxy
|
205
|
-
prx = proxy_config
|
206
|
-
return nil unless prx
|
207
|
-
|
208
|
-
if prx.user
|
209
|
-
OCI::ApiClientProxySettings.new(prx.host, prx.port, prx.user,
|
210
|
-
prx.password)
|
211
|
-
else
|
212
|
-
OCI::ApiClientProxySettings.new(prx.host, prx.port)
|
213
|
-
end
|
214
|
-
end
|
215
|
-
|
216
|
-
#############
|
217
|
-
# API setup #
|
218
|
-
#############
|
219
|
-
def generic_api(klass)
|
220
|
-
api_prx = api_proxy
|
221
|
-
if config[:use_instance_principals]
|
222
|
-
sign = OCI::Auth::Signers::InstancePrincipalsSecurityTokenSigner.new
|
223
|
-
params = { signer: sign }
|
224
|
-
elsif config[:use_token_auth]
|
225
|
-
pkey_content = oci_config.key_content || IO.read(oci_config.key_file).strip
|
226
|
-
pkey = OpenSSL::PKey::RSA.new(pkey_content, oci_config.pass_phrase)
|
227
|
-
|
228
|
-
token = IO.read(oci_config.security_token_file).strip
|
229
|
-
sign = OCI::Auth::Signers::SecurityTokenSigner.new(token, pkey)
|
230
|
-
params = { config: oci_config, signer: sign }
|
231
|
-
else
|
232
|
-
params = { config: oci_config }
|
233
|
-
end
|
234
|
-
params[:proxy_settings] = api_prx if api_prx
|
235
|
-
klass.new(**params)
|
236
|
-
end
|
237
|
-
|
238
|
-
def comp_api
|
239
|
-
generic_api(OCI::Core::ComputeClient)
|
240
|
-
end
|
241
|
-
|
242
|
-
def net_api
|
243
|
-
generic_api(OCI::Core::VirtualNetworkClient)
|
244
|
-
end
|
245
|
-
|
246
|
-
def dbaas_api
|
247
|
-
generic_api(OCI::Database::DatabaseClient)
|
248
|
-
end
|
249
|
-
|
250
|
-
def ident_api
|
251
|
-
generic_api(OCI::Identity::IdentityClient)
|
252
|
-
end
|
253
|
-
|
254
|
-
def blockstorage_api
|
255
|
-
generic_api(OCI::Core::BlockstorageClient)
|
256
|
-
end
|
257
|
-
|
258
|
-
##################
|
259
|
-
# Common methods #
|
260
|
-
##################
|
261
|
-
def launch_instance(state)
|
262
|
-
if instance_type == 'compute'
|
263
|
-
launch_compute_instance(state)
|
264
|
-
elsif instance_type == 'dbaas'
|
265
|
-
launch_dbaas_instance
|
266
|
-
end
|
267
|
-
end
|
268
|
-
|
269
|
-
def public_ip_allowed?
|
270
|
-
subnet = net_api.get_subnet(config[:subnet_id]).data
|
271
|
-
!subnet.prohibit_public_ip_on_vnic
|
272
|
-
end
|
273
|
-
|
274
|
-
def instance_ip(instance_id)
|
275
|
-
if instance_type == 'compute'
|
276
|
-
compute_instance_ip(instance_id)
|
277
|
-
elsif instance_type == 'dbaas'
|
278
|
-
dbaas_instance_ip(instance_id)
|
279
|
-
end
|
280
|
-
end
|
281
|
-
|
282
|
-
def pubkey
|
283
|
-
if instance_type == 'compute'
|
284
|
-
File.readlines(config[:ssh_keypath]).first.chomp
|
285
|
-
elsif instance_type == 'dbaas'
|
286
|
-
result = []
|
287
|
-
result << File.readlines(config[:ssh_keypath]).first.chomp
|
288
|
-
end
|
289
|
-
end
|
290
|
-
|
291
|
-
def generate_hostname
|
292
|
-
prefix = config[:hostname_prefix]
|
293
|
-
if instance_type == 'compute'
|
294
|
-
[prefix, random_hostname(instance.name)].compact.join('-')
|
295
|
-
elsif instance_type == 'dbaas'
|
296
|
-
# 30 character limit for hostname in DBaaS
|
297
|
-
if prefix.length >= 30
|
298
|
-
[prefix[0, 26], 'db1'].compact.join('-')
|
299
|
-
else
|
300
|
-
[prefix, random_string(25 - prefix.length), 'db1'].compact.join('-')
|
301
|
-
end
|
302
|
-
end
|
303
|
-
end
|
304
|
-
|
305
|
-
def random_hostname(prefix)
|
306
|
-
"#{prefix}-#{random_string(6)}"
|
307
|
-
end
|
308
|
-
|
309
|
-
def random_password
|
310
|
-
if instance_type == 'compute'
|
311
|
-
special_chars = %w[@ - ( ) .]
|
312
|
-
elsif instance_type == 'dbaas'
|
313
|
-
special_chars = %w[# _ -]
|
314
|
-
end
|
315
|
-
|
316
|
-
(Array.new(5) { special_chars.sample } +
|
317
|
-
Array.new(5) { ('a'..'z').to_a.sample } +
|
318
|
-
Array.new(5) { ('A'..'Z').to_a.sample } +
|
319
|
-
Array.new(5) { ('0'..'9').to_a.sample }).shuffle.join
|
320
|
-
end
|
321
|
-
|
322
|
-
def random_string(length)
|
323
|
-
Array.new(length) { ('a'..'z').to_a.sample }.join
|
324
|
-
end
|
325
|
-
|
326
|
-
def random_number(length)
|
327
|
-
Array.new(length) { ('0'..'9').to_a.sample }.join
|
328
|
-
end
|
329
|
-
|
330
|
-
###################
|
331
|
-
# Compute methods #
|
332
|
-
###################
|
333
|
-
def launch_compute_instance(state)
|
334
|
-
request = compute_instance_request(state)
|
335
|
-
response = comp_api.launch_instance(request)
|
336
|
-
instance_id = response.data.id
|
337
|
-
|
338
|
-
comp_api.get_instance(instance_id).wait_until(
|
339
|
-
:lifecycle_state,
|
340
|
-
OCI::Core::Models::Instance::LIFECYCLE_STATE_RUNNING
|
341
|
-
)
|
342
|
-
instance_id
|
343
|
-
end
|
344
|
-
|
345
|
-
def compute_instance_request(state)
|
346
|
-
request = compute_launch_details
|
347
|
-
|
348
|
-
inject_powershell(state) if config[:setup_winrm] == true
|
349
|
-
|
350
|
-
metadata = {}
|
351
|
-
md = config[:custom_metadata]
|
352
|
-
md.each do |key, value|
|
353
|
-
metadata.store(key, value)
|
354
|
-
end
|
355
|
-
metadata.store('ssh_authorized_keys', pubkey)
|
356
|
-
data = user_data
|
357
|
-
metadata.store('user_data', data) if config[:user_data] && !config[:user_data].empty?
|
358
|
-
request.metadata = metadata
|
359
|
-
request
|
360
|
-
end
|
361
|
-
|
362
|
-
def compute_launch_details # rubocop:disable Metrics/MethodLength
|
363
|
-
OCI::Core::Models::LaunchInstanceDetails.new.tap do |l|
|
364
|
-
hostname = generate_hostname
|
365
|
-
l.availability_domain = config[:availability_domain]
|
366
|
-
l.compartment_id = compartment_id
|
367
|
-
l.display_name = hostname
|
368
|
-
l.source_details = instance_source_details
|
369
|
-
l.shape = config[:shape]
|
370
|
-
l.create_vnic_details = create_vnic_details(hostname)
|
371
|
-
l.freeform_tags = process_freeform_tags(config[:freeform_tags])
|
372
|
-
l.defined_tags = config[:defined_tags]
|
373
|
-
l.preemptible_instance_config = preemptible_instance_config if config[:preemptible_instance]
|
374
|
-
l.shape_config = shape_config unless config[:shape_config].empty?
|
375
|
-
end
|
376
|
-
end
|
377
|
-
|
378
|
-
def instance_source_details
|
379
|
-
OCI::Core::Models::InstanceSourceViaImageDetails.new(
|
380
|
-
sourceType: 'image',
|
381
|
-
imageId: config[:image_id],
|
382
|
-
bootVolumeSizeInGBs: config[:boot_volume_size_in_gbs],
|
383
|
-
)
|
384
|
-
end
|
385
|
-
|
386
|
-
def preemptible_instance_config
|
387
|
-
OCI::Core::Models::PreemptibleInstanceConfigDetails.new(
|
388
|
-
preemption_action:
|
389
|
-
OCI::Core::Models::TerminatePreemptionAction.new(
|
390
|
-
type: 'TERMINATE', preserve_boot_volume: true
|
391
|
-
)
|
392
|
-
)
|
393
|
-
end
|
394
|
-
|
395
|
-
def shape_config
|
396
|
-
OCI::Core::Models::LaunchInstanceShapeConfigDetails.new(
|
397
|
-
ocpus: config[:shape_config][:ocpus],
|
398
|
-
memory_in_gbs: config[:shape_config][:memory_in_gbs],
|
399
|
-
baseline_ocpu_utilization: config[:shape_config][:baseline_ocpu_utilization] || 'BASELINE_1_1'
|
400
|
-
)
|
401
|
-
end
|
402
|
-
|
403
|
-
def create_vnic_details(name)
|
404
|
-
nsg_ids = config[:nsg_ids] || []
|
405
|
-
raise 'nsg_ids cannot have more than 5 NSGs.' if nsg_ids.length > 5
|
406
|
-
OCI::Core::Models::CreateVnicDetails.new(
|
407
|
-
assign_public_ip: public_ip_allowed?,
|
408
|
-
display_name: name,
|
409
|
-
hostname_label: name,
|
410
|
-
nsg_ids: nsg_ids,
|
411
|
-
subnetId: config[:subnet_id]
|
412
|
-
)
|
413
|
-
end
|
414
|
-
|
415
|
-
def vnics(instance_id)
|
416
|
-
vnic_attachments(instance_id).map do |att|
|
417
|
-
net_api.get_vnic(att.vnic_id).data
|
418
|
-
end
|
419
|
-
end
|
420
|
-
|
421
|
-
def vnic_attachments(instance_id)
|
422
|
-
att = comp_api.list_vnic_attachments(
|
423
|
-
compartment_id,
|
424
|
-
instance_id: instance_id
|
425
|
-
).data
|
426
|
-
|
427
|
-
raise 'Could not find any VNIC attachments' unless att.any?
|
428
|
-
|
429
|
-
att
|
430
|
-
end
|
431
|
-
|
432
|
-
def compute_instance_ip(instance_id)
|
433
|
-
vnic = vnics(instance_id).select(&:is_primary).first
|
434
|
-
if public_ip_allowed?
|
435
|
-
config[:use_private_ip] ? vnic.private_ip : vnic.public_ip
|
436
|
-
else
|
437
|
-
vnic.private_ip
|
438
|
-
end
|
439
|
-
end
|
440
|
-
|
441
|
-
def winrm_ps1(state)
|
442
|
-
filename = File.join(__dir__, %w[.. .. .. tpl setup_winrm.ps1.erb])
|
443
|
-
tpl = ERB.new(File.read(filename))
|
444
|
-
tpl.result(binding)
|
445
|
-
end
|
446
|
-
|
447
|
-
def inject_powershell(state)
|
448
|
-
data = winrm_ps1(state)
|
449
|
-
config[:user_data] ||= []
|
450
|
-
config[:user_data] << {
|
451
|
-
type: 'x-shellscript',
|
452
|
-
inline: data,
|
453
|
-
filename: 'setup_winrm.ps1'
|
454
|
-
}
|
455
|
-
end
|
456
|
-
|
457
|
-
def read_part(part)
|
458
|
-
if part[:path]
|
459
|
-
content = File.read part[:path]
|
460
|
-
elsif part[:inline]
|
461
|
-
content = part[:inline]
|
462
|
-
else
|
463
|
-
raise 'Invalid user data'
|
464
|
-
end
|
465
|
-
content.split("\n")
|
466
|
-
end
|
79
|
+
# dbaas configs
|
80
|
+
default_config :dbaas, {}
|
467
81
|
|
468
|
-
|
469
|
-
|
470
|
-
config[:user_data].each do |m|
|
471
|
-
msg << "--#{boundary}"
|
472
|
-
msg << "Content-Disposition: attachment; filename=\"#{m[:filename]}\""
|
473
|
-
msg << 'Content-Transfer-Encoding: 7bit'
|
474
|
-
msg << "Content-Type: text/#{m[:type]}" << 'Mime-Version: 1.0' << ''
|
475
|
-
msg << read_part(m) << ''
|
476
|
-
end
|
477
|
-
msg << "--#{boundary}--"
|
478
|
-
msg
|
82
|
+
validations[:instance_type] = lambda do |attr, val, driver|
|
83
|
+
validation_error("[:#{attr}] #{val} is not a valid instance_type. must be either compute or dbaas.", driver) unless %w{compute dbaas}.include?(val.downcase)
|
479
84
|
end
|
480
85
|
|
481
|
-
|
482
|
-
if
|
483
|
-
boundary = "MIMEBOUNDARY_#{random_string(20)}"
|
484
|
-
msg = ["Content-Type: multipart/mixed; boundary=\"#{boundary}\"",
|
485
|
-
'MIME-Version: 1.0', '']
|
486
|
-
msg += mime_parts(boundary)
|
487
|
-
txt = msg.join("\n") + "\n"
|
488
|
-
gzip = Zlib::GzipWriter.new(StringIO.new)
|
489
|
-
gzip << txt
|
490
|
-
Base64.encode64(gzip.close.string).delete("\n")
|
491
|
-
elsif config[:user_data].is_a? String
|
492
|
-
Base64.encode64(config[:user_data]).delete("\n")
|
493
|
-
end
|
86
|
+
validations[:nsg_ids] = lambda do |attr, val, driver|
|
87
|
+
validation_error("[:#{attr}] list cannot be longer than 5 items", driver) if val.length > 5
|
494
88
|
end
|
495
89
|
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
created_vol = []
|
501
|
-
config[:volumes].each do |vol_settings|
|
502
|
-
# convert to hash because otherwise it's an an OCI API Object and won't load
|
503
|
-
volume_attachment_type = vol_settings[:type] ? vol_settings[:type].downcase : 'paravirtual'
|
504
|
-
unless %w[iscsi paravirtual].include?(volume_attachment_type)
|
505
|
-
info("invalid volume attachment type: #{volume_attachment_type}")
|
506
|
-
next
|
90
|
+
validations[:volumes] = lambda do |attr, val, driver|
|
91
|
+
val.each do |vol_attr|
|
92
|
+
unless ["iscsi", "paravirtual", nil].include?(vol_attr[:type])
|
93
|
+
validation_error("[:#{attr}][:type] #{vol_attr[:type]} is not a valid volume type for #{vol_attr[:name]}", driver)
|
507
94
|
end
|
508
|
-
volume = volume_create(
|
509
|
-
config[:availability_domain],
|
510
|
-
vol_settings[:name],
|
511
|
-
vol_settings[:size_in_gbs],
|
512
|
-
vol_settings[:vpus_per_gb] || 10
|
513
|
-
).to_hash
|
514
|
-
# convert to string otherwise it's a ruby datetime object and won't load
|
515
|
-
volume[:attachment_type] = volume_attachment_type
|
516
|
-
created_vol << volume
|
517
|
-
end
|
518
|
-
created_vol
|
519
|
-
end
|
520
|
-
|
521
|
-
def volume_create(availability_domain, display_name, size_in_gbs, vpus_per_gb)
|
522
|
-
info("Creating <#{display_name}>...")
|
523
|
-
result = blockstorage_api.create_volume(
|
524
|
-
OCI::Core::Models::CreateVolumeDetails.new(
|
525
|
-
compartment_id: compartment_id,
|
526
|
-
availability_domain: availability_domain,
|
527
|
-
display_name: display_name,
|
528
|
-
size_in_gbs: size_in_gbs,
|
529
|
-
vpus_per_gb: vpus_per_gb
|
530
|
-
)
|
531
|
-
)
|
532
|
-
get_volume_response = blockstorage_api.get_volume(result.data.id)
|
533
|
-
.wait_until(:lifecycle_state, OCI::Core::Models::Volume::LIFECYCLE_STATE_AVAILABLE)
|
534
|
-
info("Finished creating <#{display_name}>.")
|
535
|
-
{
|
536
|
-
id: get_volume_response.data.id,
|
537
|
-
display_name: get_volume_response.data.display_name
|
538
|
-
}
|
539
|
-
end
|
540
|
-
|
541
|
-
def volume_delete(volume_id)
|
542
|
-
info("Deleting <#{volume_id}>...")
|
543
|
-
blockstorage_api.delete_volume(volume_id)
|
544
|
-
blockstorage_api.get_volume(volume_id)
|
545
|
-
.wait_until(:lifecycle_state, OCI::Core::Models::Volume::LIFECYCLE_STATE_TERMINATED)
|
546
|
-
info("Finished deleting <#{volume_id}>.")
|
547
|
-
end
|
548
|
-
|
549
|
-
def process_volume_attachments(state)
|
550
|
-
attachments = []
|
551
|
-
state[:volumes].each do |volume|
|
552
|
-
info("Attaching <#{volume[:display_name]}>...")
|
553
|
-
details = volume_create_attachment_details(volume, state[:server_id])
|
554
|
-
attachment = volume_attach(details).to_hash
|
555
|
-
attachments << attachment
|
556
|
-
info("Finished attaching <#{volume[:display_name]}>.")
|
557
|
-
end
|
558
|
-
attachments
|
559
|
-
end
|
560
|
-
|
561
|
-
def volume_create_attachment_details(volume, instance_id)
|
562
|
-
if volume[:attachment_type].eql?('iscsi')
|
563
|
-
OCI::Core::Models::AttachIScsiVolumeDetails.new(
|
564
|
-
display_name: 'iSCSIAttachment',
|
565
|
-
volume_id: volume[:id],
|
566
|
-
instance_id: instance_id
|
567
|
-
)
|
568
|
-
elsif volume[:attachment_type].eql?('paravirtual')
|
569
|
-
OCI::Core::Models::AttachParavirtualizedVolumeDetails.new(
|
570
|
-
display_name: 'paravirtAttachment',
|
571
|
-
volume_id: volume[:id],
|
572
|
-
instance_id: instance_id
|
573
|
-
)
|
574
|
-
end
|
575
|
-
end
|
576
|
-
|
577
|
-
def volume_attach(volume_attachment_details)
|
578
|
-
result = comp_api.attach_volume(volume_attachment_details)
|
579
|
-
get_volume_attachment_response =
|
580
|
-
comp_api.get_volume_attachment(result.data.id)
|
581
|
-
.wait_until(:lifecycle_state, OCI::Core::Models::VolumeAttachment::LIFECYCLE_STATE_ATTACHED)
|
582
|
-
state_data = {
|
583
|
-
id: get_volume_attachment_response.data.id
|
584
|
-
}
|
585
|
-
if get_volume_attachment_response.data.attachment_type == 'iscsi'
|
586
|
-
state_data.store(:iqn_ipv4, get_volume_attachment_response.data.ipv4)
|
587
|
-
state_data.store(:iqn, get_volume_attachment_response.data.iqn)
|
588
|
-
state_data.store(:port, get_volume_attachment_response.data.port)
|
589
95
|
end
|
590
|
-
state_data
|
591
96
|
end
|
592
97
|
|
593
|
-
def
|
594
|
-
|
595
|
-
comp_api.detach_volume(volume_attachment[:id])
|
596
|
-
comp_api.get_volume_attachment(volume_attachment[:id])
|
597
|
-
.wait_until(:lifecycle_state, OCI::Core::Models::VolumeAttachment::LIFECYCLE_STATE_DETACHED)
|
598
|
-
info("Finished detaching <#{volume_attachment[:id]}>.")
|
98
|
+
def self.validation_error(message, driver)
|
99
|
+
raise UserError, "#{driver.class}<#{driver.instance.name}>#config#{message}"
|
599
100
|
end
|
600
101
|
|
601
|
-
|
602
|
-
# DBaaS methods #
|
603
|
-
#################
|
604
|
-
def launch_dbaas_instance
|
605
|
-
request = dbaas_launch_details
|
606
|
-
response = dbaas_api.launch_db_system(request)
|
607
|
-
instance_id = response.data.id
|
608
|
-
|
609
|
-
dbaas_api.get_db_system(instance_id).wait_until(
|
610
|
-
:lifecycle_state,
|
611
|
-
OCI::Database::Models::DbSystem::LIFECYCLE_STATE_AVAILABLE,
|
612
|
-
max_interval_seconds: 900,
|
613
|
-
max_wait_seconds: 21600
|
614
|
-
)
|
615
|
-
instance_id
|
616
|
-
end
|
102
|
+
include Kitchen::Driver::Oci::Models
|
617
103
|
|
618
|
-
def
|
619
|
-
|
620
|
-
database_edition = config[:dbaas][:database_edition] ||= OCI::Database::Models::DbSystem::DATABASE_EDITION_ENTERPRISE_EDITION
|
621
|
-
initial_data_storage_size_in_gb = config[:dbaas][:initial_data_storage_size_in_gb] ||= 256
|
622
|
-
license_model = config[:dbaas][:license_model] ||= OCI::Database::Models::DbSystem::LICENSE_MODEL_BRING_YOUR_OWN_LICENSE
|
104
|
+
def create(state)
|
105
|
+
return if state[:server_id]
|
623
106
|
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
l.shape = config[:shape]
|
633
|
-
l.ssh_public_keys = pubkey
|
634
|
-
l.cluster_name = generate_cluster_name
|
635
|
-
l.initial_data_storage_size_in_gb = initial_data_storage_size_in_gb
|
636
|
-
l.node_count = 1
|
637
|
-
l.license_model = license_model
|
638
|
-
l.subnet_id = config[:subnet_id]
|
639
|
-
l.freeform_tags = process_freeform_tags(config[:freeform_tags])
|
640
|
-
l.defined_tags = config[:defined_tags]
|
641
|
-
end
|
107
|
+
validate_config!
|
108
|
+
oci, api = auth(__method__)
|
109
|
+
inst = instance_class(config, state, oci, api, __method__)
|
110
|
+
state_details = inst.launch
|
111
|
+
state.merge!(state_details)
|
112
|
+
instance.transport.connection(state).wait_until_ready
|
113
|
+
create_and_attach_volumes(config, state, oci, api)
|
114
|
+
process_post_script(state)
|
642
115
|
end
|
643
116
|
|
644
|
-
def
|
645
|
-
|
117
|
+
def destroy(state)
|
118
|
+
return unless state[:server_id]
|
646
119
|
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
120
|
+
oci, api = auth(__method__)
|
121
|
+
instance.transport.connection(state).close
|
122
|
+
detatch_and_delete_volumes(state, oci, api) if state[:volumes]
|
123
|
+
inst = instance_class(config, state, oci, api, __method__)
|
124
|
+
inst.terminate
|
652
125
|
end
|
653
126
|
|
654
|
-
|
655
|
-
character_set = config[:dbaas][:character_set] ||= 'AL32UTF8'
|
656
|
-
ncharacter_set = config[:dbaas][:ncharacter_set] ||= 'AL16UTF16'
|
657
|
-
db_workload = config[:dbaas][:db_workload] ||= OCI::Database::Models::CreateDatabaseDetails::DB_WORKLOAD_OLTP
|
658
|
-
admin_password = config[:dbaas][:admin_password] ||= random_password
|
659
|
-
db_name = config[:dbaas][:db_name] ||= 'dbaas1'
|
127
|
+
private
|
660
128
|
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
l.ncharacter_set = ncharacter_set
|
667
|
-
l.pdb_name = config[:dbaas][:pdb_name]
|
668
|
-
l.db_backup_config = db_backup_config
|
669
|
-
end
|
129
|
+
def auth(action)
|
130
|
+
oci = Oci::Config.new(config)
|
131
|
+
api = Oci::Api.new(oci.config, config)
|
132
|
+
oci.compartment if action == :create
|
133
|
+
[oci, api]
|
670
134
|
end
|
671
135
|
|
672
|
-
def
|
673
|
-
|
674
|
-
l.auto_backup_enabled = false
|
675
|
-
end
|
676
|
-
end
|
136
|
+
def create_and_attach_volumes(config, state, oci, api)
|
137
|
+
return if config[:volumes].empty?
|
677
138
|
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
[
|
139
|
+
volume_state = { volumes: [], volume_attachments: [] }
|
140
|
+
config[:volumes].each do |volume|
|
141
|
+
vol = volume_class(volume[:type], config, state, oci, api)
|
142
|
+
volume_details, vol_state = vol.create_volume(volume)
|
143
|
+
attach_state = vol.attach_volume(volume_details, state[:server_id])
|
144
|
+
volume_state[:volumes] << vol_state
|
145
|
+
volume_state[:volume_attachments] << attach_state
|
685
146
|
end
|
147
|
+
state.merge!(volume_state)
|
686
148
|
end
|
687
149
|
|
688
|
-
def
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
).data
|
150
|
+
def detatch_and_delete_volumes(state, oci, api)
|
151
|
+
bls = Blockstorage.new(config, state, oci, api, :destroy)
|
152
|
+
state[:volume_attachments].each { |att| bls.detatch_volume(att) }
|
153
|
+
state[:volumes].each { |vol| bls.delete_volume(vol) }
|
693
154
|
end
|
694
155
|
|
695
|
-
def
|
696
|
-
|
697
|
-
end
|
156
|
+
def process_post_script(state)
|
157
|
+
return if config[:post_create_script].nil?
|
698
158
|
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
net_api.get_vnic(vnic).data.public_ip
|
703
|
-
else
|
704
|
-
net_api.get_vnic(vnic).data.private_ip
|
705
|
-
end
|
159
|
+
info("Running post create script")
|
160
|
+
script = config[:post_create_script]
|
161
|
+
instance.transport.connection(state).execute(script)
|
706
162
|
end
|
707
163
|
end
|
708
164
|
end
|
709
165
|
end
|
710
|
-
|
711
|
-
# rubocop:enable Metrics/AbcSize
|