chef-vpc-toolkit 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/COPYING +26 -0
- data/README.rdoc +75 -0
- data/Rakefile +34 -0
- data/VERSION +1 -0
- data/bin/chef-vpc-toolkit +80 -0
- data/config/chef_installer.yml +20 -0
- data/config/databags.json.example +24 -0
- data/config/nodes.json +10 -0
- data/config/server_group.json +16 -0
- data/contrib/doc/ChefVPCToolkit.odt +0 -0
- data/contrib/doc/ChefVPCToolkit.pdf +0 -0
- data/contrib/etc/chef_vpc_toolkit.conf +10 -0
- data/contrib/rake/Rakefile +23 -0
- data/cookbook-repos/local/README +5 -0
- data/cookbook-repos/local/Rakefile +66 -0
- data/cookbook-repos/local/certificates/README +1 -0
- data/cookbook-repos/local/config/client.rb.example +21 -0
- data/cookbook-repos/local/config/knife.rb.example +10 -0
- data/cookbook-repos/local/config/rake.rb +60 -0
- data/cookbook-repos/local/config/server.rb.example +42 -0
- data/cookbook-repos/local/config/solo.rb.example +13 -0
- data/cookbook-repos/local/cookbooks/README +4 -0
- data/cookbook-repos/local/cookbooks/motd/README.rdoc +15 -0
- data/cookbook-repos/local/cookbooks/motd/attributes/motd.rb +1 -0
- data/cookbook-repos/local/cookbooks/motd/metadata.rb +6 -0
- data/cookbook-repos/local/cookbooks/motd/recipes/default.rb +13 -0
- data/cookbook-repos/local/cookbooks/motd/templates/default/motd.erb +1 -0
- data/cookbook-repos/local/roles/README +4 -0
- data/lib/chef-vpc-toolkit.rb +6 -0
- data/lib/chef-vpc-toolkit/chef-0.9.bash +232 -0
- data/lib/chef-vpc-toolkit/chef_bootstrap/centos.bash +47 -0
- data/lib/chef-vpc-toolkit/chef_bootstrap/fedora.bash +41 -0
- data/lib/chef-vpc-toolkit/chef_bootstrap/rhel.bash +38 -0
- data/lib/chef-vpc-toolkit/chef_bootstrap/ubuntu.bash +32 -0
- data/lib/chef-vpc-toolkit/chef_installer.rb +276 -0
- data/lib/chef-vpc-toolkit/cloud_files.bash +67 -0
- data/lib/chef-vpc-toolkit/cloud_servers_vpc.rb +285 -0
- data/lib/chef-vpc-toolkit/http_util.rb +117 -0
- data/lib/chef-vpc-toolkit/ssh_util.rb +22 -0
- data/lib/chef-vpc-toolkit/util.rb +56 -0
- data/lib/chef-vpc-toolkit/version.rb +8 -0
- data/rake/chef_vpc_toolkit.rake +284 -0
- data/test/cloud_servers_vpc_test.rb +174 -0
- data/test/test_helper.rb +25 -0
- 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
|