nightcrawler_swift 0.11.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/Gemfile.lock +5 -5
- data/README.md +18 -2
- data/lib/nightcrawler_swift/commands/delete.rb +1 -1
- data/lib/nightcrawler_swift/commands/upload.rb +5 -1
- data/lib/nightcrawler_swift/connection.rb +30 -10
- data/lib/nightcrawler_swift/version.rb +1 -1
- data/spec/fixtures/auth_success.json +81 -56
- data/spec/lib/nightcrawler_swift/commands/delete_spec.rb +15 -1
- data/spec/lib/nightcrawler_swift/commands/upload_spec.rb +7 -0
- data/spec/lib/nightcrawler_swift/connection_spec.rb +55 -19
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7368872bbed33a3a4928360a3162cbec161ad31d
|
4
|
+
data.tar.gz: 7599b6e39f3e7a0c376518beed4deb89978a68f9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c4d15c9c9fc23c71b7dfa04cceaa00bf0152603fae8372c5527a5a0cbfce9bbf54438d4c334d39ad483e94b4d2ff724f90e663273e7ca833aef65341243bcbc0
|
7
|
+
data.tar.gz: 866b75e3f995ce7754712bb12b13967ff5009d2db7a5e657aba7b14bc6865b9b5222b212ba7b21c96331d6b0ed9663b57ba6bcdd7853ac5502470e21c2348bb8
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
ruby-2.1
|
1
|
+
ruby-2.4.1
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
nightcrawler_swift (0.
|
4
|
+
nightcrawler_swift (1.0.0)
|
5
5
|
concurrent-ruby (~> 0.8.0)
|
6
6
|
multi_mime (>= 1.1.0)
|
7
7
|
rest-client
|
@@ -21,7 +21,7 @@ GEM
|
|
21
21
|
debugger-linecache (1.2.0)
|
22
22
|
diff-lcs (1.2.5)
|
23
23
|
docile (1.1.5)
|
24
|
-
domain_name (0.5.
|
24
|
+
domain_name (0.5.20170404)
|
25
25
|
unf (>= 0.0.5, < 1.0.0)
|
26
26
|
http-cookie (1.0.3)
|
27
27
|
domain_name (~> 0.5)
|
@@ -33,7 +33,7 @@ GEM
|
|
33
33
|
netrc (0.11.0)
|
34
34
|
rake (10.3.2)
|
35
35
|
ref (1.0.5)
|
36
|
-
rest-client (2.0.
|
36
|
+
rest-client (2.0.2)
|
37
37
|
http-cookie (>= 1.0.2, < 2.0)
|
38
38
|
mime-types (>= 1.16, < 4.0)
|
39
39
|
netrc (~> 0.8)
|
@@ -58,7 +58,7 @@ GEM
|
|
58
58
|
timecop (0.8.0)
|
59
59
|
unf (0.1.4)
|
60
60
|
unf_ext
|
61
|
-
unf_ext (0.0.7.
|
61
|
+
unf_ext (0.0.7.4)
|
62
62
|
|
63
63
|
PLATFORMS
|
64
64
|
ruby
|
@@ -73,4 +73,4 @@ DEPENDENCIES
|
|
73
73
|
timecop
|
74
74
|
|
75
75
|
BUNDLED WITH
|
76
|
-
1.
|
76
|
+
1.15.3
|
data/README.md
CHANGED
@@ -6,6 +6,10 @@
|
|
6
6
|
|
7
7
|
Like the X-Men nightcrawler this gem teleports your assets to a OpenStack Swift bucket/container. It was designed to sync your assets with OpenStack Swift and allow some operations with your buckets/containers.
|
8
8
|
|
9
|
+
## Compatibility
|
10
|
+
|
11
|
+
nightcrawler_swift version 1.x is compatible with [Identity API v3](https://developer.openstack.org/api-ref/identity/v3/index.html). To use [API v2](https://developer.openstack.org/api-ref/identity/v2/index.html), you should install version 0.x.
|
12
|
+
|
9
13
|
## Installation
|
10
14
|
|
11
15
|
Add this line to your application's Gemfile:
|
@@ -14,16 +18,28 @@ Add this line to your application's Gemfile:
|
|
14
18
|
gem 'nightcrawler_swift'
|
15
19
|
```
|
16
20
|
|
21
|
+
Or, if you want to use Identity API v2:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
gem 'nightcrawler_swift', '< 1.0'
|
25
|
+
```
|
26
|
+
|
17
27
|
And then execute:
|
18
28
|
|
19
29
|
```sh
|
20
30
|
$ bundle
|
21
31
|
```
|
22
32
|
|
23
|
-
Or install it
|
33
|
+
Or install it manually:
|
34
|
+
|
35
|
+
```sh
|
36
|
+
$ gem install nightcrawler_swift # Install latest version
|
37
|
+
```
|
38
|
+
|
39
|
+
Or set a specific version:
|
24
40
|
|
25
41
|
```sh
|
26
|
-
$ gem install nightcrawler_swift
|
42
|
+
$ gem install nightcrawler_swift -v 0.11.1
|
27
43
|
```
|
28
44
|
|
29
45
|
## Usage
|
@@ -2,6 +2,10 @@ module NightcrawlerSwift
|
|
2
2
|
class Upload < Command
|
3
3
|
|
4
4
|
def execute path, file, opts = {}
|
5
|
+
if path.nil? or path.empty?
|
6
|
+
raise Exceptions::ValidationError.new "Upload command requires a path parameter"
|
7
|
+
end
|
8
|
+
|
5
9
|
body = file.read
|
6
10
|
headers = {etag: etag(body), content_type: content_type(file)}
|
7
11
|
|
@@ -18,7 +22,7 @@ module NightcrawlerSwift
|
|
18
22
|
headers.merge!(custom_headers) if custom_headers
|
19
23
|
|
20
24
|
response = put "#{connection.upload_url}/#{path}", body: body, headers: headers
|
21
|
-
|
25
|
+
response.code >= 200 && response.code < 300
|
22
26
|
end
|
23
27
|
|
24
28
|
private
|
@@ -50,7 +50,7 @@ module NightcrawlerSwift
|
|
50
50
|
headers = {content_type: :json, accept: :json}
|
51
51
|
response = Gateway.new(url).request {|r| r.post(auth_options.to_json, headers)}
|
52
52
|
|
53
|
-
@auth_response = OpenStruct.new(JSON.parse(response.body))
|
53
|
+
@auth_response = OpenStruct.new(headers: response.headers, body: JSON.parse(response.body))
|
54
54
|
rescue StandardError => e
|
55
55
|
raise Exceptions::ConnectionError.new(e)
|
56
56
|
end
|
@@ -58,32 +58,52 @@ module NightcrawlerSwift
|
|
58
58
|
def auth_options
|
59
59
|
{
|
60
60
|
auth: {
|
61
|
-
|
62
|
-
|
61
|
+
identity: {
|
62
|
+
methods: [
|
63
|
+
"password"
|
64
|
+
],
|
65
|
+
password: {
|
66
|
+
user: {
|
67
|
+
domain: {
|
68
|
+
id: "default"
|
69
|
+
},
|
70
|
+
name: opts.username,
|
71
|
+
password: opts.password
|
72
|
+
}
|
73
|
+
}
|
74
|
+
},
|
75
|
+
scope: {
|
76
|
+
project: {
|
77
|
+
domain: {
|
78
|
+
id: "default"
|
79
|
+
},
|
80
|
+
name: opts.tenant_name
|
81
|
+
}
|
82
|
+
}
|
63
83
|
}
|
64
84
|
}
|
65
85
|
end
|
66
86
|
|
67
87
|
def select_token
|
68
|
-
@token_id = auth_response.
|
69
|
-
@expires_at = auth_response.
|
88
|
+
@token_id = auth_response.headers[:x_subject_token]
|
89
|
+
@expires_at = auth_response.body["token"]["expires_at"]
|
70
90
|
@expires_at = DateTime.parse(@expires_at).to_time
|
71
91
|
end
|
72
92
|
|
73
93
|
def select_catalog
|
74
|
-
catalogs = auth_response
|
94
|
+
catalogs = auth_response["body"]["token"]["catalog"]
|
75
95
|
@catalog = catalogs.find {|catalog| catalog["type"] == "object-store"}
|
76
96
|
end
|
77
97
|
|
78
98
|
def select_endpoints
|
79
99
|
raise Exceptions::ConfigurationError.new "No catalog of type 'object-store' found" if @catalog.nil?
|
80
|
-
@endpoints = @catalog["endpoints"]
|
100
|
+
@endpoints = @catalog["endpoints"]
|
81
101
|
end
|
82
102
|
|
83
103
|
def configure_urls
|
84
|
-
@admin_url = opts.admin_url || @endpoints["
|
85
|
-
@public_url = opts.public_url || @endpoints["
|
86
|
-
@internal_url = opts.internal_url || @endpoints["
|
104
|
+
@admin_url = opts.admin_url || @endpoints.find {|e| e["interface"] == "admin"}["url"]
|
105
|
+
@public_url = opts.public_url || @endpoints.find {|e| e["interface"] == "public"}["url"]
|
106
|
+
@internal_url = opts.internal_url || @endpoints.find {|e| e["interface"] == "internal"}["url"]
|
87
107
|
@upload_url = "#{@admin_url}/#{opts.bucket}"
|
88
108
|
end
|
89
109
|
end
|
@@ -1,62 +1,87 @@
|
|
1
1
|
{
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
{
|
10
|
-
"publicURL" : "http://public-url-com",
|
11
|
-
"internalURL" : "http://internal-url-com",
|
12
|
-
"adminURL" : "http://api-url-com",
|
13
|
-
"region" : "RegionOne",
|
14
|
-
"id" : "1"
|
15
|
-
}
|
16
|
-
]
|
17
|
-
},
|
18
|
-
{
|
19
|
-
"name" : "keystone",
|
20
|
-
"endpoints_links" : [],
|
21
|
-
"type" : "identity",
|
22
|
-
"endpoints" : [
|
23
|
-
{
|
24
|
-
"publicURL" : "http://auth-url-com:123/v2.0",
|
25
|
-
"internalURL" : "https://auth-url-com:123/v2.0",
|
26
|
-
"adminURL" : "https://auth-url-com:321/v2.0",
|
27
|
-
"region" : "RegionOne",
|
28
|
-
"id" : "2"
|
29
|
-
}
|
30
|
-
]
|
31
|
-
}
|
2
|
+
"headers": {
|
3
|
+
"X-Subject-Token": "12345"
|
4
|
+
},
|
5
|
+
"body": {
|
6
|
+
"token": {
|
7
|
+
"methods": [
|
8
|
+
"password"
|
32
9
|
],
|
33
|
-
"
|
34
|
-
|
10
|
+
"roles": [
|
11
|
+
{
|
12
|
+
"id": "3fe2ff9ee4384b1894a90878d3e92bab",
|
13
|
+
"name": "_member_"
|
14
|
+
},
|
15
|
+
{
|
16
|
+
"id": "c573d00d11ed4f75a7cae8e7527eb1ed",
|
17
|
+
"name": "swiftoperator"
|
18
|
+
}
|
19
|
+
],
|
20
|
+
"expires_at": "2017-07-06T18:24:31.037559Z",
|
21
|
+
"project": {
|
22
|
+
"domain": {
|
23
|
+
"id": "default",
|
24
|
+
"name": "Default"
|
25
|
+
},
|
26
|
+
"id": "5c6a4110f7f9436b8e1f6696f9f6a13f",
|
27
|
+
"name": "tenant_username1"
|
28
|
+
},
|
29
|
+
"catalog": [
|
30
|
+
{
|
31
|
+
"name" : "swift",
|
32
|
+
"id": "0f7d75e8a91249628ad63356002b4869",
|
33
|
+
"type" : "object-store",
|
34
|
+
"endpoints" : [
|
35
|
+
{
|
36
|
+
"region_id": "RegionOne",
|
37
|
+
"url": "http://auth-url-com:123/v3/admin",
|
38
|
+
"region": "RegionOne",
|
39
|
+
"interface": "admin",
|
40
|
+
"id": "a723954cc89d3e93a65c1f658132773d"
|
41
|
+
},
|
35
42
|
{
|
36
|
-
|
43
|
+
"region_id": "RegionOne",
|
44
|
+
"url": "http://auth-url-com:123/v3/internal",
|
45
|
+
"region": "RegionOne",
|
46
|
+
"interface": "internal",
|
47
|
+
"id": "beea797b69fa47159e44a220ea05c61c"
|
48
|
+
},
|
49
|
+
{
|
50
|
+
"region_id": "RegionOne",
|
51
|
+
"url": "http://auth-url-com:123/v3/public",
|
52
|
+
"region": "RegionOne",
|
53
|
+
"interface": "public",
|
54
|
+
"id": "9e36056aba514263a1d1d8ae10cfb796"
|
37
55
|
}
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
56
|
+
]
|
57
|
+
},
|
58
|
+
{
|
59
|
+
"name" : "keystone",
|
60
|
+
"id" : "7efb509d3a0b446fa324d0bc4503e6d3",
|
61
|
+
"type" : "identity",
|
62
|
+
"endpoints" : [
|
63
|
+
{
|
64
|
+
"region_id" : "RegionOne",
|
65
|
+
"url" : "http://auth-url-com:123/v3/public",
|
66
|
+
"interface": "public",
|
67
|
+
"id" : "29e36056aba514263a1d1d8ae10cfb79"
|
68
|
+
}
|
69
|
+
]
|
70
|
+
}
|
71
|
+
],
|
72
|
+
"extras": {},
|
73
|
+
"user": {
|
74
|
+
"domain": {
|
75
|
+
"id": "default",
|
76
|
+
"name": "Default"
|
77
|
+
},
|
78
|
+
"id": "83e2c39492e343f08b0621ddc2e46eab",
|
79
|
+
"name": "username1"
|
49
80
|
},
|
50
|
-
"
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
},
|
57
|
-
"issued_at" : "2014-08-25T17:53:26.667691",
|
58
|
-
"id" : "5",
|
59
|
-
"expires" : "2014-08-25T18:53:26Z"
|
60
|
-
}
|
61
|
-
}
|
81
|
+
"audit_ids": [
|
82
|
+
"B77HzuC7QimfKre8DhDrhw"
|
83
|
+
],
|
84
|
+
"issued_at": "2017-07-06T17:24:31.037629Z"
|
85
|
+
}
|
86
|
+
}
|
62
87
|
}
|
@@ -25,7 +25,7 @@ describe NightcrawlerSwift::Delete do
|
|
25
25
|
|
26
26
|
context "success" do
|
27
27
|
let :response do
|
28
|
-
double(:response, code:
|
28
|
+
double(:response, code: 204)
|
29
29
|
end
|
30
30
|
|
31
31
|
before do
|
@@ -42,6 +42,20 @@ describe NightcrawlerSwift::Delete do
|
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
+
context "failure" do
|
46
|
+
let :response do
|
47
|
+
double(:response, code: 404)
|
48
|
+
end
|
49
|
+
|
50
|
+
before do
|
51
|
+
allow(subject).to receive(:delete).and_return(response)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "returns false" do
|
55
|
+
expect(execute).to eql false
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
45
59
|
context "when the path was not informed" do
|
46
60
|
it "raises NightcrawlerSwift::Exceptions::ValidationError" do
|
47
61
|
expect { subject.execute nil }.to raise_error NightcrawlerSwift::Exceptions::ValidationError
|
@@ -165,6 +165,13 @@ describe NightcrawlerSwift::Upload do
|
|
165
165
|
let(:response) { double(:response, code: 500) }
|
166
166
|
it { expect(execute).to be false }
|
167
167
|
end
|
168
|
+
|
169
|
+
context "when the path was not informed" do
|
170
|
+
it "raises NightcrawlerSwift::Exceptions::ValidationError" do
|
171
|
+
expect { subject.execute(nil, file) }.to raise_error NightcrawlerSwift::Exceptions::ValidationError
|
172
|
+
expect { subject.execute("", file) }.to raise_error NightcrawlerSwift::Exceptions::ValidationError
|
173
|
+
end
|
174
|
+
end
|
168
175
|
end
|
169
176
|
|
170
177
|
end
|
@@ -8,7 +8,7 @@ describe NightcrawlerSwift::Connection do
|
|
8
8
|
tenant_name: "tenant_username1",
|
9
9
|
username: "username1",
|
10
10
|
password: "some-pass",
|
11
|
-
auth_url: "https://auth-url-com:123/
|
11
|
+
auth_url: "https://auth-url-com:123/v3/auth/tokens",
|
12
12
|
max_age: 31536000 # 1 year
|
13
13
|
}
|
14
14
|
end
|
@@ -30,19 +30,48 @@ describe NightcrawlerSwift::Connection do
|
|
30
30
|
let :auth_json do
|
31
31
|
{
|
32
32
|
auth: {
|
33
|
-
|
34
|
-
|
33
|
+
identity: {
|
34
|
+
methods: [
|
35
|
+
"password"
|
36
|
+
],
|
37
|
+
password: {
|
38
|
+
user: {
|
39
|
+
domain: {
|
40
|
+
id: "default"
|
41
|
+
},
|
42
|
+
name: opts[:username],
|
43
|
+
password: opts[:password]
|
44
|
+
}
|
45
|
+
}
|
46
|
+
},
|
47
|
+
scope: {
|
48
|
+
project: {
|
49
|
+
domain: {
|
50
|
+
id: "default"
|
51
|
+
},
|
52
|
+
name: opts[:tenant_name]
|
53
|
+
}
|
54
|
+
}
|
35
55
|
}
|
36
56
|
}.to_json
|
37
57
|
end
|
38
58
|
|
39
59
|
let :auth_success_response do
|
40
60
|
path = File.join(File.dirname(__FILE__), "../..", "fixtures/auth_success.json")
|
41
|
-
|
61
|
+
file_contents = JSON.parse(File.read(File.expand_path(path)))
|
62
|
+
headers = file_contents["headers"].reduce({}) do |h, item|
|
63
|
+
key, value = item
|
64
|
+
h[key.downcase.gsub("-", "_").to_sym] = value
|
65
|
+
h
|
66
|
+
end
|
67
|
+
OpenStruct.new(headers: headers, body: file_contents["body"].to_json)
|
42
68
|
end
|
43
69
|
|
44
70
|
let :auth_success_json do
|
45
|
-
|
71
|
+
{
|
72
|
+
"headers" => auth_success_response.headers,
|
73
|
+
"body" => JSON.parse(auth_success_response.body)
|
74
|
+
}
|
46
75
|
end
|
47
76
|
|
48
77
|
describe "when it connects" do
|
@@ -61,48 +90,55 @@ describe NightcrawlerSwift::Connection do
|
|
61
90
|
and_return(auth_success_response)
|
62
91
|
end
|
63
92
|
|
64
|
-
it "stores the
|
93
|
+
it "stores the auth response body" do
|
65
94
|
subject.connect!
|
66
95
|
# This test uses 'eq' instead of 'eql' because in Ruby 1.9.x the method
|
67
96
|
# 'equal?' is different than '==' making this test fail
|
68
|
-
expect(subject.auth_response).to eq(
|
97
|
+
expect(subject.auth_response.body).to eq(auth_success_json["body"])
|
98
|
+
end
|
99
|
+
|
100
|
+
it "stores the auth response headers" do
|
101
|
+
subject.connect!
|
102
|
+
expect(subject.auth_response.headers).to eq(auth_success_json["headers"])
|
69
103
|
end
|
70
104
|
|
71
105
|
it "stores the token id" do
|
72
106
|
subject.connect!
|
73
|
-
expect(subject.token_id).
|
107
|
+
expect(subject.token_id).not_to be_nil
|
108
|
+
expect(subject.token_id).to eql(auth_success_json["headers"][:x_subject_token])
|
74
109
|
end
|
75
110
|
|
76
111
|
it "stores the expires_at" do
|
77
112
|
subject.connect!
|
78
|
-
expires_at = DateTime.parse(auth_success_json["
|
113
|
+
expires_at = DateTime.parse(auth_success_json["body"]["token"]["expires_at"]).to_time
|
79
114
|
expect(subject.expires_at).to eql(expires_at)
|
80
115
|
end
|
81
116
|
|
82
117
|
it "stores the catalog" do
|
83
|
-
|
84
|
-
expect(subject.catalog).to eql(auth_success_json["
|
118
|
+
subject.connect!
|
119
|
+
expect(subject.catalog).to eql(auth_success_json["body"]["token"]["catalog"][0])
|
120
|
+
expect(subject.catalog["type"]).to eql("object-store")
|
85
121
|
end
|
86
122
|
|
87
123
|
it "stores the admin_url" do
|
88
|
-
|
89
|
-
expect(subject.admin_url).to eql(auth_success_json["
|
124
|
+
subject.connect!
|
125
|
+
expect(subject.admin_url).to eql(auth_success_json["body"]["token"]["catalog"][0]["endpoints"][0]["url"])
|
90
126
|
end
|
91
127
|
|
92
128
|
it "stores the upload_url" do
|
129
|
+
subject.connect!
|
93
130
|
admin_url = subject.admin_url
|
94
|
-
expect(subject).to receive(:connect!).and_call_original
|
95
131
|
expect(subject.upload_url).to eql("#{admin_url}/#{opts[:bucket]}")
|
96
132
|
end
|
97
133
|
|
98
134
|
it "stores the public_url" do
|
99
|
-
|
100
|
-
expect(subject.public_url).to eql(auth_success_json["
|
135
|
+
subject.connect!
|
136
|
+
expect(subject.public_url).to eql(auth_success_json["body"]["token"]["catalog"][0]["endpoints"][2]["url"])
|
101
137
|
end
|
102
138
|
|
103
139
|
it "stores the internal_url" do
|
104
|
-
|
105
|
-
expect(subject.internal_url).to eql(auth_success_json["
|
140
|
+
subject.connect!
|
141
|
+
expect(subject.internal_url).to eql(auth_success_json["body"]["token"]["catalog"][0]["endpoints"][1]["url"])
|
106
142
|
end
|
107
143
|
|
108
144
|
it "returns self" do
|
@@ -111,7 +147,7 @@ describe NightcrawlerSwift::Connection do
|
|
111
147
|
|
112
148
|
context "and there isn't any catalog configured" do
|
113
149
|
before do
|
114
|
-
auth_success_json["
|
150
|
+
auth_success_json["body"]["token"]["catalog"] = []
|
115
151
|
allow(subject).to receive(:auth_response).and_return(OpenStruct.new(auth_success_json))
|
116
152
|
end
|
117
153
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nightcrawler_swift
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- tulios
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2017-08-22 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rest-client
|
@@ -230,7 +230,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
230
230
|
version: '0'
|
231
231
|
requirements: []
|
232
232
|
rubyforge_project:
|
233
|
-
rubygems_version: 2.5.
|
233
|
+
rubygems_version: 2.5.2
|
234
234
|
signing_key:
|
235
235
|
specification_version: 4
|
236
236
|
summary: Like the X-Men nightcrawler this gem teleports your assets to a OpenStack
|