chef-vpc-toolkit 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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