canistor 0.4.0 → 0.5.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/lib/canistor.rb +30 -9
- data/lib/canistor/storage.rb +2 -0
- data/lib/canistor/storage/bucket.rb +47 -24
- data/lib/canistor/storage/bucket/settings.rb +83 -0
- data/lib/canistor/storage/delete_marker.rb +39 -0
- data/lib/canistor/storage/object.rb +83 -30
- data/lib/canistor/storage/objects.rb +50 -0
- data/lib/canistor/storage/part.rb +0 -1
- data/lib/canistor/version.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a6c462186c93c3bac4903417214765c33f95816760224eec56c1fb704cf7d5f2
|
4
|
+
data.tar.gz: 29ae2992af7256c0b4d86de177c61626f5ebf413b51f04c0ed69ba80e7df2db6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 53b6b59d4f5260cc6393996a48b7c20a533bc0f2d5723cba82722044be0bd093184bc666b81e0a6363bf60418a536b62a1d4eb2d5c1859118309ecc2792b2eb5
|
7
|
+
data.tar.gz: 5341e4bef20532532f6f2e7b3669b1fd5130b6bc46fa3f45a851d9fd260d2cf4533f488b1675275f57977e1eb306fdc56e8027d884d8690ed7712a3a48c691a3
|
data/lib/canistor.rb
CHANGED
@@ -23,28 +23,42 @@ require "thread"
|
|
23
23
|
# credentials to be useful. It can be configured using either the
|
24
24
|
# config method on the instance or by specifying the buckets one by one.
|
25
25
|
#
|
26
|
-
# In the example below Canistor will have two accounts and
|
26
|
+
# In the example below Canistor will have two accounts and four buckets. It
|
27
27
|
# also specifies which accounts can access the buckets.
|
28
28
|
#
|
29
29
|
# Canistor.config(
|
30
30
|
# logger: Rails.logger,
|
31
31
|
# credentials: {
|
32
|
-
#
|
32
|
+
# {
|
33
33
|
# access_key_id: 'AKIAIXXXXXXXXXXXXXX1',
|
34
34
|
# secret_access_key: 'phRL+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1'
|
35
35
|
# },
|
36
|
-
#
|
36
|
+
# {
|
37
37
|
# access_key_id: 'AKIAIXXXXXXXXXXXXXX2',
|
38
38
|
# secret_access_key: 'phRL+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2'
|
39
39
|
# }
|
40
40
|
# },
|
41
41
|
# buckets: {
|
42
42
|
# 'us-east-1' => {
|
43
|
-
# '
|
44
|
-
#
|
43
|
+
# 'io-canistor-production-images' => {
|
44
|
+
# allow_access_keys: ['AKIAIXXXXXXXXXXXXXX1'],
|
45
|
+
# replicate_to: [
|
46
|
+
# 'eu-central-1:io-canistor-production-images-replica'
|
47
|
+
# ],
|
48
|
+
# versioned: true
|
49
|
+
# },
|
50
|
+
# 'io-canistor-production-books' => {
|
51
|
+
# allow_access_keys: ['AKIAIXXXXXXXXXXXXXX1', 'AKIAIXXXXXXXXXXXXXX2']
|
52
|
+
# }
|
45
53
|
# },
|
46
54
|
# 'eu-central-1' => {
|
47
|
-
# '
|
55
|
+
# 'io-canistor-production-sales' => {
|
56
|
+
# allow_access_keys: ['AKIAIXXXXXXXXXXXXXX1']
|
57
|
+
# },
|
58
|
+
# 'io-canistor-production-images-replica' => {
|
59
|
+
# allow_access_keys: ['AKIAIXXXXXXXXXXXXXX1'],
|
60
|
+
# versioned: true
|
61
|
+
# }
|
48
62
|
# }
|
49
63
|
# }
|
50
64
|
# )
|
@@ -53,6 +67,10 @@ require "thread"
|
|
53
67
|
# verifies authentication information. It does not implement control lists so
|
54
68
|
# all accounts have full access to the buckets and objects.
|
55
69
|
#
|
70
|
+
# It's possible to turn on replication and versioning. Note that replication is
|
71
|
+
# instant in the mock. On actual S3 it takes a while for objects to replicate.
|
72
|
+
# Not the entire replication and versioning API is implemented.
|
73
|
+
#
|
56
74
|
# The mock can simulate a number of failures. These are triggered by setting
|
57
75
|
# the operation which needs to fail on the mock. For more information see
|
58
76
|
# [Canistor.fail].
|
@@ -60,6 +78,9 @@ require "thread"
|
|
60
78
|
# In most cases you should configure the suite to clear the mock before running
|
61
79
|
# each example with [Canistor.clear].
|
62
80
|
module Canistor
|
81
|
+
# Raised when Canistor's own configuration is somehow unusable.
|
82
|
+
class ConfigurationError < StandardError; end
|
83
|
+
|
63
84
|
class << self
|
64
85
|
attr_accessor :logger
|
65
86
|
attr_accessor :store
|
@@ -102,15 +123,15 @@ module Canistor
|
|
102
123
|
|
103
124
|
def self.buckets=(buckets)
|
104
125
|
buckets.each do |region, attributes|
|
105
|
-
attributes.each do |bucket,
|
126
|
+
attributes.each do |bucket, options|
|
106
127
|
bucket = create_bucket(region, bucket)
|
107
|
-
bucket.
|
128
|
+
bucket.update_settings(options)
|
108
129
|
bucket
|
109
130
|
end
|
110
131
|
end
|
111
132
|
end
|
112
133
|
|
113
|
-
# Configures a bucket in the mock implementation. Use #
|
134
|
+
# Configures a bucket in the mock implementation. Use #update_settings on
|
114
135
|
# the Container object returned by this method to configure who may access
|
115
136
|
# the bucket.
|
116
137
|
def self.create_bucket(region, bucket_name)
|
data/lib/canistor/storage.rb
CHANGED
@@ -6,6 +6,8 @@ require 'nokogiri'
|
|
6
6
|
require 'securerandom'
|
7
7
|
|
8
8
|
require_relative "storage/bucket"
|
9
|
+
require_relative "storage/delete_marker"
|
9
10
|
require_relative "storage/object"
|
11
|
+
require_relative "storage/objects"
|
10
12
|
require_relative "storage/part"
|
11
13
|
require_relative "storage/upload"
|
@@ -6,6 +6,8 @@ require 'nokogiri'
|
|
6
6
|
require 'singleton'
|
7
7
|
require 'securerandom'
|
8
8
|
|
9
|
+
require_relative "bucket/settings"
|
10
|
+
|
9
11
|
module Canistor
|
10
12
|
module Storage
|
11
13
|
# Holds information about a bucket and implements interaction with it.
|
@@ -13,18 +15,24 @@ module Canistor
|
|
13
15
|
attr_accessor :region
|
14
16
|
attr_accessor :name
|
15
17
|
|
16
|
-
attr_reader :
|
18
|
+
attr_reader :settings
|
17
19
|
attr_reader :objects
|
18
20
|
attr_reader :uploads
|
19
21
|
|
20
22
|
def initialize(**attributes)
|
21
|
-
@
|
23
|
+
@settings = Settings.new
|
22
24
|
clear
|
23
25
|
attributes.each do |name, value|
|
24
26
|
public_send("#{name}=", value)
|
25
27
|
end
|
26
28
|
end
|
27
29
|
|
30
|
+
# Update bucket settings, see Canistor::Storage::Bucket::Settings for
|
31
|
+
# supported configuration.
|
32
|
+
def update_settings(settings)
|
33
|
+
@settings.update(settings)
|
34
|
+
end
|
35
|
+
|
28
36
|
def [](name)
|
29
37
|
@objects[name]
|
30
38
|
end
|
@@ -38,7 +46,7 @@ module Canistor
|
|
38
46
|
end
|
39
47
|
|
40
48
|
def head(context, access_key_id, subject)
|
41
|
-
if !access_keys.include?(access_key_id)
|
49
|
+
if !settings.access_keys.include?(access_key_id)
|
42
50
|
Canistor::ErrorHandler.serve_access_denied(context, subject)
|
43
51
|
elsif object = objects[subject.key]
|
44
52
|
object.head(context, subject)
|
@@ -50,7 +58,7 @@ module Canistor
|
|
50
58
|
def get(context, access_key_id, subject)
|
51
59
|
params = CGI::parse(context.http_request.endpoint.query.to_s)
|
52
60
|
catch(:rendered_error) do
|
53
|
-
if !access_keys.include?(access_key_id)
|
61
|
+
if !settings.access_keys.include?(access_key_id)
|
54
62
|
Canistor::ErrorHandler.serve_access_denied(context, subject)
|
55
63
|
elsif params.has_key?('uploads')
|
56
64
|
list_bucket_uploads(context)
|
@@ -67,7 +75,7 @@ module Canistor
|
|
67
75
|
end
|
68
76
|
|
69
77
|
def put(context, access_key_id, subject)
|
70
|
-
if access_keys.include?(access_key_id)
|
78
|
+
if settings.access_keys.include?(access_key_id)
|
71
79
|
Canistor.take_fail(:store) { return }
|
72
80
|
params = CGI::parse(context.http_request.endpoint.query.to_s)
|
73
81
|
catch(:rendered_error) do
|
@@ -85,7 +93,7 @@ module Canistor
|
|
85
93
|
end
|
86
94
|
|
87
95
|
def post(context, access_key_id, subject)
|
88
|
-
if access_keys.include?(access_key_id)
|
96
|
+
if settings.access_keys.include?(access_key_id)
|
89
97
|
Canistor.take_fail(:store) { return }
|
90
98
|
params = CGI::parse(context.http_request.endpoint.query.to_s)
|
91
99
|
catch(:rendered_error) do
|
@@ -105,10 +113,10 @@ module Canistor
|
|
105
113
|
end
|
106
114
|
|
107
115
|
def delete(context, access_key_id, subject)
|
108
|
-
if !access_keys.include?(access_key_id)
|
116
|
+
if !settings.access_keys.include?(access_key_id)
|
109
117
|
Canistor::ErrorHandler.serve_access_denied(context, subject)
|
110
|
-
elsif
|
111
|
-
@objects.delete(
|
118
|
+
elsif objects[subject.key]
|
119
|
+
object = @objects.delete(subject.key)
|
112
120
|
object.delete(context, subject)
|
113
121
|
else
|
114
122
|
Canistor::ErrorHandler.serve_no_such_key(context, subject)
|
@@ -116,7 +124,9 @@ module Canistor
|
|
116
124
|
end
|
117
125
|
|
118
126
|
def clear
|
119
|
-
@objects =
|
127
|
+
@objects = Canistor::Storage::Objects.new(
|
128
|
+
versioned: settings.versioned?
|
129
|
+
)
|
120
130
|
@uploads = {}
|
121
131
|
end
|
122
132
|
|
@@ -133,22 +143,41 @@ module Canistor
|
|
133
143
|
access_keys.merge(access_key_ids)
|
134
144
|
end
|
135
145
|
|
136
|
-
def
|
137
|
-
|
146
|
+
def store_replica(object)
|
147
|
+
replica = object.copy
|
148
|
+
replica.versioned = settings.versioned?
|
149
|
+
self[object.key] = replica
|
138
150
|
end
|
139
151
|
|
140
152
|
private
|
141
153
|
|
142
|
-
def
|
143
|
-
Canistor::Storage::
|
154
|
+
def build_object(subject)
|
155
|
+
Canistor::Storage::Object.new(
|
144
156
|
region: subject.region,
|
145
157
|
bucket: subject.bucket,
|
146
|
-
key: subject.key
|
158
|
+
key: subject.key,
|
159
|
+
versioned: settings.versioned?
|
147
160
|
)
|
148
161
|
end
|
149
162
|
|
150
|
-
def
|
151
|
-
|
163
|
+
def put_object(context, subject)
|
164
|
+
object = build_object(subject)
|
165
|
+
object.versioned = settings.versioned?
|
166
|
+
object.put(context, subject)
|
167
|
+
self[subject.key] = object
|
168
|
+
replicate(object)
|
169
|
+
end
|
170
|
+
|
171
|
+
def replicate(object)
|
172
|
+
if settings.replicated?
|
173
|
+
settings.replicate_to_buckets.each do |bucket|
|
174
|
+
bucket.store_replica(object)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def build_upload(subject)
|
180
|
+
Canistor::Storage::Upload.new(
|
152
181
|
region: subject.region,
|
153
182
|
bucket: subject.bucket,
|
154
183
|
key: subject.key
|
@@ -156,7 +185,7 @@ module Canistor
|
|
156
185
|
end
|
157
186
|
|
158
187
|
def post_upload(context, subject)
|
159
|
-
upload = build_upload(
|
188
|
+
upload = build_upload(subject)
|
160
189
|
@uploads[upload.id] = upload
|
161
190
|
upload.put(context, subject)
|
162
191
|
end
|
@@ -180,12 +209,6 @@ module Canistor
|
|
180
209
|
end
|
181
210
|
end
|
182
211
|
|
183
|
-
def put_object(context, subject)
|
184
|
-
object = find_or_build_object(context, subject)
|
185
|
-
self[subject.key] = object
|
186
|
-
object.put(context, subject)
|
187
|
-
end
|
188
|
-
|
189
212
|
# Iterate over all objects in the bucket using the filter and pagination
|
190
213
|
# options which exist in S3.
|
191
214
|
def each(prefix:, marker:, max_keys:, &block)
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Canistor
|
4
|
+
module Storage
|
5
|
+
class Bucket
|
6
|
+
# Stores settings for the bucket.
|
7
|
+
#
|
8
|
+
# +allow_access_keys+: Allow full access to the bucket using these
|
9
|
+
# access keys.
|
10
|
+
# +replicate_to+: Replicate all updates to the bucket to this bucket.
|
11
|
+
# +versioned+: Use versioning on the bucket.
|
12
|
+
class Settings
|
13
|
+
def initialize(settings = {})
|
14
|
+
clear
|
15
|
+
update(settings)
|
16
|
+
end
|
17
|
+
|
18
|
+
def update(settings)
|
19
|
+
case settings
|
20
|
+
# Old style setup allowed settings to be an array of allowed access
|
21
|
+
# keys.
|
22
|
+
when Set, Array
|
23
|
+
allow_access_keys(settings)
|
24
|
+
else
|
25
|
+
settings.each do |name, value|
|
26
|
+
public_send("#{name}", value)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def access_keys
|
32
|
+
@access_keys || []
|
33
|
+
end
|
34
|
+
|
35
|
+
def replicates_to
|
36
|
+
@replicates_to || []
|
37
|
+
end
|
38
|
+
|
39
|
+
def versioned?
|
40
|
+
!!@versioned
|
41
|
+
end
|
42
|
+
|
43
|
+
def allow_access_keys(access_keys)
|
44
|
+
@access_keys = access_keys
|
45
|
+
end
|
46
|
+
|
47
|
+
def replicated?
|
48
|
+
@replicates_to && !@replicates_to.empty?
|
49
|
+
end
|
50
|
+
|
51
|
+
def replicate_to(replicates_to)
|
52
|
+
@replicates_to = replicates_to
|
53
|
+
end
|
54
|
+
|
55
|
+
def replicate_to_buckets
|
56
|
+
return [] unless replicated?
|
57
|
+
@replicates_to.map do |location|
|
58
|
+
if bucket = Canistor.store.dig(*location.split(':'))
|
59
|
+
bucket
|
60
|
+
else
|
61
|
+
raise(
|
62
|
+
Canistor::ConfigurationError,
|
63
|
+
"Can't locate bucket `#{location}' when trying to replicate " \
|
64
|
+
"object. Please make sure the replication location is in the " \
|
65
|
+
"form of region:bucket and configured in Canistor."
|
66
|
+
)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def versioned(versioned)
|
72
|
+
@versioned = versioned
|
73
|
+
end
|
74
|
+
|
75
|
+
def clear
|
76
|
+
@access_keys = []
|
77
|
+
@replicates_to = []
|
78
|
+
@versioned = false
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Canistor
|
4
|
+
module Storage
|
5
|
+
# Represents the non-existence of an object on a versioned bucket.
|
6
|
+
class DeleteMarker
|
7
|
+
attr_reader :version_id
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@version_id = generate_version_id
|
11
|
+
end
|
12
|
+
|
13
|
+
def headers
|
14
|
+
{
|
15
|
+
'x-amz-delete-marker' => 'true',
|
16
|
+
'x-amz-version-id' => version_id
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def head(context, subject)
|
21
|
+
Canistor::ErrorHandler.serve_no_such_key(context, subject)
|
22
|
+
end
|
23
|
+
|
24
|
+
def get(context, subject)
|
25
|
+
Canistor::ErrorHandler.serve_no_such_key(context, subject)
|
26
|
+
end
|
27
|
+
|
28
|
+
def delete(context, subject)
|
29
|
+
context.http_response.signal_headers(200, headers)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def generate_version_id
|
35
|
+
Base64.strict_encode64(SecureRandom.hex(16)).gsub('=', '')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -1,10 +1,38 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "digest/
|
3
|
+
require "digest/md5"
|
4
4
|
|
5
5
|
module Canistor
|
6
6
|
module Storage
|
7
7
|
class Object
|
8
|
+
# Renders the result of a copy request.
|
9
|
+
class CopyObjectResult
|
10
|
+
def initialize(source_object, object)
|
11
|
+
@source_object, @object = source_object, object
|
12
|
+
end
|
13
|
+
|
14
|
+
def headers
|
15
|
+
@object.identity_headers.merge(
|
16
|
+
'x-amz-copy-source-version-id' => @source_object.version_id
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_xml
|
21
|
+
Nokogiri::XML::Builder.new do |xml|
|
22
|
+
xml.CopyObjectResult do
|
23
|
+
xml.LastModified @object.last_modified
|
24
|
+
xml.ETag @object.etag
|
25
|
+
end
|
26
|
+
end.to_xml
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.serve(context, subject, source_object, object)
|
30
|
+
result = new(source_object, object)
|
31
|
+
context.http_response.signal_headers(200, result.headers)
|
32
|
+
context.http_response.signal_data(result.to_xml)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
8
36
|
attr_accessor :region
|
9
37
|
attr_accessor :bucket
|
10
38
|
attr_accessor :key
|
@@ -13,6 +41,7 @@ module Canistor
|
|
13
41
|
attr_reader :last_modified
|
14
42
|
|
15
43
|
def initialize(**attributes)
|
44
|
+
@versioned = false
|
16
45
|
@headers = {}
|
17
46
|
@data = ''
|
18
47
|
attributes.each do |name, value|
|
@@ -21,6 +50,20 @@ module Canistor
|
|
21
50
|
@digest = nil
|
22
51
|
end
|
23
52
|
|
53
|
+
def versioned=(versioned)
|
54
|
+
@versioned = versioned
|
55
|
+
end
|
56
|
+
|
57
|
+
def versioned?
|
58
|
+
!!@versioned
|
59
|
+
end
|
60
|
+
|
61
|
+
def version_id
|
62
|
+
if versioned?
|
63
|
+
@version_id ||= generate_version_id
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
24
67
|
def size
|
25
68
|
data&.size
|
26
69
|
end
|
@@ -35,7 +78,7 @@ module Canistor
|
|
35
78
|
end
|
36
79
|
|
37
80
|
def digest
|
38
|
-
@digest ||= Digest::
|
81
|
+
@digest ||= Digest::MD5.hexdigest(data)
|
39
82
|
end
|
40
83
|
|
41
84
|
def etag
|
@@ -65,11 +108,12 @@ module Canistor
|
|
65
108
|
|
66
109
|
def identity_headers
|
67
110
|
{
|
68
|
-
'
|
111
|
+
'etag' => etag,
|
69
112
|
'x-amz-id' => digest[0, 16],
|
70
113
|
'x-amz-id-2' => Base64.strict_encode64(digest),
|
71
|
-
'
|
72
|
-
|
114
|
+
'x-amz-request-id' => Base64.strict_encode64(SecureRandom.hex(16)),
|
115
|
+
'x-amz-version-id' => version_id
|
116
|
+
}.compact
|
73
117
|
end
|
74
118
|
|
75
119
|
def head(context, subject)
|
@@ -82,23 +126,38 @@ module Canistor
|
|
82
126
|
end
|
83
127
|
|
84
128
|
def put(context, subject)
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
object_data(context, source_object)
|
90
|
-
)
|
91
|
-
context.http_response.signal_headers(200, identity_headers)
|
129
|
+
if source_object = find_source(context, subject)
|
130
|
+
apply_source_object(context, subject, source_object)
|
131
|
+
else
|
132
|
+
apply_request(context, subject)
|
92
133
|
end
|
93
134
|
end
|
94
135
|
|
136
|
+
def copy
|
137
|
+
object = self.class.new(region: region, bucket: bucket, key: key)
|
138
|
+
object.write(headers, data)
|
139
|
+
object
|
140
|
+
end
|
141
|
+
|
95
142
|
def delete(context, subject)
|
96
143
|
context.http_response.signal_headers(200, {})
|
97
144
|
end
|
98
145
|
|
146
|
+
def copy_headers(context)
|
147
|
+
directive = context.http_request.headers['x-amz-metadata-directive']
|
148
|
+
case directive
|
149
|
+
when 'COPY'
|
150
|
+
identity_headers
|
151
|
+
when 'REPLACE', nil
|
152
|
+
context.http_request.headers
|
153
|
+
else
|
154
|
+
raise ArgumentError, "Unsupported metadata directive: `#{directive}'"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
99
158
|
private
|
100
159
|
|
101
|
-
def
|
160
|
+
def find_source(context, subject)
|
102
161
|
if source = context.http_request.headers['x-amz-copy-source']
|
103
162
|
bucket_name, key = source.split('/', 2)
|
104
163
|
if bucket = Canistor.store.dig(region, bucket_name)
|
@@ -115,24 +174,18 @@ module Canistor
|
|
115
174
|
end
|
116
175
|
end
|
117
176
|
|
118
|
-
def
|
119
|
-
|
120
|
-
|
121
|
-
else
|
122
|
-
context.http_request.body
|
123
|
-
end
|
177
|
+
def apply_request(context, subject)
|
178
|
+
write(context.http_request.headers, context.http_request.body)
|
179
|
+
context.http_response.signal_headers(200, identity_headers)
|
124
180
|
end
|
125
181
|
|
126
|
-
def
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
else
|
134
|
-
raise ArgumentError, "Unsupported metadata directive: `#{directive}'"
|
135
|
-
end
|
182
|
+
def apply_source_object(context, subject, source_object)
|
183
|
+
write(source_object.copy_headers(context), source_object.data)
|
184
|
+
CopyObjectResult.serve(context, subject, source_object, self)
|
185
|
+
end
|
186
|
+
|
187
|
+
def generate_version_id
|
188
|
+
Base64.strict_encode64(SecureRandom.hex(16)).gsub('=', '')
|
136
189
|
end
|
137
190
|
|
138
191
|
META_HEADERS = %w(
|
@@ -143,7 +196,7 @@ module Canistor
|
|
143
196
|
def headers=(headers)
|
144
197
|
return if headers.nil?
|
145
198
|
headers.each do |name, value|
|
146
|
-
if META_HEADERS.include?(name)
|
199
|
+
if META_HEADERS.include?(name) || name.start_with?('x-amz-meta')
|
147
200
|
@headers[name] = value
|
148
201
|
end
|
149
202
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Canistor
|
4
|
+
module Storage
|
5
|
+
# Manages a lookup table of versioned objects.
|
6
|
+
class Objects
|
7
|
+
def initialize(versioned: true)
|
8
|
+
@versioned = versioned
|
9
|
+
@objects = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def versioned?
|
13
|
+
@versioned
|
14
|
+
end
|
15
|
+
|
16
|
+
def [](key)
|
17
|
+
if stack = @objects[key]
|
18
|
+
stack.last
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def []=(key, object)
|
23
|
+
@objects[key] ||= []
|
24
|
+
if versioned?
|
25
|
+
@objects[key].push(object)
|
26
|
+
else
|
27
|
+
@objects[key] = [object]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def delete(key)
|
32
|
+
if versioned?
|
33
|
+
self[key] = Canistor::Storage::DeleteMarker.new
|
34
|
+
else
|
35
|
+
@objects.delete(key).last
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def dig(*segments)
|
40
|
+
@objects.dig(*segments)&.last
|
41
|
+
end
|
42
|
+
|
43
|
+
def each
|
44
|
+
@objects.each do |key, stack|
|
45
|
+
yield [key, stack.last]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/canistor/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: canistor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Manfred Stienstra
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-10-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -98,7 +98,10 @@ files:
|
|
98
98
|
- lib/canistor/plugin.rb
|
99
99
|
- lib/canistor/storage.rb
|
100
100
|
- lib/canistor/storage/bucket.rb
|
101
|
+
- lib/canistor/storage/bucket/settings.rb
|
102
|
+
- lib/canistor/storage/delete_marker.rb
|
101
103
|
- lib/canistor/storage/object.rb
|
104
|
+
- lib/canistor/storage/objects.rb
|
102
105
|
- lib/canistor/storage/part.rb
|
103
106
|
- lib/canistor/storage/upload.rb
|
104
107
|
- lib/canistor/subject.rb
|
@@ -123,7 +126,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
123
126
|
version: '0'
|
124
127
|
requirements: []
|
125
128
|
rubyforge_project:
|
126
|
-
rubygems_version: 2.7.
|
129
|
+
rubygems_version: 2.7.6
|
127
130
|
signing_key:
|
128
131
|
specification_version: 4
|
129
132
|
summary: Canistor is mock for Aws::S3 defined by the AWS SDK gem.
|