kitchen-oci 1.15.1 → 1.16.1
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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 54d56f663b88d438c3b210755b53c04b1103445cd3f7736dd783679cda016bb7
|
4
|
+
data.tar.gz: d21f780ff87721d8027d2a4cbcbee1c449644515d61b36207d52d0478651be0a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 810d92317048752b4d2ad40b952aa8656153c14243fb19fbde92610c6dbc668094d49af4f130f6f3ab4b584e85199d08369ecb35d0f48bd627483b4d272254d9
|
7
|
+
data.tar.gz: b17e5a0695d27d9fab17fb3e415820bb2e62a81124a83bd506e6552f89bfc01d816e66fd3bf681a87629e661d3fed3d3c4b3902c697db854aabdef97136c61ab
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Author:: Justin Steele (<justin.steele@oracle.com>)
|
5
|
+
#
|
6
|
+
# Copyright:: (C) 2024, Stephen Pearson
|
7
|
+
#
|
8
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
9
|
+
# you may not use this file except in compliance with the License.
|
10
|
+
# You may obtain a copy of the License at
|
11
|
+
#
|
12
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
13
|
+
#
|
14
|
+
# Unless required by applicable law or agreed to in writing, software
|
15
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
16
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
17
|
+
# See the License for the specific language governing permissions and
|
18
|
+
# limitations under the License.
|
19
|
+
|
20
|
+
module Kitchen
|
21
|
+
module Driver
|
22
|
+
class Oci
|
23
|
+
# Api class that defines the various API classes used to interact with OCI
|
24
|
+
class Api
|
25
|
+
attr_reader :oci_config, :config
|
26
|
+
def initialize(oci_config, config)
|
27
|
+
@oci_config = oci_config
|
28
|
+
@config = config
|
29
|
+
end
|
30
|
+
|
31
|
+
def compute
|
32
|
+
generic_api(OCI::Core::ComputeClient)
|
33
|
+
end
|
34
|
+
|
35
|
+
def network
|
36
|
+
generic_api(OCI::Core::VirtualNetworkClient)
|
37
|
+
end
|
38
|
+
|
39
|
+
def dbaas
|
40
|
+
generic_api(OCI::Database::DatabaseClient)
|
41
|
+
end
|
42
|
+
|
43
|
+
def identity
|
44
|
+
generic_api(OCI::Identity::IdentityClient)
|
45
|
+
end
|
46
|
+
|
47
|
+
def blockstorage
|
48
|
+
generic_api(OCI::Core::BlockstorageClient)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def generic_api(klass)
|
54
|
+
params = {}
|
55
|
+
params[:proxy_settings] = api_proxy if api_proxy
|
56
|
+
params[:signer] = if config[:use_instance_principals]
|
57
|
+
OCI::Auth::Signers::InstancePrincipalsSecurityTokenSigner.new
|
58
|
+
elsif config[:use_token_auth]
|
59
|
+
token_signer
|
60
|
+
end
|
61
|
+
params[:config] = oci_config unless config[:use_instance_principals]
|
62
|
+
klass.new(**params.compact)
|
63
|
+
end
|
64
|
+
|
65
|
+
def token_signer
|
66
|
+
pkey_content = oci_config.key_content || File.read(oci_config.key_file).strip
|
67
|
+
pkey = OpenSSL::PKey::RSA.new(pkey_content, oci_config.pass_phrase)
|
68
|
+
|
69
|
+
token = File.read(oci_config.security_token_file).strip
|
70
|
+
OCI::Auth::Signers::SecurityTokenSigner.new(token, pkey)
|
71
|
+
end
|
72
|
+
|
73
|
+
def proxy_config
|
74
|
+
if config[:proxy_url]
|
75
|
+
URI.parse(config[:proxy_url])
|
76
|
+
else
|
77
|
+
URI.parse("http://example.com").find_proxy
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def api_proxy
|
82
|
+
prx = proxy_config
|
83
|
+
return unless prx
|
84
|
+
|
85
|
+
if prx.user
|
86
|
+
OCI::ApiClientProxySettings.new(prx.host, prx.port, prx.user,
|
87
|
+
prx.password)
|
88
|
+
else
|
89
|
+
OCI::ApiClientProxySettings.new(prx.host, prx.port)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Author:: Justin Steele (<justin.steele@oracle.com>)
|
4
|
+
#
|
5
|
+
# Copyright (C) 2024, Stephen Pearson
|
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
|
+
module Kitchen
|
20
|
+
module Driver
|
21
|
+
class Oci
|
22
|
+
# generic class for blockstorage
|
23
|
+
class Blockstorage < Oci
|
24
|
+
require_relative "api"
|
25
|
+
require_relative "config"
|
26
|
+
require_relative "models/iscsi"
|
27
|
+
require_relative "models/paravirtual"
|
28
|
+
|
29
|
+
attr_accessor :config, :state, :oci, :api, :volume_state, :volume_attachment_state
|
30
|
+
|
31
|
+
def initialize(config, state, oci, api, action = :create)
|
32
|
+
super()
|
33
|
+
@config = config
|
34
|
+
@state = state
|
35
|
+
@oci = oci
|
36
|
+
@api = api
|
37
|
+
@volume_state = {}
|
38
|
+
@volume_attachment_state = {}
|
39
|
+
oci.compartment if action == :create
|
40
|
+
end
|
41
|
+
|
42
|
+
def create_volume(volume)
|
43
|
+
info("Creating <#{volume[:name]}>...")
|
44
|
+
result = api.blockstorage.create_volume(volume_details(volume))
|
45
|
+
response = volume_response(result.data.id)
|
46
|
+
info("Finished creating <#{volume[:name]}>.")
|
47
|
+
[response, final_state(response)]
|
48
|
+
end
|
49
|
+
|
50
|
+
def attach_volume(volume_details, server_id)
|
51
|
+
info("Attaching <#{volume_details.display_name}>...")
|
52
|
+
attach_volume = api.compute.attach_volume(attachment_details(volume_details, server_id))
|
53
|
+
response = attachment_response(attach_volume.data.id)
|
54
|
+
info("Finished attaching <#{volume_details.display_name}>.")
|
55
|
+
final_state(response)
|
56
|
+
end
|
57
|
+
|
58
|
+
def delete_volume(volume)
|
59
|
+
info("Deleting <#{volume[:display_name]}>...")
|
60
|
+
api.blockstorage.delete_volume(volume[:id])
|
61
|
+
api.blockstorage.get_volume(volume[:id])
|
62
|
+
.wait_until(:lifecycle_state, OCI::Core::Models::Volume::LIFECYCLE_STATE_TERMINATED)
|
63
|
+
info("Finished deleting <#{volume[:display_name]}>.")
|
64
|
+
end
|
65
|
+
|
66
|
+
def detatch_volume(volume_attachment)
|
67
|
+
info("Detaching <#{attachment_name(volume_attachment)}>...")
|
68
|
+
api.compute.detach_volume(volume_attachment[:id])
|
69
|
+
api.compute.get_volume_attachment(volume_attachment[:id])
|
70
|
+
.wait_until(:lifecycle_state, OCI::Core::Models::VolumeAttachment::LIFECYCLE_STATE_DETACHED)
|
71
|
+
info("Finished detaching <#{attachment_name(volume_attachment)}>.")
|
72
|
+
end
|
73
|
+
|
74
|
+
def detatch_and_delete
|
75
|
+
state[:volume_attachments].each do |att|
|
76
|
+
detatch_volume(att)
|
77
|
+
end
|
78
|
+
|
79
|
+
state[:volumes].each do |vol|
|
80
|
+
delete_volume(vol)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def final_state(response)
|
85
|
+
case response
|
86
|
+
when OCI::Core::Models::Volume
|
87
|
+
final_volume_state(response)
|
88
|
+
when OCI::Core::Models::VolumeAttachment
|
89
|
+
final_volume_attachment_state(response)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def volume_response(volume_id)
|
96
|
+
api.blockstorage.get_volume(volume_id)
|
97
|
+
.wait_until(:lifecycle_state, OCI::Core::Models::Volume::LIFECYCLE_STATE_AVAILABLE).data
|
98
|
+
end
|
99
|
+
|
100
|
+
def attachment_response(attachment_id)
|
101
|
+
api.compute.get_volume_attachment(attachment_id)
|
102
|
+
.wait_until(:lifecycle_state, OCI::Core::Models::VolumeAttachment::LIFECYCLE_STATE_ATTACHED).data
|
103
|
+
end
|
104
|
+
|
105
|
+
def volume_details(volume)
|
106
|
+
OCI::Core::Models::CreateVolumeDetails.new(
|
107
|
+
compartment_id: oci.compartment,
|
108
|
+
availability_domain: config[:availability_domain],
|
109
|
+
display_name: volume[:name],
|
110
|
+
size_in_gbs: volume[:size_in_gbs],
|
111
|
+
vpus_per_gb: volume[:vpus_per_gb] || 10
|
112
|
+
)
|
113
|
+
end
|
114
|
+
|
115
|
+
def attachment_name(attachment)
|
116
|
+
attachment[:display_name].gsub(/(?:paravirtual|iscsi)-/, "")
|
117
|
+
end
|
118
|
+
|
119
|
+
def final_volume_state(response)
|
120
|
+
volume_state.store(:id, response.id)
|
121
|
+
volume_state.store(:display_name, response.display_name)
|
122
|
+
volume_state
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Author:: Justin Steele (<justin.steele@oracle.com>)
|
5
|
+
#
|
6
|
+
# Copyright (C) 2024, Stephen Pearson
|
7
|
+
#
|
8
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
9
|
+
# you may not use this file except in compliance with the License.
|
10
|
+
# You may obtain a copy of the License at
|
11
|
+
#
|
12
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
13
|
+
#
|
14
|
+
# Unless required by applicable law or agreed to in writing, software
|
15
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
16
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
17
|
+
# See the License for the specific language governing permissions and
|
18
|
+
# limitations under the License.
|
19
|
+
|
20
|
+
require_relative "api"
|
21
|
+
|
22
|
+
module Kitchen
|
23
|
+
module Driver
|
24
|
+
class Oci
|
25
|
+
# Config class that defines the oci config that will be used for the API calls
|
26
|
+
class Config
|
27
|
+
attr_reader :config
|
28
|
+
|
29
|
+
def initialize(driver_config)
|
30
|
+
setup_driver_config(driver_config)
|
31
|
+
@config = oci_config
|
32
|
+
end
|
33
|
+
|
34
|
+
def oci_config
|
35
|
+
# OCI::Config is missing this and we're definitely using compartment and security_token_file if specified in the config
|
36
|
+
OCI::Config.class_eval { attr_accessor :security_token_file } if @driver_config[:use_token_auth]
|
37
|
+
conf = config_loader(config_file_location: @driver_config[:oci_config_file], profile_name: @driver_config[:oci_profile_name])
|
38
|
+
@driver_config[:oci_config].each do |key, value|
|
39
|
+
conf.send("#{key}=", value) unless value.nil? || value.empty?
|
40
|
+
end
|
41
|
+
conf
|
42
|
+
end
|
43
|
+
|
44
|
+
def compartment
|
45
|
+
@compartment ||= @compartment_id
|
46
|
+
return @compartment if @compartment
|
47
|
+
|
48
|
+
raise "must specify either compartment_id or compartment_name" unless [@compartment_id, @compartment_name].any?
|
49
|
+
|
50
|
+
@compartment ||= compartment_id_by_name(@compartment_name)
|
51
|
+
raise "compartment not found" unless @compartment
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def setup_driver_config(config)
|
57
|
+
@driver_config = config
|
58
|
+
@compartment_id = config[:compartment_id]
|
59
|
+
@compartment_name = config[:compartment_name]
|
60
|
+
end
|
61
|
+
|
62
|
+
def config_loader(opts = {})
|
63
|
+
OCI::ConfigFileLoader.load_config(**opts.compact)
|
64
|
+
rescue OCI::ConfigFileLoader::Errors::ConfigFileNotFoundError
|
65
|
+
OCI::Config.new
|
66
|
+
end
|
67
|
+
|
68
|
+
def tenancy
|
69
|
+
if @driver_config[:use_instance_principals]
|
70
|
+
sign = OCI::Auth::Signers::InstancePrincipalsSecurityTokenSigner.new
|
71
|
+
sign.instance_variable_get "@tenancy_id"
|
72
|
+
else
|
73
|
+
config.tenancy
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def compartment_id_by_name(name)
|
78
|
+
api = Oci::Api.new(config, @driver_config).identity
|
79
|
+
all_compartments(api, config.tenancy).select { |c| c.name == name }&.first&.id
|
80
|
+
end
|
81
|
+
|
82
|
+
def all_compartments(api, tenancy, compartments = [], page = nil)
|
83
|
+
current_compartments = api.list_compartments(tenancy, page: page)
|
84
|
+
next_page = current_compartments.next_page
|
85
|
+
compartments << current_compartments.data
|
86
|
+
all_compartments(api, tenancy, compartments, next_page) unless next_page.nil?
|
87
|
+
compartments.flatten
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Author:: Justin Steele (<justin.steele@oracle.com>)
|
5
|
+
#
|
6
|
+
# Copyright (C) 2024, Stephen Pearson
|
7
|
+
#
|
8
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
9
|
+
# you may not use this file except in compliance with the License.
|
10
|
+
# You may obtain a copy of the License at
|
11
|
+
#
|
12
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
13
|
+
#
|
14
|
+
# Unless required by applicable law or agreed to in writing, software
|
15
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
16
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
17
|
+
# See the License for the specific language governing permissions and
|
18
|
+
# limitations under the License.
|
19
|
+
|
20
|
+
module Kitchen
|
21
|
+
module Driver
|
22
|
+
class Oci
|
23
|
+
# generic class for instance models
|
24
|
+
class Instance < Oci
|
25
|
+
require_relative "api"
|
26
|
+
require_relative "config"
|
27
|
+
require_relative "models/compute"
|
28
|
+
require_relative "models/dbaas"
|
29
|
+
|
30
|
+
attr_accessor :config, :state, :oci, :api
|
31
|
+
|
32
|
+
def initialize(config, state, oci, api, action)
|
33
|
+
super()
|
34
|
+
@config = config
|
35
|
+
@state = state
|
36
|
+
@oci = oci
|
37
|
+
@api = api
|
38
|
+
end
|
39
|
+
|
40
|
+
def compartment_id
|
41
|
+
launch_details.compartment_id = oci.compartment
|
42
|
+
end
|
43
|
+
|
44
|
+
def availability_domain
|
45
|
+
launch_details.availability_domain = config[:availability_domain]
|
46
|
+
end
|
47
|
+
|
48
|
+
def defined_tags
|
49
|
+
launch_details.defined_tags = config[:defined_tags]
|
50
|
+
end
|
51
|
+
|
52
|
+
def shape
|
53
|
+
launch_details.shape = config[:shape]
|
54
|
+
end
|
55
|
+
|
56
|
+
def freeform_tags
|
57
|
+
launch_details.freeform_tags = process_freeform_tags
|
58
|
+
end
|
59
|
+
|
60
|
+
def final_state(state, instance_id)
|
61
|
+
state.store(:server_id, instance_id)
|
62
|
+
state.store(:hostname, instance_ip(instance_id))
|
63
|
+
state
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def public_ip_allowed?
|
69
|
+
subnet = api.network.get_subnet(config[:subnet_id]).data
|
70
|
+
!subnet.prohibit_public_ip_on_vnic
|
71
|
+
end
|
72
|
+
|
73
|
+
def random_password(special_chars)
|
74
|
+
(Array.new(5) { special_chars.sample } +
|
75
|
+
Array.new(5) { ("a".."z").to_a.sample } +
|
76
|
+
Array.new(5) { ("A".."Z").to_a.sample } +
|
77
|
+
Array.new(5) { ("0".."9").to_a.sample }).shuffle.join
|
78
|
+
end
|
79
|
+
|
80
|
+
def random_string(length)
|
81
|
+
Array.new(length) { ("a".."z").to_a.sample }.join
|
82
|
+
end
|
83
|
+
|
84
|
+
def random_number(length)
|
85
|
+
Array.new(length) { ("0".."9").to_a.sample }.join
|
86
|
+
end
|
87
|
+
|
88
|
+
def process_freeform_tags
|
89
|
+
tags = %w{run_list policyfile}
|
90
|
+
fft = config[:freeform_tags]
|
91
|
+
tags.each do |tag|
|
92
|
+
unless fft[tag.to_sym].nil? || fft[tag.to_sym].empty?
|
93
|
+
fft[tag] =
|
94
|
+
prov[tag.to_sym].join(",")
|
95
|
+
end
|
96
|
+
end
|
97
|
+
fft[:kitchen] = true
|
98
|
+
fft
|
99
|
+
end
|
100
|
+
|
101
|
+
def user_data
|
102
|
+
case config[:user_data]
|
103
|
+
when Array
|
104
|
+
Base64.encode64(multi_part_user_data.close.string).delete("\n")
|
105
|
+
when String
|
106
|
+
Base64.encode64(config[:user_data]).delete("\n")
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def multi_part_user_data
|
111
|
+
boundary = "MIMEBOUNDARY_#{random_string(20)}"
|
112
|
+
msg = ["Content-Type: multipart/mixed; boundary=\"#{boundary}\"",
|
113
|
+
"MIME-Version: 1.0", ""]
|
114
|
+
msg += mime_parts(boundary)
|
115
|
+
txt = "#{msg.join("\n")}\n"
|
116
|
+
gzip = Zlib::GzipWriter.new(StringIO.new)
|
117
|
+
gzip << txt
|
118
|
+
end
|
119
|
+
|
120
|
+
def mime_parts(boundary)
|
121
|
+
msg = []
|
122
|
+
config[:user_data].each do |m|
|
123
|
+
msg << "--#{boundary}"
|
124
|
+
msg << "Content-Disposition: attachment; filename=\"#{m[:filename]}\""
|
125
|
+
msg << "Content-Transfer-Encoding: 7bit"
|
126
|
+
msg << "Content-Type: text/#{m[:type]}" << "Mime-Version: 1.0" << ""
|
127
|
+
msg << read_part(m) << ""
|
128
|
+
end
|
129
|
+
msg << "--#{boundary}--"
|
130
|
+
msg
|
131
|
+
end
|
132
|
+
|
133
|
+
def read_part(part)
|
134
|
+
if part[:path]
|
135
|
+
content = File.read part[:path]
|
136
|
+
elsif part[:inline]
|
137
|
+
content = part[:inline]
|
138
|
+
else
|
139
|
+
raise "Invalid user data"
|
140
|
+
end
|
141
|
+
content.split("\n")
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Author:: Justin Steele (<justin.steele@oracle.com>)
|
4
|
+
#
|
5
|
+
# Copyright (C) 2024, Stephen Pearson
|
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
|
+
module Kitchen
|
20
|
+
module Driver
|
21
|
+
class Oci
|
22
|
+
module Models
|
23
|
+
# Compute instance model
|
24
|
+
class Compute < Instance # rubocop:disable Metrics/ClassLength
|
25
|
+
attr_accessor :launch_details
|
26
|
+
|
27
|
+
def initialize(config, state, oci, api, action = :create)
|
28
|
+
super
|
29
|
+
@launch_details = OCI::Core::Models::LaunchInstanceDetails.new
|
30
|
+
end
|
31
|
+
|
32
|
+
def launch
|
33
|
+
process_windows_options
|
34
|
+
response = api.compute.launch_instance(launch_instance_details)
|
35
|
+
instance_id = response.data.id
|
36
|
+
api.compute.get_instance(instance_id).wait_until(:lifecycle_state, OCI::Core::Models::Instance::LIFECYCLE_STATE_RUNNING )
|
37
|
+
final_state(state, instance_id)
|
38
|
+
end
|
39
|
+
|
40
|
+
def terminate
|
41
|
+
api.compute.terminate_instance(state[:server_id])
|
42
|
+
api.compute.get_instance(state[:server_id]).wait_until(:lifecycle_state, OCI::Core::Models::Instance::LIFECYCLE_STATE_TERMINATING)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def launch_instance_details # rubocop:disable Metrics/MethodLength
|
48
|
+
compartment_id
|
49
|
+
availability_domain
|
50
|
+
defined_tags
|
51
|
+
shape
|
52
|
+
freeform_tags
|
53
|
+
hostname_display_name
|
54
|
+
instance_source_details
|
55
|
+
instance_metadata
|
56
|
+
preemptible_instance_config
|
57
|
+
shape_config
|
58
|
+
launch_details
|
59
|
+
end
|
60
|
+
|
61
|
+
def hostname_display_name
|
62
|
+
display_name = hostname
|
63
|
+
launch_details.display_name = display_name
|
64
|
+
launch_details.create_vnic_details = create_vnic_details(display_name)
|
65
|
+
end
|
66
|
+
|
67
|
+
def hostname
|
68
|
+
[config[:hostname_prefix], random_string(6)].compact.join("-")
|
69
|
+
end
|
70
|
+
|
71
|
+
def preemptible_instance_config
|
72
|
+
return unless config[:preemptible_instance]
|
73
|
+
|
74
|
+
launch_details.preemptible_instance_config = OCI::Core::Models::PreemptibleInstanceConfigDetails.new(
|
75
|
+
preemption_action:
|
76
|
+
OCI::Core::Models::TerminatePreemptionAction.new(
|
77
|
+
type: "TERMINATE", preserve_boot_volume: true
|
78
|
+
)
|
79
|
+
)
|
80
|
+
end
|
81
|
+
|
82
|
+
def shape_config
|
83
|
+
return if config[:shape_config].empty?
|
84
|
+
|
85
|
+
launch_details.shape_config = OCI::Core::Models::LaunchInstanceShapeConfigDetails.new(
|
86
|
+
ocpus: config[:shape_config][:ocpus],
|
87
|
+
memory_in_gbs: config[:shape_config][:memory_in_gbs],
|
88
|
+
baseline_ocpu_utilization: config[:shape_config][:baseline_ocpu_utilization] || "BASELINE_1_1"
|
89
|
+
)
|
90
|
+
end
|
91
|
+
|
92
|
+
def instance_source_details
|
93
|
+
launch_details.source_details = OCI::Core::Models::InstanceSourceViaImageDetails.new(
|
94
|
+
sourceType: "image",
|
95
|
+
imageId: config[:image_id],
|
96
|
+
bootVolumeSizeInGBs: config[:boot_volume_size_in_gbs]
|
97
|
+
)
|
98
|
+
end
|
99
|
+
|
100
|
+
def create_vnic_details(name)
|
101
|
+
OCI::Core::Models::CreateVnicDetails.new(
|
102
|
+
assign_public_ip: public_ip_allowed?,
|
103
|
+
display_name: name,
|
104
|
+
hostname_label: name,
|
105
|
+
nsg_ids: config[:nsg_ids],
|
106
|
+
subnetId: config[:subnet_id]
|
107
|
+
)
|
108
|
+
end
|
109
|
+
|
110
|
+
def pubkey
|
111
|
+
File.readlines(config[:ssh_keypath]).first.chomp
|
112
|
+
end
|
113
|
+
|
114
|
+
def instance_metadata
|
115
|
+
launch_details.metadata = metadata
|
116
|
+
end
|
117
|
+
|
118
|
+
def metadata
|
119
|
+
md = {}
|
120
|
+
inject_powershell
|
121
|
+
config[:custom_metadata]&.each { |k, v| md.store(k, v) }
|
122
|
+
md.store("ssh_authorized_keys", pubkey)
|
123
|
+
md.store("user_data", user_data) if config[:user_data] && !config[:user_data].empty?
|
124
|
+
md
|
125
|
+
end
|
126
|
+
|
127
|
+
def vnics(instance_id)
|
128
|
+
vnic_attachments(instance_id).map { |att| api.network.get_vnic(att.vnic_id).data }
|
129
|
+
end
|
130
|
+
|
131
|
+
def vnic_attachments(instance_id)
|
132
|
+
att = api.compute.list_vnic_attachments(oci.compartment, instance_id: instance_id).data
|
133
|
+
raise "Could not find any VNIC attachments" unless att.any?
|
134
|
+
|
135
|
+
att
|
136
|
+
end
|
137
|
+
|
138
|
+
def instance_ip(instance_id)
|
139
|
+
vnic = vnics(instance_id).select(&:is_primary).first
|
140
|
+
if public_ip_allowed?
|
141
|
+
config[:use_private_ip] ? vnic.private_ip : vnic.public_ip
|
142
|
+
else
|
143
|
+
vnic.private_ip
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def process_windows_options
|
148
|
+
return unless windows_state?
|
149
|
+
|
150
|
+
state.store(:username, config[:winrm_user])
|
151
|
+
state.store(:password, config[:winrm_password] || random_password(%w{@ - ( ) .}))
|
152
|
+
end
|
153
|
+
|
154
|
+
def windows_state?
|
155
|
+
config[:setup_winrm] && config[:password].nil? && state[:password].nil?
|
156
|
+
end
|
157
|
+
|
158
|
+
def winrm_ps1
|
159
|
+
filename = File.join(__dir__, %w{.. .. .. .. .. tpl setup_winrm.ps1.erb})
|
160
|
+
tpl = ERB.new(File.read(filename))
|
161
|
+
tpl.result(binding)
|
162
|
+
end
|
163
|
+
|
164
|
+
def inject_powershell
|
165
|
+
return unless config[:setup_winrm]
|
166
|
+
|
167
|
+
data = winrm_ps1
|
168
|
+
config[:user_data] ||= []
|
169
|
+
config[:user_data] << {
|
170
|
+
type: "x-shellscript",
|
171
|
+
inline: data,
|
172
|
+
filename: "setup_winrm.ps1",
|
173
|
+
}
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|