bucket_store 0.5.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 +26 -6
- data/lib/bucket_store/disk.rb +9 -10
- data/lib/bucket_store/gcs.rb +25 -15
- data/lib/bucket_store/in_memory.rb +9 -8
- data/lib/bucket_store/key_storage.rb +100 -37
- data/lib/bucket_store/s3.rb +5 -10
- data/lib/bucket_store/version.rb +1 -1
- 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
|
@@ -163,10 +179,14 @@ BucketStore.for("inmemory://bucket/path/file.xml").delete!
|
|
163
179
|
|
164
180
|
### Running tests
|
165
181
|
BucketStore comes with both unit and integration tests. While unit tests can be run by simply
|
166
|
-
executing `bundle exec rspec`, integration tests require running minio locally. We provide
|
167
|
-
|
168
|
-
|
169
|
-
|
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
|
+
```
|
170
190
|
|
171
191
|
## License & Contributing
|
172
192
|
|
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
|
|
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:)
|
@@ -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
|
@@ -159,13 +230,5 @@ module BucketStore
|
|
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
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
|