kitchen-oci 1.15.1 → 1.16.1
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/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
|
|
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
|
|
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
|