knife-oci 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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