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
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
|