bucket_store 0.4.0 → 0.6.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.
- checksums.yaml +4 -4
- data/README.md +31 -2
- data/lib/bucket_store/disk.rb +10 -11
- data/lib/bucket_store/gcs.rb +25 -15
- data/lib/bucket_store/in_memory.rb +9 -8
- data/lib/bucket_store/key_context.rb +22 -5
- data/lib/bucket_store/key_storage.rb +101 -38
- data/lib/bucket_store/s3.rb +5 -10
- data/lib/bucket_store/version.rb +1 -1
- data/lib/bucket_store.rb +3 -2
- metadata +15 -42
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4a5f88aa8903c932c2de1c7ed531bf78f58f72e9785ba8732a4cca7e44605549
|
4
|
+
data.tar.gz: f2d8e01ad1f54acf7c44a71df268576131df013e656dfced91f700b80927f036
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 22ba164e42115a064b4aa6f7833d3d5c86931fe7c35d2da873725fa2488ab6849ce2481bf09bc9d93f7eab085a0132d957e1d81fad8855e89060663895aa5428
|
7
|
+
data.tar.gz: 9c88e6bf881ecab6686255cabfa416be09985a8f48b200726f89e7589e5925dd5ab13700a865aa69c49b4050d654447c09e802b71852b62ef331c5e80568b369
|
data/README.md
CHANGED
@@ -135,18 +135,34 @@ in mind:
|
|
135
135
|
|
136
136
|
## Examples
|
137
137
|
|
138
|
-
### Uploading a
|
138
|
+
### Uploading a string to a bucket
|
139
139
|
```ruby
|
140
140
|
BucketStore.for("inmemory://bucket/path/file.xml").upload!("hello world")
|
141
141
|
=> "inmemory://bucket/path/file.xml"
|
142
142
|
```
|
143
143
|
|
144
|
-
### Accessing a
|
144
|
+
### Accessing a string in a bucket
|
145
145
|
```ruby
|
146
146
|
BucketStore.for("inmemory://bucket/path/file.xml").download
|
147
147
|
=> {:bucket=>"bucket", :key=>"path/file.xml", :content=>"hello world"}
|
148
148
|
```
|
149
149
|
|
150
|
+
### Uploading a file-like object to a bucket
|
151
|
+
```ruby
|
152
|
+
buffer = StringIO.new("This could also be an actual file")
|
153
|
+
BucketStore.for("inmemory://bucket/path/file.xml").stream.upload!(file: buffer)
|
154
|
+
=> "inmemory://bucket/path/file.xml"
|
155
|
+
```
|
156
|
+
|
157
|
+
### Downloading to a file-like object from a bucket
|
158
|
+
```ruby
|
159
|
+
buffer = StringIO.new
|
160
|
+
BucketStore.for("inmemory://bucket/path/file.xml").stream.download(file: buffer)
|
161
|
+
=> {:bucket=>"bucket", :key=>"path/file.xml", :file=>buffer}
|
162
|
+
buffer.string
|
163
|
+
=> "This could also be an actual file"
|
164
|
+
```
|
165
|
+
|
150
166
|
### Listing all keys under a prefix
|
151
167
|
```ruby
|
152
168
|
BucketStore.for("inmemory://bucket/path/").list
|
@@ -159,6 +175,19 @@ BucketStore.for("inmemory://bucket/path/file.xml").delete!
|
|
159
175
|
=> true
|
160
176
|
```
|
161
177
|
|
178
|
+
## Development
|
179
|
+
|
180
|
+
### Running tests
|
181
|
+
BucketStore comes with both unit and integration tests. While unit tests can be run by simply
|
182
|
+
executing `bundle exec rspec`, integration tests require running minio locally. We provide a
|
183
|
+
docker-compose file that spins up pre-configured simulator instances for S3 and GCS with
|
184
|
+
test buckets. Running the integration tests is as easy as:
|
185
|
+
|
186
|
+
```
|
187
|
+
docker-compose up
|
188
|
+
bundle exec rspec --tag integration
|
189
|
+
```
|
190
|
+
|
162
191
|
## License & Contributing
|
163
192
|
|
164
193
|
* BucketStore is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/lib/bucket_store/disk.rb
CHANGED
@@ -13,23 +13,22 @@ module BucketStore
|
|
13
13
|
@base_dir = File.expand_path(base_dir)
|
14
14
|
end
|
15
15
|
|
16
|
-
def upload!(bucket:, key:,
|
17
|
-
File.open(key_path(bucket, key), "w") do |
|
18
|
-
|
16
|
+
def upload!(bucket:, key:, file:)
|
17
|
+
File.open(key_path(bucket, key), "w") do |output_file|
|
18
|
+
output_file.write(file.read)
|
19
|
+
output_file.rewind
|
19
20
|
end
|
21
|
+
|
20
22
|
{
|
21
23
|
bucket: bucket,
|
22
24
|
key: key,
|
23
25
|
}
|
24
26
|
end
|
25
27
|
|
26
|
-
def download(bucket:, key:)
|
27
|
-
File.open(key_path(bucket, key), "r") do |
|
28
|
-
|
29
|
-
|
30
|
-
key: key,
|
31
|
-
content: file.read,
|
32
|
-
}
|
28
|
+
def download(bucket:, key:, file:)
|
29
|
+
File.open(key_path(bucket, key), "r") do |saved_file|
|
30
|
+
file.write(saved_file.read)
|
31
|
+
file.rewind
|
33
32
|
end
|
34
33
|
end
|
35
34
|
|
@@ -78,7 +77,7 @@ module BucketStore
|
|
78
77
|
end
|
79
78
|
|
80
79
|
def sanitize_filename(filename)
|
81
|
-
filename.gsub(%r{[^0-9A-z
|
80
|
+
filename.gsub(%r{[^0-9A-z.\- /]}, "_")
|
82
81
|
end
|
83
82
|
end
|
84
83
|
end
|
data/lib/bucket_store/gcs.rb
CHANGED
@@ -14,14 +14,27 @@ module BucketStore
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def initialize(timeout_seconds)
|
17
|
-
|
17
|
+
# Ruby's GCS library does not natively support setting up a simulator, but it allows
|
18
|
+
# for a specific endpoint to be passed down which has the same effect. The simulator
|
19
|
+
# needs to be special cased as in that case we want to bypass authentication,
|
20
|
+
# which we can only do by accessing the `.anonymous` version of the Storage class.
|
21
|
+
simulator_endpoint = ENV["STORAGE_EMULATOR_HOST"]
|
22
|
+
is_simulator = !simulator_endpoint.nil?
|
23
|
+
|
24
|
+
args = {
|
25
|
+
endpoint: simulator_endpoint,
|
18
26
|
timeout: timeout_seconds,
|
19
|
-
|
27
|
+
}.compact
|
28
|
+
|
29
|
+
@storage = if is_simulator
|
30
|
+
Google::Cloud::Storage.anonymous(**args)
|
31
|
+
else
|
32
|
+
Google::Cloud::Storage.new(**args)
|
33
|
+
end
|
20
34
|
end
|
21
35
|
|
22
|
-
def upload!(bucket:, key:,
|
23
|
-
|
24
|
-
get_bucket(bucket).create_file(buffer, key)
|
36
|
+
def upload!(bucket:, key:, file:)
|
37
|
+
get_bucket(bucket).create_file(file, key)
|
25
38
|
|
26
39
|
{
|
27
40
|
bucket: bucket,
|
@@ -29,17 +42,14 @@ module BucketStore
|
|
29
42
|
}
|
30
43
|
end
|
31
44
|
|
32
|
-
def download(bucket:, key:)
|
33
|
-
file
|
34
|
-
|
35
|
-
|
36
|
-
|
45
|
+
def download(bucket:, key:, file:)
|
46
|
+
file.tap do |f|
|
47
|
+
get_bucket(bucket).
|
48
|
+
file(key).
|
49
|
+
download(f)
|
37
50
|
|
38
|
-
|
39
|
-
|
40
|
-
key: key,
|
41
|
-
content: buffer.string,
|
42
|
-
}
|
51
|
+
f.rewind
|
52
|
+
end
|
43
53
|
end
|
44
54
|
|
45
55
|
def list(bucket:, key:, page_size:)
|
@@ -24,8 +24,10 @@ module BucketStore
|
|
24
24
|
@buckets = Hash.new { |hash, key| hash[key] = {} }
|
25
25
|
end
|
26
26
|
|
27
|
-
def upload!(bucket:, key:,
|
28
|
-
|
27
|
+
def upload!(bucket:, key:, file:)
|
28
|
+
file.tap do |f|
|
29
|
+
@buckets[bucket][key] = f.read
|
30
|
+
end
|
29
31
|
|
30
32
|
{
|
31
33
|
bucket: bucket,
|
@@ -33,12 +35,11 @@ module BucketStore
|
|
33
35
|
}
|
34
36
|
end
|
35
37
|
|
36
|
-
def download(bucket:, key:)
|
37
|
-
|
38
|
-
bucket
|
39
|
-
|
40
|
-
|
41
|
-
}
|
38
|
+
def download(bucket:, key:, file:)
|
39
|
+
file.tap do |f|
|
40
|
+
f.write(@buckets[bucket].fetch(key))
|
41
|
+
f.rewind
|
42
|
+
end
|
42
43
|
end
|
43
44
|
|
44
45
|
def list(bucket:, key:, page_size:)
|
@@ -19,18 +19,35 @@ module BucketStore
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def self.parse(raw_key)
|
22
|
-
uri = URI(raw_key)
|
22
|
+
uri = URI(escape(raw_key))
|
23
|
+
|
24
|
+
scheme = unescape(uri.scheme)
|
25
|
+
bucket = unescape(uri.host)
|
23
26
|
|
24
27
|
# A key should never be `nil` but can be empty. Depending on the operation, this may
|
25
28
|
# or may not be a valid configuration (e.g. an empty key is likely valid on a
|
26
29
|
# `list`, but not during a `download`).
|
27
|
-
key = uri.path.sub!(%r{/}, "") || ""
|
30
|
+
key = unescape(uri.path).sub!(%r{/}, "") || ""
|
28
31
|
|
29
|
-
raise KeyParseException if [
|
32
|
+
raise KeyParseException if [scheme, bucket, key].map(&:nil?).any?
|
30
33
|
|
31
|
-
KeyContext.new(adapter:
|
32
|
-
bucket:
|
34
|
+
KeyContext.new(adapter: scheme,
|
35
|
+
bucket: bucket,
|
33
36
|
key: key)
|
34
37
|
end
|
38
|
+
|
39
|
+
def self.escape(key)
|
40
|
+
return key if key.nil?
|
41
|
+
|
42
|
+
URI::DEFAULT_PARSER.escape(key)
|
43
|
+
end
|
44
|
+
private_class_method :escape
|
45
|
+
|
46
|
+
def self.unescape(key)
|
47
|
+
return key if key.nil?
|
48
|
+
|
49
|
+
URI::DEFAULT_PARSER.unescape(key)
|
50
|
+
end
|
51
|
+
private_class_method :unescape
|
35
52
|
end
|
36
53
|
end
|
@@ -15,6 +15,90 @@ module BucketStore
|
|
15
15
|
disk: Disk,
|
16
16
|
}.freeze
|
17
17
|
|
18
|
+
# Defines a streaming interface for download and upload operations.
|
19
|
+
#
|
20
|
+
# Note that individual adapters may require additional configuration for the correct
|
21
|
+
# behavior of the streaming interface.
|
22
|
+
class KeyStreamer
|
23
|
+
attr_reader :bucket, :key, :adapter_type
|
24
|
+
|
25
|
+
def initialize(adapter:, adapter_type:, bucket:, key:)
|
26
|
+
@adapter = adapter
|
27
|
+
@adapter_type = adapter_type
|
28
|
+
@bucket = bucket
|
29
|
+
@key = key
|
30
|
+
end
|
31
|
+
|
32
|
+
# Streams the content of the reference key into a file-like object
|
33
|
+
# @param [IO] file a writeable IO instance, or a file-like object such as `StringIO`
|
34
|
+
# @return hash containing the bucket, the key and file like object passed in as input
|
35
|
+
#
|
36
|
+
# @see KeyStorage#download
|
37
|
+
# @example Download a key
|
38
|
+
# buffer = StringIO.new
|
39
|
+
# BucketStore.for("inmemory://bucket/file.xml").stream.download(file: buffer)
|
40
|
+
# buffer.string == "Imagine I'm a 2GB file"
|
41
|
+
def download(file:)
|
42
|
+
BucketStore.logger.info(event: "key_storage.download_started")
|
43
|
+
|
44
|
+
start = BucketStore::Timing.monotonic_now
|
45
|
+
adapter.download(
|
46
|
+
bucket: bucket,
|
47
|
+
key: key,
|
48
|
+
file: file,
|
49
|
+
)
|
50
|
+
|
51
|
+
BucketStore.logger.info(event: "key_storage.download_finished",
|
52
|
+
duration: BucketStore::Timing.monotonic_now - start)
|
53
|
+
|
54
|
+
{
|
55
|
+
bucket: bucket,
|
56
|
+
key: key,
|
57
|
+
file: file,
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
# Performs a streaming upload to the backing object store
|
62
|
+
# @param [IO] file a readable IO instance, or a file-like object such as `StringIO`
|
63
|
+
# @return the generated key for the new object
|
64
|
+
#
|
65
|
+
# @see KeyStorage#upload!
|
66
|
+
# @example Upload a key
|
67
|
+
# buffer = StringIO.new("Imagine I'm a 2GB file")
|
68
|
+
# BucketStore.for("inmemory://bucket/file.xml").stream.upload!(file: buffer)
|
69
|
+
def upload!(file:)
|
70
|
+
raise ArgumentError, "Key cannot be empty" if key.empty?
|
71
|
+
|
72
|
+
BucketStore.logger.info(event: "key_storage.upload_started",
|
73
|
+
**log_context)
|
74
|
+
|
75
|
+
start = BucketStore::Timing.monotonic_now
|
76
|
+
adapter.upload!(
|
77
|
+
bucket: bucket,
|
78
|
+
key: key,
|
79
|
+
file: file,
|
80
|
+
)
|
81
|
+
|
82
|
+
BucketStore.logger.info(event: "key_storage.upload_finished",
|
83
|
+
duration: BucketStore::Timing.monotonic_now - start,
|
84
|
+
**log_context)
|
85
|
+
|
86
|
+
"#{adapter_type}://#{bucket}/#{key}"
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
attr_reader :adapter
|
92
|
+
|
93
|
+
def log_context
|
94
|
+
{
|
95
|
+
bucket: bucket,
|
96
|
+
key: key,
|
97
|
+
adapter_type: adapter_type,
|
98
|
+
}.compact
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
18
102
|
attr_reader :bucket, :key, :adapter_type
|
19
103
|
|
20
104
|
def initialize(adapter:, bucket:, key:)
|
@@ -40,45 +124,32 @@ module BucketStore
|
|
40
124
|
# @example Download a key
|
41
125
|
# BucketStore.for("inmemory://bucket/file.xml").download
|
42
126
|
def download
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
result = adapter.download(bucket: bucket, key: key)
|
49
|
-
|
50
|
-
BucketStore.logger.info(event: "key_storage.download_finished",
|
51
|
-
duration: BucketStore::Timing.monotonic_now - start)
|
52
|
-
|
53
|
-
result
|
127
|
+
buffer = StringIO.new
|
128
|
+
stream.download(file: buffer).tap do |result|
|
129
|
+
result.delete(:file)
|
130
|
+
result[:content] = buffer.string
|
131
|
+
end
|
54
132
|
end
|
55
133
|
|
56
|
-
# Uploads the given
|
134
|
+
# Uploads the given file to the reference key location.
|
57
135
|
#
|
58
136
|
# If the `key` already exists, its content will be replaced by the one in input.
|
59
137
|
#
|
60
|
-
# @param [String] content
|
138
|
+
# @param [String] content Contents of the file
|
61
139
|
# @return [String] The final `key` where the content has been uploaded
|
62
140
|
# @example Upload a file
|
63
|
-
# BucketStore.for("inmemory://bucket/file.xml").upload("hello world")
|
141
|
+
# BucketStore.for("inmemory://bucket/file.xml").upload!("hello world")
|
64
142
|
def upload!(content)
|
65
|
-
|
66
|
-
|
67
|
-
BucketStore.logger.info(event: "key_storage.upload_started",
|
68
|
-
**log_context)
|
69
|
-
|
70
|
-
start = BucketStore::Timing.monotonic_now
|
71
|
-
result = adapter.upload!(
|
72
|
-
bucket: bucket,
|
73
|
-
key: key,
|
74
|
-
content: content,
|
75
|
-
)
|
143
|
+
stream.upload!(file: StringIO.new(content))
|
144
|
+
end
|
76
145
|
|
77
|
-
|
78
|
-
|
79
|
-
|
146
|
+
# Returns an interface for streaming operations
|
147
|
+
#
|
148
|
+
# @return [KeyStreamer] An interface for streaming operations
|
149
|
+
def stream
|
150
|
+
raise ArgumentError, "Key cannot be empty" if key.empty?
|
80
151
|
|
81
|
-
|
152
|
+
KeyStreamer.new(adapter: adapter, adapter_type: adapter_type, bucket: bucket, key: key)
|
82
153
|
end
|
83
154
|
|
84
155
|
# Lists all keys for the current adapter that have the reference key as prefix
|
@@ -153,19 +224,11 @@ module BucketStore
|
|
153
224
|
#
|
154
225
|
# @return [bool] `true` if the given key exists, `false` if not
|
155
226
|
def exists?
|
156
|
-
list.first == "#{adapter_type}://#{bucket}/#{key}"
|
227
|
+
list(page_size: 1).first == "#{adapter_type}://#{bucket}/#{key}"
|
157
228
|
end
|
158
229
|
|
159
230
|
private
|
160
231
|
|
161
232
|
attr_reader :adapter
|
162
|
-
|
163
|
-
def log_context
|
164
|
-
{
|
165
|
-
bucket: bucket,
|
166
|
-
key: key,
|
167
|
-
adapter_type: adapter_type,
|
168
|
-
}.compact
|
169
|
-
end
|
170
233
|
end
|
171
234
|
end
|
data/lib/bucket_store/s3.rb
CHANGED
@@ -20,11 +20,11 @@ module BucketStore
|
|
20
20
|
)
|
21
21
|
end
|
22
22
|
|
23
|
-
def upload!(bucket:, key:,
|
23
|
+
def upload!(bucket:, key:, file:)
|
24
24
|
storage.put_object(
|
25
25
|
bucket: bucket,
|
26
26
|
key: key,
|
27
|
-
body:
|
27
|
+
body: file,
|
28
28
|
)
|
29
29
|
|
30
30
|
{
|
@@ -33,17 +33,12 @@ module BucketStore
|
|
33
33
|
}
|
34
34
|
end
|
35
35
|
|
36
|
-
def download(bucket:, key:)
|
37
|
-
|
36
|
+
def download(bucket:, key:, file:)
|
37
|
+
storage.get_object(
|
38
|
+
response_target: file,
|
38
39
|
bucket: bucket,
|
39
40
|
key: key,
|
40
41
|
)
|
41
|
-
|
42
|
-
{
|
43
|
-
bucket: bucket,
|
44
|
-
key: key,
|
45
|
-
content: file.body.read,
|
46
|
-
}
|
47
42
|
end
|
48
43
|
|
49
44
|
def list(bucket:, key:, page_size:)
|
data/lib/bucket_store/version.rb
CHANGED
data/lib/bucket_store.rb
CHANGED
@@ -4,6 +4,7 @@ require "bucket_store/version"
|
|
4
4
|
require "bucket_store/configuration"
|
5
5
|
require "bucket_store/key_context"
|
6
6
|
require "bucket_store/key_storage"
|
7
|
+
require "bucket_store/uri_builder"
|
7
8
|
|
8
9
|
# An abstraction layer on the top of file cloud storage systems such as Google Cloud
|
9
10
|
# Storage or S3. This module exposes a generic interface that allows interoperability
|
@@ -41,8 +42,8 @@ module BucketStore
|
|
41
42
|
# Given a `key` in the format of `adapter://bucket/key` returns the corresponding
|
42
43
|
# adapter that will allow to manipulate (e.g. download, upload or list) such key.
|
43
44
|
#
|
44
|
-
# Currently supported adapters are `gs` (Google Cloud Storage), `
|
45
|
-
# in-memory key-value storage) and `disk` (a disk-backed key-value store).
|
45
|
+
# Currently supported adapters are `gs` (Google Cloud Storage), `s3` (AWS S3),
|
46
|
+
# `inmemory` (an in-memory key-value storage) and `disk` (a disk-backed key-value store).
|
46
47
|
#
|
47
48
|
# @param [String] key The reference key
|
48
49
|
# @return [KeyStorage] An interface to the adapter that can handle requests on the given key
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bucket_store
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GoCardless Engineering
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-09-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: aws-sdk-s3
|
@@ -44,14 +44,14 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
47
|
+
version: 4.3.0
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
54
|
+
version: 4.3.0
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: pry-byebug
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -81,61 +81,33 @@ dependencies:
|
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '3.10'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
84
|
+
name: rspec-github
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version:
|
89
|
+
version: 2.4.0
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version:
|
96
|
+
version: 2.4.0
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: rubocop
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
|
-
- - "
|
102
|
-
- !ruby/object:Gem::Version
|
103
|
-
version: '1.22'
|
104
|
-
type: :development
|
105
|
-
prerelease: false
|
106
|
-
version_requirements: !ruby/object:Gem::Requirement
|
107
|
-
requirements:
|
108
|
-
- - "~>"
|
109
|
-
- !ruby/object:Gem::Version
|
110
|
-
version: '1.22'
|
111
|
-
- !ruby/object:Gem::Dependency
|
112
|
-
name: rubocop-performance
|
113
|
-
requirement: !ruby/object:Gem::Requirement
|
114
|
-
requirements:
|
115
|
-
- - "~>"
|
116
|
-
- !ruby/object:Gem::Version
|
117
|
-
version: '1.11'
|
118
|
-
type: :development
|
119
|
-
prerelease: false
|
120
|
-
version_requirements: !ruby/object:Gem::Requirement
|
121
|
-
requirements:
|
122
|
-
- - "~>"
|
123
|
-
- !ruby/object:Gem::Version
|
124
|
-
version: '1.11'
|
125
|
-
- !ruby/object:Gem::Dependency
|
126
|
-
name: rubocop-rspec
|
127
|
-
requirement: !ruby/object:Gem::Requirement
|
128
|
-
requirements:
|
129
|
-
- - "~>"
|
101
|
+
- - ">="
|
130
102
|
- !ruby/object:Gem::Version
|
131
|
-
version: '
|
103
|
+
version: '1.49'
|
132
104
|
type: :development
|
133
105
|
prerelease: false
|
134
106
|
version_requirements: !ruby/object:Gem::Requirement
|
135
107
|
requirements:
|
136
|
-
- - "
|
108
|
+
- - ">="
|
137
109
|
- !ruby/object:Gem::Version
|
138
|
-
version: '
|
110
|
+
version: '1.49'
|
139
111
|
description: " A helper library to access cloud storage services such as Google
|
140
112
|
Cloud Storage or S3.\n"
|
141
113
|
email:
|
@@ -159,7 +131,8 @@ files:
|
|
159
131
|
homepage: https://github.com/gocardless/bucket-store
|
160
132
|
licenses:
|
161
133
|
- MIT
|
162
|
-
metadata:
|
134
|
+
metadata:
|
135
|
+
rubygems_mfa_required: 'true'
|
163
136
|
post_install_message:
|
164
137
|
rdoc_options: []
|
165
138
|
require_paths:
|
@@ -168,14 +141,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
168
141
|
requirements:
|
169
142
|
- - ">="
|
170
143
|
- !ruby/object:Gem::Version
|
171
|
-
version: '2.
|
144
|
+
version: '2.7'
|
172
145
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
173
146
|
requirements:
|
174
147
|
- - ">="
|
175
148
|
- !ruby/object:Gem::Version
|
176
149
|
version: '0'
|
177
150
|
requirements: []
|
178
|
-
rubygems_version: 3.
|
151
|
+
rubygems_version: 3.3.3
|
179
152
|
signing_key:
|
180
153
|
specification_version: 4
|
181
154
|
summary: A helper library to access cloud storage services
|