chef-vpc-toolkit 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.
Files changed (46) hide show
  1. data/.gitignore +2 -0
  2. data/COPYING +26 -0
  3. data/README.rdoc +75 -0
  4. data/Rakefile +34 -0
  5. data/VERSION +1 -0
  6. data/bin/chef-vpc-toolkit +80 -0
  7. data/config/chef_installer.yml +20 -0
  8. data/config/databags.json.example +24 -0
  9. data/config/nodes.json +10 -0
  10. data/config/server_group.json +16 -0
  11. data/contrib/doc/ChefVPCToolkit.odt +0 -0
  12. data/contrib/doc/ChefVPCToolkit.pdf +0 -0
  13. data/contrib/etc/chef_vpc_toolkit.conf +10 -0
  14. data/contrib/rake/Rakefile +23 -0
  15. data/cookbook-repos/local/README +5 -0
  16. data/cookbook-repos/local/Rakefile +66 -0
  17. data/cookbook-repos/local/certificates/README +1 -0
  18. data/cookbook-repos/local/config/client.rb.example +21 -0
  19. data/cookbook-repos/local/config/knife.rb.example +10 -0
  20. data/cookbook-repos/local/config/rake.rb +60 -0
  21. data/cookbook-repos/local/config/server.rb.example +42 -0
  22. data/cookbook-repos/local/config/solo.rb.example +13 -0
  23. data/cookbook-repos/local/cookbooks/README +4 -0
  24. data/cookbook-repos/local/cookbooks/motd/README.rdoc +15 -0
  25. data/cookbook-repos/local/cookbooks/motd/attributes/motd.rb +1 -0
  26. data/cookbook-repos/local/cookbooks/motd/metadata.rb +6 -0
  27. data/cookbook-repos/local/cookbooks/motd/recipes/default.rb +13 -0
  28. data/cookbook-repos/local/cookbooks/motd/templates/default/motd.erb +1 -0
  29. data/cookbook-repos/local/roles/README +4 -0
  30. data/lib/chef-vpc-toolkit.rb +6 -0
  31. data/lib/chef-vpc-toolkit/chef-0.9.bash +232 -0
  32. data/lib/chef-vpc-toolkit/chef_bootstrap/centos.bash +47 -0
  33. data/lib/chef-vpc-toolkit/chef_bootstrap/fedora.bash +41 -0
  34. data/lib/chef-vpc-toolkit/chef_bootstrap/rhel.bash +38 -0
  35. data/lib/chef-vpc-toolkit/chef_bootstrap/ubuntu.bash +32 -0
  36. data/lib/chef-vpc-toolkit/chef_installer.rb +276 -0
  37. data/lib/chef-vpc-toolkit/cloud_files.bash +67 -0
  38. data/lib/chef-vpc-toolkit/cloud_servers_vpc.rb +285 -0
  39. data/lib/chef-vpc-toolkit/http_util.rb +117 -0
  40. data/lib/chef-vpc-toolkit/ssh_util.rb +22 -0
  41. data/lib/chef-vpc-toolkit/util.rb +56 -0
  42. data/lib/chef-vpc-toolkit/version.rb +8 -0
  43. data/rake/chef_vpc_toolkit.rake +284 -0
  44. data/test/cloud_servers_vpc_test.rb +174 -0
  45. data/test/test_helper.rb +25 -0
  46. metadata +153 -0
