activestorage-sftp 0.1.1 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 53fb9c020a024d48787fccfbdd311b88f1eef5b6
4
- data.tar.gz: 396a0b2ffc2cde6bb0f88bf169cdbf2932110855
3
+ metadata.gz: 86f61c010b69d4972dbd32fd3eeec9ad415d5051
4
+ data.tar.gz: 3d82a1f0208e565c98a791506c8c11619faa7a4e
5
5
  SHA512:
6
- metadata.gz: 930f62d8856cfda94bfa90285ab6c59fc5d3d4ccdd14ce851a2dece838e5f78114438c87195d2c467738aa01031be7eea591e1f7244f40222507aaa52f30ffbe
7
- data.tar.gz: 5a7f980c04c712750beec2f4a328bc676d0be570cc9c52854c46c2e053d0f99fe69f6a47b955fb3157eb554d5efff98579d9bf0b1cb14099ce8718d2b57689b1
6
+ metadata.gz: 6ccd930d3aa5c2cd6f26dd6476b64000e935e1984f01536efd7abd46191f587ed48defdeac2f74efe3ee7155d1a45fe79ce14034613ac3b6c84002192bbca61c
7
+ data.tar.gz: cd1c9b1f1983508558e833af339105e2089adb47e23f70b4903c775beac5d75a2bc67c06d844908c15fafebfa183debce548b0df38d60b05f5459e83d81d286b
data/README.md CHANGED
@@ -20,8 +20,8 @@ Or install it yourself as:
20
20
 
21
21
  ## Usage
22
22
 
23
-
24
23
  Each application server saves blobs to file server through SFTP:
24
+
25
25
  ```yml
26
26
  # config/storage.yml
27
27
  sftp:
@@ -30,7 +30,9 @@ sftp:
30
30
  root: /var/www/proj/shared/storage
31
31
  host: file.intranet
32
32
  public_host: https://file.internet
33
+ password: <%= ENV['PASSWORD'] %> # optional
33
34
  ```
35
+
34
36
  File server serves blobs using DiskService:
35
37
  ```yml
36
38
  # config/storage.yml
@@ -54,6 +56,36 @@ sftp:
54
56
  host: secure.backup
55
57
  ```
56
58
 
59
+ ### Further configuration options:
60
+
61
+ #### use_public_url: Generate plain ("dumb") URLs of upload server
62
+
63
+ By default the generated URLs will include parameters for `content_disposition`, expiration hints etc. A generated blobs URL might thus look like:
64
+
65
+ https://publichost/PATH/rails/active_storage/disk/hash-hash/name.JPG?content_type=image%2Fjpeg&disposition=inline%3B+filename%3D
66
+
67
+ If you prefer simple URLs like
68
+
69
+ https://publichost/PATH/hash
70
+
71
+ you can set a configuration option:
72
+
73
+ ```yml
74
+ # config/storage.yml
75
+ sftp:
76
+ simple_public_urls: true # defaults to false
77
+ ```
78
+
79
+
80
+ #### verify_via_http_get: Faster existence verification via HTTP GET
81
+
82
+ The default way of verifying that a blob does exist is to login to the sftp server and stat() the relevant file. This is done e.g. before re-transforming and uploading an image variant. While other "caching" solutions exist to speed up that process, a simple and efficient way of verifying the existence of a file is to query the relevant server with an HTTP HEAD request. Depending on the setup this might not always be a viable way, so it can be switched on with a configuration option.
83
+
84
+ ```yml
85
+ # config/storage.yml
86
+ sftp:
87
+ verify_via_http_get: true # defaults to false
88
+ ```
57
89
 
58
90
  ## Contributing
59
91
 
@@ -23,8 +23,8 @@ Gem::Specification.new do |spec|
23
23
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
24
  spec.require_paths = ["lib"]
25
25
 
26
- spec.add_dependency "rails", "> 5.2.0"
27
- spec.add_dependency "net-sftp", "~> 2.1.2"
26
+ spec.add_dependency "rails", ">= 5.2.0"
27
+ spec.add_dependency "net-sftp", ">= 2.1.2"
28
28
 
29
29
  spec.add_development_dependency "bundler", "~> 1.17"
30
30
  spec.add_development_dependency "rake", "~> 10.0"
@@ -11,12 +11,15 @@ module ActiveStorage
11
11
 
12
12
  attr_reader :host, :user, :root, :public_host, :public_root
13
13
 
14
- def initialize(host:, user:, public_host: nil, root: './', public_root: nil)
14
+ def initialize(host:, user:, public_host: nil, root: './', public_root: './', password: nil, simple_public_urls: false, verify_via_http_get: false)
15
15
  @host = host
16
16
  @user = user
17
17
  @root = root
18
18
  @public_host = public_host
19
19
  @public_root = public_root
20
+ @password = password
21
+ @simple_public_urls = simple_public_urls
22
+ @verify_via_http_get = verify_via_http_get
20
23
  end
21
24
 
22
25
  def upload(key, io, checksum: nil, **)
@@ -116,37 +119,76 @@ module ActiveStorage
116
119
  def exist?(key)
117
120
  instrument :exist, key: key do |payload|
118
121
  answer = false
