knife-sce 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +32 -0
- data/Gemfile +7 -0
- data/LICENSE +201 -0
- data/README.rdoc +130 -0
- data/knife-sce.gemspec +21 -0
- data/lib/chef/knife/sce_address_create.rb +77 -0
- data/lib/chef/knife/sce_address_delete.rb +51 -0
- data/lib/chef/knife/sce_address_list.rb +71 -0
- data/lib/chef/knife/sce_address_offerings.rb +68 -0
- data/lib/chef/knife/sce_base.rb +128 -0
- data/lib/chef/knife/sce_image_describe.rb +82 -0
- data/lib/chef/knife/sce_instance_data.rb +55 -0
- data/lib/chef/knife/sce_key_create.rb +58 -0
- data/lib/chef/knife/sce_key_delete.rb +66 -0
- data/lib/chef/knife/sce_key_get.rb +61 -0
- data/lib/chef/knife/sce_key_list.rb +61 -0
- data/lib/chef/knife/sce_location_list.rb +105 -0
- data/lib/chef/knife/sce_server_create.rb +415 -0
- data/lib/chef/knife/sce_server_delete.rb +119 -0
- data/lib/chef/knife/sce_server_list.rb +103 -0
- data/lib/chef/knife/sce_storage_offerings.rb +90 -0
- data/lib/chef/knife/sce_vlan_list.rb +58 -0
- data/lib/chef/knife/sce_volume_attach.rb +49 -0
- data/lib/chef/knife/sce_volume_create.rb +126 -0
- data/lib/chef/knife/sce_volume_delete.rb +51 -0
- data/lib/chef/knife/sce_volume_detach.rb +50 -0
- data/lib/chef/knife/sce_volume_list.rb +85 -0
- data/lib/knife-sce/version.rb +6 -0
- metadata +171 -0
@@ -0,0 +1,415 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Rad Gruchalski (<radek@gruchalski.com>)
|
3
|
+
# License:: Apache License, Version 2.0
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
|
18
|
+
require 'chef/knife/sce_base'
|
19
|
+
|
20
|
+
class Chef
|
21
|
+
class Knife
|
22
|
+
class SceServerCreate < Knife
|
23
|
+
|
24
|
+
include Knife::SceBase
|
25
|
+
|
26
|
+
deps do
|
27
|
+
require 'fog'
|
28
|
+
require 'readline'
|
29
|
+
require 'chef/json_compat'
|
30
|
+
require 'chef/knife/bootstrap'
|
31
|
+
Chef::Knife::Bootstrap.load_deps
|
32
|
+
end
|
33
|
+
|
34
|
+
banner "knife sce server create (options)"
|
35
|
+
|
36
|
+
attr_accessor :initial_sleep_delay
|
37
|
+
attr_reader :server
|
38
|
+
|
39
|
+
option :sce_image,
|
40
|
+
:short => "-I IMAGE",
|
41
|
+
:long => "--image IMAGE",
|
42
|
+
:description => "The SCE image ID for the server",
|
43
|
+
:proc => Proc.new { |i| Chef::Config[:knife][:sce_image] = i }
|
44
|
+
|
45
|
+
option :sce_flavor,
|
46
|
+
:short => "-f FLAVOR",
|
47
|
+
:long => "--flavor FLAVOR",
|
48
|
+
:description => "The flavor of server (Copper, Bronze, Gold, Platinum)",
|
49
|
+
:proc => Proc.new { |f| Chef::Config[:knife][:sce_flavor] = f }
|
50
|
+
|
51
|
+
option :datacenter,
|
52
|
+
:short => "-Z LOCATION_ID",
|
53
|
+
:long => "--data-center LOCATION_ID",
|
54
|
+
:description => "Data center location ID, use knife sce location list to learn more about possible locations.",
|
55
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:sce_location_id] = key }
|
56
|
+
|
57
|
+
option :identity_file,
|
58
|
+
:short => "-i IDENTITY_FILE",
|
59
|
+
:long => "--identity-file IDENTITY_FILE",
|
60
|
+
:description => "The SSH identity file used for authentication"
|
61
|
+
|
62
|
+
option :associate_ip,
|
63
|
+
:long => "--associate-ip IP_ADDRESS",
|
64
|
+
:description => "Associate existing IP address with instance after launch"
|
65
|
+
|
66
|
+
option :vlan_id,
|
67
|
+
:long => "--vlan-id VLAN_ID",
|
68
|
+
:description => "The VLAN to use for the instance",
|
69
|
+
:proc => Proc.new { |vlan_id| Chef::Config[:knife][:sce_vlan_id] = vlan_id }
|
70
|
+
|
71
|
+
option :ssh_key_name,
|
72
|
+
:long => "--ssh-key-name SCE_KEY_NAME",
|
73
|
+
:description => "The SCE_KEY to use for the instance",
|
74
|
+
:proc => Proc.new { |ssh_key_name| Chef::Config[:knife][:sce_key_name] = ssh_key_name}
|
75
|
+
|
76
|
+
option :is_mini_ephemeral,
|
77
|
+
:long => "--is-mini-ephemeral",
|
78
|
+
:boolean => true,
|
79
|
+
:default => false,
|
80
|
+
:description => "No additional storage"
|
81
|
+
|
82
|
+
option :anti_collocation_instance,
|
83
|
+
:long => "--anti-collocation-instance INSTANCE_ID",
|
84
|
+
:description => "No additional storage",
|
85
|
+
:default => nil
|
86
|
+
|
87
|
+
option :volume_id,
|
88
|
+
:long => "--volume-id VOLUME_ID",
|
89
|
+
:description => "Existing persistent volume to attach to the instances at launch",
|
90
|
+
:default => nil
|
91
|
+
|
92
|
+
option :secondary_ip,
|
93
|
+
:long => "--secondary-ip IP_ID[,IP_ID,IP_ID]",
|
94
|
+
:description => "Add a secondary IP address to this instance (i.e. multi-homed)",
|
95
|
+
:default => nil
|
96
|
+
|
97
|
+
option :chef_node_name,
|
98
|
+
:short => "-N NAME",
|
99
|
+
:long => "--node-name NAME",
|
100
|
+
:description => "The Chef node name for your new node",
|
101
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:chef_node_name] = key }
|
102
|
+
|
103
|
+
option :sce_ssh_user,
|
104
|
+
:short => "-x USERNAME",
|
105
|
+
:long => "--ssh-user USERNAME",
|
106
|
+
:description => "The ssh username",
|
107
|
+
:default => "idcuser"
|
108
|
+
|
109
|
+
option :ssh_password,
|
110
|
+
:short => "-P PASSWORD",
|
111
|
+
:long => "--ssh-password PASSWORD",
|
112
|
+
:description => "The ssh password"
|
113
|
+
|
114
|
+
option :ssh_port,
|
115
|
+
:short => "-p PORT",
|
116
|
+
:long => "--ssh-port PORT",
|
117
|
+
:description => "The ssh port",
|
118
|
+
:default => "22",
|
119
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:ssh_port] = key }
|
120
|
+
|
121
|
+
option :ssh_gateway,
|
122
|
+
:short => "-w GATEWAY",
|
123
|
+
:long => "--ssh-gateway GATEWAY",
|
124
|
+
:description => "The ssh gateway server",
|
125
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:ssh_gateway] = key }
|
126
|
+
|
127
|
+
option :prerelease,
|
128
|
+
:long => "--prerelease",
|
129
|
+
:description => "Install the pre-release chef gems"
|
130
|
+
|
131
|
+
option :bootstrap_version,
|
132
|
+
:long => "--bootstrap-version VERSION",
|
133
|
+
:description => "The version of Chef to install",
|
134
|
+
:proc => Proc.new { |v| Chef::Config[:knife][:bootstrap_version] = v }
|
135
|
+
|
136
|
+
option :distro,
|
137
|
+
:short => "-d DISTRO",
|
138
|
+
:long => "--distro DISTRO",
|
139
|
+
:description => "Bootstrap a distro using a template; default is 'chef-full'",
|
140
|
+
:proc => Proc.new { |d| Chef::Config[:knife][:distro] = d }
|
141
|
+
|
142
|
+
option :template_file,
|
143
|
+
:long => "--template-file TEMPLATE",
|
144
|
+
:description => "Full path to location of template to use",
|
145
|
+
:proc => Proc.new { |t| Chef::Config[:knife][:template_file] = t },
|
146
|
+
:default => false
|
147
|
+
|
148
|
+
option :run_list,
|
149
|
+
:short => "-r RUN_LIST",
|
150
|
+
:long => "--run-list RUN_LIST",
|
151
|
+
:description => "Comma separated list of roles/recipes to apply",
|
152
|
+
:proc => lambda { |o| o.split(/[\s,]+/) }
|
153
|
+
|
154
|
+
option :json_attributes,
|
155
|
+
:short => "-j JSON",
|
156
|
+
:long => "--json-attributes JSON",
|
157
|
+
:description => "A JSON string to be added to the first run of chef-client",
|
158
|
+
:proc => lambda { |o| JSON.parse(o) }
|
159
|
+
|
160
|
+
option :host_key_verify,
|
161
|
+
:long => "--[no-]host-key-verify",
|
162
|
+
:description => "Verify host key, enabled by default.",
|
163
|
+
:boolean => true,
|
164
|
+
:default => true
|
165
|
+
|
166
|
+
option :hint,
|
167
|
+
:long => "--hint HINT_NAME[=HINT_FILE]",
|
168
|
+
:description => "Specify Ohai Hint to be set on the bootstrap target. Use multiple --hint options to specify multiple hints.",
|
169
|
+
:proc => Proc.new { |h|
|
170
|
+
Chef::Config[:knife][:hints] ||= {}
|
171
|
+
name, path = h.split("=")
|
172
|
+
Chef::Config[:knife][:hints][name] = path ? JSON.parse(::File.read(path)) : Hash.new
|
173
|
+
}
|
174
|
+
|
175
|
+
option :server_connect_attribute,
|
176
|
+
:long => "--server-connect-attribute ATTRIBUTE",
|
177
|
+
:short => "-a ATTRIBUTE",
|
178
|
+
:description => "The EC2 server attribute to use for SSH connection",
|
179
|
+
:default => nil
|
180
|
+
|
181
|
+
option :no_bootstrap,
|
182
|
+
:long => "--no-bootstrap",
|
183
|
+
:description => "Don't bootstrap the instance, just launch it",
|
184
|
+
:default => false
|
185
|
+
|
186
|
+
def tcp_test_ssh(hostname, ssh_port)
|
187
|
+
tcp_socket = TCPSocket.new(hostname, ssh_port)
|
188
|
+
readable = IO.select([tcp_socket], nil, nil, 5)
|
189
|
+
if readable
|
190
|
+
Chef::Log.debug("sshd accepting connections on #{hostname}, banner is #{tcp_socket.gets}")
|
191
|
+
yield
|
192
|
+
true
|
193
|
+
else
|
194
|
+
false
|
195
|
+
end
|
196
|
+
rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, IOError
|
197
|
+
sleep 2
|
198
|
+
false
|
199
|
+
rescue Errno::EPERM, Errno::ETIMEDOUT
|
200
|
+
false
|
201
|
+
ensure
|
202
|
+
tcp_socket && tcp_socket.close
|
203
|
+
end
|
204
|
+
|
205
|
+
def wait_for_server_active(server)
|
206
|
+
server.wait_for { print "."; ready? }
|
207
|
+
end
|
208
|
+
|
209
|
+
def run
|
210
|
+
|
211
|
+
$stdout.sync = true
|
212
|
+
|
213
|
+
Fog.timeout = Chef::Config[:knife][:sce_max_timeout] || 6000
|
214
|
+
|
215
|
+
validate!
|
216
|
+
|
217
|
+
requested_ip = config[:associate_ip] if config[:associate_ip]
|
218
|
+
|
219
|
+
# For VPC EIP assignment we need the allocation ID so fetch full EIP details
|
220
|
+
# elastic_ip = connection.addresses.detect{|addr| addr if addr.public_ip == requested_elastic_ip}
|
221
|
+
|
222
|
+
begin
|
223
|
+
definition = create_server_def
|
224
|
+
# SCE library gives me an excon response object, we have to fetch the server object ourselves:
|
225
|
+
excon_response = connection.create_instance(definition[:name], definition[:image_id], definition[:instance_type], definition[:location], definition)
|
226
|
+
@server = connection.servers.get(excon_response.data[:body]["instances"][0]["id"])
|
227
|
+
|
228
|
+
raise "Creating a server failed." if @server.nil?
|
229
|
+
|
230
|
+
msg_pair("Instance ID", @server.id.to_s)
|
231
|
+
msg_pair("Name", @server.name.to_s)
|
232
|
+
msg_pair("Flavor", @server.instance_type.to_s)
|
233
|
+
msg_pair("Image", @server.image.name.to_s)
|
234
|
+
msg_pair("Region", @server.location.location)
|
235
|
+
msg_pair("SSH Key", @server.key_name.to_s)
|
236
|
+
msg_pair("Owner", @server.owner.to_s)
|
237
|
+
msg_pair("Environment", config[:environment] || '_default')
|
238
|
+
msg_pair("Run List", (config[:run_list] || []).join(', '))
|
239
|
+
msg_pair("JSON Attributes",config[:json_attributes]) unless !config[:json_attributes] || config[:json_attributes].empty?
|
240
|
+
msg_pair("VLAN ID", @server.primary_ip["vlan"]["name"].to_s) if @server.primary_ip["vlan"]
|
241
|
+
msg_pair("Volume IDs", @server.volume_ids.join(",").to_s) if @server.volume_ids
|
242
|
+
|
243
|
+
print "\n#{ui.color("Waiting for server", :magenta)}"
|
244
|
+
@server.wait_for { print "."; ready? }
|
245
|
+
|
246
|
+
msg_pair("\nPublic DNS Name", @server.primary_ip["hostname"].to_s)
|
247
|
+
msg_pair("Public IP Address", @server.primary_ip["ip"].to_s)
|
248
|
+
if @server.secondary_ip
|
249
|
+
ips = []
|
250
|
+
@server.secondary_ip.each {|item| ips << item['ip']}
|
251
|
+
msg_pair("Secondary IP Addresses", ips.join(","))
|
252
|
+
end
|
253
|
+
|
254
|
+
wait_for_sshd(ssh_connect_host)
|
255
|
+
|
256
|
+
Chef::Config[:knife][:hints] ||= {}
|
257
|
+
Chef::Config[:knife][:hints]["sce"] ||= {}
|
258
|
+
Chef::Config[:knife][:hints]["sce"].merge!({
|
259
|
+
'server_id' => @server.id.to_s,
|
260
|
+
'region' => @server.location.location,
|
261
|
+
'flavor' => @server.instance_type.to_s,
|
262
|
+
'image' => @server.image.name.to_s
|
263
|
+
})
|
264
|
+
if @server.primary_ip["vlan"]
|
265
|
+
Chef::Config[:knife][:hints]["sce"].merge!({
|
266
|
+
'vlan' => @server.primary_ip["vlan"]["name"].to_s
|
267
|
+
})
|
268
|
+
end
|
269
|
+
if @server.volume_ids
|
270
|
+
Chef::Config[:knife][:hints]["sce"].merge!({
|
271
|
+
'volumes' => @server.volume_ids
|
272
|
+
})
|
273
|
+
end
|
274
|
+
|
275
|
+
bootstrap_for_node(@server, ssh_connect_host).run unless locate_config_value(:no_bootstrap)
|
276
|
+
|
277
|
+
rescue Excon::Errors::PreconditionFailed => e
|
278
|
+
ui.error e.response.data[:body]
|
279
|
+
exit 1
|
280
|
+
end
|
281
|
+
|
282
|
+
end
|
283
|
+
|
284
|
+
def bootstrap_for_node(server,ssh_host)
|
285
|
+
|
286
|
+
# Chef::Knife:Ssh is going to use ssh_user setting from knife.rb
|
287
|
+
# over the one that we hand to it.
|
288
|
+
# To overrule this setting we have to override to Chef knife.rb setting.
|
289
|
+
Chef::Config[:knife][:ssh_user] = config[:sce_ssh_user]
|
290
|
+
|
291
|
+
bootstrap = Chef::Knife::Bootstrap.new
|
292
|
+
bootstrap.name_args = [ssh_host]
|
293
|
+
bootstrap.config[:run_list] = locate_config_value(:run_list) || []
|
294
|
+
bootstrap.config[:ssh_user] = config[:sce_ssh_user]
|
295
|
+
bootstrap.config[:ssh_port] = config[:ssh_port]
|
296
|
+
bootstrap.config[:ssh_gateway] = config[:ssh_gateway]
|
297
|
+
bootstrap.config[:identity_file] = config[:identity_file]
|
298
|
+
bootstrap.config[:chef_node_name] = locate_config_value(:chef_node_name) || server.id
|
299
|
+
bootstrap.config[:prerelease] = config[:prerelease]
|
300
|
+
bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
|
301
|
+
bootstrap.config[:first_boot_attributes] = locate_config_value(:json_attributes) || {}
|
302
|
+
bootstrap.config[:distro] = locate_config_value(:distro) || "chef-full"
|
303
|
+
bootstrap.config[:use_sudo] = true unless config[:sce_ssh_user] == 'root'
|
304
|
+
bootstrap.config[:template_file] = locate_config_value(:template_file)
|
305
|
+
bootstrap.config[:environment] = config[:environment]
|
306
|
+
bootstrap.config[:host_key_verify] = config[:host_key_verify]
|
307
|
+
bootstrap
|
308
|
+
end
|
309
|
+
|
310
|
+
def vlan_mode?
|
311
|
+
# Amazon Virtual Private Cloud requires a subnet_id. If
|
312
|
+
# present, do a few things differently
|
313
|
+
!!locate_config_value(:vlan_id)
|
314
|
+
end
|
315
|
+
|
316
|
+
def validate!
|
317
|
+
|
318
|
+
super([:ibm_username, :ibm_password])
|
319
|
+
|
320
|
+
if locate_config_value(:sce_flavor).nil?
|
321
|
+
ui.error("No flavor provided. Use knife sce image describe to list supported flavors for used image.")
|
322
|
+
exit 1
|
323
|
+
else
|
324
|
+
|
325
|
+
flavor_found = false
|
326
|
+
requested_image = connection.images.get(locate_config_value(:sce_image))
|
327
|
+
requested_image.supported_instance_types.each do |sit|
|
328
|
+
if sit.id.to_s.eql?( locate_config_value(:sce_flavor) )
|
329
|
+
flavor_found = true
|
330
|
+
end
|
331
|
+
end
|
332
|
+
if !flavor_found
|
333
|
+
ui.error("Flavor #{config[:sce_flavor]} is not supported for image #{locate_config_value(:sce_image)}. Use knife sce image describe to list supported flavors for used image.")
|
334
|
+
exit 1
|
335
|
+
end
|
336
|
+
|
337
|
+
end
|
338
|
+
|
339
|
+
end
|
340
|
+
|
341
|
+
def eip_scope
|
342
|
+
if vlan_mode?
|
343
|
+
"vpc"
|
344
|
+
else
|
345
|
+
"standard"
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
def create_server_def
|
350
|
+
server_def = {
|
351
|
+
:name => locate_config_value(:chef_node_name),
|
352
|
+
:image_id => locate_config_value(:sce_image),
|
353
|
+
:instance_type => locate_config_value(:sce_flavor),
|
354
|
+
:location => datacenter_id,
|
355
|
+
:key_name => locate_config_value(:sce_key_name)
|
356
|
+
}
|
357
|
+
if locate_config_value(:sce_vlan_id)
|
358
|
+
server_def[:vlan_id] = locate_config_value(:sce_vlan_id)
|
359
|
+
end
|
360
|
+
if locate_config_value(:secondary_ip)
|
361
|
+
server_def[:secondary_ip] = locate_config_value(:secondary_ip)
|
362
|
+
end
|
363
|
+
|
364
|
+
%w{ip is_mini_ephemeral configuration_data anti_collocation_instance volume_id}.each do |parm|
|
365
|
+
server_def[parm.to_sym] = locate_config_value(parm.to_sym) if locate_config_value(parm.to_sym)
|
366
|
+
end
|
367
|
+
server_def
|
368
|
+
end
|
369
|
+
|
370
|
+
def wait_for_sshd(hostname)
|
371
|
+
config[:ssh_gateway] ? wait_for_tunnelled_sshd(hostname) : wait_for_direct_sshd(hostname, config[:ssh_port])
|
372
|
+
end
|
373
|
+
|
374
|
+
def wait_for_tunnelled_sshd(hostname)
|
375
|
+
print(".")
|
376
|
+
print(".") until tunnel_test_ssh(ssh_connect_host) {
|
377
|
+
sleep @initial_sleep_delay ||= (vlan_mode? ? 40 : 10)
|
378
|
+
puts("done")
|
379
|
+
}
|
380
|
+
end
|
381
|
+
|
382
|
+
def tunnel_test_ssh(hostname, &block)
|
383
|
+
gw_host, gw_user = config[:ssh_gateway].split('@').reverse
|
384
|
+
gw_host, gw_port = gw_host.split(':')
|
385
|
+
gateway = Net::SSH::Gateway.new(gw_host, gw_user, :port => gw_port || 22)
|
386
|
+
status = false
|
387
|
+
gateway.open(hostname, config[:ssh_port]) do |local_tunnel_port|
|
388
|
+
status = tcp_test_ssh('localhost', local_tunnel_port, &block)
|
389
|
+
end
|
390
|
+
status
|
391
|
+
rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, IOError
|
392
|
+
sleep 2
|
393
|
+
false
|
394
|
+
rescue Errno::EPERM, Errno::ETIMEDOUT
|
395
|
+
false
|
396
|
+
end
|
397
|
+
|
398
|
+
def wait_for_direct_sshd(hostname, ssh_port)
|
399
|
+
print(".") until tcp_test_ssh(ssh_connect_host, ssh_port) {
|
400
|
+
sleep @initial_sleep_delay ||= (vlan_mode? ? 40 : 10)
|
401
|
+
puts("done")
|
402
|
+
}
|
403
|
+
end
|
404
|
+
|
405
|
+
def ssh_connect_host
|
406
|
+
@ssh_connect_host ||= if config[:server_connect_attribute]
|
407
|
+
server.send(config[:server_connect_attribute])
|
408
|
+
else
|
409
|
+
server.ip.to_s
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
end
|
414
|
+
end
|
415
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Rad Gruchalski (<radek@gruchalski.com>)
|
3
|
+
# License:: Apache License, Version 2.0
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
|
18
|
+
require 'chef/knife/sce_base'
|
19
|
+
|
20
|
+
# These two are needed for the '--purge' deletion case
|
21
|
+
require 'chef/node'
|
22
|
+
require 'chef/api_client'
|
23
|
+
|
24
|
+
class Chef
|
25
|
+
class Knife
|
26
|
+
class SceServerDelete < Knife
|
27
|
+
|
28
|
+
include Knife::SceBase
|
29
|
+
|
30
|
+
banner "knife sce server delete SERVER [SERVER] (options)"
|
31
|
+
|
32
|
+
attr_reader :server
|
33
|
+
|
34
|
+
option :purge,
|
35
|
+
:short => "-P",
|
36
|
+
:long => "--purge",
|
37
|
+
:boolean => true,
|
38
|
+
:default => false,
|
39
|
+
:description => "Destroy corresponding node and client on the Chef Server, in addition to destroying the SCE node itself. Assumes node and client have the same name as the server (if not, add the '--node-name' option)."
|
40
|
+
|
41
|
+
option :chef_node_name,
|
42
|
+
:short => "-N NAME",
|
43
|
+
:long => "--node-name NAME",
|
44
|
+
:description => "The name of the node and client to delete, if it differs from the server name. Only has meaning when used with the '--purge' option."
|
45
|
+
|
46
|
+
# Extracted from Chef::Knife.delete_object, because it has a
|
47
|
+
# confirmation step built in... By specifying the '--purge'
|
48
|
+
# flag (and also explicitly confirming the server destruction!)
|
49
|
+
# the user is already making their intent known. It is not
|
50
|
+
# necessary to make them confirm two more times.
|
51
|
+
def destroy_item(klass, name, type_name)
|
52
|
+
begin
|
53
|
+
object = klass.load(name)
|
54
|
+
object.destroy
|
55
|
+
ui.warn("Deleted #{type_name} #{name}")
|
56
|
+
rescue Net::HTTPServerException
|
57
|
+
ui.warn("Could not find a #{type_name} named #{name} to delete!")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def run
|
62
|
+
|
63
|
+
validate!
|
64
|
+
|
65
|
+
@name_args.each do |instance_id|
|
66
|
+
|
67
|
+
begin
|
68
|
+
|
69
|
+
@server = connection.servers.get(instance_id)
|
70
|
+
|
71
|
+
if @server.nil?
|
72
|
+
connection.servers.all.each do |s|
|
73
|
+
if s.name.to_s == instance_id
|
74
|
+
@server = s
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
msg_pair("Instance ID", @server.id.to_s)
|
80
|
+
msg_pair("Name", @server.name.to_s)
|
81
|
+
msg_pair("Flavor", @server.instance_type.to_s)
|
82
|
+
msg_pair("Image", @server.image_id.to_s)
|
83
|
+
msg_pair("Region", connection.locations.get(@server.location_id.to_i).name.to_s)
|
84
|
+
msg_pair("SSH Key", @server.key_name.to_s)
|
85
|
+
msg_pair("Public DNS Name", @server.primary_ip["hostname"].to_s)
|
86
|
+
msg_pair("Public IP Address", @server.primary_ip["ip"].to_s)
|
87
|
+
msg_pair("Expires at", @server.expires_at.to_s)
|
88
|
+
|
89
|
+
puts "\n"
|
90
|
+
confirm("Do you really want to delete this server")
|
91
|
+
|
92
|
+
begin
|
93
|
+
@server.destroy
|
94
|
+
rescue Excon::Errors::PreconditionFailed => e
|
95
|
+
if e.data[:body].index("Active or Failed").nil?
|
96
|
+
ui.error e.data[:body].to_s
|
97
|
+
exit 1
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
ui.warn("Deleted server #{@server.id}")
|
102
|
+
|
103
|
+
if config[:purge]
|
104
|
+
thing_to_delete = config[:chef_node_name] || instance_id
|
105
|
+
destroy_item(Chef::Node, thing_to_delete, "node")
|
106
|
+
destroy_item(Chef::ApiClient, thing_to_delete, "client")
|
107
|
+
else
|
108
|
+
ui.warn("Corresponding node and client for the #{instance_id} server were not deleted and remain registered with the Chef Server")
|
109
|
+
end
|
110
|
+
|
111
|
+
rescue NoMethodError
|
112
|
+
ui.error("Could not locate server '#{instance_id}'. Please verify it was provisioned.")
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Rad Gruchalski (<radek@gruchalski.com>)
|
3
|
+
# License:: Apache License, Version 2.0
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
|
18
|
+
require 'chef/knife/sce_base'
|
19
|
+
|
20
|
+
class Chef
|
21
|
+
class Knife
|
22
|
+
class SceServerList < Knife
|
23
|
+
|
24
|
+
include Knife::SceBase
|
25
|
+
|
26
|
+
banner "knife sce server list (options)"
|
27
|
+
|
28
|
+
option :name,
|
29
|
+
:short => "-n",
|
30
|
+
:long => "--no-name",
|
31
|
+
:boolean => true,
|
32
|
+
:default => true,
|
33
|
+
:description => "Do not display name tag in output"
|
34
|
+
|
35
|
+
option :owner,
|
36
|
+
:short => "-o",
|
37
|
+
:long => "--no-owner",
|
38
|
+
:boolean => true,
|
39
|
+
:default => true,
|
40
|
+
:description => "Do not display owner in output"
|
41
|
+
|
42
|
+
def run!
|
43
|
+
connection.servers.all
|
44
|
+
end
|
45
|
+
|
46
|
+
def run
|
47
|
+
$stdout.sync = true
|
48
|
+
|
49
|
+
validate!
|
50
|
+
|
51
|
+
server_list = [
|
52
|
+
ui.color('Instance ID', :bold),
|
53
|
+
if config[:name]
|
54
|
+
ui.color("Name", :bold)
|
55
|
+
end,
|
56
|
+
if config[:owner]
|
57
|
+
ui.color("Owner", :bold)
|
58
|
+
end,
|
59
|
+
ui.color('Public IP', :bold),
|
60
|
+
ui.color('Secondary IPs', :bold),
|
61
|
+
ui.color('Flavor', :bold),
|
62
|
+
ui.color('Image', :bold),
|
63
|
+
ui.color('SSH Key', :bold),
|
64
|
+
ui.color('Expires', :bold),
|
65
|
+
ui.color('Request', :bold),
|
66
|
+
ui.color('State', :bold)
|
67
|
+
|
68
|
+
].flatten.compact
|
69
|
+
|
70
|
+
output_column_count = server_list.length
|
71
|
+
|
72
|
+
servers = run!
|
73
|
+
|
74
|
+
servers.each do |server|
|
75
|
+
server_list << server.id.to_s
|
76
|
+
if config[:name]
|
77
|
+
server_list << server.name.to_s
|
78
|
+
end
|
79
|
+
if config[:owner]
|
80
|
+
server_list << server.owner.to_s
|
81
|
+
end
|
82
|
+
server_list << server.primary_ip['hostname'].to_s
|
83
|
+
if server.secondary_ip.empty?
|
84
|
+
server_list << "n/a"
|
85
|
+
else
|
86
|
+
ips = []
|
87
|
+
server.secondary_ip.each {|sip| ips << sip['ip'] }
|
88
|
+
server_list << ips.join(",")
|
89
|
+
end
|
90
|
+
server_list << server.instance_type.to_s
|
91
|
+
server_list << server.image_id.to_s
|
92
|
+
server_list << server.key_name.to_s
|
93
|
+
server_list << server.expires_at.to_s
|
94
|
+
server_list << server.request_id.to_s
|
95
|
+
server_list << server.state.to_s
|
96
|
+
end
|
97
|
+
|
98
|
+
puts ui.list(server_list, :uneven_columns_across, output_column_count)
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|