kytoon 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +29 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +81 -0
- data/Rakefile +35 -0
- data/VERSION +1 -0
- data/config/server_group_vpc.json +14 -0
- data/config/server_group_xen.json +24 -0
- data/lib/kytoon.rb +8 -0
- data/lib/kytoon/providers/cloud_servers_vpc.rb +6 -0
- data/lib/kytoon/providers/cloud_servers_vpc/client.rb +197 -0
- data/lib/kytoon/providers/cloud_servers_vpc/connection.rb +148 -0
- data/lib/kytoon/providers/cloud_servers_vpc/server.rb +121 -0
- data/lib/kytoon/providers/cloud_servers_vpc/server_group.rb +401 -0
- data/lib/kytoon/providers/cloud_servers_vpc/ssh_public_key.rb +29 -0
- data/lib/kytoon/providers/cloud_servers_vpc/vpn_network_interface.rb +33 -0
- data/lib/kytoon/providers/xenserver.rb +1 -0
- data/lib/kytoon/providers/xenserver/server_group.rb +371 -0
- data/lib/kytoon/server_group.rb +46 -0
- data/lib/kytoon/ssh_util.rb +23 -0
- data/lib/kytoon/util.rb +118 -0
- data/lib/kytoon/version.rb +8 -0
- data/lib/kytoon/vpn/vpn_connection.rb +46 -0
- data/lib/kytoon/vpn/vpn_network_manager.rb +237 -0
- data/lib/kytoon/vpn/vpn_openvpn.rb +112 -0
- data/lib/kytoon/xml_util.rb +15 -0
- data/rake/kytoon.rake +115 -0
- data/test/client_test.rb +111 -0
- data/test/helper.rb +18 -0
- data/test/server_group_test.rb +253 -0
- data/test/server_test.rb +69 -0
- data/test/ssh_util_test.rb +22 -0
- data/test/test_helper.rb +194 -0
- data/test/test_kytoon.rb +7 -0
- data/test/util_test.rb +23 -0
- data/test/vpn_network_manager_test.rb +40 -0
- metadata +247 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
module Kytoon
|
2
|
+
|
3
|
+
module Providers
|
4
|
+
|
5
|
+
module CloudServersVPC
|
6
|
+
|
7
|
+
class SshPublicKey
|
8
|
+
|
9
|
+
attr_accessor :id
|
10
|
+
attr_accessor :description
|
11
|
+
attr_accessor :public_key
|
12
|
+
attr_accessor :server_group_id
|
13
|
+
|
14
|
+
def initialize(options={})
|
15
|
+
|
16
|
+
@id=options[:id]
|
17
|
+
@description=options[:description]
|
18
|
+
@public_key=options[:public_key]
|
19
|
+
@server_group_id=options[:server_group_id]
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Kytoon
|
2
|
+
|
3
|
+
module Providers
|
4
|
+
|
5
|
+
module CloudServersVPC
|
6
|
+
|
7
|
+
class VpnNetworkInterface
|
8
|
+
|
9
|
+
attr_accessor :id
|
10
|
+
attr_accessor :vpn_ip_addr
|
11
|
+
attr_accessor :ptp_ip_addr
|
12
|
+
attr_accessor :client_key
|
13
|
+
attr_accessor :client_cert
|
14
|
+
attr_accessor :ca_cert
|
15
|
+
|
16
|
+
def initialize(options={})
|
17
|
+
|
18
|
+
@id=options[:id].to_i
|
19
|
+
@vpn_ip_addr=options[:vpn_ip_addr]
|
20
|
+
@ptp_ip_addr=options[:ptp_ip_addr]
|
21
|
+
@client_key=options[:client_key]
|
22
|
+
@client_cert=options[:client_cert]
|
23
|
+
@ca_cert=options[:ca_cert]
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'kytoon/providers/xenserver/server_group'
|
@@ -0,0 +1,371 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'builder'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'rexml/document'
|
5
|
+
require 'kytoon/util'
|
6
|
+
require 'base64'
|
7
|
+
|
8
|
+
module Kytoon
|
9
|
+
|
10
|
+
module Providers
|
11
|
+
|
12
|
+
module Xenserver
|
13
|
+
# All in one XenServer server group provider.
|
14
|
+
#
|
15
|
+
# Required setup:
|
16
|
+
# 1) add your ssh key to the XenServer box.
|
17
|
+
#
|
18
|
+
# 2) Pre-download any .xva images you'd like to use. These images *must*
|
19
|
+
# have the OpenStack guest agent installed (including Xen Guest Tools).
|
20
|
+
#
|
21
|
+
# 3) Generate an ssh keypair on your XenServer host.
|
22
|
+
#
|
23
|
+
# 4) Add an ip to the private Xen bridge you'd like to use for instances.
|
24
|
+
# This IP should match the range you'd like to use in your server group
|
25
|
+
# config file. Example:
|
26
|
+
# ip addr add 192.168.0.1/24 brd 192.168.0.255 scope global dev xenbr1
|
27
|
+
#
|
28
|
+
class ServerGroup
|
29
|
+
|
30
|
+
@@data_dir=File.join(KYTOON_PROJECT, "tmp", "xenserver")
|
31
|
+
|
32
|
+
def self.data_dir
|
33
|
+
@@data_dir
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.data_dir=(dir)
|
37
|
+
@@data_dir=dir
|
38
|
+
end
|
39
|
+
|
40
|
+
CONFIG_FILE = KYTOON_PROJECT + File::SEPARATOR + "config" + File::SEPARATOR + "server_group.json"
|
41
|
+
|
42
|
+
attr_accessor :id
|
43
|
+
attr_accessor :name
|
44
|
+
attr_accessor :gateway_ip
|
45
|
+
attr_accessor :netmask
|
46
|
+
attr_accessor :gateway
|
47
|
+
attr_accessor :broadcast
|
48
|
+
attr_accessor :network_type
|
49
|
+
attr_accessor :bridge
|
50
|
+
attr_accessor :public_ip_bridge
|
51
|
+
attr_accessor :dns_nameserver
|
52
|
+
|
53
|
+
def initialize(options={})
|
54
|
+
@id = options[:id] || Time.now.to_i
|
55
|
+
@name = options[:name]
|
56
|
+
@netmask = options[:netmask]
|
57
|
+
@gateway = options[:gateway]
|
58
|
+
@broadcast = options[:broadcast]
|
59
|
+
@network_type = options[:network_type]
|
60
|
+
@bridge = options[:bridge]
|
61
|
+
@public_ip_bridge = options[:public_ip_bridge]
|
62
|
+
@dns_nameserver = options[:dns_nameserver]
|
63
|
+
@gateway_ip = options[:gateway_ip]
|
64
|
+
@gateway_ip = ENV['GATEWAY_IP'] if @gateway_ip.nil?
|
65
|
+
raise "Please specify a GATEWAY_IP" if @gateway_ip.nil?
|
66
|
+
|
67
|
+
@servers=[]
|
68
|
+
end
|
69
|
+
|
70
|
+
def server(name)
|
71
|
+
@servers.select {|s| s['hostname'] == name}[0] if @servers.size > 0
|
72
|
+
end
|
73
|
+
|
74
|
+
def servers
|
75
|
+
@servers
|
76
|
+
end
|
77
|
+
|
78
|
+
def gateway_ip
|
79
|
+
@gateway_ip
|
80
|
+
end
|
81
|
+
|
82
|
+
# generate a Server Group XML from server_group.json
|
83
|
+
def self.from_json(json)
|
84
|
+
|
85
|
+
json_hash=JSON.parse(json)
|
86
|
+
|
87
|
+
sg=ServerGroup.new(
|
88
|
+
:id => json_hash["id"],
|
89
|
+
:name => json_hash["name"],
|
90
|
+
:netmask => json_hash['netmask'],
|
91
|
+
:gateway => json_hash['gateway'],
|
92
|
+
:gateway_ip => json_hash['gateway_ip'],
|
93
|
+
:broadcast => json_hash['broadcast'],
|
94
|
+
:dns_nameserver => json_hash['dns_nameserver'],
|
95
|
+
:network_type => json_hash['network_type'],
|
96
|
+
:public_ip_bridge => json_hash['public_ip_bridge'],
|
97
|
+
:bridge => json_hash['bridge']
|
98
|
+
)
|
99
|
+
json_hash["servers"].each do |server_hash|
|
100
|
+
sg.servers << {
|
101
|
+
'hostname' => server_hash['hostname'],
|
102
|
+
'ip_address' => server_hash['ip_address'],
|
103
|
+
'mac' => server_hash['mac'],
|
104
|
+
'image_path' => server_hash['image_path']
|
105
|
+
}
|
106
|
+
end
|
107
|
+
return sg
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
def pretty_print
|
112
|
+
|
113
|
+
puts "Group ID: #{@id}"
|
114
|
+
puts "name: #{@name}"
|
115
|
+
puts "Servers:"
|
116
|
+
servers.each do |server|
|
117
|
+
puts "\tname: #{server['hostname']}"
|
118
|
+
puts "\t--"
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
def server_names
|
124
|
+
|
125
|
+
names=[]
|
126
|
+
|
127
|
+
servers.each do |server|
|
128
|
+
if block_given? then
|
129
|
+
yield server['hostname']
|
130
|
+
else
|
131
|
+
names << server['hostname']
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
names
|
136
|
+
|
137
|
+
end
|
138
|
+
|
139
|
+
def cache_to_disk
|
140
|
+
|
141
|
+
sg_hash = {
|
142
|
+
'id' => @id,
|
143
|
+
'name' => @name,
|
144
|
+
'netmask' => @netmask,
|
145
|
+
'gateway' => @gateway,
|
146
|
+
'gateway_ip' => @gateway_ip,
|
147
|
+
'broadcast' => @broadcast,
|
148
|
+
'dns_nameserver' => @dns_nameserver,
|
149
|
+
'network_type' => @network_type,
|
150
|
+
'public_ip_bridge' => @public_ip_bridge,
|
151
|
+
'bridge' => @bridge,
|
152
|
+
'servers' => []
|
153
|
+
}
|
154
|
+
@servers.each do |server|
|
155
|
+
sg_hash['servers'] << {'hostname' => server['hostname'], 'ip_address' => server['ip_address'], 'image_path' => server['image_path'], 'mac' => server['mac']}
|
156
|
+
end
|
157
|
+
|
158
|
+
FileUtils.mkdir_p(@@data_dir)
|
159
|
+
File.open(File.join(@@data_dir, "#{@id}.json"), 'w') do |f|
|
160
|
+
f.chmod(0600)
|
161
|
+
f.write(sg_hash.to_json)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def delete
|
166
|
+
ServerGroup.cleanup_instances(@gateway_ip)
|
167
|
+
out_file=File.join(@@data_dir, "#{@id}.json")
|
168
|
+
File.delete(out_file) if File.exists?(out_file)
|
169
|
+
end
|
170
|
+
|
171
|
+
def self.create(sg)
|
172
|
+
sg.cache_to_disk
|
173
|
+
init_host(sg)
|
174
|
+
status, host_ssh_public_key = Kytoon::Util.remote_exec(%{
|
175
|
+
if [ -f /root/.ssh/id_rsa.pub ]; then
|
176
|
+
cat /root/.ssh/id_rsa.pub
|
177
|
+
elif [ -f /root/.ssh/id_dsa.pub ]; then
|
178
|
+
cat /root/.ssh/id_dsa.pub
|
179
|
+
else
|
180
|
+
exit 1
|
181
|
+
fi
|
182
|
+
}, sg.gateway_ip)
|
183
|
+
sg.servers.each do |server|
|
184
|
+
create_instance(sg.gateway_ip, server['image_path'], server['hostname'], server['mac'], sg.bridge, host_ssh_public_key)
|
185
|
+
network_type = sg.network_type
|
186
|
+
if network_type == 'static' then
|
187
|
+
configure_static_networking(sg.gateway_ip, server['hostname'], server['ip_address'], sg.netmask, sg.gateway, sg.broadcast, server['mac'], sg.dns_nameserver)
|
188
|
+
else
|
189
|
+
raise "Unsupported network type '#{sg.network_type}'"
|
190
|
+
end
|
191
|
+
end
|
192
|
+
sg
|
193
|
+
end
|
194
|
+
|
195
|
+
def self.get(options={})
|
196
|
+
id = options[:id]
|
197
|
+
if id.nil? then
|
198
|
+
group=ServerGroup.most_recent
|
199
|
+
raise "No server group files exist." if group.nil?
|
200
|
+
id=group.id
|
201
|
+
end
|
202
|
+
|
203
|
+
out_file=File.join(@@data_dir, "#{id}.json")
|
204
|
+
raise "No server group files exist." if not File.exists?(out_file)
|
205
|
+
ServerGroup.from_json(IO.read(out_file))
|
206
|
+
end
|
207
|
+
|
208
|
+
def self.index(options={})
|
209
|
+
|
210
|
+
server_groups=[]
|
211
|
+
Dir[File.join(ServerGroup.data_dir, '*.json')].each do |file|
|
212
|
+
server_groups << ServerGroup.from_json(IO.read(file))
|
213
|
+
end
|
214
|
+
server_groups
|
215
|
+
|
216
|
+
end
|
217
|
+
|
218
|
+
def self.most_recent
|
219
|
+
server_groups=[]
|
220
|
+
Dir[File.join(@@data_dir, "*.json")].each do |file|
|
221
|
+
server_groups << ServerGroup.from_json(IO.read(file))
|
222
|
+
end
|
223
|
+
if server_groups.size > 0 then
|
224
|
+
server_groups.sort { |a,b| b.id <=> a.id }[0]
|
225
|
+
else
|
226
|
+
nil
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def self.init_host(sg)
|
231
|
+
|
232
|
+
hosts_file_data = "127.0.0.1\tlocalhost localhost.localdomain\n"
|
233
|
+
sg.servers.each do |server|
|
234
|
+
hosts_file_data += "#{server['ip_address']}\t#{server['hostname']}\n"
|
235
|
+
end
|
236
|
+
|
237
|
+
Kytoon::Util.remote_exec(%{
|
238
|
+
cat > /etc/hosts <<-EOF_CAT
|
239
|
+
#{hosts_file_data}
|
240
|
+
EOF_CAT
|
241
|
+
# FIXME... probably a bit insecure but most people are probably using
|
242
|
+
# boxes behind another firewall anyway.
|
243
|
+
iptables -F
|
244
|
+
iptables -P INPUT ACCEPT
|
245
|
+
iptables -P FORWARD ACCEPT
|
246
|
+
iptables -P OUTPUT ACCEPT
|
247
|
+
iptables -t nat -A POSTROUTING -o #{sg.public_ip_bridge} -j MASQUERADE
|
248
|
+
echo 1 > /proc/sys/net/ipv4/ip_forward
|
249
|
+
}, sg.gateway_ip)
|
250
|
+
end
|
251
|
+
|
252
|
+
def self.create_instance(gw_ip, image_path, hostname, mac, xen_bridge='xenbr1', ssh_public_key=nil)
|
253
|
+
file_data = Base64.encode64("/root/.ssh/authorized_keys,#{ssh_public_key}")
|
254
|
+
|
255
|
+
Kytoon::Util.remote_exec(%{
|
256
|
+
UUID=$(xe vm-import filename=#{image_path})
|
257
|
+
xe vm-param-set name-label=#{hostname} uuid=$UUID
|
258
|
+
NETWORK_UUID=$(xe network-list bridge=#{xen_bridge} | grep -P "^uuid" | cut -f2 -d: | cut -f2 -d" ")
|
259
|
+
xe vif-destroy uuid=$VIF_UUID &> /dev/null
|
260
|
+
for VIF_UUID in $(xe vif-list vm-uuid=$UUID | grep uuid | sed -e 's|.*: ||'); do
|
261
|
+
echo "Destroying Xen VIF uuid: $VIF_UUID"
|
262
|
+
xe vif-destroy uuid=$VIF_UUID &> /dev/null
|
263
|
+
done
|
264
|
+
xe vif-create vm-uuid=$UUID mac=#{mac} network-uuid=$NETWORK_UUID device=0 &> /dev/null
|
265
|
+
xe vm-start uuid=$UUID &> /dev/null
|
266
|
+
|
267
|
+
# inject ssh from host
|
268
|
+
DOMID=$(xe vm-param-get uuid=$UUID param-name="dom-id")
|
269
|
+
xenstore-rm -s /local/domain/$DOMID/data/guest/ssh_key 2> /dev/null
|
270
|
+
xenstore-write -s /local/domain/$DOMID/data/host/ssh_key '{"name": "injectfile", "value": "#{file_data}"}'
|
271
|
+
until [ -n "$INJECT_RETVAL" ]; do
|
272
|
+
INJECT_RETVAL=$(xenstore-read -s /local/domain/$DOMID/data/guest/ssh_key 2> /dev/null)
|
273
|
+
done
|
274
|
+
xenstore-rm -s /local/domain/$DOMID/data/host/ssh_key
|
275
|
+
|
276
|
+
}, gw_ip) do |ok, out|
|
277
|
+
if not ok
|
278
|
+
puts out
|
279
|
+
raise "Failed to create instance #{hostname}."
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
def self.cleanup_instances(gw_ip)
|
285
|
+
Kytoon::Util.remote_exec(%{
|
286
|
+
rpm -ev openstack-xen-plugins &> /dev/null
|
287
|
+
yum clean all &> /dev/null
|
288
|
+
|
289
|
+
for UUID in $(xe vm-list is-control-domain=false | grep uuid | sed -e 's|.*: ||'); do
|
290
|
+
echo "Destroying Xen instance uuid: $UUID"
|
291
|
+
xe vm-shutdown uuid=$UUID
|
292
|
+
xe vm-destroy uuid=$UUID
|
293
|
+
done
|
294
|
+
|
295
|
+
for UUID in $(xe vdi-list read-only=false | grep "^uuid" | sed -e 's|.*: ||'); do
|
296
|
+
echo "Destroying VDI uuid: $UUID"
|
297
|
+
xe vdi-destroy uuid=$UUID
|
298
|
+
done
|
299
|
+
|
300
|
+
for UUID in $(xe vif-list | grep uuid | sed -e 's|.*: ||'); do
|
301
|
+
echo "Destroying Xen VIF uuid: $UUID"
|
302
|
+
xe vif-destroy uuid=$UUID
|
303
|
+
done
|
304
|
+
|
305
|
+
}, gw_ip) do |ok, out|
|
306
|
+
if not ok
|
307
|
+
puts out
|
308
|
+
raise "Failed to cleanup instances."
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
def self.configure_static_networking(gw_ip, hostname, ip_address, netmask, gateway, broadcast, mac, dns_nameserver)
|
314
|
+
|
315
|
+
# networking
|
316
|
+
network_info = {
|
317
|
+
"label" => "public",
|
318
|
+
"broadcast" => broadcast,
|
319
|
+
"ips" => [{
|
320
|
+
"ip" => ip_address,
|
321
|
+
"netmask" => netmask,
|
322
|
+
"enabled" => "1"}],
|
323
|
+
"mac" => mac,
|
324
|
+
"dns" => [dns_nameserver],
|
325
|
+
"gateway" => gateway
|
326
|
+
}
|
327
|
+
|
328
|
+
Kytoon::Util.remote_exec(%{
|
329
|
+
UUID=$(xe vm-list name-label=#{hostname} | grep uuid | sed -e 's|.*: ||')
|
330
|
+
DOMID=$(xe vm-param-get uuid=$UUID param-name="dom-id")
|
331
|
+
xenstore-write -s /local/domain/$DOMID/vm-data/hostname '#{hostname}'
|
332
|
+
xenstore-write -s /local/domain/$DOMID/vm-data/networking/123_nw_info '#{network_info.to_json}'
|
333
|
+
|
334
|
+
xenstore-write -s /local/domain/$DOMID/data/host/123_reset_nw '{"name": "resetnetwork", "value": ""}'
|
335
|
+
xenstore-rm -s /local/domain/$DOMID/data/guest/123_reset_nw 2> /dev/null
|
336
|
+
until [ -n "$NW_RETVAL" ]; do
|
337
|
+
NW_RETVAL=$(xenstore-read -s /local/domain/$DOMID/data/guest/123_reset_nw 2> /dev/null)
|
338
|
+
done
|
339
|
+
xenstore-rm -s /local/domain/$DOMID/data/host/123_reset_nw
|
340
|
+
xe vm-reboot uuid=$UUID &> /dev/null
|
341
|
+
COUNT=0
|
342
|
+
until ping -c 1 #{hostname} &> /dev/null; do
|
343
|
+
COUNT=$(( $COUNT + 1 ))
|
344
|
+
[ $COUNT -eq 10 ] && break
|
345
|
+
done
|
346
|
+
until ssh -o ConnectTimeout=1 #{hostname} &> /dev/null; do
|
347
|
+
COUNT=$(( $COUNT + 1 ))
|
348
|
+
[ $COUNT -eq 20 ] && break
|
349
|
+
sleep 1
|
350
|
+
done
|
351
|
+
ssh #{hostname} bash <<-EOF_SSH_BASH
|
352
|
+
hostname #{hostname}
|
353
|
+
EOF_SSH_BASH
|
354
|
+
scp /etc/hosts #{hostname}:/etc/hosts
|
355
|
+
}, gw_ip) do |ok, out|
|
356
|
+
puts out
|
357
|
+
if not ok
|
358
|
+
puts out
|
359
|
+
raise "Failed to setup static networking for #{hostname}."
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
end
|
364
|
+
|
365
|
+
end
|
366
|
+
|
367
|
+
end
|
368
|
+
|
369
|
+
end
|
370
|
+
|
371
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'kytoon/providers/cloud_servers_vpc'
|
2
|
+
require 'kytoon/providers/xenserver'
|
3
|
+
|
4
|
+
class ServerGroup
|
5
|
+
|
6
|
+
@@group_class = nil
|
7
|
+
|
8
|
+
# called to init the configured group class we will use
|
9
|
+
def self.init
|
10
|
+
return if not @@group_class.nil?
|
11
|
+
configs = Util.load_configs
|
12
|
+
group_type = ENV['GROUP_TYPE'] || configs['group_type']
|
13
|
+
if group_type == "cloud_server_vpc" then
|
14
|
+
@@group_class = Kytoon::Providers::CloudServersVPC::ServerGroup
|
15
|
+
elsif group_type == "xenserver" then
|
16
|
+
@@group_class = Kytoon::Providers::Xenserver::ServerGroup
|
17
|
+
else
|
18
|
+
raise "Invalid 'group_type' specified in config file."
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.index(options)
|
23
|
+
@@group_class.index(options)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.create
|
27
|
+
json_config_file=ENV['SERVER_GROUP_JSON']
|
28
|
+
if json_config_file.nil? then
|
29
|
+
json_config_file = @@group_class::CONFIG_FILE
|
30
|
+
end
|
31
|
+
sg = @@group_class.from_json(IO.read(json_config_file))
|
32
|
+
@@group_class.create(sg)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.get
|
36
|
+
id = ENV['GROUP_ID']
|
37
|
+
@@group_class.get(:id => id)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.delete
|
41
|
+
id = ENV['GROUP_ID']
|
42
|
+
sg = @@group_class.get(:id => id)
|
43
|
+
sg.delete
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|