attache 1.0.5 → 1.1.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 +17 -0
- data/config.ru +1 -0
- data/config/vhost.example.yml +3 -0
- data/lib/attache.rb +1 -0
- data/lib/attache/backup.rb +29 -0
- data/lib/attache/delete.rb +20 -5
- data/lib/attache/download.rb +33 -8
- data/lib/attache/version.rb +1 -1
- data/lib/attache/vhost.rb +13 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8a5ec5e8b4431d1505fb6519428f01091da7a658
|
4
|
+
data.tar.gz: 1bdfa46bee9d2015ff5afc0fd9caaff55bb3f5f2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 00659d9c0716246aa7c501dd6a37f8e6fd6a0e2c4e9ec23f17418f7536362f013ee05c885b9cbd74b7159b68ed12d33cb83326b4c1ff6ef8eca2b31ab46cb0fa
|
7
|
+
data.tar.gz: 1b0cb02c0d32909d23f5b5cb120e5a463f0aaa323c9304367ed28fc6f4b9d884ff2ce085f2db99654cca892d01355e7a99e00bd7482a456f768ca63179cd70b2
|
data/README.md
CHANGED
@@ -173,6 +173,23 @@ Removing 1 or more files from the local cache and remote storage can be done via
|
|
173
173
|
|
174
174
|
The `paths` value should be delimited by the newline character, aka `\n`. In the example above, 3 files will be requested for deletion: `image1.jpg`, `prefix2/image2.jpg`, and `image3.jpg`.
|
175
175
|
|
176
|
+
#### Backup
|
177
|
+
|
178
|
+
> ```
|
179
|
+
> POST /backup
|
180
|
+
> paths=image1.jpg%0Aprefix2%2Fimage2.jpg%0Aimage3.jpg
|
181
|
+
> ```
|
182
|
+
|
183
|
+
Copying 1 or more files from the default remote storage to the backup remote storage (backup) can be done via a http `POST` request to `/backup`, with a `paths` parameter in the request body.
|
184
|
+
|
185
|
+
The `paths` value should be delimited by the newline character, aka `\n`. In the example above, 3 files will be requested for backup: `image1.jpg`, `prefix2/image2.jpg`, and `image3.jpg`.
|
186
|
+
|
187
|
+
If backup remote storage is not configured, this API call will be a noop. If configured, the backup storage must be accessible by the same credentials as default cloud storage as the system. Please refer to the `BACKUP_CONFIG` configuration illustrated in `config/vhost.example.yml` file in this repository.
|
188
|
+
|
189
|
+
By default, `backup` operation is performed synchronously. Set `BACKUP_ASYNC` environment variable to make it follow the same synchronicity as `delete`
|
190
|
+
|
191
|
+
The main reason to configure a backup storage is to make the default cloud storage auto expire files; mitigating [abuse](https://github.com/choonkeat/attache/issues/13). You should consult the documentation of your cloud storage provider on how to setup auto expiry, e.g. [here](https://aws.amazon.com/blogs/aws/amazon-s3-object-expiration/) or [here](https://cloud.google.com/storage/docs/lifecycle)
|
192
|
+
|
176
193
|
## License
|
177
194
|
|
178
195
|
MIT
|
data/config.ru
CHANGED
data/config/vhost.example.yml
CHANGED
@@ -20,6 +20,9 @@
|
|
20
20
|
"aws_secret_access_key": CHANGEME
|
21
21
|
"bucket": CHANGEME
|
22
22
|
"region": us-west-1
|
23
|
+
"BACKUP_CONFIG":
|
24
|
+
"bucket": CHANGEME_BAK
|
25
|
+
# only supports 1 key: `bucket`
|
23
26
|
|
24
27
|
# This section will only take effect if a request is made to `localhost:9292`
|
25
28
|
"localhost:9292":
|
data/lib/attache.rb
CHANGED
@@ -0,0 +1,29 @@
|
|
1
|
+
class Attache::Backup < Attache::Base
|
2
|
+
def initialize(app)
|
3
|
+
@app = app
|
4
|
+
end
|
5
|
+
|
6
|
+
def _call(env, config)
|
7
|
+
case env['PATH_INFO']
|
8
|
+
when '/backup'
|
9
|
+
request = Rack::Request.new(env)
|
10
|
+
params = request.params
|
11
|
+
return config.unauthorized unless config.authorized?(params)
|
12
|
+
|
13
|
+
if config.storage && config.bucket
|
14
|
+
sync_method = (ENV['BACKUP_ASYNC'] ? :async : :send)
|
15
|
+
params['paths'].to_s.split("\n").each do |relpath|
|
16
|
+
Attache.logger.info "BACKUP remote #{relpath}"
|
17
|
+
config.send(sync_method, :backup_file, relpath: relpath)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
[200, config.headers_with_cors, []]
|
21
|
+
else
|
22
|
+
@app.call(env)
|
23
|
+
end
|
24
|
+
rescue Exception
|
25
|
+
Attache.logger.error $@
|
26
|
+
Attache.logger.error $!
|
27
|
+
[500, { 'X-Exception' => $!.to_s }, []]
|
28
|
+
end
|
29
|
+
end
|
data/lib/attache/delete.rb
CHANGED
@@ -11,13 +11,28 @@ class Attache::Delete < Attache::Base
|
|
11
11
|
return config.unauthorized unless config.authorized?(params)
|
12
12
|
|
13
13
|
params['paths'].to_s.split("\n").each do |relpath|
|
14
|
-
|
15
|
-
|
16
|
-
Attache.cache
|
14
|
+
threads = []
|
15
|
+
|
16
|
+
if Attache.cache
|
17
|
+
threads << Thread.new do
|
18
|
+
Attache.logger.info "DELETING local #{relpath}"
|
19
|
+
cachekey = File.join(request_hostname(env), relpath)
|
20
|
+
Attache.cache.delete(cachekey)
|
21
|
+
end
|
22
|
+
end
|
17
23
|
if config.storage && config.bucket
|
18
|
-
|
19
|
-
|
24
|
+
threads << Thread.new do
|
25
|
+
Attache.logger.info "DELETING remote #{relpath}"
|
26
|
+
config.async(:storage_destroy, relpath: relpath)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
if config.backup
|
30
|
+
threads << Thread.new do
|
31
|
+
Attache.logger.info "DELETING backup #{relpath}"
|
32
|
+
config.backup.async(:storage_destroy, relpath: relpath)
|
33
|
+
end
|
20
34
|
end
|
35
|
+
threads.each(&:join)
|
21
36
|
end
|
22
37
|
[200, config.headers_with_cors, []]
|
23
38
|
else
|
data/lib/attache/download.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
require 'connection_pool'
|
2
2
|
|
3
3
|
class Attache::Download < Attache::Base
|
4
|
-
REMOTE_GEOMETRY = ENV.fetch('REMOTE_GEOMETRY') { 'remote' }
|
5
4
|
OUTPUT_EXTENSIONS = %w[png jpg jpeg gif]
|
6
5
|
RESIZE_JOB_POOL = ConnectionPool.new(JSON.parse(ENV.fetch('RESIZE_POOL') { '{ "size": 2, "timeout": 60 }' }).symbolize_keys) { Attache::ResizeJob.new }
|
7
6
|
|
@@ -12,10 +11,14 @@ class Attache::Download < Attache::Base
|
|
12
11
|
def _call(env, config)
|
13
12
|
case env['PATH_INFO']
|
14
13
|
when %r{\A/view/}
|
14
|
+
vhosts = {}
|
15
|
+
vhosts[ENV.fetch('REMOTE_GEOMETRY') { 'remote' }] = config.storage && config.bucket && config
|
16
|
+
vhosts[ENV.fetch('BACKUP_GEOMETRY') { 'backup' }] = config.backup
|
17
|
+
|
15
18
|
parse_path_info(env['PATH_INFO']['/view/'.length..-1]) do |dirname, geometry, basename, relpath|
|
16
|
-
if
|
17
|
-
headers =
|
18
|
-
'Location' =>
|
19
|
+
if vhost = vhosts[geometry]
|
20
|
+
headers = vhost.download_headers.merge({
|
21
|
+
'Location' => vhost.storage_url(relpath: relpath),
|
19
22
|
'Cache-Control' => 'private, no-cache',
|
20
23
|
})
|
21
24
|
return [302, headers, []]
|
@@ -24,11 +27,11 @@ class Attache::Download < Attache::Base
|
|
24
27
|
file = begin
|
25
28
|
cachekey = File.join(request_hostname(env), relpath)
|
26
29
|
Attache.cache.fetch(cachekey) do
|
27
|
-
|
30
|
+
get_first_result_async(vhosts.inject({}) {|sum,(k,v)|
|
31
|
+
v ? sum.merge("#{k} #{relpath}" => lambda { v.storage_get(relpath: relpath) }) : sum
|
32
|
+
})
|
28
33
|
end
|
29
34
|
rescue Exception # Errno::ECONNREFUSED, OpenURI::HTTPError, Excon::Errors, Fog::Errors::Error
|
30
|
-
Attache.logger.error $@
|
31
|
-
Attache.logger.error $!
|
32
35
|
Attache.logger.error "ERROR REFERER #{env['HTTP_REFERER'].inspect}"
|
33
36
|
nil
|
34
37
|
end
|
@@ -37,7 +40,8 @@ class Attache::Download < Attache::Base
|
|
37
40
|
return [404, config.download_headers, []]
|
38
41
|
end
|
39
42
|
|
40
|
-
thumbnail =
|
43
|
+
thumbnail = case geometry
|
44
|
+
when 'original', *vhosts.keys
|
41
45
|
file
|
42
46
|
else
|
43
47
|
extension = basename.split(/\W+/).last
|
@@ -83,4 +87,25 @@ class Attache::Download < Attache::Base
|
|
83
87
|
end
|
84
88
|
end
|
85
89
|
|
90
|
+
def get_first_result_async(name_code_pairs)
|
91
|
+
result = nil
|
92
|
+
threads = name_code_pairs.collect {|name, code|
|
93
|
+
Thread.new do
|
94
|
+
Thread.handle_interrupt(BasicObject => :on_blocking) { # if killed
|
95
|
+
begin
|
96
|
+
if current_result = code.call
|
97
|
+
result = current_result
|
98
|
+
(threads - [Thread.current]).each(&:kill) # kill siblings
|
99
|
+
Attache.logger.info "[POOL] found #{name.inspect}"
|
100
|
+
end
|
101
|
+
rescue Exception
|
102
|
+
Attache.logger.info "[POOL] not found #{name.inspect}"
|
103
|
+
ensure
|
104
|
+
end
|
105
|
+
}
|
106
|
+
end
|
107
|
+
}
|
108
|
+
threads.each(&:join)
|
109
|
+
result
|
110
|
+
end
|
86
111
|
end
|
data/lib/attache/version.rb
CHANGED
data/lib/attache/vhost.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
class Attache::VHost
|
2
2
|
attr_accessor :remotedir,
|
3
3
|
:secret_key,
|
4
|
+
:backup,
|
4
5
|
:bucket,
|
5
6
|
:storage,
|
6
7
|
:download_headers,
|
@@ -14,6 +15,11 @@ class Attache::VHost
|
|
14
15
|
if env['FOG_CONFIG']
|
15
16
|
self.bucket = env['FOG_CONFIG'].fetch('bucket')
|
16
17
|
self.storage = Fog::Storage.new(env['FOG_CONFIG'].except('bucket').symbolize_keys)
|
18
|
+
|
19
|
+
if env['BACKUP_CONFIG']
|
20
|
+
backup_fog = env['FOG_CONFIG'].merge(env['BACKUP_CONFIG'])
|
21
|
+
self.backup = Attache::VHost.new(env.except('BACKUP_CONFIG').merge('FOG_CONFIG' => backup_fog))
|
22
|
+
end
|
17
23
|
end
|
18
24
|
self.download_headers = {
|
19
25
|
"Cache-Control" => "public, max-age=31536000"
|
@@ -86,4 +92,11 @@ class Attache::VHost
|
|
86
92
|
def unauthorized
|
87
93
|
[401, headers_with_cors.merge('X-Exception' => 'Authorization failed'), []]
|
88
94
|
end
|
95
|
+
|
96
|
+
def backup_file(args)
|
97
|
+
if backup
|
98
|
+
key = File.join(*remotedir, args[:relpath])
|
99
|
+
storage.copy_object(bucket, key, backup.bucket, key)
|
100
|
+
end
|
101
|
+
end
|
89
102
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: attache
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- choonkeat
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-12-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -249,6 +249,7 @@ files:
|
|
249
249
|
- config/vhost.example.yml
|
250
250
|
- exe/attache
|
251
251
|
- lib/attache.rb
|
252
|
+
- lib/attache/backup.rb
|
252
253
|
- lib/attache/base.rb
|
253
254
|
- lib/attache/delete.rb
|
254
255
|
- lib/attache/deleteme.log
|