kitchen-oci 1.26.0 → 1.27.0

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.
@@ -23,69 +23,85 @@ module Kitchen
23
23
  module Driver
24
24
  class Oci
25
25
  class Instance
26
- # setter methods that populate the details of OCI::Database::Models::LaunchDbSystemDetails
26
+ # Setter methods that populate the details of OCI::Database::Models::LaunchDbSystemDetails.
27
+ #
28
+ # @author Justin Steele <justin.steele@oracle.com>
27
29
  module DbaasLaunchDetails
28
30
  include DatabaseDetails
29
31
  include DbHomeDetails
30
32
  #
31
33
  # TODO: add support for the #domain property
32
34
  #
35
+
36
+ # Adds the db_home property to the launch_details.
33
37
  def db_home
34
38
  launch_details.db_home = db_home_details
35
39
  end
36
40
 
41
+ # Adds the subnet_id property to the launch_details.
37
42
  def subnet_id
38
43
  launch_details.subnet_id = config[:subnet_id]
39
44
  end
40
45
 
46
+ # Adds the nsg_ids property to the launch_details.
41
47
  def nsg_ids
42
48
  launch_details.nsg_ids = config[:nsg_ids]
43
49
  end
44
50
 
51
+ # Adds the hostname property to the launch_details.
52
+ # The hostname must begin with an alphabetic character, and can contain alphanumeric characters and hyphens (-).
53
+ # The maximum length of the hostname is 16 characters.
45
54
  def hostname
46
- # The hostname must begin with an alphabetic character, and can contain alphanumeric characters and hyphens (-).
47
- # The maximum length of the hostname is 16 characters
48
55
  long_name = [hostname_prefix, long_hostname_suffix].compact.join("-")
49
56
  trimmed_name = [hostname_prefix[0, 12], random_string(3)].compact.join("-")
50
57
  launch_details.hostname = [long_name, trimmed_name].min { |l, t| l.size <=> t.size }
51
58
  end
52
59
 
60
+ # Adds the display_name property to the launch details.
61
+ # The user-friendly name for the DB system. The name does not have to be unique.
53
62
  def display_name
54
- # The user-friendly name for the DB system. The name does not have to be unique.
55
63
  launch_details.display_name = [config[:hostname_prefix], random_string(4), random_number(2)].compact.join("-")
56
64
  end
57
65
 
66
+ # Adds the node_count property to the launch_details.
58
67
  def node_count
59
68
  launch_details.node_count = 1
60
69
  end
61
70
 
71
+ # Adds the ssh_public_keys property to the launch_details.
62
72
  def pubkey
63
73
  result = []
64
74
  result << read_public_key
65
75
  launch_details.ssh_public_keys = result
66
76
  end
67
77
 
78
+ # Adds the cpu_core_count property to the launch_details.
68
79
  def cpu_core_count
69
80
  launch_details.cpu_core_count = config[:dbaas][:cpu_core_count] ||= 2
70
81
  end
71
82
 
83
+ # Adds the license_model property to the launch_details.
72
84
  def license_model
73
85
  license = config[:dbaas][:license_model] ||= OCI::Database::Models::DbSystem::LICENSE_MODEL_BRING_YOUR_OWN_LICENSE
74
86
  launch_details.license_model = license
75
87
  end
76
88
 
89
+ # Adds the initial_data_size_in_gb property to the launch_details.
77
90
  def initial_data_storage_size_in_gb
78
91
  launch_details.initial_data_storage_size_in_gb = config[:dbaas][:initial_data_storage_size_in_gb] ||= 256
79
92
  end
80
93
 
94
+ # Adds the database_edition property to the launch_details.
81
95
  def database_edition
82
96
  db_edition = config[:dbaas][:database_edition] ||= OCI::Database::Models::DbSystem::DATABASE_EDITION_ENTERPRISE_EDITION
83
97
  launch_details.database_edition = db_edition
84
98
  end
85
99
 
100
+ # Adds the cluster_name property to the launch_details.
101
+ # 11 character limit for cluster_name in DBaaS.
86
102
  def cluster_name
87
103
  prefix = config[:hostname_prefix].split("-")[0]
