knife-oci 2.0.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.
@@ -0,0 +1,328 @@
1
+ # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved.
2
+
3
+ require 'chef/knife'
4
+ require 'chef/knife/oci_helper'
5
+ require 'chef/knife/oci_common_options'
6
+
7
+ class Chef
8
+ class Knife
9
+ # Server Create Command: Launch an instance and bootstrap it.
10
+ class OciServerCreate < Knife
11
+ banner 'knife oci server create (options)'
12
+
13
+ include OciHelper
14
+ include OciCommonOptions
15
+
16
+ # Port for SSH - might want to parameterize this in the future.
17
+ SSH_PORT = 22
18
+
19
+ WAIT_FOR_SSH_INTERVAL_SECONDS = 2
20
+ DEFAULT_WAIT_FOR_SSH_MAX_SECONDS = 180
21
+ DEFAULT_WAIT_TO_STABILIZE_SECONDS = 40
22
+
23
+ deps do
24
+ require 'oci'
25
+ require 'chef/knife/bootstrap'
26
+ Chef::Knife::Bootstrap.load_deps
27
+ end
28
+
29
+ option :oci_config_file,
30
+ long: '--oci-config-file FILE',
31
+ description: 'The path to the OCI config file. Default: ~/.oci/config'
32
+
33
+ option :oci_profile,
34
+ long: '--oci-profile PROFILE',
35
+ description: 'The profile to load from the OCI config file. Default: DEFAULT'
36
+
37
+ option :availability_domain,
38
+ long: '--availability-domain AD',
39
+ description: 'The Availability Domain of the instance. (required)'
40
+
41
+ option :display_name,
42
+ long: '--display-name NAME',
43
+ description: "A user-friendly name for the instance. Does not have to be unique, and it's changeable."
44
+
45
+ option :hostname_label,
46
+ long: '--hostname-label HOSTNAME',
47
+ description: 'The hostname for the VNIC that is created during instance launch. Used for DNS. The value is the hostname '\
48
+ "portion of the instance's fully qualified domain name (FQDN). Must be unique across all VNICs in the subnet "\
49
+ 'and comply with RFC 952 and RFC 1123. The value cannot be changed, and it can be retrieved from the Vnic object.'
50
+
51
+ option :image_id,
52
+ long: '--image-id IMAGE',
53
+ description: 'The OCID of the image used to boot the instance. (required)'
54
+
55
+ option :metadata,
56
+ long: '--metadata METADATA',
57
+ description: 'Custom metadata key/value pairs in JSON format.'
58
+
59
+ option :shape,
60
+ long: '--shape SHAPE',
61
+ description: 'The shape of an instance. The shape determines the number of CPUs, amount of memory, and other resources allocated to the instance. (required)'
62
+
63
+ option :ssh_authorized_keys_file,
64
+ long: '--ssh-authorized-keys-file FILE',
65
+ description: 'A file containing one or more public SSH keys to be included in the ~/.ssh/authorized_keys file for the default user on the instance. '\
66
+ 'Use a newline character to separate multiple keys. The SSH keys must be in the format necessary for the authorized_keys file. This parameter '\
67
+ "is a convenience wrapper around the 'ssh_authorized_keys' field of the --metadata parameter. Populating both values in the same call will result "\
68
+ 'in an error. For more info see documentation: https://docs.us-phoenix-1.oraclecloud.com/api/#/en/iaas/20160918/requests/LaunchInstanceDetails. (required)'
69
+
70
+ option :subnet_id,
71
+ long: '--subnet-id SUBNET',
72
+ description: 'The OCID of the subnet. (required)'
73
+
74
+ option :user_data_file,
75
+ long: '--user-data-file FILE',
76
+ description: 'A file containing data that Cloud-Init can use to run custom scripts or provide custom Cloud-Init configuration. This parameter is a convenience '\
77
+ "wrapper around the 'user_data' field of the --metadata parameter. Populating both values in the same call will result in an error. For more info "\
78
+ 'see Cloud-Init documentation: https://cloudinit.readthedocs.org/en/latest/topics/format.html.'
79
+
80
+ option :ssh_user,
81
+ short: '-x USERNAME',
82
+ long: '--ssh-user USERNAME',
83
+ description: 'The SSH username. Defaults to opc.',
84
+ default: 'opc'
85
+
86
+ option :ssh_password,
87
+ short: '-P PASSWORD',
88
+ long: '--ssh-password PASSWORD',
89
+ description: 'The SSH password'
90
+
91
+ option :identity_file,
92
+ short: '-i FILE',
93
+ long: '--identity-file IDENTITY_FILE',
94
+ description: 'The SSH identity file used for authentication. This must correspond to a public SSH key provided by --ssh-authorized-keys-file. (required)'
95
+
96
+ option :chef_node_name,
97
+ short: '-N NAME',
98
+ long: '--node-name NAME',
99
+ description: 'The Chef node name for the new node. If not specified, the instance display name will be used.'
100
+
101
+ option :run_list,
102
+ short: '-r RUN_LIST',
103
+ long: '--run-list RUN_LIST',
104
+ description: 'A comma-separated list of roles or recipes.',
105
+ proc: ->(o) { o.split(/[\s,]+/) },
106
+ default: []
107
+
108
+ option :wait_to_stabilize,
109
+ long: '--wait-to-stabilize SECONDS',
110
+ description: "Duration to pause after SSH becomes reachable. Default: #{DEFAULT_WAIT_TO_STABILIZE_SECONDS}"
111
+
112
+ option :wait_for_ssh_max,
113
+ long: '--wait-for-ssh-max SECONDS',
114
+ description: "The maximum time to wait for SSH to become reachable. Default: #{DEFAULT_WAIT_FOR_SSH_MAX_SECONDS}"
115
+
116
+ def run
117
+ $stdout.sync = true
118
+ validate_required_params(%i[availability_domain image_id shape subnet_id identity_file ssh_authorized_keys_file], config)
119
+ validate_wait_options
120
+
121
+ metadata = merge_metadata
122
+ error_and_exit 'SSH authorized keys must be specified.' unless metadata['ssh_authorized_keys']
123
+
124
+ request = OCI::Core::Models::LaunchInstanceDetails.new
125
+ request.availability_domain = config[:availability_domain]
126
+ request.compartment_id = compartment_id
127
+ request.display_name = config[:display_name]
128
+ request.hostname_label = config[:hostname_label]
129
+ request.image_id = config[:image_id]
130
+ request.metadata = metadata
131
+ request.shape = config[:shape]
132
+ request.subnet_id = config[:subnet_id]
133
+
134
+ response = compute_client.launch_instance(request)
135
+ instance = response.data
136
+
137
+ ui.msg "Launched instance '#{instance.display_name}' [#{instance.id}]"
138
+ show_value('Display Name', instance.display_name)
139
+ show_value('Instance ID', instance.id)
140
+ show_value('Availability Domain', instance.availability_domain)
141
+ show_value('Compartment ID', instance.compartment_id)
142
+ show_value('Region', instance.region)
143
+ show_value('Image ID', instance.image_id)
144
+ show_value('Shape', instance.shape)
145
+
146
+ instance = wait_for_instance_running(instance.id)
147
+
148
+ ui.msg "Instance '#{instance.display_name}' is now running."
149
+
150
+ vnic = get_vnic(instance.id, instance.compartment_id)
151
+ show_value('Public IP Address', vnic.public_ip)
152
+ show_value('Private IP Address', vnic.private_ip)
153
+
154
+ unless wait_for_ssh(vnic.public_ip, SSH_PORT, WAIT_FOR_SSH_INTERVAL_SECONDS, config[:wait_for_ssh_max])
155
+ error_and_exit 'Timed out while waiting for SSH access.'
156
+ end
157
+
158
+ wait_to_stabilize
159
+
160
+ config[:chef_node_name] = instance.display_name unless config[:chef_node_name]
161
+
162
+ ui.msg "Bootstrapping with node name '#{config[:chef_node_name]}'."
163
+
164
+ # TODO: Consider adding a use_private_ip option.
165
+ bootstrap(vnic.public_ip)
166
+
167
+ ui.msg "Created and bootstrapped node '#{config[:chef_node_name]}'."
168
+ ui.msg "\n"
169
+
170
+ display_server_info(config, instance, [vnic])
171
+ end
172
+
173
+ def bootstrap(name)
174
+ bootstrap = Chef::Knife::Bootstrap.new
175
+
176
+ bootstrap.name_args = [name]
177
+ bootstrap.config[:chef_node_name] = config[:chef_node_name]
178
+ bootstrap.config[:ssh_user] = config[:ssh_user]
179
+ bootstrap.config[:ssh_password] = config[:ssh_password]
180
+ bootstrap.config[:identity_file] = config[:identity_file]
181
+ bootstrap.config[:use_sudo] = true
182
+ bootstrap.config[:ssh_gateway] = config[:ssh_user] + '@' + name
183
+ bootstrap.config[:run_list] = config[:run_list]
184
+
185
+ bootstrap.config[:yes] = true if config[:yes]
186
+
187
+ bootstrap.run
188
+ end
189
+
190
+ def validate_wait_option(p, default)
191
+ arg_name = "--#{p.to_s.tr('_', '-')}"
192
+ config[p] = config[p].to_s.empty? ? default : Integer(config[p])
193
+ error_and_exit "#{arg_name} must be 0 or greater" if config[p] < 0
194
+ rescue
195
+ error_and_exit "#{arg_name} must be numeric"
196
+ end
197
+
198
+ def validate_wait_options
199
+ validate_wait_option(:wait_to_stabilize, DEFAULT_WAIT_TO_STABILIZE_SECONDS)
200
+ validate_wait_option(:wait_for_ssh_max, DEFAULT_WAIT_FOR_SSH_MAX_SECONDS)
201
+ end
202
+
203
+ def wait_to_stabilize
204
+ # This extra sleep even after getting SSH access is necessary. It's not clear why, but without it we often get
205
+ # errors about missing a password for ssh, or sometimes errors during bootstrapping. (Note that plugins for other
206
+ # cloud providers have similar sleeps.)
207
+ Kernel.sleep(config[:wait_to_stabilize])
208
+ end
209
+
210
+ def wait_for_ssh(hostname, ssh_port, interval_seconds, max_time_seconds)
211
+ print ui.color('Waiting for ssh access...', :magenta)
212
+
213
+ end_time = Time.now + max_time_seconds
214
+
215
+ begin
216
+ while Time.now < end_time
217
+ return true if can_ssh(hostname, ssh_port)
218
+
219
+ show_progress
220
+ sleep interval_seconds
221
+ end
222
+ ensure
223
+ end_progress_indicator
224
+ end
225
+
226
+ false
227
+ end
228
+
229
+ def can_ssh(hostname, ssh_port)
230
+ socket = TCPSocket.new(hostname, ssh_port)
231
+ # Wait up to 5 seconds.
232
+ readable = IO.select([socket], nil, nil, 5)
233
+ if readable
234
+ content = socket.gets
235
+ # Make sure some content was actually returned.
236
+ return true unless content.nil? || content.empty?
237
+ else
238
+ false
239
+ end
240
+ rescue SocketError, IOError, Errno::ETIMEDOUT, Errno::EPERM, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, Errno::ECONNRESET, Errno::ENOTCONN
241
+ false
242
+ ensure
243
+ socket && socket.close
244
+ end
245
+
246
+ def wait_for_instance_running(instance_id)
247
+ print ui.color('Waiting for instance to reach running state...', :magenta)
248
+
249
+ begin
250
+ response = compute_client.get_instance(instance_id).wait_until(:lifecycle_state,
251
+ OCI::Core::Models::Instance::LIFECYCLE_STATE_RUNNING,
252
+ max_interval_seconds: 3) do |poll_response|
253
+ if poll_response.data.lifecycle_state == OCI::Core::Models::Instance::LIFECYCLE_STATE_TERMINATED ||
254
+ poll_response.data.lifecycle_state == OCI::Core::Models::Instance::LIFECYCLE_STATE_TERMINATING
255
+ throw :stop_succeed
256
+ end
257
+
258
+ show_progress
259
+ end
260
+ ensure
261
+ end_progress_indicator
262
+ end
263
+
264
+ if response.data.lifecycle_state != OCI::Core::Models::Instance::LIFECYCLE_STATE_RUNNING
265
+ error_and_exit 'Instance failed to provision.'
266
+ end
267
+
268
+ response.data
269
+ end
270
+
271
+ # Return the first VNIC found (which should be the only VNIC).
272
+ def get_vnic(instance_id, compartment)
273
+ compute_client.list_vnic_attachments(compartment, instance_id: instance_id).each do |response|
274
+ response.data.each do |vnic_attachment|
275
+ return network_client.get_vnic(vnic_attachment.vnic_id).data
276
+ end
277
+ end
278
+ end
279
+
280
+ def merge_metadata
281
+ metadata = config[:metadata]
282
+
283
+ if metadata
284
+ begin
285
+ metadata = JSON.parse(metadata)
286
+ rescue JSON::ParserError
287
+ error_and_exit('Metadata value must be in JSON format. Example: \'{"key1":"value1", "key2":"value2"}\'')
288
+ end
289
+ else
290
+ metadata = {}
291
+ end
292
+
293
+ ssh_authorized_keys = get_file_content(:ssh_authorized_keys_file)
294
+ user_data = get_file_content(:user_data_file)
295
+ user_data = Base64.strict_encode64(user_data) if user_data
296
+
297
+ if ssh_authorized_keys
298
+ error_and_exit('Cannot specify ssh-authorized-keys as part of both --ssh-authorized-keys-file and --metadata.') if metadata.key? 'ssh_authorized_keys'
299
+ metadata['ssh_authorized_keys'] = ssh_authorized_keys
300
+ end
301
+
302
+ if user_data
303
+ error_and_exit('Cannot specify CloudInit user-data as part of both --user-data-file and --metadata.') if metadata.key? 'user_data'
304
+ metadata['user_data'] = user_data
305
+ end
306
+
307
+ metadata
308
+ end
309
+
310
+ def show_progress
311
+ print ui.color('.', :magenta)
312
+ $stdout.flush
313
+ end
314
+
315
+ def end_progress_indicator
316
+ print ui.color("done\n", :magenta)
317
+ end
318
+
319
+ def get_file_content(file_name_param)
320
+ file_name = config[file_name_param]
321
+ return if file_name.nil?
322
+
323
+ file_name = File.expand_path(file_name)
324
+ File.open(file_name, 'r').read
325
+ end
326
+ end
327
+ end
328
+ end
@@ -0,0 +1,142 @@
1
+ # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved.
2
+
3
+ require 'chef/knife'
4
+ require 'chef/knife/oci_helper'
5
+ require 'chef/knife/oci_common_options'
6
+
7
+ class Chef
8
+ class Knife
9
+ # Server Delete Command: Delete an OCI instance.
10
+ class OciServerDelete < Knife
11
+ banner 'knife oci server delete (options)'
12
+
13
+ include OciHelper
14
+ include OciCommonOptions
15
+
16
+ # max interval for polling the server state
17
+ MAX_INTERVAL_SECONDS = 3
18
+
19
+ deps do
20
+ require 'oci'
21
+ require 'chef/knife/bootstrap'
22
+ end
23
+
24
+ option :instance_id,
25
+ long: '--instance-id INSTANCE',
26
+ description: 'The OCID of the instance to be deleted. (required)'
27
+
28
+ option :wait,
29
+ long: '--wait SECONDS',
30
+ description: 'Wait for the instance to be terminated. 0=infinite'
31
+
32
+ option :purge,
33
+ long: '--purge',
34
+ description: 'Remove the corresponding node from the Chef Server. The instance display name will be used as the node name, unless --node-name is specified.'
35
+
36
+ option :chef_node_name,
37
+ short: '-N NAME',
38
+ long: '--node-name NAME',
39
+ description: 'The name of the Chef node to be removed when using the --purge option. If not specified, the instance display name will be used.'
40
+
41
+ def run
42
+ $stdout.sync = true
43
+ validate_required_params(%i[instance_id], config)
44
+ wait_for = validate_wait
45
+ if config[:chef_node_name] && !config[:purge]
46
+ error_and_exit('--node-name requires --purge argument')
47
+ end
48
+
49
+ response = check_can_access_instance(config[:instance_id])
50
+
51
+ ui.msg "Instance name: #{response.data.display_name}"
52
+ deletion_prompt = 'Delete server? (y/n)'
53
+ chef_node = nil
54
+ if config[:purge]
55
+ deletion_prompt = 'Delete server and chef node? (y/n)'
56
+ node_name = response.data.display_name
57
+ node_name = config[:chef_node_name] if config[:chef_node_name]
58
+ chef_node = get_chef_node(node_name)
59
+ ui.msg "Chef node name: #{chef_node.name}"
60
+ end
61
+ confirm_deletion(deletion_prompt)
62
+
63
+ terminate_instance(config[:instance_id])
64
+ delete_chef_node(chef_node) if config[:purge]
65
+
66
+ wait_for_instance_terminated(config[:instance_id], wait_for) if wait_for
67
+ end
68
+
69
+ def terminate_instance(instance_id)
70
+ compute_client.terminate_instance(instance_id)
71
+
72
+ ui.msg "Initiated delete of instance #{instance_id}"
73
+ end
74
+
75
+ def get_chef_node(node_name)
76
+ node = Chef::Node.load(node_name)
77
+ node
78
+ end
79
+
80
+ def delete_chef_node(node)
81
+ node.destroy
82
+ ui.msg "Deleted Chef node '#{node.name}'"
83
+ end
84
+
85
+ def wait_for_instance_terminated(instance_id, wait_for)
86
+ print ui.color('Waiting for instance to terminate...', :magenta)
87
+ begin
88
+ begin
89
+ compute_client.get_instance(instance_id).wait_until(:lifecycle_state,
90
+ OCI::Core::Models::Instance::LIFECYCLE_STATE_TERMINATED,
91
+ get_wait_options(wait_for)) do
92
+ show_progress
93
+ end
94
+ ensure
95
+ end_progress_indicator
96
+ end
97
+ rescue OCI::Waiter::Errors::MaximumWaitTimeExceededError
98
+ error_and_exit 'Timeout exceeded while waiting for instance to terminate'
99
+ rescue OCI::Errors::ServiceError => service_error
100
+ raise unless service_error.service_code == 'NotAuthorizedOrNotFound'
101
+ # we'll soak this exception since the terminate may have completed before we started waiting for it.
102
+ ui.warn 'Instance not authorized or not found'
103
+ end
104
+ end
105
+
106
+ def validate_wait
107
+ wait_for = nil
108
+ if config[:wait]
109
+ wait_for = Integer(config[:wait])
110
+ error_and_exit 'Wait value must be 0 or greater' if wait_for < 0
111
+ end
112
+ wait_for
113
+ end
114
+
115
+ def get_wait_options(wait_for)
116
+ opts = {
117
+ max_interval_seconds: MAX_INTERVAL_SECONDS
118
+ }
119
+ opts[:max_wait_seconds] = wait_for if wait_for > 0
120
+ opts
121
+ end
122
+
123
+ def confirm_deletion(prompt)
124
+ if confirm(prompt)
125
+ # we have user's confirmation, so avoid any further confirmation prompts from Chef
126
+ config[:yes] = true
127
+ return
128
+ end
129
+ error_and_exit 'Server delete canceled.'
130
+ end
131
+
132
+ def show_progress
133
+ print ui.color('.', :magenta)
134
+ $stdout.flush
135
+ end
136
+
137
+ def end_progress_indicator
138
+ print ui.color("done\n", :magenta)
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,49 @@
1
+ # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved.
2
+
3
+ require 'chef/knife'
4
+ require 'chef/knife/oci_common_options'
5
+ require 'chef/knife/oci_helper'
6
+
7
+ class Chef
8
+ class Knife
9
+ # List OCI instances. Note that this lists all instances in a
10
+ # compartment, not just those that are set up as Chef nodes.
11
+ class OciServerList < Knife
12
+ banner 'knife oci server list (options)'
13
+
14
+ include OciHelper
15
+ include OciCommonOptions
16
+
17
+ deps do
18
+ require 'oci'
19
+ end
20
+
21
+ option :limit,
22
+ long: '--limit LIMIT',
23
+ description: 'The maximum number of items to return.'
24
+
25
+ def run
26
+ options = {}
27
+ options[:limit] = config[:limit] if config[:limit]
28
+
29
+ columns = ['Display Name', 'State', 'ID']
30
+
31
+ list_for_display = config[:format] == 'summary' ? bold(columns) : []
32
+ list_data, last_response = get_display_results(options) do |client_options|
33
+ response = compute_client.list_instances(compartment_id, client_options)
34
+
35
+ items = response_to_list(response) do |item|
36
+ [item.display_name,
37
+ item.lifecycle_state,
38
+ item.id]
39
+ end
40
+ [response, items]
41
+ end
42
+ list_for_display += list_data
43
+
44
+ display_list_from_array(list_for_display, columns.length)
45
+ warn_if_page_is_truncated(last_response)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,119 @@
1
+ # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved.
2
+
3
+ require 'chef/knife'
4
+ require 'chef/knife/oci_common_options'
5
+ require 'chef/knife/oci_helper'
6
+
7
+ # Methods to extend the instance model
8
+ module ServerDetails
9
+ attr_accessor :compartment_name
10
+ attr_accessor :image_name
11
+ attr_accessor :launchtime
12
+ attr_accessor :vcn_id
13
+ attr_accessor :vcn_name
14
+ end
15
+
16
+ # Methods to extend the vnic model
17
+ module VnicDetails
18
+ attr_accessor :fqdn
19
+ attr_accessor :subnet_name
20
+ attr_accessor :vcn_id
21
+ end
22
+
23
+ class Chef
24
+ class Knife
25
+ # List OCI instances. Note that this lists all instances in a
26
+ # compartment, not just those that are set up as Chef nodes.
27
+ class OciServerShow < Knife
28
+ banner 'knife oci server show (options)'
29
+
30
+ include OciHelper
31
+ include OciCommonOptions
32
+
33
+ deps do
34
+ require 'oci'
35
+ end
36
+
37
+ option :instance_id,
38
+ long: '--instance_id LIMIT',
39
+ description: 'The OCID of the server to display. (required)'
40
+
41
+ def lookup_compartment_name(compartment_id)
42
+ compartment = identity_client.get_compartment(compartment_id, {})
43
+ rescue OCI::Errors::ServiceError => service_error
44
+ raise unless service_error.service_code == 'NotAuthorizedOrNotFound'
45
+ else
46
+ compartment.data.name
47
+ end
48
+
49
+ def lookup_image_name(image_id)
50
+ image = compute_client.get_image(image_id, {})
51
+ rescue OCI::Errors::ServiceError => service_error
52
+ raise unless service_error.service_code == 'NotAuthorizedOrNotFound'
53
+ else
54
+ image.data.display_name
55
+ end
56
+
57
+ def lookup_vcn_name(vcn_id)
58
+ vcn = network_client.get_vcn(vcn_id, {})
59
+ rescue OCI::Errors::ServiceError => service_error
60
+ raise unless service_error.service_code == 'NotAuthorizedOrNotFound'
61
+ else
62
+ vcn.data.display_name
63
+ end
64
+
65
+ def add_server_details(server, vcn_id)
66
+ server.extend ServerDetails
67
+
68
+ server.launchtime = server.time_created.strftime('%a, %e %b %Y %T %Z')
69
+ server.compartment_name = lookup_compartment_name(server.compartment_id)
70
+ server.image_name = lookup_image_name(server.image_id)
71
+ server.vcn_id = vcn_id
72
+ server.vcn_name = lookup_vcn_name(vcn_id)
73
+ end
74
+
75
+ def add_vnic_details(vnic)
76
+ vnic.extend VnicDetails
77
+
78
+ begin
79
+ subnet = network_client.get_subnet(vnic.subnet_id, {})
80
+ rescue OCI::Errors::ServiceError => service_error
81
+ raise unless service_error.service_code == 'NotAuthorizedOrNotFound'
82
+ else
83
+ vnic.fqdn = vnic.hostname_label + '.' + subnet.data.subnet_domain_name if
84
+ subnet.data && subnet.data.subnet_domain_name && vnic.hostname_label
85
+ vnic.subnet_name = subnet.data.display_name if
86
+ subnet.data && subnet.data.display_name
87
+ # piggyback the vcn_id from here, so we can avoid a few network calls
88
+ vnic.vcn_id = subnet.data.vcn_id
89
+ end
90
+ end
91
+
92
+ def run
93
+ validate_required_params(%i[instance_id], config)
94
+ vnic_array = []
95
+ server = check_can_access_instance(config[:instance_id])
96
+ error_and_exit 'Unable to retrieve instance' unless server.data
97
+ vnics = compute_client.list_vnic_attachments(compartment_id, instance_id: config[:instance_id])
98
+ vnics.data && vnics.data.each do |vnic|
99
+ next unless vnic.lifecycle_state == 'ATTACHED'
100
+ begin
101
+ vnic_info = network_client.get_vnic(vnic.vnic_id, {})
102
+ rescue OCI::Errors::ServiceError => service_error
103
+ raise unless service_error.service_code == 'NotAuthorizedOrNotFound'
104
+ else
105
+ add_vnic_details(vnic_info.data)
106
+ # for now, only display information for primary vnic
107
+ if vnic_info.data.is_primary == true
108
+ vnic_array.push(vnic_info.data)
109
+ break
110
+ end
111
+ end
112
+ end
113
+ add_server_details(server.data, vnic_array[0] ? vnic_array[0].vcn_id : nil)
114
+
115
+ display_server_info(config, server.data, vnic_array)
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,55 @@
1
+ # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved.
2
+
3
+ require 'chef/knife'
4
+ require 'chef/knife/oci_common_options'
5
+ require 'chef/knife/oci_helper'
6
+
7
+ class Chef
8
+ class Knife
9
+ # List available shapes
10
+ class OciShapeList < Knife
11
+ banner 'knife oci shape list (options)'
12
+
13
+ include OciHelper
14
+ include OciCommonOptions
15
+
16
+ deps do
17
+ require 'oci'
18
+ end
19
+
20
+ option :availability_domain,
21
+ long: '--availability-domain AD',
22
+ description: 'The Availability Domain of the instance.'
23
+
24
+ option :image_id,
25
+ long: '--image-id IMAGE',
26
+ description: 'The OCID of the image used to boot the instance.'
27
+
28
+ option :limit,
29
+ long: '--limit LIMIT',
30
+ description: 'The maximum number of items to return.'
31
+
32
+ def run
33
+ options = {}
34
+ options[:availability_domain] = config[:availability_domain] if config[:availability_domain]
35
+ options[:image_id] = config[:image_id] if config[:image_id]
36
+ options[:limit] = config[:limit] if config[:limit]
37
+
38
+ columns = []
39
+
40
+ list_for_display, last_response = get_display_results(options) do |client_options|
41
+ response = compute_client.list_shapes(compartment_id, client_options)
42
+
43
+ items = response_to_list(response) do |item|
44
+ [item.shape]
45
+ end
46
+ [response, items]
47
+ end
48
+
49
+ list_for_display.uniq!
50
+ display_list_from_array(list_for_display, columns.length)
51
+ warn_if_page_is_truncated(last_response)
52
+ end
53
+ end
54
+ end
55
+ end