canistor 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|