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.
- checksums.yaml +7 -0
- data/LICENSE.txt +82 -0
- data/lib/chef/knife/oci_ad_list.rb +38 -0
- data/lib/chef/knife/oci_common_options.rb +34 -0
- data/lib/chef/knife/oci_compartment_list.rb +46 -0
- data/lib/chef/knife/oci_helper.rb +208 -0
- data/lib/chef/knife/oci_image_list.rb +46 -0
- data/lib/chef/knife/oci_server_create.rb +328 -0
- data/lib/chef/knife/oci_server_delete.rb +142 -0
- data/lib/chef/knife/oci_server_list.rb +49 -0
- data/lib/chef/knife/oci_server_show.rb +119 -0
- data/lib/chef/knife/oci_shape_list.rb +55 -0
- data/lib/chef/knife/oci_subnet_list.rb +51 -0
- data/lib/chef/knife/oci_vcn_list.rb +46 -0
- data/lib/knife-oci/version.rb +7 -0
- metadata +80 -0
@@ -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
|