knife-cloudstack 0.0.11

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