middleman-s3_sync 4.0.3 → 4.6.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/.s3_sync.sample +20 -0
- data/README.md +216 -40
- data/lib/middleman/redirect.rb +21 -0
- data/lib/middleman/s3_sync/cloudfront.rb +192 -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 +87 -38
- data/lib/middleman-s3_sync/commands.rb +32 -1
- data/lib/middleman-s3_sync/extension.rb +19 -3
- data/lib/middleman-s3_sync.rb +1 -0
- data/middleman-s3_sync.gemspec +9 -5
- data/spec/cloudfront_spec.rb +391 -0
- data/spec/resource_spec.rb +78 -13
- data/spec/s3_sync_integration_spec.rb +132 -0
- data/spec/spec_helper.rb +41 -1
- metadata +77 -19
@@ -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,12 +1,13 @@
|
|
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'
|
6
5
|
require 'middleman/s3_sync/caching_policy'
|
7
6
|
require 'middleman/s3_sync/status'
|
8
7
|
require 'middleman/s3_sync/resource'
|
8
|
+
require 'middleman/s3_sync/cloudfront'
|
9
9
|
require 'middleman-s3_sync/extension'
|
10
|
+
require 'middleman/redirect'
|
10
11
|
require 'parallel'
|
11
12
|
require 'ruby-progressbar'
|
12
13
|
require 'thread'
|
@@ -24,12 +25,23 @@ module Middleman
|
|
24
25
|
attr_accessor :mm_resources
|
25
26
|
attr_reader :app
|
26
27
|
|
28
|
+
THREADS_COUNT = 8
|
29
|
+
|
30
|
+
# Track paths that were changed during sync for CloudFront invalidation
|
31
|
+
attr_accessor :invalidation_paths
|
32
|
+
|
27
33
|
def sync()
|
28
34
|
@app ||= ::Middleman::Application.new
|
35
|
+
@invalidation_paths = []
|
29
36
|
|
30
37
|
say_status "Let's see if there's work to be done..."
|
31
38
|
unless work_to_be_done?
|
32
39
|
say_status "All S3 files are up to date."
|
40
|
+
|
41
|
+
# Still run CloudFront invalidation if requested for all paths
|
42
|
+
if s3_sync_options.cloudfront_invalidate && s3_sync_options.cloudfront_invalidate_all
|
43
|
+
CloudFront.invalidate([], s3_sync_options)
|
44
|
+
end
|
33
45
|
return
|
34
46
|
end
|
35
47
|
|
@@ -43,13 +55,18 @@ module Middleman
|
|
43
55
|
create_resources
|
44
56
|
update_resources
|
45
57
|
delete_resources
|
58
|
+
|
59
|
+
# Invalidate CloudFront cache if requested
|
60
|
+
if s3_sync_options.cloudfront_invalidate
|
61
|
+
CloudFront.invalidate(@invalidation_paths, s3_sync_options)
|
62
|
+
end
|
46
63
|
end
|
47
64
|
|
48
65
|
def bucket
|
49
66
|
@@bucket_lock.synchronize do
|
50
67
|
@bucket ||= begin
|
51
|
-
bucket =
|
52
|
-
raise "Bucket #{s3_sync_options.bucket} doesn't exist!" unless bucket
|
68
|
+
bucket = s3_resource.bucket(s3_sync_options.bucket)
|
69
|
+
raise "Bucket #{s3_sync_options.bucket} doesn't exist!" unless bucket.exists?
|
53
70
|
bucket
|
54
71
|
end
|
55
72
|
end
|
@@ -58,6 +75,13 @@ module Middleman
|
|
58
75
|
def add_local_resource(mm_resource)
|
59
76
|
s3_sync_resources[mm_resource.destination_path] = S3Sync::Resource.new(mm_resource, remote_resource_for_path(mm_resource.destination_path)).tap(&:status)
|
60
77
|
end
|
78
|
+
|
79
|
+
def add_invalidation_path(path)
|
80
|
+
@invalidation_paths ||= []
|
81
|
+
# Normalize path for CloudFront (ensure it starts with /)
|
82
|
+
normalized_path = path.start_with?('/') ? path : "/#{path}"
|
83
|
+
@invalidation_paths << normalized_path unless @invalidation_paths.include?(normalized_path)
|
84
|
+
end
|
61
85
|
|
62
86
|
def remote_only_paths
|
63
87
|
paths - s3_sync_resources.keys
|
@@ -73,7 +97,12 @@ module Middleman
|
|
73
97
|
|
74
98
|
protected
|
75
99
|
def update_bucket_versioning
|
76
|
-
|
100
|
+
s3_client.put_bucket_versioning({
|
101
|
+
bucket: s3_sync_options.bucket,
|
102
|
+
versioning_configuration: {
|
103
|
+
status: "Enabled"
|
104
|
+
}
|
105
|
+
}) if s3_sync_options.version_bucket
|
77
106
|
end
|
78
107
|
|
79
108
|
def update_bucket_website
|
@@ -87,30 +116,47 @@ module Middleman
|
|
87
116
|
|
88
117
|
unless opts.empty?
|
89
118
|
say_status "Putting bucket website: #{opts.to_json}"
|
90
|
-
|
119
|
+
s3_client.put_bucket_website({
|
120
|
+
bucket: s3_sync_options.bucket,
|
121
|
+
website_configuration: opts
|
122
|
+
})
|
91
123
|
end
|
92
124
|
end
|
93
125
|
|
94
|
-
def
|
95
|
-
|
96
|
-
|
97
|
-
:path_style => s3_sync_options.path_style
|
98
|
-
}
|
126
|
+
def s3_client
|
127
|
+
@s3_client ||= Aws::S3::Client.new(connection_options)
|
128
|
+
end
|
99
129
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
connection_options
|
107
|
-
|
130
|
+
def s3_resource
|
131
|
+
@s3_resource ||= Aws::S3::Resource.new(client: s3_client)
|
132
|
+
end
|
133
|
+
|
134
|
+
def connection_options
|
135
|
+
@connection_options ||= begin
|
136
|
+
connection_options = {
|
137
|
+
endpoint: s3_sync_options.endpoint,
|
138
|
+
region: s3_sync_options.region,
|
139
|
+
force_path_style: s3_sync_options.path_style
|
140
|
+
}
|
141
|
+
|
142
|
+
if s3_sync_options.aws_access_key_id && s3_sync_options.aws_secret_access_key
|
143
|
+
connection_options.merge!({
|
144
|
+
access_key_id: s3_sync_options.aws_access_key_id,
|
145
|
+
secret_access_key: s3_sync_options.aws_secret_access_key
|
146
|
+
})
|
108
147
|
|
109
|
-
|
148
|
+
# If using an assumed role
|
149
|
+
connection_options.merge!({
|
150
|
+
session_token: s3_sync_options.aws_session_token
|
151
|
+
}) if s3_sync_options.aws_session_token
|
152
|
+
end
|
153
|
+
|
154
|
+
connection_options
|
155
|
+
end
|
110
156
|
end
|
111
157
|
|
112
158
|
def remote_resource_for_path(path)
|
113
|
-
bucket_files
|
159
|
+
bucket_files[path]
|
114
160
|
end
|
115
161
|
|
116
162
|
def s3_sync_resources
|
@@ -125,7 +171,7 @@ module Middleman
|
|
125
171
|
|
126
172
|
def remote_paths
|
127
173
|
@remote_paths ||= if s3_sync_options.delete
|
128
|
-
bucket_files.
|
174
|
+
bucket_files.keys
|
129
175
|
else
|
130
176
|
[]
|
131
177
|
end
|
@@ -133,42 +179,45 @@ module Middleman
|
|
133
179
|
|
134
180
|
def bucket_files
|
135
181
|
@@bucket_files_lock.synchronize do
|
136
|
-
@bucket_files ||=
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
182
|
+
@bucket_files ||= begin
|
183
|
+
files = {}
|
184
|
+
bucket.objects.each do |object|
|
185
|
+
files[object.key] = object
|
186
|
+
end
|
187
|
+
files
|
188
|
+
end
|
141
189
|
end
|
142
190
|
end
|
143
191
|
|
144
192
|
def create_resources
|
145
|
-
files_to_create
|
146
|
-
|
193
|
+
Parallel.map(files_to_create, in_threads: THREADS_COUNT) do |resource|
|
194
|
+
resource.create!
|
195
|
+
add_invalidation_path(resource.path)
|
147
196
|
end
|
148
197
|
end
|
149
198
|
|
150
199
|
def update_resources
|
151
|
-
files_to_update
|
152
|
-
|
200
|
+
Parallel.map(files_to_update, in_threads: THREADS_COUNT) do |resource|
|
201
|
+
resource.update!
|
202
|
+
add_invalidation_path(resource.path)
|
153
203
|
end
|
154
204
|
end
|
155
205
|
|
156
206
|
def delete_resources
|
157
|
-
files_to_delete
|
158
|
-
|
207
|
+
Parallel.map(files_to_delete, in_threads: THREADS_COUNT) do |resource|
|
208
|
+
resource.destroy!
|
209
|
+
add_invalidation_path(resource.path)
|
159
210
|
end
|
160
211
|
end
|
161
212
|
|
162
213
|
def ignore_resources
|
163
|
-
files_to_ignore
|
164
|
-
r.ignore!
|
165
|
-
end
|
214
|
+
Parallel.map(files_to_ignore, in_threads: THREADS_COUNT, &:ignore!)
|
166
215
|
end
|
167
216
|
|
168
217
|
def work_to_be_done?
|
169
|
-
Parallel.each(mm_resources, in_threads:
|
218
|
+
Parallel.each(mm_resources, in_threads: THREADS_COUNT, progress: "Processing sitemap") { |mm_resource| add_local_resource(mm_resource) }
|
170
219
|
|
171
|
-
Parallel.each(remote_only_paths, in_threads:
|
220
|
+
Parallel.each(remote_only_paths, in_threads: THREADS_COUNT, progress: "Processing remote files") do |remote_path|
|
172
221
|
s3_sync_resources[remote_path] ||= S3Sync::Resource.new(nil, remote_resource_for_path(remote_path)).tap(&:status)
|
173
222
|
end
|
174
223
|
|
@@ -65,13 +65,39 @@ module Middleman
|
|
65
65
|
type: :string,
|
66
66
|
desc: 'Print instrument messages.'
|
67
67
|
|
68
|
+
class_option :cloudfront_distribution_id,
|
69
|
+
aliases: '-d',
|
70
|
+
type: :string,
|
71
|
+
desc: 'CloudFront distribution ID for invalidation.'
|
72
|
+
|
73
|
+
class_option :cloudfront_invalidate,
|
74
|
+
aliases: '-c',
|
75
|
+
type: :boolean,
|
76
|
+
desc: 'Invalidate CloudFront cache after sync.'
|
77
|
+
|
78
|
+
class_option :cloudfront_invalidate_all,
|
79
|
+
aliases: '-a',
|
80
|
+
type: :boolean,
|
81
|
+
desc: 'Invalidate all paths (/*) instead of only changed files.'
|
82
|
+
|
83
|
+
class_option :cloudfront_invalidation_batch_size,
|
84
|
+
type: :numeric,
|
85
|
+
desc: 'Maximum number of paths to invalidate in a single request (default: 1000).'
|
86
|
+
|
87
|
+
class_option :cloudfront_wait,
|
88
|
+
aliases: '-w',
|
89
|
+
type: :boolean,
|
90
|
+
desc: 'Wait for CloudFront invalidation to complete before exiting.'
|
91
|
+
|
68
92
|
def s3_sync
|
69
93
|
env = options[:environment].to_s.to_sym
|
70
94
|
verbose = options[:verbose] ? 0 : 1
|
71
95
|
instrument = options[:instrument]
|
72
96
|
|
97
|
+
mode = options[:build] ? :build : :config
|
98
|
+
|
73
99
|
::Middleman::S3Sync.app = ::Middleman::Application.new do
|
74
|
-
config[:mode] =
|
100
|
+
config[:mode] = mode
|
75
101
|
config[:environment] = env
|
76
102
|
::Middleman::Logger.singleton(verbose, instrument)
|
77
103
|
end
|
@@ -97,6 +123,11 @@ module Middleman
|
|
97
123
|
s3_sync_options.prefix = s3_sync_options.prefix.end_with?('/') ? s3_sync_options.prefix : s3_sync_options.prefix + '/'
|
98
124
|
end
|
99
125
|
s3_sync_options.dry_run = options[:dry_run] if options[:dry_run]
|
126
|
+
s3_sync_options.cloudfront_distribution_id = options[:cloudfront_distribution_id] if options[:cloudfront_distribution_id]
|
127
|
+
s3_sync_options.cloudfront_invalidate = options[:cloudfront_invalidate] if options[:cloudfront_invalidate]
|
128
|
+
s3_sync_options.cloudfront_invalidate_all = options[:cloudfront_invalidate_all] if options[:cloudfront_invalidate_all]
|
129
|
+
s3_sync_options.cloudfront_invalidation_batch_size = options[:cloudfront_invalidation_batch_size] if options[:cloudfront_invalidation_batch_size]
|
130
|
+
s3_sync_options.cloudfront_wait = options[:cloudfront_wait] if options[:cloudfront_wait]
|
100
131
|
|
101
132
|
::Middleman::S3Sync.sync()
|
102
133
|
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'
|
@@ -26,6 +28,12 @@ module Middleman
|
|
26
28
|
option :index_document, nil, 'S3 custom index document path'
|
27
29
|
option :error_document, nil, 'S3 custom error document path'
|
28
30
|
option :content_types, {}, 'Custom content types'
|
31
|
+
option :ignore_paths, [], 'Paths that should be ignored during sync, strings or regex are allowed'
|
32
|
+
option :cloudfront_distribution_id, nil, 'CloudFront distribution ID for invalidation'
|
33
|
+
option :cloudfront_invalidate, false, 'Whether to invalidate CloudFront cache after sync'
|
34
|
+
option :cloudfront_invalidate_all, false, 'Whether to invalidate all paths (/*) or only changed files'
|
35
|
+
option :cloudfront_invalidation_batch_size, 1000, 'Maximum number of paths to invalidate in a single request'
|
36
|
+
option :cloudfront_wait, false, 'Whether to wait for CloudFront invalidation to complete'
|
29
37
|
|
30
38
|
expose_to_config :s3_sync_options, :default_caching_policy, :caching_policy
|
31
39
|
|
@@ -40,6 +48,7 @@ module Middleman
|
|
40
48
|
read_config
|
41
49
|
options.aws_access_key_id ||= ENV['AWS_ACCESS_KEY_ID']
|
42
50
|
options.aws_secret_access_key ||= ENV['AWS_SECRET_ACCESS_KEY']
|
51
|
+
options.aws_session_token ||= ENV['AWS_SESSION_TOKEN'] || ENV['AWS_SECURITY_TOKEN']
|
43
52
|
options.bucket ||= ENV['AWS_BUCKET']
|
44
53
|
options.http_prefix = app.http_prefix if app.respond_to? :http_prefix
|
45
54
|
options.build_dir ||= app.build_dir if app.respond_to? :build_dir
|
@@ -54,8 +63,15 @@ module Middleman
|
|
54
63
|
::Middleman::S3Sync.sync() if options.after_build
|
55
64
|
end
|
56
65
|
|
57
|
-
def manipulate_resource_list(
|
58
|
-
::Middleman::S3Sync.mm_resources =
|
66
|
+
def manipulate_resource_list(resources)
|
67
|
+
::Middleman::S3Sync.mm_resources = resources.each_with_object([]) do |resource, list|
|
68
|
+
next if resource.ignored?
|
69
|
+
|
70
|
+
list << resource
|
71
|
+
list << resource.target_resource if resource.respond_to?(:target_resource)
|
72
|
+
end
|
73
|
+
|
74
|
+
resources
|
59
75
|
end
|
60
76
|
|
61
77
|
def s3_sync_options
|
@@ -73,7 +89,7 @@ module Middleman
|
|
73
89
|
config_file_path = File.join(root_path, ".s3_sync")
|
74
90
|
|
75
91
|
# skip if config file does not exist
|
76
|
-
return unless File.
|
92
|
+
return unless File.exist?(config_file_path)
|
77
93
|
|
78
94
|
io = File.open(config_file_path, "r")
|
79
95
|
end
|
data/lib/middleman-s3_sync.rb
CHANGED
data/middleman-s3_sync.gemspec
CHANGED
@@ -18,22 +18,26 @@ 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
|
+
gem.add_runtime_dependency 'aws-sdk-cloudfront'
|
25
26
|
gem.add_runtime_dependency 'map'
|
26
27
|
gem.add_runtime_dependency 'parallel'
|
27
28
|
gem.add_runtime_dependency 'ruby-progressbar'
|
28
29
|
gem.add_runtime_dependency 'ansi', '~> 1.5.0'
|
30
|
+
gem.add_runtime_dependency 'mime-types', '~> 3.1'
|
31
|
+
gem.add_runtime_dependency 'base64'
|
32
|
+
gem.add_runtime_dependency 'nokogiri', '>= 1.18.4'
|
29
33
|
|
30
34
|
gem.add_development_dependency 'rake'
|
31
35
|
gem.add_development_dependency 'pry'
|
32
36
|
gem.add_development_dependency 'pry-byebug'
|
33
|
-
gem.add_development_dependency 'rspec'
|
37
|
+
gem.add_development_dependency 'rspec'
|
38
|
+
gem.add_development_dependency 'rspec-support'
|
34
39
|
gem.add_development_dependency 'rspec-its'
|
35
40
|
gem.add_development_dependency 'rspec-mocks'
|
36
41
|
gem.add_development_dependency 'timerizer'
|
37
|
-
gem.add_development_dependency '
|
38
|
-
gem.add_development_dependency 'travis-lint'
|
42
|
+
gem.add_development_dependency 'webrick'
|
39
43
|
end
|