ucloudstack 0.0.1
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.
- data/CHANGES.rdoc +68 -0
- data/LICENSE +202 -0
- data/README.rdoc +215 -0
- data/lib/chef/knife/cs_hosts.rb +83 -0
- data/lib/chef/knife/cs_network_list.rb +84 -0
- data/lib/chef/knife/cs_product_list.rb +70 -0
- data/lib/chef/knife/cs_server_create.rb +321 -0
- data/lib/chef/knife/cs_server_delete.rb +157 -0
- data/lib/chef/knife/cs_server_list.rb +92 -0
- data/lib/chef/knife/cs_server_reboot.rb +103 -0
- data/lib/chef/knife/cs_server_start.rb +103 -0
- data/lib/chef/knife/cs_server_stop.rb +114 -0
- data/lib/chef/knife/cs_service_list.rb +93 -0
- data/lib/chef/knife/cs_stack_create.rb +325 -0
- data/lib/chef/knife/cs_stack_delete.rb +88 -0
- data/lib/chef/knife/cs_template_list.rb +100 -0
- data/lib/chef/knife/cs_zone_list.rb +78 -0
- data/lib/knife-cloudstack/connection.rb +633 -0
- metadata +90 -0
@@ -0,0 +1,70 @@
|
|
1
|
+
#
|
2
|
+
# Author::
|
3
|
+
# Copyright:: Copyright (c) 2011 Edmunds, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'chef/knife'
|
20
|
+
|
21
|
+
module KnifeCloudstack
|
22
|
+
class CsProductList < Chef::Knife
|
23
|
+
|
24
|
+
deps do
|
25
|
+
require 'knife-cloudstack/connection'
|
26
|
+
end
|
27
|
+
|
28
|
+
banner "knife cs product list (options)"
|
29
|
+
|
30
|
+
def run
|
31
|
+
|
32
|
+
connection = CloudstackClient::Connection.new(
|
33
|
+
locate_config_value(:cloudstack_url),
|
34
|
+
locate_config_value(:cloudstack_api_key),
|
35
|
+
locate_config_value(:cloudstack_secret_key)
|
36
|
+
)
|
37
|
+
|
38
|
+
product_list = [
|
39
|
+
ui.color('id', :bold),
|
40
|
+
ui.color('serviceid', :bold),
|
41
|
+
ui.color('desc', :bold),
|
42
|
+
ui.color('diskid', :bold),
|
43
|
+
ui.color('desc', :bold),
|
44
|
+
ui.color('templateid', :bold),
|
45
|
+
ui.color('desc', :bold),
|
46
|
+
ui.color('zoneid', :bold),
|
47
|
+
ui.color('desc', :bold)
|
48
|
+
]
|
49
|
+
products = connection.list_products
|
50
|
+
products.each do |product|
|
51
|
+
product_list << product['productid']
|
52
|
+
product_list << product['serviceofferingid']
|
53
|
+
product_list << product['serviceofferingdesc']
|
54
|
+
product_list << product['diskofferingid']
|
55
|
+
product_list << product['diskofferingdesc']
|
56
|
+
product_list << product['templateid']
|
57
|
+
product_list << product['templatedesc']
|
58
|
+
product_list << product['zoneid']
|
59
|
+
product_list << product['zonedesc']
|
60
|
+
end
|
61
|
+
puts ui.list(product_list, :columns_across, 9)
|
62
|
+
end
|
63
|
+
|
64
|
+
def locate_config_value(key)
|
65
|
+
key = key.to_sym
|
66
|
+
Chef::Config[:knife][key] || config[key]
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,321 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Ryan Holmes (<rholmes@edmunds.com>)
|
3
|
+
# Copyright:: Copyright (c) 2011 Edmunds, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'chef/knife'
|
20
|
+
require 'json'
|
21
|
+
|
22
|
+
module KnifeCloudstack
|
23
|
+
class CsServerCreate < Chef::Knife
|
24
|
+
|
25
|
+
# Seconds to delay between detecting ssh and initiating the bootstrap
|
26
|
+
BOOTSTRAP_DELAY = 3
|
27
|
+
|
28
|
+
# Seconds to wait between ssh pings
|
29
|
+
SSH_POLL_INTERVAL = 2
|
30
|
+
|
31
|
+
deps do
|
32
|
+
require 'chef/knife/bootstrap'
|
33
|
+
Chef::Knife::Bootstrap.load_deps
|
34
|
+
require 'socket'
|
35
|
+
require 'net/ssh/multi'
|
36
|
+
require 'chef/json_compat'
|
37
|
+
require 'knife-cloudstack/connection'
|
38
|
+
end
|
39
|
+
|
40
|
+
banner "knife cs server create [SERVER_NAME] (options)"
|
41
|
+
|
42
|
+
option :cloudstack_service,
|
43
|
+
:short => "-S SERVICE",
|
44
|
+
:long => "--service SERVICE",
|
45
|
+
:description => "The CloudStack service offering name",
|
46
|
+
:proc => Proc.new { |o| Chef::Config[:knife][:cloudstack_service] = o },
|
47
|
+
:default => "M"
|
48
|
+
|
49
|
+
option :cloudstack_template,
|
50
|
+
:short => "-T TEMPLATE",
|
51
|
+
:long => "--template TEMPLATE",
|
52
|
+
:description => "The CloudStack template for the server",
|
53
|
+
:proc => Proc.new { |t| Chef::Config[:knife][:cloudstack_template] = t }
|
54
|
+
|
55
|
+
option :cloudstack_zone,
|
56
|
+
:short => "-Z ZONE",
|
57
|
+
:long => "--zone ZONE",
|
58
|
+
:description => "The CloudStack zone for the server",
|
59
|
+
:proc => Proc.new { |z| Chef::Config[:knife][:cloudstack_zone] = z }
|
60
|
+
|
61
|
+
option :cloudstack_networks,
|
62
|
+
:short => "-W NETWORKS",
|
63
|
+
:long => "--networks NETWORK",
|
64
|
+
:description => "Comma separated list of CloudStack network names",
|
65
|
+
:proc => lambda { |n| n.split(',').map {|sn| sn.strip}} ,
|
66
|
+
:default => []
|
67
|
+
|
68
|
+
option :public_ip,
|
69
|
+
:long => "--[no-]public-ip",
|
70
|
+
:description => "Allocate a public IP for this server",
|
71
|
+
:boolean => true,
|
72
|
+
:default => true
|
73
|
+
|
74
|
+
option :chef_node_name,
|
75
|
+
:short => "-N NAME",
|
76
|
+
:long => "--node-name NAME",
|
77
|
+
:description => "The Chef node name for your new node"
|
78
|
+
|
79
|
+
option :ssh_user,
|
80
|
+
:short => "-x USERNAME",
|
81
|
+
:long => "--ssh-user USERNAME",
|
82
|
+
:description => "The ssh username"
|
83
|
+
|
84
|
+
option :ssh_password,
|
85
|
+
:short => "-P PASSWORD",
|
86
|
+
:long => "--ssh-password PASSWORD",
|
87
|
+
:description => "The ssh password"
|
88
|
+
|
89
|
+
option :identity_file,
|
90
|
+
:short => "-i IDENTITY_FILE",
|
91
|
+
:long => "--identity-file IDENTITY_FILE",
|
92
|
+
:description => "The SSH identity file used for authentication"
|
93
|
+
|
94
|
+
option :cloudstack_url,
|
95
|
+
:short => "-U URL",
|
96
|
+
:long => "--cloudstack-url URL",
|
97
|
+
:description => "The CloudStack API endpoint URL",
|
98
|
+
:proc => Proc.new { |u| Chef::Config[:knife][:cloudstack_url] = u }
|
99
|
+
|
100
|
+
option :cloudstack_api_key,
|
101
|
+
:short => "-A KEY",
|
102
|
+
:long => "--cloudstack-api-key KEY",
|
103
|
+
:description => "Your CloudStack API key",
|
104
|
+
:proc => Proc.new { |k| Chef::Config[:knife][:cloudstack_api_key] = k }
|
105
|
+
|
106
|
+
option :cloudstack_secret_key,
|
107
|
+
:short => "-K SECRET",
|
108
|
+
:long => "--cloudstack-secret-key SECRET",
|
109
|
+
:description => "Your CloudStack secret key",
|
110
|
+
:proc => Proc.new { |s| Chef::Config[:knife][:cloudstack_secret_key] = s }
|
111
|
+
|
112
|
+
option :prerelease,
|
113
|
+
:long => "--prerelease",
|
114
|
+
:description => "Install the pre-release chef gems"
|
115
|
+
|
116
|
+
option :bootstrap_version,
|
117
|
+
:long => "--bootstrap-version VERSION",
|
118
|
+
:description => "The version of Chef to install",
|
119
|
+
:proc => Proc.new { |v| Chef::Config[:knife][:bootstrap_version] = v }
|
120
|
+
|
121
|
+
option :distro,
|
122
|
+
:short => "-d DISTRO",
|
123
|
+
:long => "--distro DISTRO",
|
124
|
+
:description => "Bootstrap a distro using a template",
|
125
|
+
:proc => Proc.new { |d| Chef::Config[:knife][:distro] = d },
|
126
|
+
:default => "ubuntu10.04-gems"
|
127
|
+
|
128
|
+
option :template_file,
|
129
|
+
:long => "--template-file TEMPLATE",
|
130
|
+
:description => "Full path to location of template to use",
|
131
|
+
:proc => Proc.new { |t| Chef::Config[:knife][:template_file] = t },
|
132
|
+
:default => false
|
133
|
+
|
134
|
+
option :run_list,
|
135
|
+
:short => "-r RUN_LIST",
|
136
|
+
:long => "--run-list RUN_LIST",
|
137
|
+
:description => "Comma separated list of roles/recipes to apply",
|
138
|
+
:proc => lambda { |o| o.split(/[\s,]+/) },
|
139
|
+
:default => []
|
140
|
+
|
141
|
+
option :no_host_key_verify,
|
142
|
+
:long => "--no-host-key-verify",
|
143
|
+
:description => "Disable host key verification",
|
144
|
+
:boolean => true,
|
145
|
+
:default => false
|
146
|
+
|
147
|
+
option :no_bootstrap,
|
148
|
+
:long => "--no-bootstrap",
|
149
|
+
:description => "Disable Chef bootstrap",
|
150
|
+
:boolean => true,
|
151
|
+
:default => false
|
152
|
+
|
153
|
+
option :port_rules,
|
154
|
+
:short => "-p PORT_RULES",
|
155
|
+
:long => "--port-rules PORT_RULES",
|
156
|
+
:description => "Comma separated list of port forwarding rules, e.g. '25,53:4053,80:8080:TCP'",
|
157
|
+
:proc => lambda { |o| o.split(/[\s,]+/) },
|
158
|
+
:default => []
|
159
|
+
|
160
|
+
|
161
|
+
def run
|
162
|
+
|
163
|
+
# validate hostname and options
|
164
|
+
hostname = @name_args.first
|
165
|
+
unless /^[a-zA-Z0-9][a-zA-Z0-9-]*$/.match hostname then
|
166
|
+
ui.error "Invalid hostname. Please specify a short hostname, not an fqdn (e.g. 'myhost' instead of 'myhost.domain.com')."
|
167
|
+
exit 1
|
168
|
+
end
|
169
|
+
validate_options
|
170
|
+
|
171
|
+
$stdout.sync = true
|
172
|
+
|
173
|
+
connection = CloudstackClient::Connection.new(
|
174
|
+
locate_config_value(:cloudstack_url),
|
175
|
+
locate_config_value(:cloudstack_api_key),
|
176
|
+
locate_config_value(:cloudstack_secret_key)
|
177
|
+
)
|
178
|
+
|
179
|
+
print "#{ui.color("Waiting for server", :magenta)}"
|
180
|
+
server = connection.create_server(
|
181
|
+
hostname,
|
182
|
+
locate_config_value(:cloudstack_service),
|
183
|
+
locate_config_value(:cloudstack_template),
|
184
|
+
locate_config_value(:cloudstack_zone),
|
185
|
+
locate_config_value(:cloudstack_networks)
|
186
|
+
)
|
187
|
+
|
188
|
+
public_ip = find_or_create_public_ip(server, connection)
|
189
|
+
|
190
|
+
puts "\n\n"
|
191
|
+
puts "#{ui.color("Name", :cyan)}: #{server['name']}"
|
192
|
+
puts "#{ui.color("Public IP", :cyan)}: #{public_ip}"
|
193
|
+
|
194
|
+
return if config[:no_bootstrap]
|
195
|
+
|
196
|
+
print "\n#{ui.color("Waiting for sshd", :magenta)}"
|
197
|
+
|
198
|
+
print(".") until is_ssh_open?(public_ip) {
|
199
|
+
sleep BOOTSTRAP_DELAY
|
200
|
+
puts "\n"
|
201
|
+
}
|
202
|
+
|
203
|
+
config[:ssh_password] = server['password']
|
204
|
+
|
205
|
+
bootstrap_for_node(public_ip).run
|
206
|
+
|
207
|
+
puts "\n"
|
208
|
+
puts "#{ui.color("Name", :cyan)}: #{server['name']}"
|
209
|
+
puts "#{ui.color("Public IP", :cyan)}: #{public_ip}"
|
210
|
+
puts "#{ui.color("Environment", :cyan)}: #{config[:environment] || '_default'}"
|
211
|
+
puts "#{ui.color("Run List", :cyan)}: #{config[:run_list].join(', ')}"
|
212
|
+
|
213
|
+
end
|
214
|
+
|
215
|
+
def validate_options
|
216
|
+
|
217
|
+
unless locate_config_value :cloudstack_template
|
218
|
+
ui.error "Cloudstack template not specified"
|
219
|
+
exit 1
|
220
|
+
end
|
221
|
+
|
222
|
+
unless locate_config_value :cloudstack_service
|
223
|
+
ui.error "Cloudstack service offering not specified"
|
224
|
+
exit 1
|
225
|
+
end
|
226
|
+
|
227
|
+
identity_file = locate_config_value :identity_file
|
228
|
+
ssh_user = locate_config_value :ssh_user
|
229
|
+
ssh_password = locate_config_value :ssh_password
|
230
|
+
unless identity_file || (ssh_user && ssh_password)
|
231
|
+
ui.error("You must specify either an ssh identity file or an ssh user and password")
|
232
|
+
#exit 1
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
|
237
|
+
def find_or_create_public_ip(server, connection)
|
238
|
+
nic = connection.get_server_default_nic(server) || {}
|
239
|
+
puts "#{ui.color("Not allocating public IP for server", :red)}" unless config[:public_ip]
|
240
|
+
|
241
|
+
if (config[:public_ip] == false) || (nic['type'] != 'Virtual') then
|
242
|
+
nic['ipaddress']
|
243
|
+
else
|
244
|
+
# create ip address, ssh forwarding rule and optional forwarding rules
|
245
|
+
ip_address = connection.associate_ip_address(server['zoneid'])
|
246
|
+
ssh_rule = connection.create_port_forwarding_rule(ip_address['id'], "22", "TCP", "22", server['id'])
|
247
|
+
create_port_forwarding_rules(ip_address['id'], server['id'], connection)
|
248
|
+
ssh_rule['ipaddress']
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def create_port_forwarding_rules(ip_address_id, server_id, connection)
|
253
|
+
rules = locate_config_value(:port_rules)
|
254
|
+
return unless rules
|
255
|
+
|
256
|
+
rules.each do |rule|
|
257
|
+
args = rule.split(':')
|
258
|
+
public_port = args[0]
|
259
|
+
private_port = args[1] || args[0]
|
260
|
+
protocol = args[2] || "TCP"
|
261
|
+
connection.create_port_forwarding_rule(ip_address_id, private_port, protocol, public_port, server_id)
|
262
|
+
end
|
263
|
+
|
264
|
+
end
|
265
|
+
|
266
|
+
#noinspection RubyArgCount,RubyResolve
|
267
|
+
def is_ssh_open?(ip)
|
268
|
+
s = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
|
269
|
+
sa = Socket.sockaddr_in(22, ip)
|
270
|
+
|
271
|
+
begin
|
272
|
+
s.connect_nonblock(sa)
|
273
|
+
rescue Errno::EINPROGRESS
|
274
|
+
resp = IO.select(nil, [s], nil, 1)
|
275
|
+
if resp.nil?
|
276
|
+
sleep SSH_POLL_INTERVAL
|
277
|
+
return false
|
278
|
+
end
|
279
|
+
|
280
|
+
begin
|
281
|
+
s.connect_nonblock(sa)
|
282
|
+
rescue Errno::EISCONN
|
283
|
+
Chef::Log.debug("sshd accepting connections on #{ip}")
|
284
|
+
yield
|
285
|
+
return true
|
286
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
|
287
|
+
sleep SSH_POLL_INTERVAL
|
288
|
+
return false
|
289
|
+
end
|
290
|
+
ensure
|
291
|
+
s && s.close
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
|
296
|
+
def bootstrap_for_node(host)
|
297
|
+
bootstrap = Chef::Knife::Bootstrap.new
|
298
|
+
bootstrap.name_args = [host]
|
299
|
+
bootstrap.config[:run_list] = config[:run_list]
|
300
|
+
bootstrap.config[:ssh_user] = config[:ssh_user]
|
301
|
+
bootstrap.config[:ssh_password] = config[:ssh_password]
|
302
|
+
bootstrap.config[:identity_file] = config[:identity_file]
|
303
|
+
bootstrap.config[:chef_node_name] = config[:chef_node_name] if config[:chef_node_name]
|
304
|
+
bootstrap.config[:prerelease] = config[:prerelease]
|
305
|
+
bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
|
306
|
+
bootstrap.config[:distro] = locate_config_value(:distro)
|
307
|
+
bootstrap.config[:use_sudo] = true
|
308
|
+
bootstrap.config[:template_file] = locate_config_value(:template_file)
|
309
|
+
bootstrap.config[:environment] = config[:environment]
|
310
|
+
# may be needed for vpc_mode
|
311
|
+
bootstrap.config[:no_host_key_verify] = config[:no_host_key_verify]
|
312
|
+
bootstrap
|
313
|
+
end
|
314
|
+
|
315
|
+
def locate_config_value(key)
|
316
|
+
key = key.to_sym
|
317
|
+
Chef::Config[:knife][key] || config[key]
|
318
|
+
end
|
319
|
+
|
320
|
+
end # class
|
321
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Ryan Holmes (<rholmes@edmunds.com>)
|
3
|
+
# Copyright:: Copyright (c) 2011 Edmunds, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'chef/knife'
|
20
|
+
|
21
|
+
module KnifeCloudstack
|
22
|
+
class CsServerDelete < Chef::Knife
|
23
|
+
|
24
|
+
deps do
|
25
|
+
require 'knife-cloudstack/connection'
|
26
|
+
require 'chef/api_client'
|
27
|
+
end
|
28
|
+
|
29
|
+
banner "knife cs server delete SERVER_NAME [SERVER_NAME ...] (options)"
|
30
|
+
|
31
|
+
option :cloudstack_url,
|
32
|
+
:short => "-U URL",
|
33
|
+
:long => "--cloudstack-url URL",
|
34
|
+
:description => "The CloudStack endpoint URL",
|
35
|
+
:proc => Proc.new { |url| Chef::Config[:knife][:cloudstack_url] = url }
|
36
|
+
|
37
|
+
option :cloudstack_api_key,
|
38
|
+
:short => "-A KEY",
|
39
|
+
:long => "--cloudstack-api-key KEY",
|
40
|
+
:description => "Your CloudStack API key",
|
41
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:cloudstack_api_key] = key }
|
42
|
+
|
43
|
+
option :cloudstack_secret_key,
|
44
|
+
:short => "-K SECRET",
|
45
|
+
:long => "--cloudstack-secret-key SECRET",
|
46
|
+
:description => "Your CloudStack secret key",
|
47
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:cloudstack_secret_key] = key }
|
48
|
+
|
49
|
+
def run
|
50
|
+
|
51
|
+
@name_args.each do |hostname|
|
52
|
+
server = connection.get_server(hostname)
|
53
|
+
|
54
|
+
if !server then
|
55
|
+
ui.error("Server '#{hostname}' not found")
|
56
|
+
next
|
57
|
+
end
|
58
|
+
|
59
|
+
if server['state'] == 'Destroyed' then
|
60
|
+
ui.warn("Server '#{hostname}' already destroyed")
|
61
|
+
next
|
62
|
+
end
|
63
|
+
|
64
|
+
puts "\n"
|
65
|
+
msg("Name", server['name'])
|
66
|
+
msg("Public IP", connection.get_server_public_ip(server) || '?')
|
67
|
+
msg("Service", server['serviceofferingname'])
|
68
|
+
msg("Template", server['templatename'])
|
69
|
+
msg("Domain", server['domain'])
|
70
|
+
msg("Zone", server['zonename'])
|
71
|
+
msg("State", server['state'])
|
72
|
+
|
73
|
+
puts "\n"
|
74
|
+
ui.confirm("Do you really want to delete this server")
|
75
|
+
|
76
|
+
print "#{ui.color("Waiting for deletion", :magenta)}"
|
77
|
+
disassociate_virtual_ip_address server
|
78
|
+
connection.delete_server hostname
|
79
|
+
puts "\n"
|
80
|
+
ui.msg("Deleted server #{hostname}")
|
81
|
+
|
82
|
+
# delete chef client and node
|
83
|
+
node_name = connection.get_server_fqdn server
|
84
|
+
ui.confirm("Do you want to delete the chef node and client '#{node_name}")
|
85
|
+
delete_node node_name
|
86
|
+
delete_client node_name
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
def disassociate_virtual_ip_address(server)
|
92
|
+
nic = server['nic'].first || {}
|
93
|
+
return unless nic['type'] == 'Virtual'
|
94
|
+
|
95
|
+
# get the ssh rule for this server
|
96
|
+
ssh_rule = connection.get_ssh_port_forwarding_rule(server)
|
97
|
+
return unless ssh_rule
|
98
|
+
|
99
|
+
# get all rules for the same ip address
|
100
|
+
rules = connection.list_port_forwarding_rules(ssh_rule['ipaddressid'])
|
101
|
+
return unless rules
|
102
|
+
|
103
|
+
# ensure ip address has rules only for this server
|
104
|
+
rules.each { |r|
|
105
|
+
return if r['virtualmachineid'] != server['id']
|
106
|
+
}
|
107
|
+
|
108
|
+
# dissassociate the ip address if all tests passed
|
109
|
+
connection.disassociate_ip_address(ssh_rule['ipaddressid'])
|
110
|
+
end
|
111
|
+
|
112
|
+
def delete_client(name)
|
113
|
+
begin
|
114
|
+
client = Chef::ApiClient.load(name)
|
115
|
+
rescue Net::HTTPServerException
|
116
|
+
return
|
117
|
+
end
|
118
|
+
|
119
|
+
client.destroy
|
120
|
+
ui.msg "Deleted client #{name}"
|
121
|
+
end
|
122
|
+
|
123
|
+
def delete_node(name)
|
124
|
+
begin
|
125
|
+
node = Chef::Node.load(name)
|
126
|
+
rescue Net::HTTPServerException
|
127
|
+
return
|
128
|
+
end
|
129
|
+
|
130
|
+
node.destroy
|
131
|
+
ui.msg "Deleted node #{name}"
|
132
|
+
end
|
133
|
+
|
134
|
+
def connection
|
135
|
+
unless @connection
|
136
|
+
@connection = CloudstackClient::Connection.new(
|
137
|
+
locate_config_value(:cloudstack_url),
|
138
|
+
locate_config_value(:cloudstack_api_key),
|
139
|
+
locate_config_value(:cloudstack_secret_key)
|
140
|
+
)
|
141
|
+
end
|
142
|
+
@connection
|
143
|
+
end
|
144
|
+
|
145
|
+
def msg(label, value)
|
146
|
+
if value && !value.empty?
|
147
|
+
puts "#{ui.color(label, :cyan)}: #{value}"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def locate_config_value(key)
|
152
|
+
key = key.to_sym
|
153
|
+
Chef::Config[:knife][key] || config[key]
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Ryan Holmes (<rholmes@edmunds.com>)
|
3
|
+
# Copyright:: Copyright (c) 2011 Edmunds, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'chef/knife'
|
20
|
+
|
21
|
+
module KnifeCloudstack
|
22
|
+
class CsServerList < Chef::Knife
|
23
|
+
|
24
|
+
deps do
|
25
|
+
require 'knife-cloudstack/connection'
|
26
|
+
end
|
27
|
+
|
28
|
+
banner "knife cs server list (options)"
|
29
|
+
|
30
|
+
option :cloudstack_url,
|
31
|
+
:short => "-U URL",
|
32
|
+
:long => "--cloudstack-url URL",
|
33
|
+
:description => "The CloudStack endpoint URL",
|
34
|
+
:proc => Proc.new { |url| Chef::Config[:knife][:cloudstack_url] = url }
|
35
|
+
|
36
|
+
option :cloudstack_api_key,
|
37
|
+
:short => "-A KEY",
|
38
|
+
:long => "--cloudstack-api-key KEY",
|
39
|
+
:description => "Your CloudStack API key",
|
40
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:cloudstack_api_key] = key }
|
41
|
+
|
42
|
+
option :cloudstack_secret_key,
|
43
|
+
:short => "-K SECRET",
|
44
|
+
:long => "--cloudstack-secret-key SECRET",
|
45
|
+
:description => "Your CloudStack secret key",
|
46
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:cloudstack_secret_key] = key }
|
47
|
+
|
48
|
+
def run
|
49
|
+
|
50
|
+
$stdout.sync = true
|
51
|
+
|
52
|
+
connection = CloudstackClient::Connection.new(
|
53
|
+
locate_config_value(:cloudstack_url),
|
54
|
+
locate_config_value(:cloudstack_api_key),
|
55
|
+
locate_config_value(:cloudstack_secret_key)
|
56
|
+
)
|
57
|
+
|
58
|
+
server_list = [
|
59
|
+
ui.color('Name', :bold),
|
60
|
+
ui.color('Public IP', :bold),
|
61
|
+
ui.color('Service', :bold),
|
62
|
+
ui.color('Template', :bold),
|
63
|
+
ui.color('State', :bold)
|
64
|
+
]
|
65
|
+
|
66
|
+
servers = connection.list_servers
|
67
|
+
rules = connection.list_port_forwarding_rules
|
68
|
+
|
69
|
+
servers.each do |server|
|
70
|
+
|
71
|
+
name = server['name']
|
72
|
+
display_name = server['displayname']
|
73
|
+
if display_name && !display_name.empty? && display_name != name
|
74
|
+
name << " (#{display_name})"
|
75
|
+
end
|
76
|
+
server_list << server['name']
|
77
|
+
server_list << (connection.get_server_public_ip(server, rules) || '')
|
78
|
+
server_list << server['serviceofferingname']
|
79
|
+
server_list << server['templatename']
|
80
|
+
server_list << server['state']
|
81
|
+
end
|
82
|
+
puts ui.list(server_list, :columns_across, 5)
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
def locate_config_value(key)
|
87
|
+
key = key.to_sym
|
88
|
+
Chef::Config[:knife][key] || config[key]
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|