@@ -0,0 +1,285 @@
1
+ require 'json'
2
+ require 'builder'
3
+ require 'rexml/document'
4
+ require 'rexml/xpath'
5
+
6
+ module ChefVPCToolkit
7
+
8
+ module CloudServersVPC
9
+
10
+ SERVER_GROUP_CONFIG_FILE = CHEF_VPC_PROJECT + File::SEPARATOR + "config" + File::SEPARATOR + "server_group.json"
11
+
12
+ def self.load_public_key
13
+
14
+ ssh_dir=ENV['HOME']+File::SEPARATOR+".ssh"+File::SEPARATOR
15
+ if File.exists?(ssh_dir+"id_rsa.pub")
16
+ pubkey=IO.read(ssh_dir+"id_rsa.pub")
17
+ elsif File.exists?(ssh_dir+"id_dsa.pub")
18
+ pubkey=IO.read(ssh_dir+"id_dsa.pub")
19
+ else
20
+ raise "Failed to load SSH key. Please create a SSH public key pair in your HOME directory."
21
+ end
22
+
23
+ pubkey.chomp
24
+
25
+ end
26
+
27
+ # generate a Server Group XML from server_group.json
28
+ def self.server_group_xml(config_file=SERVER_GROUP_CONFIG_FILE, owner=ENV['USER'])
29
+
30
+ json_hash=JSON.parse(IO.read(config_file))
31
+
32
+ xml = Builder::XmlMarkup.new
33
+ xml.tag! "server-group" do |sg|
34
+ sg.name(json_hash["name"])
35
+ sg.description(json_hash["description"])
36
+ sg.tag! "owner-name", owner
37
+ sg.tag! "domain-name", json_hash["domain_name"]
38
+ if json_hash["vpn_network"] then
39
+ sg.tag! "vpn-network", json_hash["vpn_network"]
40
+ else
41
+ sg.tag! "vpn-network", "172.19.0.0"
42
+ end
43
+ if json_hash["vpn_subnet"] then
44
+ sg.tag! "vpn-subnet", json_hash["vpn_subnet"]
45
+ else
46
+ sg.tag! "vpn-subnet", "255.255.128.0"
47
+ end
48
+ sg.servers("type" => "array") do |servers|
49
+ json_hash["servers"].each_pair do |server_name, server_config|
50
+ servers.server do |server|
51
+ server.name(server_name)
52
+ if server_config["description"] then
53
+ server.description(server_config["description"])
54
+ else
55
+ server.description(server_name)
56
+ end
57
+ server.tag! "flavor-id", server_config["flavor_id"]
58
+ server.tag! "image-id", server_config["image_id"]
59
+ if server_config["openvpn_server"]
60
+ server.tag! "openvpn-server", "true", { "type" => "boolean"}
61
+ end
62
+ end
63
+ end
64
+ end
65
+ sg.tag! "ssh-public-keys", { "type" => "array"} do |ssh_keys|
66
+ ssh_keys.tag! "ssh-public-key" do |ssh_public_key|
67
+ ssh_public_key.description "#{ENV['USER']}'s public key"
68
+ ssh_public_key.tag! "public-key", self.load_public_key
69
+ end
70
+ end
71
+ end
72
+ xml.target!
73
+
74
+ end
75
+
76
+ def self.server_group_hash(xml)
77
+
78
+ hash={}
79
+ dom = REXML::Document.new(xml)
80
+ REXML::XPath.each(dom, "/server-group") do |sg|
81
+
82
+ hash["name"]=sg.elements["name"].text
83
+ hash["description"]=sg.elements["description"].text
84
+ hash["id"]=sg.elements["id"].text
85
+ hash["domain-name"]=sg.elements["domain-name"].text
86
+
87
+ hash["servers"]={}
88
+ REXML::XPath.each(dom, "//server") do |server|
89
+ server_name=server.elements["name"].text
90
+ server_attributes={
91
+ "id" => server.elements["id"].text,
92
+ "cloud-server-id-number" => server.elements["cloud-server-id-number"].text,
93
+ "status" => server.elements["status"].text,
94
+ "external-ip-addr" => server.elements["external-ip-addr"].text,
95
+ "internal-ip-addr" => server.elements["internal-ip-addr"].text,
96
+ "error-message" => server.elements["error-message"].text,
97
+ "image-id" => server.elements["image-id"].text,
98
+ "retry-count" => server.elements["retry-count"].text,
99
+ "openvpn-server" => server.elements["openvpn-server"].text
100
+ }
101
+ if server.elements["openvpn-server"].text and server.elements["openvpn-server"].text == "true" and server.elements["external-ip-addr"].text then
102
+ hash["vpn-gateway"]=server.elements["external-ip-addr"].text
103
+ end
104
+ hash["servers"].store(server_name, server_attributes)
105
+ end
106
+ end
107
+
108
+ hash
109
+
110
+ end
111
+
112
+ def self.server_group_xml_for_id(configs, dir, id=nil)
113
+
114
+ if id then
115
+ xml=HttpUtil.get(
116
+ configs["cloud_servers_vpc_url"]+"/server_groups/#{id}.xml",
117
+ configs["cloud_servers_vpc_username"],
118
+ configs["cloud_servers_vpc_password"]
119
+ )
120
+ else
121
+ recent_hash=CloudServersVPC.most_recent_server_group_hash(dir)
122
+ raise "No server group files exist." if recent_hash.nil?
123
+ xml=HttpUtil.get(
124
+ configs["cloud_servers_vpc_url"]+"/server_groups/#{recent_hash['id']}.xml",
125
+ configs["cloud_servers_vpc_username"],
126
+ configs["cloud_servers_vpc_password"]
127
+ )
128
+
129
+ end
130
+
131
+ end
132
+
133
+ def self.most_recent_server_group_hash(dir_pattern)
134
+ server_groups=[]
135
+ Dir[dir_pattern].each do |file|
136
+ server_groups << CloudServersVPC.server_group_hash(IO.read(file))
137
+ end
138
+ if server_groups.size > 0 then
139
+ server_groups.sort { |a,b| b["id"].to_i <=> a["id"].to_i }[0]
140
+ else
141
+ nil
142
+ end
143
+ end
144
+
145
+ def self.print_server_group(hash)
146
+
147
+ puts "Cloud Group ID: #{hash["id"]}"
148
+ puts "name: #{hash["name"]}"
149
+ puts "description: #{hash["description"]}"
150
+ puts "domain name: #{hash["domain-name"]}"
151
+ puts "VPN gateway IP: #{hash["vpn-gateway"]}"
152
+ puts "Servers:"
153
+ hash["servers"].each_pair do |name, attrs|
154
+ puts "\tname: #{name} (id: #{attrs['cloud-server-id-number']})"
155
+ puts "\tstatus: #{attrs['status']}"
156
+ if attrs["openvpn-server"] and attrs["openvpn-server"] == "true" then
157
+ puts "\tOpenVPN server: #{attrs['openvpn-server']}"
158
+ end
159
+ if attrs["error-message"] then
160
+ puts "\tlast error message: #{attrs['error-message']}"
161
+ end
162
+ puts "\t--"
163
+ end
164
+
165
+ end
166
+
167
+ def self.server_names(hash)
168
+
169
+ names=[]
170
+
171
+ hash["servers"].each_pair do |name, hash|
172
+ if block_given? then
173
+ yield name
174
+ else
175
+ names << name
176
+ end
177
+ end
178
+
179
+ names
180
+
181
+ end
182
+
183
+ # default timeout of 20 minutes
184
+ def self.poll_until_online(group_id, timeout=1200)
185
+
186
+ configs=Util.load_configs
187
+
188
+ online = false
189
+ count=0
190
+ until online or (count*20) >= timeout.to_i do
191
+ count+=1
192
+ begin
193
+ xml=HttpUtil.get(
194
+ configs["cloud_servers_vpc_url"]+"/server_groups/#{group_id}.xml",
195
+ configs["cloud_servers_vpc_username"],
196
+ configs["cloud_servers_vpc_password"]
197
+ )
198
+
199
+ hash=CloudServersVPC.server_group_hash(xml)
200
+
201
+ online=true
202
+ hash["servers"].each_pair do |name, attrs|
203
+ if ["Pending", "Rebuilding"].include?(attrs["status"]) then
204
+ online=false
205
+ end
206
+ if attrs["status"] == "Failed" then
207
+ raise "Failed to create server group with the following message: #{attrs['error-message']}"
208
+ end
209
+ end
210
+ if not online
211
+ yield hash if block_given?
212
+ sleep 20
213
+ end
214
+ rescue EOFError
215
+ end
216
+ end
217
+ if (count*20) >= timeout.to_i then
218
+ raise "Timeout waiting for server groups to come online."
219
+ end
220
+
221
+ end
222
+
223
+ def self.os_types(server_group_hash)
224
+
225
+ os_types={}
226
+ server_group_hash["servers"].each_pair do |name, attrs|
227
+ os_type = case attrs["image-id"].to_i
228
+ when 51 # Centos 5.5
229
+ "centos"
230
+ when 187811 # Centos 5.4
231
+ "centos"
232
+ when 53 # Fedora 13
233
+ "fedora"
234
+ when 17 # Fedora 12
235
+ "fedora"
236
+ when 14 # RHEL 5.4
237
+ "rhel"
238
+ when 62 # RHEL 5.5
239
+ "rhel"
240
+ when 49 # Ubuntu 10.04
241
+ "ubuntu"
242
+ when 14362 # Ubuntu 9.10
243
+ "ubuntu"
244
+ when 8 # Ubuntu 9.04
245
+ "ubuntu"
246
+ else
247
+ "unknown"
248
+ end
249
+ if block_given? then
250
+ yield name, os_type
251
+ else
252
+ os_types.store(name, os_type)
253
+ end
254
+ end
255
+ os_types
256
+
257
+ end
258
+
259
+ def self.rebuild(server_group_hash, server_name)
260
+
261
+ configs=Util.load_configs
262
+
263
+ server_id=nil
264
+ image_id=nil
265
+ server_group_hash["servers"].each_pair do |name, attrs|
266
+ if name == server_name then
267
+ raise "Error: Rebuilding the OpenVPN server is not supported at this time." if attrs["openvpn-server"] == "true"
268
+ server_id=attrs["id"]
269
+ image_id=attrs["image-id"]
270
+ end
271
+ end
272
+ raise "Unable to find server name: #{server_name}" if server_id.nil?
273
+
274
+ HttpUtil.post(
275
+ configs["cloud_servers_vpc_url"]+"/servers/#{server_id}/rebuild",
276
+ {},
277
+ configs["cloud_servers_vpc_username"],
278
+ configs["cloud_servers_vpc_password"]
279
+ )
280
+
281
+ end
282
+
283
+ end
284
+
285
+ end
@@ -0,0 +1,117 @@
1
+ require 'uri'
2
+ require 'net/http'
3
+ require 'net/https'
4
+
5
+ module ChefVPCToolkit
6
+
7
+ module HttpUtil
8
+
9
+ MULTI_PART_BOUNDARY="jtZ!pZ1973um"
10
+
11
+ def self.file_upload(url_string, file_data={}, post_data={}, auth_user=nil, auth_password=nil)
12
+ url=URI.parse(url_string)
13
+ http = Net::HTTP.new(url.host,url.port)
14
+ req = Net::HTTP::Post.new(url.path)
15
+
16
+ post_arr=[]
17
+ post_data.each_pair do |key, value|
18
+ post_arr << "--#{MULTI_PART_BOUNDARY}\r\n"
19
+ post_arr << "Content-Disposition: form-data; name=\"#{key}\"\r\n"
20
+ post_arr << "\r\n"
21
+ post_arr << value
22
+ post_arr << "\r\n"
23
+ end
24
+
25
+ file_data.each_pair do |name, file|
26
+ post_arr << "--#{MULTI_PART_BOUNDARY}\r\n"
27
+ post_arr << "Content-Disposition: form-data; name=\"#{name}\"; filename=\"#{File.basename(file)}\"\r\n"
28
+ post_arr << "Content-Type: text/plain\r\n"
29
+ post_arr << "\r\n"
30
+ post_arr << File.read(file)
31
+ post_arr << "\r\n--#{MULTI_PART_BOUNDARY}--\r\n"
32
+ end
33
+ post_arr << "--#{MULTI_PART_BOUNDARY}--\r\n\r\n"
34
+
35
+ req.body=post_arr.join
36
+
37
+ if url_string =~ /^https/
38
+ http.use_ssl = true
39
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
40
+ end
41
+ req.basic_auth auth_user, auth_password if auth_user and auth_password
42
+ req["Content-Type"] = "multipart/form-data, boundary=#{MULTI_PART_BOUNDARY}"
43
+
44
+ response = http.request(req)
45
+ case response
46
+ when Net::HTTPSuccess
47
+ return response.body
48
+ else
49
+ puts response.body
50
+ response.error!
51
+ end
52
+ end
53
+
54
+ def self.post(url_string, post_data, auth_user=nil, auth_password=nil)
55
+ url=URI.parse(url_string)
56
+ http = Net::HTTP.new(url.host,url.port)
57
+ req = Net::HTTP::Post.new(url.path)
58
+ if post_data.kind_of?(String) then
59
+ req.body=post_data
60
+ elsif post_data.kind_of?(Hash) then
61
+ req.form_data=post_data
62
+ else
63
+ raise "Invalid post data type."
64
+ end
65
+ if url_string =~ /^https/
66
+ http.use_ssl = true
67
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
68
+ end
69
+ req.basic_auth auth_user, auth_password if auth_user and auth_password
70
+ response = http.request(req)
71
+ case response
72
+ when Net::HTTPSuccess
73
+ return response.body
74
+ else
75
+ response.error!
76
+ end
77
+ end
78
+
79
+ def self.get(url_string, auth_user=nil, auth_password=nil)
80
+ url=URI.parse(url_string)
81
+ http = Net::HTTP.new(url.host,url.port)
82
+ req = Net::HTTP::Get.new(url.path)
83
+ if url_string =~ /^https/
84
+ http.use_ssl = true
85
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
86
+ end
87
+ req.basic_auth auth_user, auth_password if auth_user and auth_password
88
+ response = http.request(req)
89
+ case response
90
+ when Net::HTTPSuccess
91
+ return response.body
92
+ else
93
+ response.error!
94
+ end
95
+ end
96
+
97
+ def self.delete(url_string, auth_user=nil, auth_password=nil)
98
+ url=URI.parse(url_string)
99
+ http = Net::HTTP.new(url.host,url.port)
100
+ req = Net::HTTP::Delete.new(url.path)
101
+ if url_string =~ /^https/
102
+ http.use_ssl = true
103
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
104
+ end
105
+ req.basic_auth auth_user, auth_password if auth_user and auth_password
106
+ response = http.request(req)
107
+ case response
108
+ when Net::HTTPSuccess
109
+ return response.body
110
+ else
111
+ response.error!
112
+ end
113
+ end
114
+
115
+ end
116
+
117
+ end
@@ -0,0 +1,22 @@
1
+ module ChefVPCToolkit
2
+
3
+ module SshUtil
4
+
5
+ def self.remove_known_hosts_ip(ip, known_hosts_file=File.join(ENV['HOME'], ".ssh", "known_hosts"))
6
+
7
+ return if ip.nil? or ip.empty?
8
+
9
+ existing=IO.read(known_hosts_file)
10
+ File.open(known_hosts_file, 'w') do |file|
11
+ existing.each_line do |line|
12
+ if not line =~ Regexp.new("^#{ip}.*$") then
13
+ file.write(line)
14
+ end
15
+ end
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+
22
+ end
@@ -0,0 +1,56 @@
1
+ require 'yaml'
2
+
3
+ module ChefVPCToolkit
4
+
5
+ module Util
6
+
7
+ def self.load_configs
8
+
9
+ config_file=ENV['CLOUD_TEST_CONFIG_FILE']
10
+ if config_file.nil? then
11
+
12
+ config_file=ENV['HOME']+File::SEPARATOR+".cloud_toolkit.conf"
13
+ if not File.exists?(config_file) then
14
+ config_file="/etc/cloud_toolkit.conf"
15
+ end
16
+
17
+ end
18
+
19
+ if File.exists?(config_file) then
20
+ configs=YAML.load_file(config_file)
21
+ raise_if_nil_or_empty(configs, "cloud_servers_vpc_url")
22
+ raise_if_nil_or_empty(configs, "cloud_servers_vpc_username")
23
+ raise_if_nil_or_empty(configs, "cloud_servers_vpc_password")
24
+ return configs
25
+ else
26
+ raise "Failed to load cloud toolkit config file. Please configure /etc/cloud_toolkit.conf or create a .cloud_toolkit.conf config file in your HOME directory."
27
+ end
28
+
29
+ end
30
+
31
+ def self.raise_if_nil_or_empty(options, key)
32
+ if options[key].nil? || options[key].empty? then
33
+ raise "Please specify a valid #{key.to_s} parameter."
34
+ end
35
+ end
36
+
37
+ def self.hash_for_group(configs=Util.load_configs)
38
+
39
+ id=ENV['GROUP_ID']
40
+ configs=Util.load_configs
41
+ hash=nil
42
+ if id.nil? then
43
+ hash=CloudServersVPC.most_recent_server_group_hash(File.join(TMP_SG, '*.xml'))
44
+ else
45
+ file=File.join(TMP_SG, "#{id}.xml")
46
+ hash = CloudServersVPC.server_group_hash(IO.read(file))
47
+ end
48
+ raise "Create a cloud before running this command." if hash.nil?
49
+
50
+ hash
51
+
52
+ end
53
+
54
+ end
55
+
56
+ end