119
- through_sftp do |sftp|
120
- answer = sftp.stat(path_for(key)).present?
122
+
123
+ if @verify_via_http_get
124
+ uri = URI([public_host, relative_folder_for(key), key].join('/'))
125
+ request = Net::HTTP.new uri.host
126
+ response = request.request_head uri.path
127
+
128
+ answer = (response.code.to_i == 200)
129
+ else
130
+ through_sftp do |sftp|
131
+ # TODO Probably adviseable to let some more exceptions go through
132
+ begin
133
+ sftp.stat!(path_for(key)) do |response|
134
+ answer = response.ok?
135
+ end
136
+ rescue Net::SFTP::StatusException => e
137
+ answer = false
138
+ end
139
+ end
121
140
  end
141
+
122
142
  payload[:exist] = answer
123
143
  answer
124
144
  end
125
145
  end
126
146
 
127
147
  def url(key, expires_in:, filename:, disposition:, content_type:)
128
- instrument :url, key: key do |payload|
129
- raise "public_host not defined." unless public_host
130
- content_disposition = content_disposition_with(type: disposition, filename: filename)
131
- verified_key_with_expiration = ActiveStorage.verifier.generate(
132
- {
133
- key: key,
134
- disposition: content_disposition,
135
- content_type: content_type
136
- },
137
- { expires_in: expires_in,
138
- purpose: :blob_key }
139
- )
140
-
141
- generated_url = url_helpers.rails_disk_service_url(verified_key_with_expiration,
142
- host: public_host,
143
- disposition: content_disposition,
144
- content_type: content_type,
145
- filename: filename
146
- )
147
- payload[:url] = generated_url
148
-
149
- generated_url
148
+ if @simple_public_urls
149
+ public_url(key)
150
+ else
151
+ classic_url(key,
152
+ expires_in: expires_in,
153
+ filename: filename,
154
+ disposition: disposition,
155
+ content_type: content_type)
156
+ end
157
+ end
158
+
159
+ def classic_url(key, expires_in:, filename:, disposition:, content_type:)
160
+ instrument :url, key: key do |payload|
161
+ raise NotConfigured, "public_host not defined." unless public_host
162
+ content_disposition = content_disposition_with(type: disposition, filename: filename)
163
+ verified_key_with_expiration = ActiveStorage.verifier.generate(
164
+ {
165
+ key: key,
166
+ disposition: content_disposition,
167
+ content_type: content_type
168
+ },
169
+ {
170
+ expires_in: expires_in,
171
+ purpose: :blob_key
172
+ }
173
+ )
174
+
175
+ generated_url = url_helpers.rails_disk_service_url(verified_key_with_expiration,
176
+ host: public_host,
177
+ disposition: content_disposition,
178
+ content_type: content_type,
179
+ filename: filename
180
+ )
181
+ payload[:url] = generated_url
182
+ generated_url
183
+ end
184
+ end
185
+
186
+ def public_url(key)
187
+ instrument :url, key: key do |payload|
188
+ raise NotConfigured, "public_host not defined." unless public_host
189
+ generated_url = File.join(public_host, public_root, path_for(key), key)
190
+ payload[:url] = generated_url
191
+ generated_url
150
192
  end
151
193
  end
152
194
 
@@ -183,7 +225,8 @@ module ActiveStorage
183
225
 
184
226
  protected
185
227
  def through_sftp(&block)
186
- Net::SFTP.start(@host, @user) do |sftp|
228
+ opts = @password.present? ? {password: @password} : {}
229
+ Net::SFTP.start(@host, @user, opts) do |sftp|
187
230
  block.call(sftp)
188
231
  end
189
232
  end
@@ -197,18 +240,20 @@ module ActiveStorage
197
240
  end
198
241
 
199
242
  def mkdir_for(key)
243
+ mkdir_p_for(path_for key)
244
+ end
245
+
246
+ def mkdir_p_for(abs_path)
200
247
  through_sftp do |sftp|
201
- sub_folder = File.join root, key[0..1]
202
- begin
203
- sftp.opendir!(sub_folder)
204
- rescue => e
205
- sftp.mkdir!(sub_folder)
206
- end
207
- sub_folder = File.join(sub_folder, key[2..3])
208
- begin
209
- sftp.opendir!(sub_folder)
210
- rescue => e
211
- sftp.mkdir!(sub_folder)
248
+ base_path = ''
249
+ abs_path.split('/')[0...-1].each do |path|
250
+ sub_folder = File.join(base_path, path)
251
+ begin
252
+ sftp.opendir!(sub_folder)
253
+ rescue => e
254
+ sftp.mkdir!(sub_folder)
255
+ end
256
+ base_path = sub_folder
212
257
  end
213
258
  end
214
259
  end
@@ -6,5 +6,8 @@ module ActiveStorage
6
6
  class Error < StandardError; end
7
7
  class ChunkSizeError < Error; end
8
8
  class SFTPResponseError < Error; end
9
+ class NotConfigured < Error; end
9
10
  end
10
11
  end
12
+
13
+
@@ -1,5 +1,5 @@
1
1
  module ActiveStorage
2
2
  module SFTP
3
- VERSION = "0.1.1"
3
+ VERSION = "0.2.0"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,41 +1,41 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activestorage-sftp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - treenewbee
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-08-16 00:00:00.000000000 Z
11
+ date: 2019-10-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: 5.2.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 5.2.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: net-sftp
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: 2.1.2
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: 2.1.2
41
41
  - !ruby/object:Gem::Dependency