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.
@@ -0,0 +1,325 @@
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 CsStackCreate < Chef::Knife
23
+
24
+ attr_accessor :current_stack
25
+
26
+ deps do
27
+ require 'chef/json_compat'
28
+ require 'chef/mash'
29
+ require 'chef/search/query'
30
+ require 'chef/knife/node_run_list_remove'
31
+ require 'net/ssh'
32
+ require 'net/ssh/multi'
33
+ require 'knife-cloudstack/connection'
34
+ Chef::Knife::Ssh.load_deps
35
+ Chef::Knife::NodeRunListRemove.load_deps
36
+ KnifeCloudstack::CsServerCreate.load_deps
37
+ end
38
+
39
+ banner "knife cs stack create JSON_FILE (options)"
40
+
41
+ option :cloudstack_url,
42
+ :short => "-U URL",
43
+ :long => "--cloudstack-url URL",
44
+ :description => "The CloudStack endpoint URL",
45
+ :proc => Proc.new { |url| Chef::Config[:knife][:cloudstack_url] = url }
46
+
47
+ option :cloudstack_api_key,
48
+ :short => "-A KEY",
49
+ :long => "--cloudstack-api-key KEY",
50
+ :description => "Your CloudStack API key",
51
+ :proc => Proc.new { |key| Chef::Config[:knife][:cloudstack_api_key] = key }
52
+
53
+ option :cloudstack_secret_key,
54
+ :short => "-K SECRET",
55
+ :long => "--cloudstack-secret-key SECRET",
56
+ :description => "Your CloudStack secret key",
57
+ :proc => Proc.new { |key| Chef::Config[:knife][:cloudstack_secret_key] = key }
58
+
59
+ option :ssh_user,
60
+ :short => "-x USERNAME",
61
+ :long => "--ssh-user USERNAME",
62
+ :description => "The ssh username"
63
+
64
+ option :ssh_password,
65
+ :short => "-P PASSWORD",
66
+ :long => "--ssh-password PASSWORD",
67
+ :description => "The ssh password"
68
+
69
+ option :identity_file,
70
+ :short => "-i IDENTITY_FILE",
71
+ :long => "--identity-file IDENTITY_FILE",
72
+ :description => "The SSH identity file used for authentication"
73
+
74
+ def run
75
+ file_path = File.expand_path(@name_args.first)
76
+ unless File.exist?(file_path) then
77
+ ui.error "Stack file '#{file_path}' not found. Please check the path."
78
+ exit 1
79
+ end
80
+
81
+ data = File.read file_path
82
+ stack = Chef::JSONCompat.from_json data
83
+ create_stack stack
84
+
85
+ #puts "Stack: #{stack.inspect}"
86
+ end
87
+
88
+ def connection
89
+ if (!@connection) then
90
+ url = locate_config_value(:cloudstack_url)
91
+ api_key = locate_config_value(:cloudstack_api_key)
92
+ secret_key = locate_config_value(:cloudstack_secret_key)
93
+ @connection = CloudstackClient::Connection.new(url, api_key, secret_key)
94
+ end
95
+ @connection
96
+ end
97
+
98
+ def create_stack(stack)
99
+ @current_stack = Mash.new(stack)
100
+ current_stack[:servers].each do |server|
101
+ if server[:name]
102
+
103
+ # create server(s)
104
+ names = server[:name].split(/[\s,]+/)
105
+ names.each do |n|
106
+ s = Mash.new(server)
107
+ s[:name] = n
108
+ create_server(s)
109
+ end
110
+
111
+ end
112
+
113
+ # execute actions
114
+ run_actions server[:actions]
115
+ end
116
+
117
+ print_local_hosts
118
+ end
119
+
120
+ def create_server(server)
121
+
122
+ cmd = KnifeCloudstack::CsServerCreate.new([server[:name]])
123
+
124
+ # configure and run command
125
+ # TODO: validate parameters
126
+ cmd.config[:ssh_user] = config[:ssh_user]
127
+ cmd.config[:ssh_password] = config[:ssh_password]
128
+ cmd.config[:ssh_port] = Chef::Config[:knife][:ssh_port] || config[:ssh_port]
129
+ cmd.config[:identity_file] = config[:identity_file]
130
+ cmd.config[:cloudstack_template] = server[:template] if server[:template]
131
+ cmd.config[:cloudstack_service] = server[:service] if server[:service]
132
+ cmd.config[:cloudstack_zone] = server[:service] if server[:zone]
133
+ cmd.config[:cloudstack_networks] = server[:networks].split(/[\s,]+/) if server[:networks]
134
+ cmd.config[:run_list] = server[:run_list].split(/[\s,]+/) if server[:run_list]
135
+ cmd.config[:port_rules] = server[:port_rules].split(/[\s,]+/) if server[:port_rules]
136
+ if current_stack[:environment]
137
+ cmd.config[:environment] = current_stack[:environment]
138
+ Chef::Config[:environment] = current_stack[:environment]
139
+ end
140
+
141
+ cmd.run_with_pretty_exceptions
142
+
143
+ end
144
+
145
+ def run_actions(actions)
146
+ puts "\n"
147
+ ui.msg("Processing actions...")
148
+ sleep 1 # pause for e.g. chef solr indexing
149
+ actions ||= []
150
+ actions.each do |cmd|
151
+ cmd ||= {}
152
+ cmd.each do |name, args|
153
+ case name
154
+ when 'knife_ssh'
155
+ knife_ssh_action(*args)
156
+ when 'http_request'
157
+ http_request(args)
158
+ when 'run_list_remove'
159
+ run_list_remove(*args)
160
+ when 'sleep'
161
+ dur = args || 5
162
+ sleep dur
163
+ end
164
+ end
165
+ end
166
+
167
+ end
168
+
169
+ def search_nodes(query, attribute=nil)
170
+ if get_environment
171
+ query = "(#{query})" + " AND chef_environment:#{get_environment}"
172
+ end
173
+
174
+ Chef::Log.debug("Searching for nodes: #{query}")
175
+
176
+ q = Chef::Search::Query.new
177
+ nodes = Array(q.search(:node, query))
178
+
179
+ # the array of nodes is the first item in the array returned by the search
180
+ if nodes.length > 1
181
+ nodes = nodes.first || []
182
+ end
183
+
184
+ # return attribute values instead of nodes
185
+ if attribute
186
+ nodes.map do |node|
187
+ node[attribute.to_s]
188
+ end
189
+ else
190
+ nodes
191
+ end
192
+ end
193
+
194
+ def knife_ssh(host_list, command)
195
+
196
+ ssh = Chef::Knife::Ssh.new
197
+ ssh.name_args = [host_list, command]
198
+ ssh.config[:ssh_user] = config[:ssh_user]
199
+ ssh.config[:ssh_password] = config[:ssh_password]
200
+ ssh.config[:ssh_port] = Chef::Config[:knife][:ssh_port] || config[:ssh_port]
201
+ ssh.config[:identity_file] = config[:identity_file]
202
+ ssh.config[:manual] = true
203
+ ssh.config[:no_host_key_verify] = config[:no_host_key_verify]
204
+ ssh
205
+ end
206
+
207
+ def knife_ssh_with_password_auth(host_list, command)
208
+ ssh = knife_ssh(host_list, command)
209
+ ssh.config[:identity_file] = nil
210
+ ssh.config[:ssh_password] = ssh.get_password
211
+ ssh
212
+ end
213
+
214
+ def knife_ssh_action(query, command)
215
+
216
+ public_ips = find_public_ips(query)
217
+ return if public_ips.nil? || public_ips.empty?
218
+ host_list = public_ips.join(' ')
219
+
220
+ ssh = knife_ssh(host_list, command)
221
+ begin
222
+ ssh.run
223
+ rescue Net::SSH::AuthenticationFailed
224
+ unless config[:ssh_password]
225
+ puts "Failed to authenticate #{config[:ssh_user]} - trying password auth"
226
+ ssh = knife_ssh_with_password_auth(host_list, command)
227
+ ssh.run
228
+ end
229
+ end
230
+
231
+ end
232
+
233
+ def http_request(url)
234
+ match_data = /\$\{([a-zA-Z0-9-]+)\}/.match url
235
+ if match_data
236
+ server_name = match_data[1]
237
+ ip = public_ip_for_host(server_name)
238
+ url = url.sub(/\$\{#{server_name}\}/, ip)
239
+ end
240
+
241
+
242
+ puts "HTTP Request: #{url}"
243
+ puts `curl -s -m 5 #{url}`
244
+ end
245
+
246
+ def run_list_remove(query, entry)
247
+ nodes = search_nodes(query)
248
+ return unless nodes
249
+
250
+ nodes.each do |n|
251
+ cmd = Chef::Knife::NodeRunListRemove.new([n.name, entry])
252
+ cmd.run_with_pretty_exceptions
253
+ end
254
+ end
255
+
256
+ def find_public_ips(query)
257
+ hostnames = search_nodes(query, 'hostname')
258
+ puts "Found hostnames: #{hostnames.inspect}"
259
+ ips = hostnames.map { |h|
260
+ public_ip_for_host h
261
+ }
262
+ ips.compact.uniq
263
+ end
264
+
265
+ def public_ip_for_host(name)
266
+ return nil unless name
267
+ @public_ip_cache ||= {}
268
+
269
+ if !@public_ip_cache[name] then
270
+ server = connection.get_server(name)
271
+ return nil unless server
272
+
273
+ ip = connection.get_server_public_ip(server)
274
+ @public_ip_cache[name] = ip if ip
275
+ end
276
+
277
+ @public_ip_cache[name]
278
+ end
279
+
280
+ def get_environment
281
+ current_stack[:environment]
282
+ end
283
+
284
+ def destroy_all(domain, excludes=[])
285
+ servers = connection.list_servers || []
286
+ servers.each do |s|
287
+ excluded = false
288
+ excludes.each { |val|
289
+ if s['name'] =~ /#{val}/ then
290
+ excluded = true
291
+ next
292
+ end
293
+ }
294
+ next if excluded
295
+ nodename = "#{s['name']}.#{domain}"
296
+ system "knife cs server delete #{s['name']} -y"
297
+ system "knife client delete #{nodename} -y"
298
+ system "knife node delete #{nodename} -y"
299
+ end
300
+ end
301
+
302
+ def print_local_hosts
303
+ hosts = []
304
+ current_stack[:servers].each do |server|
305
+ next unless server[:local_hosts]
306
+ name = server[:name].split(' ').first
307
+ ip = public_ip_for_host(name)
308
+ server[:local_hosts].each { |host|
309
+ hostname = host.sub(/\$\{environment\}/, get_environment)
310
+ hosts << "#{ip} #{hostname}"
311
+ }
312
+ end
313
+ unless hosts.empty?
314
+ puts "\nAdd this to your /etc/hosts file:"
315
+ puts hosts.join("\n")
316
+ end
317
+ end
318
+
319
+ def locate_config_value(key)
320
+ key = key.to_sym
321
+ Chef::Config[:knife][key] || config[key]
322
+ end
323
+
324
+ end
325
+ end
@@ -0,0 +1,88 @@
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 CsStackDelete < Chef::Knife
23
+
24
+ deps do
25
+ require 'chef/json_compat'
26
+ require 'chef/mash'
27
+ require 'knife-cloudstack/connection'
28
+ KnifeCloudstack::CsServerDelete.load_deps
29
+ end
30
+
31
+ banner "knife cs stack delete JSON_FILE (options)"
32
+
33
+ option :cloudstack_url,
34
+ :short => "-U URL",
35
+ :long => "--cloudstack-url URL",
36
+ :description => "The CloudStack endpoint URL",
37
+ :proc => Proc.new { |url| Chef::Config[:knife][:cloudstack_url] = url }
38
+
39
+ option :cloudstack_api_key,
40
+ :short => "-A KEY",
41
+ :long => "--cloudstack-api-key KEY",
42
+ :description => "Your CloudStack API key",
43
+ :proc => Proc.new { |key| Chef::Config[:knife][:cloudstack_api_key] = key }
44
+
45
+ option :cloudstack_secret_key,
46
+ :short => "-K SECRET",
47
+ :long => "--cloudstack-secret-key SECRET",
48
+ :description => "Your CloudStack secret key",
49
+ :proc => Proc.new { |key| Chef::Config[:knife][:cloudstack_secret_key] = key }
50
+
51
+ def run
52
+ file_path = File.expand_path(@name_args.first)
53
+ unless File.exist?(file_path) then
54
+ ui.error "Stack file '#{file_path}' not found. Please check the path."
55
+ exit 1
56
+ end
57
+
58
+ data = File.read file_path
59
+ stack = Chef::JSONCompat.from_json data
60
+ delete_stack stack
61
+
62
+ end
63
+
64
+ def delete_stack(stack)
65
+ current_stack = Mash.new(stack)
66
+ current_stack[:servers].each do |server|
67
+ if server[:name]
68
+
69
+ # delete server(s)
70
+ names = server[:name].split(/[\s,]+/)
71
+ names.each do |name|
72
+ delete_server(name)
73
+ end
74
+
75
+ end
76
+
77
+ end
78
+ end
79
+
80
+ def delete_server(server_name)
81
+ cmd = KnifeCloudstack::CsServerDelete.new([server_name])
82
+ cmd.config[:yes] = true
83
+ cmd.run_with_pretty_exceptions
84
+ end
85
+
86
+ end
87
+
88
+ end
@@ -0,0 +1,100 @@
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 CsTemplateList < Chef::Knife
23
+
24
+ MEGABYTES = 1024 * 1024
25
+
26
+ deps do
27
+ require 'knife-cloudstack/connection'
28
+ end
29
+
30
+ banner "knife cs template list (options)"
31
+
32
+ option :filter,
33
+ :short => "-L FILTER",
34
+ :long => "--filter FILTER",
35
+ :description => "The template search filter. Default is 'featured'",
36
+ :default => "featured"
37
+
38
+ option :cloudstack_url,
39
+ :short => "-U URL",
40
+ :long => "--cloudstack-url URL",
41
+ :description => "The CloudStack endpoint URL",
42
+ :proc => Proc.new { |url| Chef::Config[:knife][:cloudstack_url] = url }
43
+
44
+ option :cloudstack_api_key,
45
+ :short => "-A KEY",
46
+ :long => "--cloudstack-api-key KEY",
47
+ :description => "Your CloudStack API key",
48
+ :proc => Proc.new { |key| Chef::Config[:knife][:cloudstack_api_key] = key }
49
+
50
+ option :cloudstack_secret_key,
51
+ :short => "-K SECRET",
52
+ :long => "--cloudstack-secret-key SECRET",
53
+ :description => "Your CloudStack secret key",
54
+ :proc => Proc.new { |key| Chef::Config[:knife][:cloudstack_secret_key] = key }
55
+
56
+ def run
57
+
58
+ connection = CloudstackClient::Connection.new(
59
+ locate_config_value(:cloudstack_url),
60
+ locate_config_value(:cloudstack_api_key),
61
+ locate_config_value(:cloudstack_secret_key)
62
+ )
63
+
64
+ template_list = [
65
+ ui.color('Name', :bold),
66
+ ui.color('Size', :bold),
67
+ ui.color('Zone', :bold),
68
+ ui.color('Public', :bold),
69
+ ui.color('Created', :bold),
70
+ ]
71
+
72
+ filter = config[:filter]
73
+ templates = connection.list_templates(filter)
74
+ templates.each do |t|
75
+ template_list << t['name']
76
+ template_list << (human_file_size(t['size']) || 'Unknown')
77
+ template_list << t['zonename']
78
+ template_list << t['ispublic'].to_s
79
+ template_list << t['created']
80
+ end
81
+ puts ui.list(template_list, :columns_across, 5)
82
+
83
+ end
84
+
85
+ def human_file_size n
86
+ count = 0
87
+ while n >= 1024 and count < 4
88
+ n /= 1024.0
89
+ count += 1
90
+ end
91
+ format("%.2f", n) + %w(B KB MB GB TB)[count]
92
+ end
93
+
94
+ def locate_config_value(key)
95
+ key = key.to_sym
96
+ Chef::Config[:knife][key] || config[key]
97
+ end
98
+
99
+ end
100
+ end
@@ -0,0 +1,78 @@
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 CsZoneList < Chef::Knife
23
+
24
+ deps do
25
+ require 'knife-cloudstack/connection'
26
+ end
27
+
28
+ banner "knife cs zone 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
+ connection = CloudstackClient::Connection.new(
51
+ locate_config_value(:cloudstack_url),
52
+ locate_config_value(:cloudstack_api_key),
53
+ locate_config_value(:cloudstack_secret_key)
54
+ )
55
+
56
+ zone_list = [
57
+ ui.color('Name', :bold),
58
+ ui.color('Network Type', :bold),
59
+ ui.color('Security Groups', :bold)
60
+ ]
61
+
62
+ zones = connection.list_zones
63
+ zones.each do |z|
64
+ zone_list << z['name']
65
+ zone_list << z['networktype']
66
+ zone_list << z['securitygroupsenabled'].to_s
67
+ end
68
+ puts ui.list(zone_list, :columns_across, 3)
69
+
70
+ end
71
+
72
+ def locate_config_value(key)
73
+ key = key.to_sym
74
+ Chef::Config[:knife][key] || config[key]
75
+ end
76
+
77
+ end
78
+ end