middleman-s3_sync 4.0.2 → 4.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 +5 -5
- data/.gitignore +1 -0
- data/.travis.yml +1 -2
- data/README.md +19 -4
- data/lib/middleman/redirect.rb +21 -0
- data/lib/middleman/s3_sync/options.rb +6 -0
- data/lib/middleman/s3_sync/resource.rb +129 -24
- data/lib/middleman/s3_sync/version.rb +1 -1
- data/lib/middleman/s3_sync.rb +59 -41
- data/lib/middleman-s3_sync/commands.rb +3 -1
- data/lib/middleman-s3_sync/extension.rb +15 -3
- data/lib/middleman-s3_sync.rb +1 -0
- data/middleman-s3_sync.gemspec +8 -5
- data/spec/caching_policy_spec.rb +26 -26
- data/spec/resource_spec.rb +99 -34
- data/spec/spec_helper.rb +41 -1
- metadata +58 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 0e217a8648de15ba3454749ebf5373e9e2bf31591078be5ae0f5128aeac21d79
|
4
|
+
data.tar.gz: 4f82b54882d96b1ece6585e2075a19d8ef54af78fb8aaec42c07ca14cf41fc7d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2640264fb1f0d4c2be50526d966842176ab9997537a4d2f271b0e1e0e29e4398da44f2c66969b08d9f9f4ddeaeef2014a2b2afe3eb68c95986725aa399524f3c
|
7
|
+
data.tar.gz: b9fa2b48b0f63638627750a532e7c7f1fd94a15cf1af4bd9a9826a24021b9fce288d83c9e5d240b4483a0e0488f57b0a7726ede24af9a861d922095bd26eabda
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Middleman::S3Sync
|
2
2
|
|
3
|
-
[](https://gitter.im/fredjean/middleman-s3_sync?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://gitter.im/fredjean/middleman-s3_sync?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://codeclimate.com/github/fredjean/middleman-s3_sync) [](https://travis-ci.org/fredjean/middleman-s3_sync)
|
4
4
|
|
5
5
|
This gem determines which files need to be added, updated and optionally deleted
|
6
6
|
and only transfer these files up. This reduces the impact of an update
|
@@ -39,7 +39,7 @@ You need to add the following code to your ```config.rb``` file:
|
|
39
39
|
|
40
40
|
```ruby
|
41
41
|
activate :s3_sync do |s3_sync|
|
42
|
-
s3_sync.bucket = 'my.bucket.com' # The name of the S3 bucket you are
|
42
|
+
s3_sync.bucket = 'my.bucket.com' # The name of the S3 bucket you are targeting. This is globally unique.
|
43
43
|
s3_sync.region = 'us-west-1' # The AWS region for your bucket.
|
44
44
|
s3_sync.aws_access_key_id = 'AWS KEY ID'
|
45
45
|
s3_sync.aws_secret_access_key = 'AWS SECRET KEY'
|
@@ -79,7 +79,8 @@ The following defaults apply to the configuration items:
|
|
79
79
|
|
80
80
|
## Setting AWS Credentials
|
81
81
|
|
82
|
-
There are several ways to provide the AWS credentials for s3_sync
|
82
|
+
There are several ways to provide the AWS credentials for s3_sync. I strongly recommend using some form of federation to assume a role with permissions to publish to your
|
83
|
+
S3 bucket. However, you can still use the following methods::We
|
83
84
|
|
84
85
|
#### Through `config.rb`
|
85
86
|
|
@@ -118,6 +119,7 @@ map to the following values:
|
|
118
119
|
| --------------------- | ---------------------------------- |
|
119
120
|
| aws_access_key_id | ```ENV['AWS_ACCESS_KEY_ID']``` |
|
120
121
|
| aws_secret_access_key | ```ENV['AWS_SECRET_ACCESS_KEY']``` |
|
122
|
+
| aws_session_token | ```ENV['AWS_SESSION_TOKEN']``` |
|
121
123
|
| bucket | ```ENV['AWS_BUCKET']``` |
|
122
124
|
|
123
125
|
The environment is used when the credentials are not set in the activate
|
@@ -182,6 +184,19 @@ You can specify which environment to run Middleman under using the
|
|
182
184
|
|
183
185
|
$ middleman s3_sync --environment=production
|
184
186
|
|
187
|
+
You can set up separate sync environments in config.rb like this:
|
188
|
+
|
189
|
+
```ruby
|
190
|
+
configure :staging do
|
191
|
+
activate :s3_sync do |s3_sync|
|
192
|
+
s3_sync.bucket = '<bucket'
|
193
|
+
...
|
194
|
+
end
|
195
|
+
end
|
196
|
+
```
|
197
|
+
|
198
|
+
See the Usage section above for all the s3_sync. options to include. Currently, the .s3_sync file does not allow separate environments.
|
199
|
+
|
185
200
|
#### Dry Run
|
186
201
|
|
187
202
|
You can perform a dry run to see what would be the result of a sync
|
@@ -278,7 +293,7 @@ The following keys can be set:
|
|
278
293
|
You can pass the `expires` key to the `caching_policy` and
|
279
294
|
`default_caching_policy` methods if you insist on setting the expires
|
280
295
|
header on a results. You will need to pass it a Time object indicating
|
281
|
-
when the
|
296
|
+
when the resource is set to expire.
|
282
297
|
|
283
298
|
> Note that the `Cache-Control` header will take precedence over the
|
284
299
|
> `Expires` header if both are present.
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Middleman
|
2
|
+
module Sitemap
|
3
|
+
class Resource
|
4
|
+
def redirect?
|
5
|
+
false
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
module Extensions
|
10
|
+
class RedirectResource < Resource
|
11
|
+
def target_url
|
12
|
+
@target_url ||= ::Middleman::Util.url_for(@store.app, @request_path, relative: false, find_resource: true)
|
13
|
+
end
|
14
|
+
|
15
|
+
def redirect?
|
16
|
+
true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -6,6 +6,7 @@ module Middleman
|
|
6
6
|
:http_prefix,
|
7
7
|
:acl,
|
8
8
|
:bucket,
|
9
|
+
:endpoint,
|
9
10
|
:region,
|
10
11
|
:aws_access_key_id,
|
11
12
|
:aws_secret_access_key,
|
@@ -22,6 +23,7 @@ module Middleman
|
|
22
23
|
:dry_run,
|
23
24
|
:verbose,
|
24
25
|
:content_types,
|
26
|
+
:ignore_paths,
|
25
27
|
:index_document,
|
26
28
|
:error_document
|
27
29
|
]
|
@@ -67,6 +69,10 @@ module Middleman
|
|
67
69
|
(@path_style.nil? ? true : @path_style)
|
68
70
|
end
|
69
71
|
|
72
|
+
def ignore_paths
|
73
|
+
@ignore_paths.nil? ? [] : @ignore_paths
|
74
|
+
end
|
75
|
+
|
70
76
|
def prefix=(prefix)
|
71
77
|
http_prefix = @http_prefix ? @http_prefix.sub(%r{^/}, "") : ""
|
72
78
|
if http_prefix.split("/").first == prefix
|
@@ -4,12 +4,19 @@ module Middleman
|
|
4
4
|
attr_accessor :path, :resource, :partial_s3_resource, :full_s3_resource, :content_type, :gzipped, :options
|
5
5
|
|
6
6
|
CONTENT_MD5_KEY = 'x-amz-meta-content-md5'
|
7
|
+
REDIRECT_KEY = 'x-amz-website-redirect-location'
|
7
8
|
|
8
9
|
include Status
|
9
10
|
|
10
11
|
def initialize(resource, partial_s3_resource)
|
11
12
|
@resource = resource
|
12
|
-
@path =
|
13
|
+
@path = if resource
|
14
|
+
resource.destination_path.sub(/^\//, '')
|
15
|
+
elsif partial_s3_resource&.key
|
16
|
+
partial_s3_resource.key.sub(/^\//, '')
|
17
|
+
else
|
18
|
+
''
|
19
|
+
end
|
13
20
|
@partial_s3_resource = partial_s3_resource
|
14
21
|
end
|
15
22
|
|
@@ -19,13 +26,33 @@ module Middleman
|
|
19
26
|
|
20
27
|
# S3 resource as returned by a HEAD request
|
21
28
|
def full_s3_resource
|
22
|
-
@full_s3_resource ||=
|
29
|
+
@full_s3_resource ||= begin
|
30
|
+
bucket.object(remote_path.sub(/^\//, '')).head
|
31
|
+
rescue Aws::S3::Errors::NotFound
|
32
|
+
nil
|
33
|
+
end
|
23
34
|
end
|
24
35
|
|
25
36
|
def remote_path
|
26
|
-
|
37
|
+
if s3_resource
|
38
|
+
if s3_resource.respond_to?(:key)
|
39
|
+
s3_resource.key.sub(/^\//, '')
|
40
|
+
else
|
41
|
+
# For HeadObjectOutput objects which don't have key method
|
42
|
+
options.prefix ? normalize_path(options.prefix, path) : path.sub(/^\//, '')
|
43
|
+
end
|
44
|
+
else
|
45
|
+
options.prefix ? normalize_path(options.prefix, path) : path.sub(/^\//, '')
|
46
|
+
end.sub(/^\//, '') # Ensure no leading slash
|
27
47
|
end
|
28
48
|
alias :key :remote_path
|
49
|
+
|
50
|
+
def normalize_path(prefix, path)
|
51
|
+
# Remove any trailing slash from prefix and leading slash from path
|
52
|
+
prefix = prefix.chomp('/')
|
53
|
+
path = path.sub(/^\//, '')
|
54
|
+
"#{prefix}/#{path}"
|
55
|
+
end
|
29
56
|
|
30
57
|
def to_h
|
31
58
|
attributes = {
|
@@ -52,18 +79,19 @@ module Middleman
|
|
52
79
|
attributes[:encryption] = 'AES256'
|
53
80
|
end
|
54
81
|
|
82
|
+
if redirect?
|
83
|
+
attributes[REDIRECT_KEY] = redirect_url
|
84
|
+
end
|
85
|
+
|
55
86
|
attributes
|
56
87
|
end
|
57
88
|
alias :attributes :to_h
|
58
89
|
|
59
90
|
def update!
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
s3_resource.save unless options.dry_run
|
66
|
-
}
|
91
|
+
say_status "#{ANSI.blue{"Updating"}} #{remote_path}#{ gzipped ? ANSI.white {' (gzipped)'} : ''}"
|
92
|
+
unless options.dry_run
|
93
|
+
upload!
|
94
|
+
end
|
67
95
|
end
|
68
96
|
|
69
97
|
def local_path
|
@@ -77,14 +105,52 @@ module Middleman
|
|
77
105
|
|
78
106
|
def destroy!
|
79
107
|
say_status "#{ANSI.red{"Deleting"}} #{remote_path}"
|
80
|
-
bucket.
|
108
|
+
bucket.object(remote_path.sub(/^\//, '')).delete unless options.dry_run
|
81
109
|
end
|
82
110
|
|
83
111
|
def create!
|
84
112
|
say_status "#{ANSI.green{"Creating"}} #{remote_path}#{ gzipped ? ANSI.white {' (gzipped)'} : ''}"
|
85
|
-
|
86
|
-
|
113
|
+
unless options.dry_run
|
114
|
+
upload!
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def upload!
|
119
|
+
object = bucket.object(remote_path.sub(/^\//, ''))
|
120
|
+
upload_options = {
|
121
|
+
body: local_content,
|
122
|
+
content_type: content_type,
|
123
|
+
acl: options.acl
|
87
124
|
}
|
125
|
+
|
126
|
+
# Add metadata if present
|
127
|
+
if local_content_md5
|
128
|
+
upload_options[:metadata] = { CONTENT_MD5_KEY => local_content_md5 }
|
129
|
+
end
|
130
|
+
|
131
|
+
# Add redirect if present
|
132
|
+
upload_options[:website_redirect_location] = redirect_url if redirect?
|
133
|
+
|
134
|
+
# Add content encoding if present
|
135
|
+
upload_options[:content_encoding] = "gzip" if options.prefer_gzip && gzipped
|
136
|
+
|
137
|
+
# Add cache control and expires if present
|
138
|
+
if caching_policy
|
139
|
+
upload_options[:cache_control] = caching_policy.cache_control
|
140
|
+
upload_options[:expires] = caching_policy.expires
|
141
|
+
end
|
142
|
+
|
143
|
+
# Add storage class if needed
|
144
|
+
if options.reduced_redundancy_storage
|
145
|
+
upload_options[:storage_class] = 'REDUCED_REDUNDANCY'
|
146
|
+
end
|
147
|
+
|
148
|
+
# Add encryption if needed
|
149
|
+
if options.encryption
|
150
|
+
upload_options[:server_side_encryption] = 'AES256'
|
151
|
+
end
|
152
|
+
|
153
|
+
object.put(upload_options)
|
88
154
|
end
|
89
155
|
|
90
156
|
def ignore!
|
@@ -122,12 +188,18 @@ module Middleman
|
|
122
188
|
status == :ignored || status == :alternate_encoding
|
123
189
|
end
|
124
190
|
|
125
|
-
def local_content
|
126
|
-
|
191
|
+
def local_content
|
192
|
+
if block_given?
|
193
|
+
File.open(local_path) { |f| yield f.read }
|
194
|
+
else
|
195
|
+
File.read(local_path)
|
196
|
+
end
|
127
197
|
end
|
128
198
|
|
129
199
|
def status
|
130
|
-
@status ||= if
|
200
|
+
@status ||= if shunned?
|
201
|
+
:ignored
|
202
|
+
elsif directory?
|
131
203
|
if remote?
|
132
204
|
:deleted
|
133
205
|
else
|
@@ -136,7 +208,7 @@ module Middleman
|
|
136
208
|
elsif local? && remote?
|
137
209
|
if options.force
|
138
210
|
:updated
|
139
|
-
elsif not
|
211
|
+
elsif not metadata_match?
|
140
212
|
:updated
|
141
213
|
elsif local_object_md5 == remote_object_md5
|
142
214
|
:identical
|
@@ -170,11 +242,36 @@ module Middleman
|
|
170
242
|
end
|
171
243
|
|
172
244
|
def remote?
|
173
|
-
|
245
|
+
!full_s3_resource.nil?
|
174
246
|
end
|
175
247
|
|
176
248
|
def redirect?
|
177
|
-
|
249
|
+
(resource && resource.respond_to?(:redirect?) && resource.redirect?) ||
|
250
|
+
(full_s3_resource && full_s3_resource.respond_to?(:website_redirect_location) && full_s3_resource.website_redirect_location)
|
251
|
+
end
|
252
|
+
|
253
|
+
def metadata_match?
|
254
|
+
redirect_match? && caching_policy_match?
|
255
|
+
end
|
256
|
+
|
257
|
+
def redirect_match?
|
258
|
+
if redirect?
|
259
|
+
redirect_url == remote_redirect_url
|
260
|
+
else
|
261
|
+
true
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def shunned?
|
266
|
+
!!path[Regexp.union(options.ignore_paths)]
|
267
|
+
end
|
268
|
+
|
269
|
+
def remote_redirect_url
|
270
|
+
full_s3_resource&.website_redirect_location
|
271
|
+
end
|
272
|
+
|
273
|
+
def redirect_url
|
274
|
+
resource.respond_to?(:target_url) ? resource.target_url : nil
|
178
275
|
end
|
179
276
|
|
180
277
|
def directory?
|
@@ -186,7 +283,7 @@ module Middleman
|
|
186
283
|
end
|
187
284
|
|
188
285
|
def remote_object_md5
|
189
|
-
s3_resource.etag
|
286
|
+
s3_resource.etag.gsub(/"/, '') if s3_resource.etag
|
190
287
|
end
|
191
288
|
|
192
289
|
def encoding_match?
|
@@ -194,7 +291,9 @@ module Middleman
|
|
194
291
|
end
|
195
292
|
|
196
293
|
def remote_content_md5
|
197
|
-
full_s3_resource.metadata
|
294
|
+
if full_s3_resource && full_s3_resource.metadata
|
295
|
+
full_s3_resource.metadata[CONTENT_MD5_KEY]
|
296
|
+
end
|
198
297
|
end
|
199
298
|
|
200
299
|
def local_object_md5
|
@@ -202,7 +301,13 @@ module Middleman
|
|
202
301
|
end
|
203
302
|
|
204
303
|
def local_content_md5
|
205
|
-
@local_content_md5 ||=
|
304
|
+
@local_content_md5 ||= begin
|
305
|
+
if File.exist?(original_path)
|
306
|
+
Digest::MD5.hexdigest(File.read(original_path))
|
307
|
+
else
|
308
|
+
nil
|
309
|
+
end
|
310
|
+
end
|
206
311
|
end
|
207
312
|
|
208
313
|
def original_path
|
@@ -211,7 +316,7 @@ module Middleman
|
|
211
316
|
|
212
317
|
def content_type
|
213
318
|
@content_type ||= Middleman::S3Sync.content_types[local_path]
|
214
|
-
@content_type ||= !resource.nil? ? resource.content_type : nil
|
319
|
+
@content_type ||= !resource.nil? && resource.respond_to?(:content_type) ? resource.content_type : nil
|
215
320
|
end
|
216
321
|
|
217
322
|
def caching_policy
|
@@ -219,7 +324,7 @@ module Middleman
|
|
219
324
|
end
|
220
325
|
|
221
326
|
def caching_policy_match?
|
222
|
-
if (
|
327
|
+
if caching_policy && full_s3_resource && full_s3_resource.respond_to?(:cache_control)
|
223
328
|
caching_policy.cache_control == full_s3_resource.cache_control
|
224
329
|
else
|
225
330
|
true
|
data/lib/middleman/s3_sync.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
require '
|
2
|
-
require 'fog/aws/storage'
|
1
|
+
require 'aws-sdk-s3'
|
3
2
|
require 'digest/md5'
|
4
3
|
require 'middleman/s3_sync/version'
|
5
4
|
require 'middleman/s3_sync/options'
|
@@ -7,6 +6,7 @@ require 'middleman/s3_sync/caching_policy'
|
|
7
6
|
require 'middleman/s3_sync/status'
|
8
7
|
require 'middleman/s3_sync/resource'
|
9
8
|
require 'middleman-s3_sync/extension'
|
9
|
+
require 'middleman/redirect'
|
10
10
|
require 'parallel'
|
11
11
|
require 'ruby-progressbar'
|
12
12
|
require 'thread'
|
@@ -24,6 +24,8 @@ module Middleman
|
|
24
24
|
attr_accessor :mm_resources
|
25
25
|
attr_reader :app
|
26
26
|
|
27
|
+
THREADS_COUNT = 8
|
28
|
+
|
27
29
|
def sync()
|
28
30
|
@app ||= ::Middleman::Application.new
|
29
31
|
|
@@ -48,8 +50,8 @@ module Middleman
|
|
48
50
|
def bucket
|
49
51
|
@@bucket_lock.synchronize do
|
50
52
|
@bucket ||= begin
|
51
|
-
bucket =
|
52
|
-
raise "Bucket #{s3_sync_options.bucket} doesn't exist!" unless bucket
|
53
|
+
bucket = s3_resource.bucket(s3_sync_options.bucket)
|
54
|
+
raise "Bucket #{s3_sync_options.bucket} doesn't exist!" unless bucket.exists?
|
53
55
|
bucket
|
54
56
|
end
|
55
57
|
end
|
@@ -73,7 +75,12 @@ module Middleman
|
|
73
75
|
|
74
76
|
protected
|
75
77
|
def update_bucket_versioning
|
76
|
-
|
78
|
+
s3_client.put_bucket_versioning({
|
79
|
+
bucket: s3_sync_options.bucket,
|
80
|
+
versioning_configuration: {
|
81
|
+
status: "Enabled"
|
82
|
+
}
|
83
|
+
}) if s3_sync_options.version_bucket
|
77
84
|
end
|
78
85
|
|
79
86
|
def update_bucket_website
|
@@ -87,30 +94,47 @@ module Middleman
|
|
87
94
|
|
88
95
|
unless opts.empty?
|
89
96
|
say_status "Putting bucket website: #{opts.to_json}"
|
90
|
-
|
97
|
+
s3_client.put_bucket_website({
|
98
|
+
bucket: s3_sync_options.bucket,
|
99
|
+
website_configuration: opts
|
100
|
+
})
|
91
101
|
end
|
92
102
|
end
|
93
103
|
|
94
|
-
def
|
95
|
-
|
96
|
-
|
97
|
-
:path_style => s3_sync_options.path_style
|
98
|
-
}
|
104
|
+
def s3_client
|
105
|
+
@s3_client ||= Aws::S3::Client.new(connection_options)
|
106
|
+
end
|
99
107
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
connection_options
|
107
|
-
|
108
|
+
def s3_resource
|
109
|
+
@s3_resource ||= Aws::S3::Resource.new(client: s3_client)
|
110
|
+
end
|
111
|
+
|
112
|
+
def connection_options
|
113
|
+
@connection_options ||= begin
|
114
|
+
connection_options = {
|
115
|
+
endpoint: s3_sync_options.endpoint,
|
116
|
+
region: s3_sync_options.region,
|
117
|
+
force_path_style: s3_sync_options.path_style
|
118
|
+
}
|
119
|
+
|
120
|
+
if s3_sync_options.aws_access_key_id && s3_sync_options.aws_secret_access_key
|
121
|
+
connection_options.merge!({
|
122
|
+
access_key_id: s3_sync_options.aws_access_key_id,
|
123
|
+
secret_access_key: s3_sync_options.aws_secret_access_key
|
124
|
+
})
|
125
|
+
|
126
|
+
# If using an assumed role
|
127
|
+
connection_options.merge!({
|
128
|
+
session_token: s3_sync_options.aws_session_token
|
129
|
+
}) if s3_sync_options.aws_session_token
|
130
|
+
end
|
108
131
|
|
109
|
-
|
132
|
+
connection_options
|
133
|
+
end
|
110
134
|
end
|
111
135
|
|
112
136
|
def remote_resource_for_path(path)
|
113
|
-
bucket_files
|
137
|
+
bucket_files[path]
|
114
138
|
end
|
115
139
|
|
116
140
|
def s3_sync_resources
|
@@ -125,7 +149,7 @@ module Middleman
|
|
125
149
|
|
126
150
|
def remote_paths
|
127
151
|
@remote_paths ||= if s3_sync_options.delete
|
128
|
-
bucket_files.
|
152
|
+
bucket_files.keys
|
129
153
|
else
|
130
154
|
[]
|
131
155
|
end
|
@@ -133,42 +157,36 @@ module Middleman
|
|
133
157
|
|
134
158
|
def bucket_files
|
135
159
|
@@bucket_files_lock.synchronize do
|
136
|
-
@bucket_files ||=
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
160
|
+
@bucket_files ||= begin
|
161
|
+
files = {}
|
162
|
+
bucket.objects.each do |object|
|
163
|
+
files[object.key] = object
|
164
|
+
end
|
165
|
+
files
|
166
|
+
end
|
141
167
|
end
|
142
168
|
end
|
143
169
|
|
144
170
|
def create_resources
|
145
|
-
files_to_create
|
146
|
-
r.create!
|
147
|
-
end
|
171
|
+
Parallel.map(files_to_create, in_threads: THREADS_COUNT, &:create!)
|
148
172
|
end
|
149
173
|
|
150
174
|
def update_resources
|
151
|
-
files_to_update
|
152
|
-
r.update!
|
153
|
-
end
|
175
|
+
Parallel.map(files_to_update, in_threads: THREADS_COUNT, &:update!)
|
154
176
|
end
|
155
177
|
|
156
178
|
def delete_resources
|
157
|
-
files_to_delete
|
158
|
-
r.destroy!
|
159
|
-
end
|
179
|
+
Parallel.map(files_to_delete, in_threads: THREADS_COUNT, &:destroy!)
|
160
180
|
end
|
161
181
|
|
162
182
|
def ignore_resources
|
163
|
-
files_to_ignore
|
164
|
-
r.ignore!
|
165
|
-
end
|
183
|
+
Parallel.map(files_to_ignore, in_threads: THREADS_COUNT, &:ignore!)
|
166
184
|
end
|
167
185
|
|
168
186
|
def work_to_be_done?
|
169
|
-
Parallel.each(mm_resources, in_threads:
|
187
|
+
Parallel.each(mm_resources, in_threads: THREADS_COUNT, progress: "Processing sitemap") { |mm_resource| add_local_resource(mm_resource) }
|
170
188
|
|
171
|
-
Parallel.each(remote_only_paths, in_threads:
|
189
|
+
Parallel.each(remote_only_paths, in_threads: THREADS_COUNT, progress: "Processing remote files") do |remote_path|
|
172
190
|
s3_sync_resources[remote_path] ||= S3Sync::Resource.new(nil, remote_resource_for_path(remote_path)).tap(&:status)
|
173
191
|
end
|
174
192
|
|
@@ -70,8 +70,10 @@ module Middleman
|
|
70
70
|
verbose = options[:verbose] ? 0 : 1
|
71
71
|
instrument = options[:instrument]
|
72
72
|
|
73
|
+
mode = options[:build] ? :build : :config
|
74
|
+
|
73
75
|
::Middleman::S3Sync.app = ::Middleman::Application.new do
|
74
|
-
config[:mode] =
|
76
|
+
config[:mode] = mode
|
75
77
|
config[:environment] = env
|
76
78
|
::Middleman::Logger.singleton(verbose, instrument)
|
77
79
|
end
|
@@ -9,9 +9,11 @@ module Middleman
|
|
9
9
|
option :http_prefix, nil, 'Path prefix of the resources'
|
10
10
|
option :acl, 'public-read', 'ACL for the resources being pushed to S3'
|
11
11
|
option :bucket, nil, 'The name of the bucket we are pushing to.'
|
12
|
+
option :endpoint, nil, 'The name of the endpoint to use - useful when using S3 compatible storage'
|
12
13
|
option :region, 'us-east-1', 'The name of the AWS region hosting the S3 bucket'
|
13
14
|
option :aws_access_key_id, ENV['AWS_ACCESS_KEY_ID'] , 'The AWS access key id'
|
14
15
|
option :aws_secret_access_key, ENV['AWS_SECRET_ACCESS_KEY'], 'The AWS secret access key'
|
16
|
+
option :aws_session_token, ENV['AWS_SESSION_TOKEN'] || ENV['AWS_SECURITY_TOKEN'], 'The AWS session token (for assuming roles)'
|
15
17
|
option :after_build, false, 'Whether to synchronize right after the build'
|
16
18
|
option :build_dir, nil, 'Where the built site is stored'
|
17
19
|
option :delete, true, 'Whether to delete resources that do not have a local equivalent'
|
@@ -25,6 +27,8 @@ module Middleman
|
|
25
27
|
option :dry_run, false, 'Whether to perform a dry-run'
|
26
28
|
option :index_document, nil, 'S3 custom index document path'
|
27
29
|
option :error_document, nil, 'S3 custom error document path'
|
30
|
+
option :content_types, {}, 'Custom content types'
|
31
|
+
option :ignore_paths, [], 'Paths that should be ignored during sync, strings or regex are allowed'
|
28
32
|
|
29
33
|
expose_to_config :s3_sync_options, :default_caching_policy, :caching_policy
|
30
34
|
|
@@ -39,6 +43,7 @@ module Middleman
|
|
39
43
|
read_config
|
40
44
|
options.aws_access_key_id ||= ENV['AWS_ACCESS_KEY_ID']
|
41
45
|
options.aws_secret_access_key ||= ENV['AWS_SECRET_ACCESS_KEY']
|
46
|
+
options.aws_session_token ||= ENV['AWS_SESSION_TOKEN'] || ENV['AWS_SECURITY_TOKEN']
|
42
47
|
options.bucket ||= ENV['AWS_BUCKET']
|
43
48
|
options.http_prefix = app.http_prefix if app.respond_to? :http_prefix
|
44
49
|
options.build_dir ||= app.build_dir if app.respond_to? :build_dir
|
@@ -53,8 +58,15 @@ module Middleman
|
|
53
58
|
::Middleman::S3Sync.sync() if options.after_build
|
54
59
|
end
|
55
60
|
|
56
|
-
def manipulate_resource_list(
|
57
|
-
::Middleman::S3Sync.mm_resources =
|
61
|
+
def manipulate_resource_list(resources)
|
62
|
+
::Middleman::S3Sync.mm_resources = resources.each_with_object([]) do |resource, list|
|
63
|
+
next if resource.ignored?
|
64
|
+
|
65
|
+
list << resource
|
66
|
+
list << resource.target_resource if resource.respond_to?(:target_resource)
|
67
|
+
end
|
68
|
+
|
69
|
+
resources
|
58
70
|
end
|
59
71
|
|
60
72
|
def s3_sync_options
|
@@ -72,7 +84,7 @@ module Middleman
|
|
72
84
|
config_file_path = File.join(root_path, ".s3_sync")
|
73
85
|
|
74
86
|
# skip if config file does not exist
|
75
|
-
return unless File.
|
87
|
+
return unless File.exist?(config_file_path)
|
76
88
|
|
77
89
|
io = File.open(config_file_path, "r")
|
78
90
|
end
|
data/lib/middleman-s3_sync.rb
CHANGED
data/middleman-s3_sync.gemspec
CHANGED
@@ -18,22 +18,25 @@ Gem::Specification.new do |gem|
|
|
18
18
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
19
|
gem.require_paths = ["lib"]
|
20
20
|
|
21
|
-
gem.add_runtime_dependency 'middleman-core'
|
21
|
+
gem.add_runtime_dependency 'middleman-core'
|
22
22
|
gem.add_runtime_dependency 'middleman-cli'
|
23
23
|
gem.add_runtime_dependency 'unf'
|
24
|
-
gem.add_runtime_dependency '
|
24
|
+
gem.add_runtime_dependency 'aws-sdk-s3'
|
25
25
|
gem.add_runtime_dependency 'map'
|
26
26
|
gem.add_runtime_dependency 'parallel'
|
27
27
|
gem.add_runtime_dependency 'ruby-progressbar'
|
28
28
|
gem.add_runtime_dependency 'ansi', '~> 1.5.0'
|
29
|
+
gem.add_runtime_dependency 'mime-types', '~> 3.1'
|
30
|
+
gem.add_runtime_dependency 'base64'
|
31
|
+
gem.add_runtime_dependency 'nokogiri', '>= 1.18.4'
|
29
32
|
|
30
33
|
gem.add_development_dependency 'rake'
|
31
34
|
gem.add_development_dependency 'pry'
|
32
35
|
gem.add_development_dependency 'pry-byebug'
|
33
|
-
gem.add_development_dependency 'rspec'
|
36
|
+
gem.add_development_dependency 'rspec'
|
37
|
+
gem.add_development_dependency 'rspec-support'
|
34
38
|
gem.add_development_dependency 'rspec-its'
|
35
39
|
gem.add_development_dependency 'rspec-mocks'
|
36
40
|
gem.add_development_dependency 'timerizer'
|
37
|
-
gem.add_development_dependency '
|
38
|
-
gem.add_development_dependency 'travis-lint'
|
41
|
+
gem.add_development_dependency 'webrick'
|
39
42
|
end
|
data/spec/caching_policy_spec.rb
CHANGED
@@ -7,59 +7,59 @@ describe Middleman::S3Sync::BrowserCachePolicy do
|
|
7
7
|
subject(:policy) { Middleman::S3Sync::BrowserCachePolicy.new(options) }
|
8
8
|
|
9
9
|
it "should be blank" do
|
10
|
-
policy.
|
11
|
-
|
12
|
-
policy.to_s.
|
13
|
-
policy.to_s.
|
14
|
-
policy.to_s.
|
15
|
-
policy.to_s.
|
16
|
-
policy.to_s.
|
17
|
-
policy.to_s.
|
18
|
-
policy.to_s.
|
19
|
-
policy.to_s.
|
20
|
-
policy.expires.
|
10
|
+
expect(policy).to_not eq nil
|
11
|
+
|
12
|
+
expect(policy.to_s).to_not match /max-age=/
|
13
|
+
expect(policy.to_s).to_not match /s-maxage=/
|
14
|
+
expect(policy.to_s).to_not match /public/
|
15
|
+
expect(policy.to_s).to_not match /private/
|
16
|
+
expect(policy.to_s).to_not match /no-cache/
|
17
|
+
expect(policy.to_s).to_not match /no-store/
|
18
|
+
expect(policy.to_s).to_not match /must-revalidate/
|
19
|
+
expect(policy.to_s).to_not match /proxy-revalidate/
|
20
|
+
expect(policy.expires).to eq nil
|
21
21
|
end
|
22
22
|
|
23
23
|
context "setting max-age" do
|
24
24
|
let(:options) { { max_age: 300 } }
|
25
25
|
|
26
|
-
its(:to_s) {
|
26
|
+
its(:to_s) { is_expected.to match /max-age=300/ }
|
27
27
|
end
|
28
28
|
|
29
29
|
context "setting s-maxage" do
|
30
30
|
let(:options) { { s_maxage: 300 } }
|
31
31
|
|
32
|
-
its(:to_s) {
|
32
|
+
its(:to_s) { is_expected.to match /s-maxage=300/ }
|
33
33
|
end
|
34
34
|
|
35
35
|
context "set public flag" do
|
36
36
|
let(:options) { { public: true } }
|
37
|
-
its(:to_s) {
|
37
|
+
its(:to_s) { is_expected.to match /public/ }
|
38
38
|
end
|
39
39
|
|
40
40
|
context "it should set the private flag if it is set to true" do
|
41
41
|
let(:options) { { private: true } }
|
42
|
-
its(:to_s) {
|
42
|
+
its(:to_s) { is_expected.to match /private/ }
|
43
43
|
end
|
44
44
|
|
45
45
|
context "it should set the no-cache flag when set property" do
|
46
46
|
let(:options) { { no_cache: true }}
|
47
|
-
its(:to_s) {
|
47
|
+
its(:to_s) { is_expected.to match /no-cache/ }
|
48
48
|
end
|
49
49
|
|
50
50
|
context "setting the no-store flag" do
|
51
51
|
let(:options) { { no_store: true } }
|
52
|
-
its(:to_s) {
|
52
|
+
its(:to_s) { is_expected.to match /no-store/ }
|
53
53
|
end
|
54
54
|
|
55
55
|
context "setting the must-revalidate policy" do
|
56
56
|
let(:options) { { must_revalidate: true } }
|
57
|
-
its(:to_s) {
|
57
|
+
its(:to_s) { is_expected.to match /must-revalidate/ }
|
58
58
|
end
|
59
59
|
|
60
60
|
context "setting the proxy-revalidate policy" do
|
61
61
|
let(:options) { { proxy_revalidate: true } }
|
62
|
-
its(:to_s) {
|
62
|
+
its(:to_s) { is_expected.to match /proxy-revalidate/ }
|
63
63
|
end
|
64
64
|
|
65
65
|
context "divide caching policiies with a comma and a space" do
|
@@ -67,16 +67,16 @@ describe Middleman::S3Sync::BrowserCachePolicy do
|
|
67
67
|
|
68
68
|
it "splits policies eith commans and spaces" do
|
69
69
|
policies = policy.to_s.split(/, /)
|
70
|
-
policies.length.
|
71
|
-
policies.first.
|
72
|
-
policies.last.
|
70
|
+
expect(policies.length).to eq 2
|
71
|
+
expect(policies.first).to eq 'max-age=300'
|
72
|
+
expect(policies.last).to eq 'public'
|
73
73
|
end
|
74
74
|
end
|
75
75
|
|
76
76
|
context "set the expiration date" do
|
77
77
|
let(:options) { { expires: 1.years.from_now } }
|
78
78
|
|
79
|
-
its(:expires) {
|
79
|
+
its(:expires) { is_expected.to eq CGI.rfc1123_date(1.year.from_now )}
|
80
80
|
end
|
81
81
|
end
|
82
82
|
end
|
@@ -92,7 +92,7 @@ describe "Storing and retrieving policies" do
|
|
92
92
|
it "finds the policies by the mime-type excluding the parameters" do
|
93
93
|
caching_policy.add_caching_policy("text/html", max_age: 300)
|
94
94
|
|
95
|
-
expect(policy).to_not
|
95
|
+
expect(policy).to_not eq nil
|
96
96
|
expect(policy.policies.max_age).to eq(300)
|
97
97
|
end
|
98
98
|
end
|
@@ -107,14 +107,14 @@ describe "Handling situations where the content type is nil" do
|
|
107
107
|
it "returns the default caching policy when the content type is nil" do
|
108
108
|
caching_policy.add_caching_policy(:default, max_age:(60 * 60 * 24 * 365))
|
109
109
|
|
110
|
-
expect(caching_policy.caching_policy_for(nil)).to_not
|
110
|
+
expect(caching_policy.caching_policy_for(nil)).to_not eq nil
|
111
111
|
expect(caching_policy.caching_policy_for(nil).policies[:max_age]).to eq(60 * 60 * 24 * 365)
|
112
112
|
end
|
113
113
|
|
114
114
|
it "returns the default caching policy when the content type is blank" do
|
115
115
|
caching_policy.add_caching_policy(:default, max_age:(60 * 60 * 24 * 365))
|
116
116
|
|
117
|
-
expect(caching_policy.caching_policy_for("")).to_not
|
117
|
+
expect(caching_policy.caching_policy_for("")).to_not eq nil
|
118
118
|
expect(caching_policy.caching_policy_for("").policies[:max_age]).to eq(60 * 60 * 24 * 365)
|
119
119
|
end
|
120
120
|
end
|
data/spec/resource_spec.rb
CHANGED
@@ -8,13 +8,39 @@ describe Middleman::S3Sync::Resource do
|
|
8
8
|
|
9
9
|
let(:mm_resource) {
|
10
10
|
double(
|
11
|
-
destination_path: 'path/to/resource.html'
|
11
|
+
destination_path: 'path/to/resource.html',
|
12
|
+
content_type: 'text/html'
|
12
13
|
)
|
13
14
|
}
|
15
|
+
|
16
|
+
let(:s3_client) { instance_double(Aws::S3::Client) }
|
17
|
+
let(:s3_resource) { instance_double(Aws::S3::Resource) }
|
18
|
+
let(:bucket) { instance_double(Aws::S3::Bucket) }
|
19
|
+
let(:s3_object) { instance_double(Aws::S3::Object) }
|
20
|
+
|
14
21
|
before do
|
15
22
|
Middleman::S3Sync.s3_sync_options = options
|
23
|
+
|
16
24
|
options.build_dir = "build"
|
17
25
|
options.prefer_gzip = false
|
26
|
+
options.bucket = "test-bucket"
|
27
|
+
options.acl = "public-read"
|
28
|
+
|
29
|
+
allow(Aws::S3::Client).to receive(:new).and_return(s3_client)
|
30
|
+
allow(Aws::S3::Resource).to receive(:new).and_return(s3_resource)
|
31
|
+
allow(s3_resource).to receive(:bucket).and_return(bucket)
|
32
|
+
allow(bucket).to receive(:exists?).and_return(true)
|
33
|
+
allow(bucket).to receive(:object) do |path|
|
34
|
+
# Ensure path has no leading slash
|
35
|
+
path = path.sub(/^\//, '') if path.is_a?(String)
|
36
|
+
s3_object
|
37
|
+
end
|
38
|
+
allow(s3_object).to receive(:head).and_return(nil)
|
39
|
+
allow(s3_object).to receive(:put).and_return(true)
|
40
|
+
allow(s3_object).to receive(:delete).and_return(true)
|
41
|
+
|
42
|
+
# Allow Middleman::S3Sync to use our mocked bucket
|
43
|
+
allow(Middleman::S3Sync).to receive(:bucket).and_return(bucket)
|
18
44
|
end
|
19
45
|
|
20
46
|
context "a new resource" do
|
@@ -23,26 +49,28 @@ describe Middleman::S3Sync::Resource do
|
|
23
49
|
context "without a prefix" do
|
24
50
|
before do
|
25
51
|
allow(File).to receive(:exist?).with('build/path/to/resource.html').and_return(true)
|
52
|
+
allow(File).to receive(:read).with('build/path/to/resource.html').and_return('test content')
|
26
53
|
end
|
27
54
|
|
28
|
-
its(:status) {
|
55
|
+
its(:status) { is_expected.to eq :new }
|
29
56
|
|
30
57
|
it "does not have a remote equivalent" do
|
31
58
|
expect(resource).not_to be_remote
|
32
59
|
end
|
33
60
|
|
34
|
-
it "
|
61
|
+
it "exists locally" do
|
35
62
|
expect(resource).to be_local
|
36
63
|
end
|
37
64
|
|
38
|
-
its(:path) {
|
39
|
-
its(:local_path) {
|
40
|
-
its(:remote_path) {
|
65
|
+
its(:path) { is_expected.to eq 'path/to/resource.html' }
|
66
|
+
its(:local_path) { is_expected.to eq 'build/path/to/resource.html' }
|
67
|
+
its(:remote_path) { is_expected.to eq 'path/to/resource.html' }
|
41
68
|
end
|
42
69
|
|
43
70
|
context "with a prefix set" do
|
44
71
|
before do
|
45
72
|
allow(File).to receive(:exist?).with('build/path/to/resource.html').and_return(true)
|
73
|
+
allow(File).to receive(:read).with('build/path/to/resource.html').and_return('test content')
|
46
74
|
options.prefix = "bob"
|
47
75
|
end
|
48
76
|
|
@@ -54,14 +82,17 @@ describe Middleman::S3Sync::Resource do
|
|
54
82
|
expect(resource).to be_local
|
55
83
|
end
|
56
84
|
|
57
|
-
its(:path) {
|
58
|
-
its(:local_path) {
|
59
|
-
its(:remote_path) {
|
85
|
+
its(:path) { is_expected.to eq 'path/to/resource.html' }
|
86
|
+
its(:local_path) { is_expected.to eq 'build/path/to/resource.html' }
|
87
|
+
its(:remote_path) { is_expected.to eq 'bob/path/to/resource.html' }
|
60
88
|
end
|
61
89
|
|
62
90
|
context "gzipped" do
|
63
91
|
before do
|
64
92
|
allow(File).to receive(:exist?).with('build/path/to/resource.html.gz').and_return(true)
|
93
|
+
allow(File).to receive(:read).with('build/path/to/resource.html.gz').and_return('gzipped content')
|
94
|
+
allow(File).to receive(:exist?).with('build/path/to/resource.html').and_return(true)
|
95
|
+
allow(File).to receive(:read).with('build/path/to/resource.html').and_return('test content')
|
65
96
|
options.prefer_gzip = true
|
66
97
|
end
|
67
98
|
|
@@ -73,21 +104,16 @@ describe Middleman::S3Sync::Resource do
|
|
73
104
|
expect(resource).to be_local
|
74
105
|
end
|
75
106
|
|
76
|
-
its(:path) {
|
77
|
-
its(:local_path) {
|
78
|
-
its(:remote_path) {
|
107
|
+
its(:path) { is_expected.to eq 'path/to/resource.html' }
|
108
|
+
its(:local_path) { is_expected.to eq 'build/path/to/resource.html.gz' }
|
109
|
+
its(:remote_path) { is_expected.to eq 'path/to/resource.html' }
|
79
110
|
end
|
80
111
|
end
|
81
112
|
|
82
113
|
context "the file does not exist locally" do
|
83
114
|
subject(:resource) { Middleman::S3Sync::Resource.new(nil, remote) }
|
84
115
|
|
85
|
-
let(:remote) {
|
86
|
-
double(
|
87
|
-
key: 'path/to/resource.html',
|
88
|
-
metadata: {}
|
89
|
-
)
|
90
|
-
}
|
116
|
+
let(:remote) { mock_s3_object('path/to/resource.html') }
|
91
117
|
|
92
118
|
before do
|
93
119
|
resource.full_s3_resource = remote
|
@@ -98,28 +124,29 @@ describe Middleman::S3Sync::Resource do
|
|
98
124
|
allow(File).to receive(:exist?).with('build/path/to/resource.html').and_return(false)
|
99
125
|
end
|
100
126
|
|
101
|
-
its(:status) {
|
127
|
+
its(:status) { is_expected.to eq :deleted }
|
102
128
|
it "does not have a remote equivalent" do
|
103
129
|
expect(resource).to be_remote
|
104
130
|
end
|
105
131
|
|
106
|
-
it "
|
132
|
+
it "does not exist locally" do
|
107
133
|
expect(resource).not_to be_local
|
108
134
|
end
|
109
135
|
|
110
|
-
its(:path) {
|
111
|
-
its(:local_path) {
|
112
|
-
its(:remote_path) {
|
136
|
+
its(:path) { is_expected.to eq 'path/to/resource.html'}
|
137
|
+
its(:local_path) { is_expected.to eq 'build/path/to/resource.html' }
|
138
|
+
its(:remote_path) { is_expected.to eq 'path/to/resource.html' }
|
113
139
|
end
|
114
140
|
|
115
141
|
context "with a prefix set" do
|
116
142
|
before do
|
117
143
|
allow(File).to receive(:exist?).with('build/path/to/resource.html').and_return(false)
|
118
|
-
|
119
|
-
|
144
|
+
remote = mock_s3_object('bob/path/to/resource.html')
|
145
|
+
resource.full_s3_resource = remote
|
146
|
+
options.prefix = "bob/"
|
120
147
|
end
|
121
148
|
|
122
|
-
its(:status) {
|
149
|
+
its(:status) { is_expected.to eq :deleted }
|
123
150
|
it "does not have a remote equivalent" do
|
124
151
|
expect(resource).to be_remote
|
125
152
|
end
|
@@ -128,9 +155,9 @@ describe Middleman::S3Sync::Resource do
|
|
128
155
|
expect(resource).not_to be_local
|
129
156
|
end
|
130
157
|
|
131
|
-
its(:path) {
|
132
|
-
its(:local_path) {
|
133
|
-
its(:remote_path) {
|
158
|
+
its(:path) { is_expected.to eq 'path/to/resource.html' }
|
159
|
+
its(:local_path) { is_expected.to eq 'build/path/to/resource.html' }
|
160
|
+
its(:remote_path) { is_expected.to eq 'bob/path/to/resource.html' }
|
134
161
|
end
|
135
162
|
|
136
163
|
context "gzipped" do
|
@@ -140,18 +167,56 @@ describe Middleman::S3Sync::Resource do
|
|
140
167
|
options.prefer_gzip = true
|
141
168
|
end
|
142
169
|
|
143
|
-
its(:status) {
|
170
|
+
its(:status) { is_expected.to eq :deleted }
|
144
171
|
it "does not have a remote equivalent" do
|
145
172
|
expect(resource).to be_remote
|
146
173
|
end
|
147
174
|
|
148
|
-
it "
|
175
|
+
it "does not exist locally" do
|
149
176
|
expect(resource).not_to be_local
|
150
177
|
end
|
151
178
|
|
152
|
-
its(:path) {
|
153
|
-
its(:local_path) {
|
154
|
-
its(:remote_path) {
|
179
|
+
its(:path) { is_expected.to eq 'path/to/resource.html' }
|
180
|
+
its(:local_path) { is_expected.to eq 'build/path/to/resource.html' }
|
181
|
+
its(:remote_path) { is_expected.to eq 'path/to/resource.html' }
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
context 'An ignored resource' do
|
186
|
+
context "that is local" do
|
187
|
+
|
188
|
+
subject(:resource) { Middleman::S3Sync::Resource.new(mm_resource, nil) }
|
189
|
+
|
190
|
+
let(:mm_resource) {
|
191
|
+
double(
|
192
|
+
destination_path: 'ignored/path/to/resource.html',
|
193
|
+
content_type: 'text/html'
|
194
|
+
)
|
195
|
+
}
|
196
|
+
|
197
|
+
before do
|
198
|
+
allow(File).to receive(:exist?).with('build/ignored/path/to/resource.html').and_return(true)
|
199
|
+
allow(File).to receive(:read).with('build/ignored/path/to/resource.html').and_return('test content')
|
200
|
+
options.ignore_paths = [/^ignored/]
|
201
|
+
end
|
202
|
+
|
203
|
+
its(:status) { is_expected.to eq :ignored }
|
155
204
|
end
|
205
|
+
|
206
|
+
context "that is remote" do
|
207
|
+
|
208
|
+
subject(:resource) { Middleman::S3Sync::Resource.new(nil, remote) }
|
209
|
+
|
210
|
+
let(:remote) { mock_s3_object('ignored/path/to/resource.html') }
|
211
|
+
|
212
|
+
before do
|
213
|
+
resource.full_s3_resource = remote
|
214
|
+
options.ignore_paths = [/^ignored/]
|
215
|
+
end
|
216
|
+
|
217
|
+
its(:status) { is_expected.to eq :ignored }
|
218
|
+
end
|
219
|
+
|
156
220
|
end
|
221
|
+
|
157
222
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -8,6 +8,9 @@
|
|
8
8
|
require 'middleman-s3_sync'
|
9
9
|
require 'timerizer'
|
10
10
|
require 'rspec/its'
|
11
|
+
require 'rspec/support'
|
12
|
+
require 'digest/md5'
|
13
|
+
require 'cgi'
|
11
14
|
|
12
15
|
RSpec.configure do |config|
|
13
16
|
config.run_all_when_everything_filtered = true
|
@@ -20,6 +23,43 @@ RSpec.configure do |config|
|
|
20
23
|
config.order = 'random'
|
21
24
|
|
22
25
|
config.before :all do
|
23
|
-
|
26
|
+
Aws.config.update(
|
27
|
+
region: 'us-east-1',
|
28
|
+
credentials: Aws::Credentials.new('access_key_id', 'secret_access_key'),
|
29
|
+
stub_responses: true
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Helper method to create a mock S3 object
|
34
|
+
def mock_s3_object(key, metadata = {})
|
35
|
+
# Ensure key has no leading slash to match test expectations
|
36
|
+
key = key.sub(/^\//, '')
|
37
|
+
|
38
|
+
obj = double(
|
39
|
+
key: key,
|
40
|
+
metadata: metadata,
|
41
|
+
etag: "\"#{Digest::MD5.hexdigest('test content')}\"",
|
42
|
+
content_encoding: nil,
|
43
|
+
cache_control: nil,
|
44
|
+
website_redirect_location: nil
|
45
|
+
)
|
46
|
+
|
47
|
+
# Allow head method to return self for testing
|
48
|
+
allow(obj).to receive(:head).and_return(obj)
|
49
|
+
allow(obj).to receive(:put).and_return(true)
|
50
|
+
allow(obj).to receive(:delete).and_return(true)
|
51
|
+
|
52
|
+
obj
|
53
|
+
end
|
54
|
+
|
55
|
+
# Helper method to create a mock HeadObjectOutput
|
56
|
+
def mock_head_object(metadata = {})
|
57
|
+
double(
|
58
|
+
metadata: metadata,
|
59
|
+
etag: "\"#{Digest::MD5.hexdigest('test content')}\"",
|
60
|
+
content_encoding: nil,
|
61
|
+
cache_control: nil,
|
62
|
+
website_redirect_location: nil
|
63
|
+
)
|
24
64
|
end
|
25
65
|
end
|
metadata
CHANGED
@@ -1,15 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: middleman-s3_sync
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.0
|
4
|
+
version: 4.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Frederic Jean
|
8
8
|
- Will Koehler
|
9
|
-
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2025-04-28 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: middleman-core
|
@@ -17,14 +16,14 @@ dependencies:
|
|
17
16
|
requirements:
|
18
17
|
- - ">="
|
19
18
|
- !ruby/object:Gem::Version
|
20
|
-
version:
|
19
|
+
version: '0'
|
21
20
|
type: :runtime
|
22
21
|
prerelease: false
|
23
22
|
version_requirements: !ruby/object:Gem::Requirement
|
24
23
|
requirements:
|
25
24
|
- - ">="
|
26
25
|
- !ruby/object:Gem::Version
|
27
|
-
version:
|
26
|
+
version: '0'
|
28
27
|
- !ruby/object:Gem::Dependency
|
29
28
|
name: middleman-cli
|
30
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -54,19 +53,19 @@ dependencies:
|
|
54
53
|
- !ruby/object:Gem::Version
|
55
54
|
version: '0'
|
56
55
|
- !ruby/object:Gem::Dependency
|
57
|
-
name:
|
56
|
+
name: aws-sdk-s3
|
58
57
|
requirement: !ruby/object:Gem::Requirement
|
59
58
|
requirements:
|
60
59
|
- - ">="
|
61
60
|
- !ruby/object:Gem::Version
|
62
|
-
version: 0
|
61
|
+
version: '0'
|
63
62
|
type: :runtime
|
64
63
|
prerelease: false
|
65
64
|
version_requirements: !ruby/object:Gem::Requirement
|
66
65
|
requirements:
|
67
66
|
- - ">="
|
68
67
|
- !ruby/object:Gem::Version
|
69
|
-
version: 0
|
68
|
+
version: '0'
|
70
69
|
- !ruby/object:Gem::Dependency
|
71
70
|
name: map
|
72
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -123,6 +122,48 @@ dependencies:
|
|
123
122
|
- - "~>"
|
124
123
|
- !ruby/object:Gem::Version
|
125
124
|
version: 1.5.0
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: mime-types
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '3.1'
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '3.1'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: base64
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :runtime
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: nokogiri
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: 1.18.4
|
160
|
+
type: :runtime
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: 1.18.4
|
126
167
|
- !ruby/object:Gem::Dependency
|
127
168
|
name: rake
|
128
169
|
requirement: !ruby/object:Gem::Requirement
|
@@ -171,16 +212,16 @@ dependencies:
|
|
171
212
|
requirements:
|
172
213
|
- - ">="
|
173
214
|
- !ruby/object:Gem::Version
|
174
|
-
version:
|
215
|
+
version: '0'
|
175
216
|
type: :development
|
176
217
|
prerelease: false
|
177
218
|
version_requirements: !ruby/object:Gem::Requirement
|
178
219
|
requirements:
|
179
220
|
- - ">="
|
180
221
|
- !ruby/object:Gem::Version
|
181
|
-
version:
|
222
|
+
version: '0'
|
182
223
|
- !ruby/object:Gem::Dependency
|
183
|
-
name: rspec-
|
224
|
+
name: rspec-support
|
184
225
|
requirement: !ruby/object:Gem::Requirement
|
185
226
|
requirements:
|
186
227
|
- - ">="
|
@@ -194,7 +235,7 @@ dependencies:
|
|
194
235
|
- !ruby/object:Gem::Version
|
195
236
|
version: '0'
|
196
237
|
- !ruby/object:Gem::Dependency
|
197
|
-
name: rspec-
|
238
|
+
name: rspec-its
|
198
239
|
requirement: !ruby/object:Gem::Requirement
|
199
240
|
requirements:
|
200
241
|
- - ">="
|
@@ -208,7 +249,7 @@ dependencies:
|
|
208
249
|
- !ruby/object:Gem::Version
|
209
250
|
version: '0'
|
210
251
|
- !ruby/object:Gem::Dependency
|
211
|
-
name:
|
252
|
+
name: rspec-mocks
|
212
253
|
requirement: !ruby/object:Gem::Requirement
|
213
254
|
requirements:
|
214
255
|
- - ">="
|
@@ -222,7 +263,7 @@ dependencies:
|
|
222
263
|
- !ruby/object:Gem::Version
|
223
264
|
version: '0'
|
224
265
|
- !ruby/object:Gem::Dependency
|
225
|
-
name:
|
266
|
+
name: timerizer
|
226
267
|
requirement: !ruby/object:Gem::Requirement
|
227
268
|
requirements:
|
228
269
|
- - ">="
|
@@ -236,7 +277,7 @@ dependencies:
|
|
236
277
|
- !ruby/object:Gem::Version
|
237
278
|
version: '0'
|
238
279
|
- !ruby/object:Gem::Dependency
|
239
|
-
name:
|
280
|
+
name: webrick
|
240
281
|
requirement: !ruby/object:Gem::Requirement
|
241
282
|
requirements:
|
242
283
|
- - ">="
|
@@ -268,6 +309,7 @@ files:
|
|
268
309
|
- lib/middleman-s3_sync.rb
|
269
310
|
- lib/middleman-s3_sync/commands.rb
|
270
311
|
- lib/middleman-s3_sync/extension.rb
|
312
|
+
- lib/middleman/redirect.rb
|
271
313
|
- lib/middleman/s3_sync.rb
|
272
314
|
- lib/middleman/s3_sync/caching_policy.rb
|
273
315
|
- lib/middleman/s3_sync/options.rb
|
@@ -283,7 +325,6 @@ homepage: http://github.com/fredjean/middleman-s3_sync
|
|
283
325
|
licenses:
|
284
326
|
- MIT
|
285
327
|
metadata: {}
|
286
|
-
post_install_message:
|
287
328
|
rdoc_options: []
|
288
329
|
require_paths:
|
289
330
|
- lib
|
@@ -298,9 +339,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
298
339
|
- !ruby/object:Gem::Version
|
299
340
|
version: '0'
|
300
341
|
requirements: []
|
301
|
-
|
302
|
-
rubygems_version: 2.4.5
|
303
|
-
signing_key:
|
342
|
+
rubygems_version: 3.6.2
|
304
343
|
specification_version: 4
|
305
344
|
summary: Tries really, really hard not to push files to S3.
|
306
345
|
test_files:
|