rbeapi 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +16 -0
- data/Gemfile +3 -1
- data/Guardfile +2 -2
- data/README.md +35 -24
- data/Rakefile +48 -18
- data/gems/inifile/inifile.spec.tmpl +50 -14
- data/gems/net_http_unix/net_http_unix.spec.tmpl +48 -15
- data/gems/netaddr/netaddr.spec.tmpl +47 -14
- data/lib/rbeapi/api/bgp.rb +100 -4
- data/lib/rbeapi/api/interfaces.rb +4 -5
- data/lib/rbeapi/api/radius.rb +1 -1
- data/lib/rbeapi/api/routemaps.rb +405 -37
- data/lib/rbeapi/api/system.rb +21 -0
- data/lib/rbeapi/api/users.rb +361 -0
- data/lib/rbeapi/api/varp.rb +50 -22
- data/lib/rbeapi/api/vrrp.rb +1072 -0
- data/lib/rbeapi/client.rb +12 -4
- data/lib/rbeapi/eapilib.rb +1 -1
- data/lib/rbeapi/version.rb +1 -1
- data/rbeapi.spec.tmpl +57 -25
- data/spec/system/rbeapi/api/dns_spec.rb +2 -2
- data/spec/system/rbeapi/api/routemaps_spec.rb +344 -0
- data/spec/system/rbeapi/api/switchports_spec.rb +1 -1
- data/spec/system/rbeapi/api/system_spec.rb +44 -4
- data/spec/system/{api_varp_interfaces_spec.rb → rbeapi/api/varp_interfaces_spec.rb} +25 -16
- data/spec/system/rbeapi/api/varp_spec.rb +76 -0
- data/spec/unit/rbeapi/api/bgp/bgp_neighbors_spec.rb +2 -0
- data/spec/unit/rbeapi/api/bgp/bgp_spec.rb +54 -1
- data/spec/unit/rbeapi/api/interfaces/portchannel_spec.rb +1 -1
- data/spec/unit/rbeapi/api/routemaps/default_spec.rb +370 -0
- data/spec/unit/rbeapi/api/routemaps/fixture_routemaps.text +27 -0
- data/spec/unit/rbeapi/api/system/default_spec.rb +102 -0
- data/spec/unit/rbeapi/api/system/fixture_system.text +2 -0
- data/spec/unit/rbeapi/api/users/default_spec.rb +280 -0
- data/spec/unit/rbeapi/api/users/fixture_users.text +4 -0
- data/spec/unit/rbeapi/api/vrrp/default_spec.rb +582 -0
- data/spec/unit/rbeapi/api/vrrp/fixture_vrrp.text +186 -0
- metadata +28 -8
- data/spec/system/api_varp_spec.rb +0 -41
@@ -0,0 +1,102 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2015, Arista Networks, Inc.
|
3
|
+
# All rights reserved.
|
4
|
+
#
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions are
|
7
|
+
# met:
|
8
|
+
#
|
9
|
+
# Redistributions of source code must retain the above copyright notice,
|
10
|
+
# this list of conditions and the following disclaimer.
|
11
|
+
#
|
12
|
+
# Redistributions in binary form must reproduce the above copyright
|
13
|
+
# notice, this list of conditions and the following disclaimer in the
|
14
|
+
# documentation and/or other materials provided with the distribution.
|
15
|
+
#
|
16
|
+
# Neither the name of Arista Networks nor the names of its
|
17
|
+
# contributors may be used to endorse or promote products derived from
|
18
|
+
# this software without specific prior written permission.
|
19
|
+
#
|
20
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
21
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
22
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
23
|
+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ARISTA NETWORKS
|
24
|
+
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
25
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
26
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
27
|
+
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
28
|
+
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
29
|
+
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
30
|
+
# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
31
|
+
#
|
32
|
+
require 'spec_helper'
|
33
|
+
|
34
|
+
require 'rbeapi/api/system'
|
35
|
+
|
36
|
+
include FixtureHelpers
|
37
|
+
|
38
|
+
describe Rbeapi::Api::System do
|
39
|
+
subject { described_class.new(node) }
|
40
|
+
|
41
|
+
let(:node) { double('node') }
|
42
|
+
|
43
|
+
let(:test) do
|
44
|
+
{ hostname: 'localhost', iprouting: true }
|
45
|
+
end
|
46
|
+
|
47
|
+
def system
|
48
|
+
system = Fixtures[:system]
|
49
|
+
return system if system
|
50
|
+
fixture('system', format: :text, dir: File.dirname(__FILE__))
|
51
|
+
end
|
52
|
+
|
53
|
+
before :each do
|
54
|
+
allow(subject.node).to receive(:running_config).and_return(system)
|
55
|
+
end
|
56
|
+
|
57
|
+
describe '#get' do
|
58
|
+
it 'returns the username collection' do
|
59
|
+
expect(subject.get).to include(test)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'returns a hash collection' do
|
63
|
+
expect(subject.get).to be_a_kind_of(Hash)
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'has two entries' do
|
67
|
+
expect(subject.get.size).to eq(2)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe '#set_hostname' do
|
72
|
+
it 'sets the hostname' do
|
73
|
+
expect(node).to receive(:config).with('hostname localhost')
|
74
|
+
expect(subject.set_hostname(value: 'localhost')).to be_truthy
|
75
|
+
expect(subject.get[:hostname]).to eq('localhost')
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe '#set_iprouting' do
|
80
|
+
it 'sets ip routing default true' do
|
81
|
+
expect(node).to receive(:config).with('default ip routing')
|
82
|
+
expect(subject.set_iprouting(default: true)).to be_truthy
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'sets ip routing default false' do
|
86
|
+
expect(node).to receive(:config).with('ip routing')
|
87
|
+
expect(subject.set_iprouting(default: false)).to be_truthy
|
88
|
+
expect(subject.get[:iprouting]).to eq(true)
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'sets ip routing enable true' do
|
92
|
+
expect(node).to receive(:config).with('ip routing')
|
93
|
+
expect(subject.set_iprouting(enable: true)).to be_truthy
|
94
|
+
expect(subject.get[:iprouting]).to eq(true)
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'sets ip routing enable false' do
|
98
|
+
expect(node).to receive(:config).with('no ip routing')
|
99
|
+
expect(subject.set_iprouting(enable: false)).to be_truthy
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,280 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2015, Arista Networks, Inc.
|
3
|
+
# All rights reserved.
|
4
|
+
#
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions are
|
7
|
+
# met:
|
8
|
+
#
|
9
|
+
# Redistributions of source code must retain the above copyright notice,
|
10
|
+
# this list of conditions and the following disclaimer.
|
11
|
+
#
|
12
|
+
# Redistributions in binary form must reproduce the above copyright
|
13
|
+
# notice, this list of conditions and the following disclaimer in the
|
14
|
+
# documentation and/or other materials provided with the distribution.
|
15
|
+
#
|
16
|
+
# Neither the name of Arista Networks nor the names of its
|
17
|
+
# contributors may be used to endorse or promote products derived from
|
18
|
+
# this software without specific prior written permission.
|
19
|
+
#
|
20
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
21
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
22
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
23
|
+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ARISTA NETWORKS
|
24
|
+
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
25
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
26
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
27
|
+
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
28
|
+
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
29
|
+
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
30
|
+
# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
31
|
+
#
|
32
|
+
require 'spec_helper'
|
33
|
+
|
34
|
+
require 'rbeapi/api/users'
|
35
|
+
|
36
|
+
include FixtureHelpers
|
37
|
+
|
38
|
+
describe Rbeapi::Api::Users do
|
39
|
+
subject { described_class.new(node) }
|
40
|
+
|
41
|
+
let(:node) { double('node') }
|
42
|
+
|
43
|
+
let(:sshkey) do
|
44
|
+
'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDKL1UtBALa4CvFUsHUipN' \
|
45
|
+
'ymA04qCXuAtTwNcMj84bTUzUI+q7mdzRCTLkllXeVxKuBnaTm2PW7W67K5C' \
|
46
|
+
'Vpl0EVCm6IY7FS7kc4nlnD/tFvTvShy/fzYQRAdM7ZfVtegW8sMSFJzBR/T' \
|
47
|
+
'/Y/sxI16Y/dQb8fC3la9T25XOrzsFrQiKRZmJGwg8d+0RLxpfMg0s/9ATwQ' \
|
48
|
+
'Kp6tPoLE4f3dKlAgSk5eENyVLA3RsypWADHpenHPcB7sa8D38e1TS+n+EUy' \
|
49
|
+
'Adb3Yov+5ESAbgLIJLd52Xv+FyYi0c2L49ByBjcRrupp4zfXn4DNRnEG4K6' \
|
50
|
+
'GcmswHuMEGZv5vjJ9OYaaaaaaa'
|
51
|
+
end
|
52
|
+
|
53
|
+
let(:test) do
|
54
|
+
{ name: 'rbeapi',
|
55
|
+
privilege: 1,
|
56
|
+
role: nil,
|
57
|
+
nopassword: false,
|
58
|
+
encryption: 'md5',
|
59
|
+
secret: '$1$Ehb5lL0D$N3MgrkfMFxmeh0FSZ5sEZ1',
|
60
|
+
sshkey: sshkey
|
61
|
+
}
|
62
|
+
end
|
63
|
+
let(:name) { test[:name] }
|
64
|
+
|
65
|
+
def users
|
66
|
+
users = Fixtures[:users]
|
67
|
+
return users if users
|
68
|
+
fixture('users', format: :text, dir: File.dirname(__FILE__))
|
69
|
+
end
|
70
|
+
|
71
|
+
before :each do
|
72
|
+
allow(subject.node).to receive(:running_config).and_return(users)
|
73
|
+
end
|
74
|
+
|
75
|
+
describe '#getall' do
|
76
|
+
let(:test1_entries) do
|
77
|
+
{ 'admin' => { name: 'admin', privilege: 1,
|
78
|
+
role: 'network-admin', nopassword: true,
|
79
|
+
encryption: nil, secret: nil, sshkey: nil },
|
80
|
+
'rbeapi' => { name: 'rbeapi', privilege: 1, role: nil,
|
81
|
+
nopassword: false, encryption: 'md5',
|
82
|
+
secret: '$1$Ehb5lL0D$N3MgrkfMFxmeh0FSZ5sEZ1',
|
83
|
+
sshkey: sshkey },
|
84
|
+
'rbeapi1' => { name: 'rbeapi1', privilege: 2,
|
85
|
+
role: 'network-minon', nopassword: false,
|
86
|
+
encryption: 'cleartext', secret: 'icanttellyou',
|
87
|
+
sshkey: nil }
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'returns the username collection' do
|
92
|
+
expect(subject.getall).to include(test1_entries)
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'returns a hash collection' do
|
96
|
+
expect(subject.getall).to be_a_kind_of(Hash)
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'has two entries' do
|
100
|
+
expect(subject.getall.size).to eq(3)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe '#get' do
|
105
|
+
it 'returns the user resource for given name' do
|
106
|
+
expect(subject.get(name)).to eq(test)
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'returns a hash' do
|
110
|
+
expect(subject.get(name)).to be_a_kind_of(Hash)
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'has two entries' do
|
114
|
+
expect(subject.get(name).size).to eq(7)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe '#create' do
|
119
|
+
it 'create a new user name with no password' do
|
120
|
+
expect(node).to receive(:config).with(['username rbeapi nopassword'])
|
121
|
+
expect(subject.create('rbeapi', nopassword: :true)).to be_truthy
|
122
|
+
end
|
123
|
+
it 'create a new user name with no password and privilege' do
|
124
|
+
expect(node).to receive(:config)
|
125
|
+
.with(['username rbeapi privilege 4 nopassword'])
|
126
|
+
expect(subject.create('rbeapi',
|
127
|
+
privilege: 4,
|
128
|
+
nopassword: :true)).to be_truthy
|
129
|
+
end
|
130
|
+
it 'create a new user name with no password, privilege, and role' do
|
131
|
+
expect(node).to receive(:config)
|
132
|
+
.with(['username rbeapi privilege 4 role net-minion nopassword'])
|
133
|
+
expect(subject.create('rbeapi',
|
134
|
+
privilege: 4,
|
135
|
+
role: 'net-minion',
|
136
|
+
nopassword: :true)).to be_truthy
|
137
|
+
end
|
138
|
+
it 'create a new user name with a password' do
|
139
|
+
expect(node).to receive(:config)
|
140
|
+
.with(['username rbeapi secret 0 icanttellyou'])
|
141
|
+
expect(subject.create('rbeapi', secret: 'icanttellyou')).to be_truthy
|
142
|
+
end
|
143
|
+
it 'create a new user name with a password and privilege' do
|
144
|
+
expect(node).to receive(:config)
|
145
|
+
.with(['username rbeapi privilege 5 secret 0 icanttellyou'])
|
146
|
+
expect(subject.create('rbeapi',
|
147
|
+
secret: 'icanttellyou',
|
148
|
+
privilege: 5)).to be_truthy
|
149
|
+
end
|
150
|
+
it 'create a new user name with a password, privilege, and role' do
|
151
|
+
expect(node).to receive(:config)
|
152
|
+
.with(['username rbeapi privilege 5 role net secret 0 icanttellyou'])
|
153
|
+
expect(subject.create('rbeapi',
|
154
|
+
secret: 'icanttellyou',
|
155
|
+
privilege: 5, role: 'net')).to be_truthy
|
156
|
+
end
|
157
|
+
it 'create a new user name with a password and md5 encryption' do
|
158
|
+
expect(node).to receive(:config)
|
159
|
+
.with(['username rbeapi secret 5 icanttellyou'])
|
160
|
+
expect(subject.create('rbeapi',
|
161
|
+
secret: 'icanttellyou',
|
162
|
+
encryption: 'md5')).to be_truthy
|
163
|
+
end
|
164
|
+
it 'create a new user name with a password and sha512 encryption' do
|
165
|
+
expect(node).to receive(:config)
|
166
|
+
.with(['username rbeapi secret sha512 icanttellyou'])
|
167
|
+
expect(subject.create('rbeapi',
|
168
|
+
secret: 'icanttellyou',
|
169
|
+
encryption: 'sha512')).to be_truthy
|
170
|
+
end
|
171
|
+
it 'create a new user name with a password, sha512 encryption, and key' do
|
172
|
+
expect(node).to receive(:config)
|
173
|
+
.with(['username rbeapi secret sha512 icanttellyou',
|
174
|
+
"username rbeapi sshkey #{sshkey}"])
|
175
|
+
expect(subject.create('rbeapi',
|
176
|
+
secret: 'icanttellyou',
|
177
|
+
encryption: 'sha512',
|
178
|
+
sshkey: sshkey)).to be_truthy
|
179
|
+
end
|
180
|
+
it 'raises ArgumentError for create without required args ' do
|
181
|
+
expect { subject.create('rbeapi') }.to \
|
182
|
+
raise_error ArgumentError
|
183
|
+
end
|
184
|
+
it 'raises ArgumentError for invalid encryption value' do
|
185
|
+
expect { subject.create('name', encryption: 'bogus') }.to \
|
186
|
+
raise_error ArgumentError
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
describe '#delete' do
|
191
|
+
it 'delete a username resource' do
|
192
|
+
expect(node).to receive(:config).with('no username user1')
|
193
|
+
expect(subject.delete('user1')).to be_truthy
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
describe '#default' do
|
198
|
+
it 'sets username resource to default value' do
|
199
|
+
expect(node).to receive(:config)
|
200
|
+
.with('default username user1')
|
201
|
+
expect(subject.default('user1')).to be_truthy
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
describe '#set_privilege' do
|
206
|
+
it 'set the privilege' do
|
207
|
+
expect(node).to receive(:config).with('username rbeapi privilege 13')
|
208
|
+
expect(subject.set_privilege('rbeapi', value: '13')).to be_truthy
|
209
|
+
end
|
210
|
+
|
211
|
+
it 'remove the privilege without a value' do
|
212
|
+
expect(node).to receive(:config).with('no username rbeapi privilege')
|
213
|
+
expect(subject.set_privilege('rbeapi', enable: false)).to be_truthy
|
214
|
+
end
|
215
|
+
|
216
|
+
it 'remove the privilege with a value' do
|
217
|
+
expect(node).to receive(:config).with('no username rbeapi privilege 13')
|
218
|
+
expect(subject.set_privilege('rbeapi', value: '13', enable: false))
|
219
|
+
.to be_truthy
|
220
|
+
end
|
221
|
+
|
222
|
+
it 'defaults the privilege without a value' do
|
223
|
+
expect(node).to receive(:config).with('default username rbeapi privilege')
|
224
|
+
expect(subject.set_privilege('rbeapi', default: true)).to be_truthy
|
225
|
+
end
|
226
|
+
|
227
|
+
it 'defaults the privilege with a value' do
|
228
|
+
expect(node).to receive(:config).with('default username rb privilege 3')
|
229
|
+
expect(subject.set_privilege('rb', value: '3', default: true))
|
230
|
+
.to be_truthy
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
describe '#set_role' do
|
235
|
+
it 'set the role' do
|
236
|
+
expect(node).to receive(:config).with('username rbeapi role net-minion')
|
237
|
+
expect(subject.set_role('rbeapi', value: 'net-minion')).to be_truthy
|
238
|
+
end
|
239
|
+
|
240
|
+
it 'remove the role without a value' do
|
241
|
+
expect(node).to receive(:config).with('no username rbeapi role')
|
242
|
+
expect(subject.set_role('rbeapi', enable: false)).to be_truthy
|
243
|
+
end
|
244
|
+
|
245
|
+
it 'remove the role with a value' do
|
246
|
+
expect(node).to receive(:config).with('no username rbeapi role net')
|
247
|
+
expect(subject.set_role('rbeapi', value: 'net', enable: false))
|
248
|
+
.to be_truthy
|
249
|
+
end
|
250
|
+
|
251
|
+
it 'defaults the role without a value' do
|
252
|
+
expect(node).to receive(:config).with('default username rbeapi role')
|
253
|
+
expect(subject.set_role('rbeapi', default: true)).to be_truthy
|
254
|
+
end
|
255
|
+
|
256
|
+
it 'defaults the role with a value' do
|
257
|
+
expect(node).to receive(:config).with('default username rbeapi role net')
|
258
|
+
expect(subject.set_role('rbeapi', value: 'net', default: true))
|
259
|
+
.to be_truthy
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
describe '#set_sshkey' do
|
264
|
+
it 'set the sshkey' do
|
265
|
+
expect(node).to receive(:config).with("username rbeapi sshkey #{sshkey}")
|
266
|
+
expect(subject.set_sshkey('rbeapi', value: sshkey)).to be_truthy
|
267
|
+
end
|
268
|
+
|
269
|
+
it 'remove the sshkey with a value' do
|
270
|
+
expect(node).to receive(:config).with("no username rb sshkey #{sshkey}")
|
271
|
+
expect(subject.set_sshkey('rb', value: sshkey, enable: false))
|
272
|
+
.to be_truthy
|
273
|
+
end
|
274
|
+
|
275
|
+
it 'defaults the sshkey without a value' do
|
276
|
+
expect(node).to receive(:config).with('default username rbeapi sshkey')
|
277
|
+
expect(subject.set_sshkey('rbeapi', default: true)).to be_truthy
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
@@ -0,0 +1,4 @@
|
|
1
|
+
username admin privilege 1 role network-admin nopassword
|
2
|
+
username rbeapi1 privilege 2 role network-minon secret 0 icanttellyou
|
3
|
+
username rbeapi privilege 1 secret 5 $1$Ehb5lL0D$N3MgrkfMFxmeh0FSZ5sEZ1
|
4
|
+
username rbeapi sshkey ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDKL1UtBALa4CvFUsHUipNymA04qCXuAtTwNcMj84bTUzUI+q7mdzRCTLkllXeVxKuBnaTm2PW7W67K5CVpl0EVCm6IY7FS7kc4nlnD/tFvTvShy/fzYQRAdM7ZfVtegW8sMSFJzBR/T/Y/sxI16Y/dQb8fC3la9T25XOrzsFrQiKRZmJGwg8d+0RLxpfMg0s/9ATwQKp6tPoLE4f3dKlAgSk5eENyVLA3RsypWADHpenHPcB7sa8D38e1TS+n+EUyAdb3Yov+5ESAbgLIJLd52Xv+FyYi0c2L49ByBjcRrupp4zfXn4DNRnEG4K6GcmswHuMEGZv5vjJ9OYaaaaaaa
|