88
- # 11 character limit for cluster_name in DBaaS
104
+
89
105
  cn = if prefix.length >= 11
90
106
  prefix[0, 11]
91
107
  else
@@ -98,4 +114,3 @@ module Kitchen
98
114
  end
99
115
  end
100
116
  end
101
-
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- #
4
3
  # Author:: Justin Steele (<justin.steele@oracle.com>)
5
4
  #
6
5
  # Copyright (C) 2024, Stephen Pearson
@@ -20,7 +19,9 @@
20
19
  module Kitchen
21
20
  module Driver
22
21
  class Oci
23
- # generic class for instance models
22
+ # Base class for instance models.
23
+ #
24
+ # @author Justin Steele <justin.steele@oracle.com>
24
25
  class Instance < Oci # rubocop:disable Metrics/ClassLength
25
26
  require_relative "api"
26
27
  require_relative "config"
@@ -39,41 +40,35 @@ module Kitchen
39
40
  @logger = opts[:logger]
40
41
  end
41
42
 
42
- #
43
- # The config provided by the driver
43
+ # The config provided by the driver.
44
44
  #
45
45
  # @return [Kitchen::LazyHash]
46
- #
47
46
  attr_accessor :config
48
47
 
49
- #
50
- # The definition of the state of the instance from the statefile
48
+ # The definition of the state of the instance from the statefile.
51
49
  #
52
50
  # @return [Hash]
53
- #
54
51
  attr_accessor :state
55
52
 
56
- #
57
- # The config object that contains properties of the authentication to OCI
53
+ # The config object that contains properties of the authentication to OCI.
58
54
  #
59
55
  # @return [Kitchen::Driver::Oci::Config]
60
- #
61
56
  attr_accessor :oci
62
57
 
63
- #
64
- # The API object that contains each of the authenticated clients for interfacing with OCI
58
+ # The API object that contains each of the authenticated clients for interfacing with OCI.
65
59
  #
66
60
  # @return [Kitchen::Driver::Oci::Api]
67
- #
68
61
  attr_accessor :api
69
62
 
70
- #
71
- # The instance of Kitchen::Logger in use by the active Kitchen::Instance
63
+ # The instance of Kitchen::Logger in use by the active Kitchen::Instance.
72
64
  #
73
65
  # @return [Kitchen::Logger]
74
- #
75
66
  attr_accessor :logger
76
67
 
68
+ # Adds the instance info into the state.
69
+ #
70
+ # @param state [Hash] The state from kitchen.
71
+ # @return [Hash]
77
72
  def final_state(state, instance_id)
78
73
  state.store(:server_id, instance_id)
79
74
  state.store(:hostname, instance_ip(instance_id))
@@ -82,6 +77,9 @@ module Kitchen
82
77
 
83
78
  private
84
79
 
80
+ # Calls all of the setter methods for the self.
81
+ #
82
+ # @return [OCI::Core::Models::LaunchInstanceDetails, OCI::Database::Models::LaunchDbSystemDetails] the fully populated launch details for the specific instance type.
85
83
  def launch_instance_details
86
84
  launch_methods = []
87
85
  self.class.ancestors.reverse.select { |m| m.is_a?(Module) && m.name.start_with?("#{self.class.superclass}::") }.each do |klass|
@@ -91,11 +89,17 @@ module Kitchen
91
89
  launch_details
92
90
  end
93
91
 
92
+ # Checks if public IP addresses are allowed in the specified subnet.
93
+ #
94
+ # @return [Boolean]
94
95
  def public_ip_allowed?
95
96
  subnet = api.network.get_subnet(config[:subnet_id]).data
96
97
  !subnet.prohibit_public_ip_on_vnic
97
98
  end
98
99
 
100
+ # Returns the location of the public ssh key.
101
+ #
102
+ # @return [String]
99
103
  def public_key_file
100
104
  if config[:ssh_keygen]
101
105
  "#{config[:kitchen_root]}/.kitchen/.ssh/#{config[:instance_name]}_rsa.pub"
@@ -104,10 +108,14 @@ module Kitchen
104
108
  end
105
109
  end
106
110
 
111
+ # Returns the name of the private key file.
112
+ #
113
+ # @return [String]
107
114
  def private_key_file
