openstack_activeresource 0.1.13 → 0.2.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.
- data/VERSION +1 -1
- data/lib/locales/openstack_activeresource.en.yml +11 -0
- data/lib/open_stack/glance/base.rb +7 -0
- data/lib/open_stack/keystone/admin/base.rb +7 -0
- data/lib/open_stack/keystone/admin/user.rb +8 -5
- data/lib/open_stack/keystone/admin/user_role.rb +1 -5
- data/lib/open_stack/keystone/public/base.rb +7 -0
- data/lib/open_stack/nova/compute/base.rb +7 -0
- data/lib/open_stack/nova/compute/key_pair.rb +4 -0
- data/lib/open_stack/nova/compute/server.rb +5 -0
- data/lib/open_stack/nova/compute/volume_attachment.rb +1 -4
- data/lib/open_stack/nova/volume/base.rb +7 -0
- data/openstack_activeresource.gemspec +4 -2
- data/test/.gitignore +2 -0
- data/test/helper.rb +14 -0
- data/test/test_configuration-sample.yml +11 -0
- data/test/test_openstack-activeresource.rb +284 -2
- metadata +5 -3
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
@@ -23,3 +23,14 @@ en:
|
|
23
23
|
unknown: "unknown"
|
24
24
|
user_powered_down: "user powered down"
|
25
25
|
|
26
|
+
tasks:
|
27
|
+
block_device_mapping: "block device mapping"
|
28
|
+
deleting: "deleting"
|
29
|
+
networking: "networking"
|
30
|
+
pausing: "pausing"
|
31
|
+
rebooting_hard: "rebooting hard"
|
32
|
+
scheduling: "scheduling"
|
33
|
+
spawning: "spawning"
|
34
|
+
starting: "starting"
|
35
|
+
stopping: "stopping"
|
36
|
+
unpausing: "unpausing"
|
@@ -31,6 +31,13 @@ module OpenStack
|
|
31
31
|
def self.site=(site)
|
32
32
|
super(site)
|
33
33
|
Thread.current[:open_stack_glance_site] = @site
|
34
|
+
# Regenerate the prefix method
|
35
|
+
default = @site.path
|
36
|
+
default << '/' unless default[-1..-1] == '/'
|
37
|
+
# generate the actual method based on the current site path
|
38
|
+
self.prefix = default
|
39
|
+
|
40
|
+
@site
|
34
41
|
end
|
35
42
|
|
36
43
|
end
|
@@ -32,6 +32,13 @@ module OpenStack
|
|
32
32
|
def self.site=(site)
|
33
33
|
super(site)
|
34
34
|
Thread.current[:open_stack_keystone_admin_site] = @site
|
35
|
+
# Regenerate the prefix method
|
36
|
+
default = @site.path
|
37
|
+
default << '/' unless default[-1..-1] == '/'
|
38
|
+
# generate the actual method based on the current site path
|
39
|
+
self.prefix = default
|
40
|
+
|
41
|
+
@site
|
35
42
|
end
|
36
43
|
|
37
44
|
end
|
@@ -82,13 +82,16 @@ module OpenStack
|
|
82
82
|
end
|
83
83
|
|
84
84
|
def self.find_by_name(name)
|
85
|
-
all.
|
86
|
-
|
87
|
-
|
85
|
+
all.detect { |user| user.name == name }
|
86
|
+
end
|
87
|
+
|
88
|
+
def tenant
|
89
|
+
OpenStack::Keystone::Admin::Tenant.find tenant_id
|
88
90
|
end
|
89
91
|
|
90
|
-
def roles(scope = :all)
|
91
|
-
OpenStack::Keystone::Admin::
|
92
|
+
def roles(scope = :all, tenant = nil)
|
93
|
+
tenant_id = tenant.is_a?(OpenStack::Keystone::Admin::Tenant) ? tenant.id : (tenant || self.tenant_id)
|
94
|
+
OpenStack::Keystone::Admin::UserRole.find(scope, :params => { :tenant_id => tenant_id, :user_id => self.id })
|
92
95
|
end
|
93
96
|
|
94
97
|
end
|
@@ -32,6 +32,13 @@ module OpenStack
|
|
32
32
|
def self.site=(site)
|
33
33
|
super(site)
|
34
34
|
Thread.current[:open_stack_keystone_public_site] = @site
|
35
|
+
# Regenerate the prefix method
|
36
|
+
default = @site.path
|
37
|
+
default << '/' unless default[-1..-1] == '/'
|
38
|
+
# generate the actual method based on the current site path
|
39
|
+
self.prefix = default
|
40
|
+
|
41
|
+
@site
|
35
42
|
end
|
36
43
|
|
37
44
|
end
|
@@ -32,6 +32,13 @@ module OpenStack
|
|
32
32
|
def self.site=(site)
|
33
33
|
super(site)
|
34
34
|
Thread.current[:open_stack_nova_compute_site] = @site
|
35
|
+
# Regenerate the prefix method
|
36
|
+
default = @site.path
|
37
|
+
default << '/' unless default[-1..-1] == '/'
|
38
|
+
# generate the actual method based on the current site path
|
39
|
+
self.prefix = default
|
40
|
+
|
41
|
+
@site
|
35
42
|
end
|
36
43
|
|
37
44
|
end
|
@@ -220,6 +220,11 @@ module OpenStack
|
|
220
220
|
SERVER_STATUSES[status]
|
221
221
|
end
|
222
222
|
|
223
|
+
# Returns a localized description for the server task (if any)
|
224
|
+
def task_description
|
225
|
+
I18n.t(task, :scope => [:openstack, :tasks]) if task.present?
|
226
|
+
end
|
227
|
+
|
223
228
|
## Actions
|
224
229
|
|
225
230
|
# Assign a floating IP to the server.
|
@@ -22,10 +22,7 @@ module OpenStack
|
|
22
22
|
class VolumeAttachment < Base
|
23
23
|
self.element_name = "volumeAttachment"
|
24
24
|
self.collection_name = "os-volume_attachments"
|
25
|
-
|
26
|
-
def self.site
|
27
|
-
superclass.site + "servers/:server_id"
|
28
|
-
end
|
25
|
+
self.site = superclass.site + "servers/:server_id"
|
29
26
|
|
30
27
|
schema do
|
31
28
|
attribute :device, :string
|
@@ -32,6 +32,13 @@ module OpenStack
|
|
32
32
|
def self.site=(site)
|
33
33
|
super(site)
|
34
34
|
Thread.current[:open_stack_nova_volume_site] = @site
|
35
|
+
# Regenerate the prefix method
|
36
|
+
default = @site.path
|
37
|
+
default << '/' unless default[-1..-1] == '/'
|
38
|
+
# generate the actual method based on the current site path
|
39
|
+
self.prefix = default
|
40
|
+
|
41
|
+
@site
|
35
42
|
end
|
36
43
|
|
37
44
|
end
|
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "openstack_activeresource"
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.2.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Davide Guerri"]
|
12
|
-
s.date = "2013-
|
12
|
+
s.date = "2013-02-06"
|
13
13
|
s.description = "OpenStack Ruby and RoR bindings implemented with ActiveResource - See also http://www.unicloud.it"
|
14
14
|
s.email = "d.guerri@rd.unidata.it"
|
15
15
|
s.extra_rdoc_files = [
|
@@ -62,7 +62,9 @@ Gem::Specification.new do |s|
|
|
62
62
|
"lib/open_stack/nova/volume/volume.rb",
|
63
63
|
"lib/openstack_activeresource.rb",
|
64
64
|
"openstack_activeresource.gemspec",
|
65
|
+
"test/.gitignore",
|
65
66
|
"test/helper.rb",
|
67
|
+
"test/test_configuration-sample.yml",
|
66
68
|
"test/test_openstack-activeresource.rb"
|
67
69
|
]
|
68
70
|
s.homepage = "https://github.com/Unidata-SpA/openstack_activeresource"
|
data/test/.gitignore
ADDED
data/test/helper.rb
CHANGED
@@ -16,3 +16,17 @@ require 'openstack_activeresource'
|
|
16
16
|
|
17
17
|
class Test::Unit::TestCase
|
18
18
|
end
|
19
|
+
|
20
|
+
# Load test configuration for OpenStack API
|
21
|
+
test_path = File.expand_path('..', __FILE__)
|
22
|
+
$:.unshift(test_path)
|
23
|
+
|
24
|
+
unless File.exist? "#{test_path}/test_configuration.ymla"
|
25
|
+
raise "\n****" +
|
26
|
+
"\n**** Please add a valid 'test_configuration.yml' file in '#{test_path}'." +
|
27
|
+
"\n**** See #{test_path}/test_configuration-sample.yml for an example" +
|
28
|
+
"\n****"
|
29
|
+
end
|
30
|
+
|
31
|
+
TEST_CONFIG = (YAML.load_file("#{test_path}/test_configuration.yml")['test_configuration']).with_indifferent_access
|
32
|
+
|
@@ -0,0 +1,11 @@
|
|
1
|
+
test_configuration:
|
2
|
+
public_base_site: "https://my.api.com:5000/v2.0/"
|
3
|
+
public_admin_site: "https://my.api.com:35357/v2.0/"
|
4
|
+
user_username: "user_username"
|
5
|
+
user_password: "user_password"
|
6
|
+
user_tenant_id: "user_tenant_id"
|
7
|
+
admin_username: "admin_username"
|
8
|
+
admin_password: "admin_password"
|
9
|
+
admin_tenant_id: "admin_tenant_id"
|
10
|
+
|
11
|
+
# If admin_* are omitted the corresponding tests will be skipped
|
@@ -1,7 +1,289 @@
|
|
1
|
+
lib_path = File.expand_path('../../lib', __FILE__)
|
2
|
+
$:.unshift(lib_path)
|
3
|
+
|
4
|
+
test_path = File.expand_path('..', __FILE__)
|
5
|
+
$:.unshift(test_path)
|
6
|
+
|
1
7
|
require 'helper'
|
8
|
+
require 'openstack_activeresource'
|
2
9
|
|
3
10
|
class TestOpenStackActiveResource < Test::Unit::TestCase
|
4
|
-
|
5
|
-
|
11
|
+
|
12
|
+
# Keystone
|
13
|
+
|
14
|
+
# Authentication
|
15
|
+
|
16
|
+
def test_authentication
|
17
|
+
OpenStack::Keystone::Public::Base.site = TEST_CONFIG[:public_base_site]
|
18
|
+
|
19
|
+
# User auth
|
20
|
+
assert_nothing_raised ActiveResource::ClientError, "Cannot authenticate as user" do
|
21
|
+
auth = OpenStack::Keystone::Public::Auth.create :username => TEST_CONFIG[:user_username],
|
22
|
+
:password => TEST_CONFIG[:user_password],
|
23
|
+
:tenant_id => TEST_CONFIG[:user_tenant_id]
|
24
|
+
|
25
|
+
assert_not_nil auth.token, "Cannot authenticate as user"
|
26
|
+
|
27
|
+
auth = OpenStack::Keystone::Public::Auth.create :username => "baduser",
|
28
|
+
:password => "badpassword",
|
29
|
+
:tenant_id => TEST_CONFIG[:user_tenant_id]
|
30
|
+
|
31
|
+
assert_nil auth.token, "Authentication seems broken!"
|
32
|
+
end
|
33
|
+
|
34
|
+
# Admin auth
|
35
|
+
return unless admin_test_possible?
|
36
|
+
|
37
|
+
assert_nothing_raised ActiveResource::ClientError, "Cannot authenticate as admin" do
|
38
|
+
auth = OpenStack::Keystone::Public::Auth.create :username => TEST_CONFIG[:admin_username],
|
39
|
+
:password => TEST_CONFIG[:admin_password],
|
40
|
+
:tenant_id => TEST_CONFIG[:admin_tenant_id]
|
41
|
+
|
42
|
+
assert_not_nil auth.token, "Cannot authenticate as admin"
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_list_tenant
|
48
|
+
return unless admin_test_possible?
|
49
|
+
|
50
|
+
auth_admin
|
51
|
+
|
52
|
+
assert_nothing_raised ActiveResource::ClientError, "Cannot list tenants" do
|
53
|
+
tenants = OpenStack::Keystone::Public::Tenant.all
|
54
|
+
|
55
|
+
assert_block("No tenants?") do
|
56
|
+
!tenants.empty?
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_simple_tenant_usage
|
63
|
+
return unless admin_test_possible?
|
64
|
+
|
65
|
+
auth_admin
|
66
|
+
|
67
|
+
assert_nothing_raised ActiveResource::ClientError, "Cannot retrieve simple_usages" do
|
68
|
+
simple_usages = OpenStack::Nova::Compute::SimpleTenantUsage.find_from_date(:all, 5.days.ago)
|
69
|
+
|
70
|
+
assert_not_nil simple_usages
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
# Nova
|
76
|
+
|
77
|
+
## Flavors
|
78
|
+
|
79
|
+
def test_list_flavor
|
80
|
+
auth_user
|
81
|
+
|
82
|
+
assert_nothing_raised ActiveResource::ClientError, "Cannot list flavors" do
|
83
|
+
flavors = OpenStack::Nova::Compute::Flavor.all
|
84
|
+
|
85
|
+
assert_block("No flavors?") do
|
86
|
+
!flavors.empty?
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Images
|
92
|
+
|
93
|
+
def test_list_images
|
94
|
+
auth_user
|
95
|
+
|
96
|
+
assert_nothing_raised ActiveResource::ClientError, "Cannot list images" do
|
97
|
+
images = OpenStack::Nova::Compute::Image.all
|
98
|
+
|
99
|
+
assert_block("No images?") do
|
100
|
+
!images.empty?
|
101
|
+
end
|
102
|
+
end
|
6
103
|
end
|
104
|
+
|
105
|
+
# Security groups
|
106
|
+
|
107
|
+
def test_list_security_groups
|
108
|
+
auth_user
|
109
|
+
|
110
|
+
assert_nothing_raised ActiveResource::ClientError, "Cannot list security group" do
|
111
|
+
security_groups = OpenStack::Nova::Compute::SecurityGroup.all
|
112
|
+
|
113
|
+
assert_block("No security_groups?") do
|
114
|
+
!security_groups.empty?
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Keypairs
|
120
|
+
|
121
|
+
def test_keypair_list
|
122
|
+
auth_user
|
123
|
+
|
124
|
+
assert_nothing_raised ActiveResource::ClientError, "Cannot list keypair" do
|
125
|
+
keys = OpenStack::Nova::Compute::KeyPair.all
|
126
|
+
|
127
|
+
assert_not_nil keys, "Cannot retrieve key-pairs"
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
def test_keypair_create_destroy
|
133
|
+
auth_user
|
134
|
+
|
135
|
+
keypair_name = '___my_new_keypair'
|
136
|
+
key = nil
|
137
|
+
assert_nothing_raised ActiveResource::ClientError, "Cannot create key pair" do
|
138
|
+
key = OpenStack::Nova::Compute::KeyPair.create :name => keypair_name
|
139
|
+
end
|
140
|
+
assert_not_nil key, "Cannot create key pair"
|
141
|
+
|
142
|
+
key = nil
|
143
|
+
assert_nothing_raised ActiveResource::ClientError, "Cannot find keypair '#{keypair_name}'" do
|
144
|
+
key = OpenStack::Nova::Compute::KeyPair.find_by_name keypair_name
|
145
|
+
end
|
146
|
+
|
147
|
+
assert_not_nil key, "Cannot find key pair"
|
148
|
+
|
149
|
+
assert_nothing_raised "Cannot destroy key pair" do
|
150
|
+
key.destroy
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
# Floating IPs
|
156
|
+
def test_floating_ip_list
|
157
|
+
auth_user
|
158
|
+
|
159
|
+
assert_nothing_raised ActiveResource::ClientError, "Cannot list floating IP" do
|
160
|
+
floating_ips = OpenStack::Nova::Compute::FloatingIp.all
|
161
|
+
|
162
|
+
assert_not_nil floating_ips, "Cannot retrieve key-pairs"
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def test_floating_ip_allocation
|
167
|
+
auth_user
|
168
|
+
|
169
|
+
assert_nothing_raised ActiveResource::ClientError, "Cannot list floating IP" do
|
170
|
+
floating_ip = nil
|
171
|
+
OpenStack::Nova::Compute::FloatingIpPool.all.each do |ip_pool|
|
172
|
+
begin
|
173
|
+
floating_ip = OpenStack::Nova::Compute::FloatingIp.create(:pool => ip_pool.name)
|
174
|
+
break
|
175
|
+
rescue ActiveResource::ClientError => e
|
176
|
+
next # Retry with the next pool
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
assert_not_nil floating_ip, "Failed to allocate a floating IP"
|
181
|
+
|
182
|
+
floating_ip.destroy
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# Servers
|
187
|
+
|
188
|
+
def test_list_server
|
189
|
+
auth_user
|
190
|
+
|
191
|
+
assert_nothing_raised ActiveResource::ClientError, "Cannot list server" do
|
192
|
+
OpenStack::Nova::Compute::Server.all
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def test_server_create_destroy
|
197
|
+
auth_user
|
198
|
+
|
199
|
+
flavor = OpenStack::Nova::Compute::Flavor.first
|
200
|
+
image = OpenStack::Nova::Compute::Image.last
|
201
|
+
security_groups = OpenStack::Nova::Compute::SecurityGroup.first
|
202
|
+
|
203
|
+
new_server_id = nil
|
204
|
+
assert_nothing_raised ActiveResource::ClientError, "Failed to create a new server" do
|
205
|
+
new_server = OpenStack::Nova::Compute::Server.create :name => 'test_server',
|
206
|
+
:flavor => flavor,
|
207
|
+
:image => image,
|
208
|
+
:security_groups => [security_groups]
|
209
|
+
assert_not_nil new_server
|
210
|
+
new_server_id = new_server.id
|
211
|
+
end
|
212
|
+
|
213
|
+
# Verify server
|
214
|
+
my_server = loop_block(5) do
|
215
|
+
begin
|
216
|
+
OpenStack::Nova::Compute::Server.find new_server_id
|
217
|
+
rescue ActiveResource::ResourceNotFound
|
218
|
+
|
219
|
+
nil
|
220
|
+
end
|
221
|
+
end
|
222
|
+
assert_not_nil my_server, "Server not spawned after 5 seconds!?!"
|
223
|
+
|
224
|
+
# Wait for a network address
|
225
|
+
my_fixed_address = loop_block(60) do
|
226
|
+
my_server = OpenStack::Nova::Compute::Server.find new_server_id
|
227
|
+
return my_server.addresses[0] if my_server.addresses.count > 0
|
228
|
+
|
229
|
+
nil
|
230
|
+
end
|
231
|
+
assert_not_nil my_fixed_address, "No fixed address after a minute!"
|
232
|
+
|
233
|
+
assert_nothing_raised ActiveResource::ClientError, "Problem retrieving the server '#{new_server_id}'" do
|
234
|
+
my_server = OpenStack::Nova::Compute::Server.find new_server_id
|
235
|
+
my_server.destroy
|
236
|
+
end
|
237
|
+
|
238
|
+
end
|
239
|
+
|
240
|
+
private
|
241
|
+
|
242
|
+
# Utilities
|
243
|
+
|
244
|
+
def auth_admin
|
245
|
+
OpenStack::Keystone::Public::Base.site = TEST_CONFIG[:public_base_site]
|
246
|
+
OpenStack::Keystone::Admin::Base.site = TEST_CONFIG[:public_admin_site]
|
247
|
+
|
248
|
+
auth = OpenStack::Keystone::Public::Auth.create :username => TEST_CONFIG[:admin_username],
|
249
|
+
:password => TEST_CONFIG[:admin_password],
|
250
|
+
:tenant_id => TEST_CONFIG[:admin_tenant_id]
|
251
|
+
|
252
|
+
OpenStack::Base.token = auth.token
|
253
|
+
OpenStack::Nova::Compute::Base.site = auth.endpoint_for('compute').publicURL
|
254
|
+
OpenStack::Nova::Volume::Base.site = auth.endpoint_for('volume').publicURL
|
255
|
+
|
256
|
+
end
|
257
|
+
|
258
|
+
def auth_user
|
259
|
+
OpenStack::Keystone::Public::Base.site = TEST_CONFIG[:public_base_site]
|
260
|
+
|
261
|
+
auth = OpenStack::Keystone::Public::Auth.create :username => TEST_CONFIG[:user_username],
|
262
|
+
:password => TEST_CONFIG[:user_password],
|
263
|
+
:tenant_id => TEST_CONFIG[:user_tenant_id]
|
264
|
+
|
265
|
+
OpenStack::Base.token = auth.token
|
266
|
+
OpenStack::Nova::Compute::Base.site = auth.endpoint_for('compute').publicURL
|
267
|
+
OpenStack::Nova::Volume::Base.site = auth.endpoint_for('volume').publicURL
|
268
|
+
|
269
|
+
end
|
270
|
+
|
271
|
+
def admin_test_possible?
|
272
|
+
TEST_CONFIG[:admin_username] and TEST_CONFIG[:admin_password] and TEST_CONFIG[:admin_tenant_id]
|
273
|
+
end
|
274
|
+
|
275
|
+
def loop_block(seconds=10)
|
276
|
+
ret = nil
|
277
|
+
if block_given? and seconds > 0
|
278
|
+
begin
|
279
|
+
ret = yield
|
280
|
+
return ret unless ret.nil?
|
281
|
+
seconds-=1
|
282
|
+
sleep 1
|
283
|
+
end while seconds > 0
|
284
|
+
end
|
285
|
+
|
286
|
+
ret
|
287
|
+
end
|
288
|
+
|
7
289
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: openstack_activeresource
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-02-06 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activemodel
|
@@ -193,7 +193,9 @@ files:
|
|
193
193
|
- lib/open_stack/nova/volume/volume.rb
|
194
194
|
- lib/openstack_activeresource.rb
|
195
195
|
- openstack_activeresource.gemspec
|
196
|
+
- test/.gitignore
|
196
197
|
- test/helper.rb
|
198
|
+
- test/test_configuration-sample.yml
|
197
199
|
- test/test_openstack-activeresource.rb
|
198
200
|
homepage: https://github.com/Unidata-SpA/openstack_activeresource
|
199
201
|
licenses:
|
@@ -210,7 +212,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
210
212
|
version: '0'
|
211
213
|
segments:
|
212
214
|
- 0
|
213
|
-
hash:
|
215
|
+
hash: 3792719235558730488
|
214
216
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
215
217
|
none: false
|
216
218
|
requirements:
|