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