108
115
  public_key_file.gsub(".pub", "")
109
116
  end
110
117
 
118
+ # Generates an RSA key pair to be used to SSH to the instance and updates the state with the full path to the private key.
111
119
  def gen_key_pair
112
120
  FileUtils.mkdir_p("#{config[:kitchen_root]}/.kitchen/.ssh")
113
121
  rsa_key = OpenSSL::PKey::RSA.new(4096)
@@ -116,16 +124,25 @@ module Kitchen
116
124
  state.store(:ssh_key, private_key_file)
117
125
  end
118
126
 
127
+ # Writes the private key.
128
+ #
129
+ # @param rsa_key [OpenSSL::PKey::RSA] the generated RSA key.
119
130
  def write_private_key(rsa_key)
120
131
  File.open(private_key_file, "wb") { |k| k.write(rsa_key.to_pem) }
121
132
  File.chmod(0600, private_key_file)
122
133
  end
123
134
 
135
+ # Writes the encoded private key as a public key.
136
+ #
137
+ # @param rsa_key [OpenSSL::PKey::RSA] the generated RSA key.
124
138
  def write_public_key(rsa_key)
125
139
  File.open(public_key_file, "wb") { |k| k.write("ssh-rsa #{encode_private_key(rsa_key)} #{config[:instance_name]}") }
126
140
  File.chmod(0600, public_key_file)
127
141
  end
128
142
 
143
+ # Encodes the private key.
144
+ #
145
+ # @param rsa_key [OpenSSL::PKey::RSA] the generated RSA key.
129
146
  def encode_private_key(rsa_key)
130
147
  prefix = "#{[7].pack("N")}ssh-rsa"
131
148
  exponent = rsa_key.e.to_s(0)
@@ -133,6 +150,10 @@ module Kitchen
133
150
  ["#{prefix}#{exponent}#{modulus}"].pack("m0")
134
151
  end
135
152
 
153
+ # Generates a random password.
154
+ #
155
+ # @param special_chars [Array] an array of special characters to include in the random password.
156
+ # @return [String]
136
157
  def random_password(special_chars)
137
158
  (Array.new(5) { special_chars.sample } +
138
159
  Array.new(5) { ("a".."z").to_a.sample } +
@@ -140,14 +161,25 @@ module Kitchen
140
161
  Array.new(5) { ("0".."9").to_a.sample }).shuffle.join
141
162
  end
142
163
 
164
+ # Generates a random string of letters.
165
+ #
166
+ # @param length [Integer] how many characters to randomize.
167
+ # @return [String]
143
168
  def random_string(length)
144
169
  Array.new(length) { ("a".."z").to_a.sample }.join
145
170
  end
146
171
 
172
+ # Generates a random string of numbers.
173
+ #
174
+ # @param length [Integer] how many numbers to randomize.
175
+ # @return [String]
147
176
  def random_number(length)
148
177
  Array.new(length) { ("0".."9").to_a.sample }.join
149
178
  end
150
179
 
180
+ # Parses freeform tags to be added to the instance by the setter method.
181
+ #
182
+ # @return [Hash]
151
183
  def process_freeform_tags
152
184
  tags = %w{run_list policyfile}
153
185
  fft = config[:freeform_tags]
@@ -161,6 +193,9 @@ module Kitchen
161
193
  fft
162
194
  end
163
195
 
196
+ # Encodes specified user_data to be added to cloud-init.
197
+ #
198
+ # @return [Base64]
164
199
  def user_data
165
200
  case config[:user_data]
166
201
  when Array
@@ -170,6 +205,9 @@ module Kitchen
170
205
  end
171
206
  end
172
207
 
208
+ # GZips processed user_data prior to being encoded to allow for multi-part inclusions.
209
+ #
210
+ # @return [Zlib::GzipWriter]
173
211
  def multi_part_user_data
174
212
  boundary = "MIMEBOUNDARY_#{random_string(20)}"
175
213
  msg = ["Content-Type: multipart/mixed; boundary=\"#{boundary}\"",
@@ -180,6 +218,10 @@ module Kitchen
180
218
  gzip << txt
181
219
  end
182
220
 
221
+ # Joins all of the bits provided by each itema in user_data with the provided boundary and content headers.
222
+ #
223
+ # @param boundary [String]
224
+ # @return [Array]
183
225
  def mime_parts(boundary)
184
226
  msg = []
185
227
  config[:user_data].each do |m|
@@ -193,6 +235,10 @@ module Kitchen
193
235
  msg
194
236
  end
195
237
 
238
+ # Reads either the specified file or the text provided inline.
239
+ #
240
+ # @param part [Hash] the current item in the user_data hash being processed.
241
+ # @return [Array]
196
242
  def read_part(part)
197
243
  if part[:path]
198
244
  content = File.read part[:path]
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Author:: Justin Steele (<justin.steele@oracle.com>)
5
+ #
6
+ # Copyright (C) 2025, 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
+ module Mixin
24
+ # Actions that can be performed on an instance.
25
+ #
26
+ # @author Justin Steele <justin.steele@oracle.com>
27
+ module Actions
28
+ # Launches an instance.
29
+ #
30
+ # @param state [Hash] (see Kitchen::StateFile)
31
+ # @param inst [Class] the specific class of instance being launched.
32
+ def launch(state, inst)
33
+ state_details = inst.launch
34
+ state.merge!(state_details)
35
+ instance.transport.connection(state).wait_until_ready
36
+ end
37
+
38
+ # Executes the post script on the instance.
39
+ #
40
+ # @param state [Hash] (see Kitchen::StateFile)
41
+ def execute_post_create_script(state)
42
+ return if config[:post_create_script].nil?
43
+
44
+ if config[:post_create_script].is_a?(String)
45
+ execute_post_create_string(state)
46
+ elsif config[:post_create_script].is_a?(Array)
47
+ execute_post_create_file(state)
48
+ end
49
+ end
50
+
51
+ # Executes the post create script from a String.
52
+ #
53
+ # @param state [Hash] (see Kitchen::StateFile)
54
+ def execute_post_create_string(state)
55
+ info("Running post create script")
56
+ script = config[:post_create_script]
57
+ instance.transport.connection(state).execute(script)
58
+ end
59
+
60
+ # Reads the specified file and executes as a post create script.
61
+ #
62
+ # @param state [Hash] (see Kitchen::StateFile)
63
+ def execute_post_create_file(state)
64
+ config[:post_create_script].each do |script|
65
+ info("Running post create script #{File.basename(script)}")
66
+ script = File.read script
67
+ instance.transport.connection(state).execute(script)
68
+ end
69
+ end
70
+
71
+ # Reboots an instance.
72
+ #
73
+ # @param state [Hash] (see Kitchen::StateFile)
74
+ # @param inst [Class] the specific class of instance being rebooted.
75
+ def reboot(state, inst)
76
+ return unless config[:post_create_reboot]
77
+
78
+ instance.transport.connection(state).close
79
+ inst.reboot
80
+ instance.transport.connection(state).wait_until_ready
81
+ end
82
+
83
+ # Terminates an instance.
84
+ #
85
+ # @param state [Hash] (see Kitchen::StateFile)
86
+ # @param inst [Class] the specific class of instance being launched.
87
+ def terminate(state, inst)
88
+ instance.transport.connection(state).close
89
+ inst.terminate
90
+ if state[:ssh_key]
91
+ FileUtils.rm_f(state[:ssh_key])
92
+ FileUtils.rm_f("#{state[:ssh_key]}.pub")
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,70 @@
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
+ module Mixin
24
+ # Instantiates the class of the specified model.
25
+ #
26
+ # @author Justin Steele <justin.steele@oracle.com>
27
+ module Models
28
+ require_relative "../instance"
29
+ require_relative "../blockstorage"
30
+
31
+ # Instantiates an instance model.
32
+ #
33
+ # @param config [Kitchen::LazyHash] The config provided by the driver.
34
+ # @param state [Hash] (see Kitchen::StateFile)
35
+ # @param oci [Kitchen::Driver::Oci::Config] a populated OCI config class.
36
+ # @param api [Kitchen::Driver::Oci::Api] an authenticated API class.
37
+ # @return [Class] the instantiated model class.
38
+ def instance_class(config, state, oci, api, action)
39
+ Oci::Models.const_get(config[:instance_type].capitalize).new(config: config, state: state, oci: oci, api: api, action: action, logger: instance.logger)
40
+ end
41
+
42
+ # Instantiates a blockstorage volume model.
43
+ #
44
+ # @param type [String] The type of volume that will be created.
45
+ # @param state [Hash] (see Kitchen::StateFile)
46
+ # @param oci [Kitchen::Driver::Oci::Config] a populated OCI config class.
47
+ # @param api [Kitchen::Driver::Oci::Api] an authenticated API class.
48
+ # @return [Class] the instantiated model class.
49
+ def volume_class(type, config, state, oci, api)
50
+ Oci::Models.const_get(volume_attachment_type(type)).new(config: config, state: state, oci: oci, api: api, logger: instance.logger)
51
+ end
52
+
53
+ private
54
+
55
+ # Returns the class name of the attachment type.
56
+ #
57
+ # @param type [String, nil]
58
+ # @return [String]
59
+ def volume_attachment_type(type)
60
+ if type.nil?
61
+ "Paravirtual"
62
+ else
63
+ type.capitalize
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,88 @@
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
+ module Mixin
24
+ # Mixins for working with volumes and attachments.
25
+ #
26
+ # @author Justin Steele <justin.steele@oracle.com>
27
+ module Volumes
28
+ # Create and attach volumes.
29
+ #
30
+ # @param config [Kitchen::LazyHash] The config provided by the driver.
31
+ # @param state [Hash] (see Kitchen::StateFile)
32
+ # @param oci [Kitchen::Driver::Oci::Config] a populated OCI config class.
33
+ # @param api [Kitchen::Driver::Oci::Api] an authenticated API class.
34
+ def create_and_attach_volumes(config, state, oci, api)
35
+ return if config[:volumes].empty?
36
+
37
+ volume_state = process_volumes(config, state, oci, api)
38
+ state.merge!(volume_state)
39
+ end
40
+
41
+ # Detatch and delete volumes.
42
+ #
43
+ # @param state [Hash] (see Kitchen::StateFile)
44
+ # @param oci [Kitchen::Driver::Oci::Config] a populated OCI config class.
45
+ # @param api [Kitchen::Driver::Oci::Api] an authenticated API class.
46
+ def detatch_and_delete_volumes(state, oci, api)
47
+ return unless state[:volumes]
48
+
49
+ bls = Blockstorage.new(config: config, state: state, oci: oci, api: api, action: :destroy, logger: instance.logger)
50
+ state[:volume_attachments].each { |att| bls.detatch_volume(att) }
51
+ state[:volumes].each { |vol| bls.delete_volume(vol) }
52
+ end
53
+
54
+ # Process volumes specified in the kitchen config.
55
+ #
56
+ # @param config [Kitchen::LazyHash] The config provided by the driver.
57
+ # @param state [Hash] (see Kitchen::StateFile)
58
+ # @param oci [Kitchen::Driver::Oci::Config] a populated OCI config class.
59
+ # @param api [Kitchen::Driver::Oci::Api] an authenticated API class.
60
+ # @return [Hash] the finalized state after the volume(s) have been created and attached.
61
+ def process_volumes(config, state, oci, api)
62
+ volume_state = { volumes: [], volume_attachments: [] }
63
+ config[:volumes].each do |volume|
64
+ vol = volume_class(volume[:type], config, state, oci, api)
65
+ volume_details, vol_state = create_volume(vol, volume)
66
+ attach_state = vol.attach_volume(volume_details, state[:server_id], volume)
67
+ volume_state[:volumes] << vol_state
68
+ volume_state[:volume_attachments] << attach_state
69
+ end
70
+ volume_state
71
+ end
72
+
73
+ # Creates or clones the volume.
74
+ #
75
+ # @param vol [OCI::Core::Models::Volume] the volume that has been created.
76
+ # @param volume [Hash] the state of the current volume being cloned.
77
+ def create_volume(vol, volume)
78
+ if volume.key?(:volume_id)
79
+ vol.create_clone_volume(volume)
80
+ else
81
+ vol.create_volume(volume)
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end