Class: Middleman::S3Sync::Resource

Inherits:
Object
  • Object
show all
Includes:
Status
Defined in:
lib/middleman/s3_sync/resource.rb

Constant Summary collapse

CONTENT_MD5_KEY =
'x-amz-meta-content-md5'
REDIRECT_KEY =
'x-amz-website-redirect-location'

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Status

#say_status

Constructor Details

#initialize(resource, partial_s3_resource) ⇒ Resource

Returns a new instance of Resource.


11
12
13
14
15
16
17
18
19
20
21
# File 'lib/middleman/s3_sync/resource.rb', line 11

def initialize(resource, partial_s3_resource)
  @resource = resource
  @path = if resource
            resource.destination_path.sub(/^\//, '')
          elsif partial_s3_resource&.key
            partial_s3_resource.key.sub(/^\//, '')
          else
            ''
          end
  @partial_s3_resource = partial_s3_resource
end

Instance Attribute Details

#content_typeObject

Returns the value of attribute content_type.


4
5
6
# File 'lib/middleman/s3_sync/resource.rb', line 4

def content_type
  @content_type
end

#full_s3_resourceObject

S3 resource as returned by a HEAD request


28
29
30
# File 'lib/middleman/s3_sync/resource.rb', line 28

def full_s3_resource
  @full_s3_resource
end

#gzippedObject

Returns the value of attribute gzipped.


4
5
6
# File 'lib/middleman/s3_sync/resource.rb', line 4

def gzipped
  @gzipped
end

#options=(value) ⇒ Object

Sets the attribute options

Parameters:

  • value

    the value to set the attribute options to.


4
5
6
# File 'lib/middleman/s3_sync/resource.rb', line 4

def options=(value)
  @options = value
end

#partial_s3_resourceObject

Returns the value of attribute partial_s3_resource.


4
5
6
# File 'lib/middleman/s3_sync/resource.rb', line 4

def partial_s3_resource
  @partial_s3_resource
end

#pathObject

Returns the value of attribute path.


4
5
6
# File 'lib/middleman/s3_sync/resource.rb', line 4

def path
  @path
end

#resourceObject

Returns the value of attribute resource.


4
5
6
# File 'lib/middleman/s3_sync/resource.rb', line 4

def resource
  @resource
end

Instance Method Details

#alternate_encoding?Boolean

Returns:

  • (Boolean)

175
176
177
# File 'lib/middleman/s3_sync/resource.rb', line 175

def alternate_encoding?
  status == :alternate_encoding
end

#caching_policyObject


322
323
324
# File 'lib/middleman/s3_sync/resource.rb', line 322

def caching_policy
  @caching_policy ||= Middleman::S3Sync.caching_policy_for(content_type)
end

#caching_policy_match?Boolean

Returns:

  • (Boolean)

326
327
328
329
330
331
332
# File 'lib/middleman/s3_sync/resource.rb', line 326

def caching_policy_match?
  if caching_policy && full_s3_resource && full_s3_resource.respond_to?(:cache_control)
    caching_policy.cache_control == full_s3_resource.cache_control
  else
    true
  end
end

#create!Object


111
112
113
114
115
116
# File 'lib/middleman/s3_sync/resource.rb', line 111

def create!
  say_status "#{ANSI.green{"Creating"}} #{remote_path}#{ gzipped ? ANSI.white {' (gzipped)'} : ''}"
  unless options.dry_run
    upload!
  end
end

#destroy!Object


106
107
108
109
# File 'lib/middleman/s3_sync/resource.rb', line 106

def destroy!
  say_status "#{ANSI.red{"Deleting"}} #{remote_path}"
  bucket.object(remote_path.sub(/^\//, '')).delete unless options.dry_run
end

#directory?Boolean

Returns:

  • (Boolean)

277
278
279
# File 'lib/middleman/s3_sync/resource.rb', line 277

def directory?
  File.directory?(local_path)
end

#encoding_match?Boolean

Returns:

  • (Boolean)

289
290
291
# File 'lib/middleman/s3_sync/resource.rb', line 289

def encoding_match?
  (options.prefer_gzip && gzipped && full_s3_resource.content_encoding == 'gzip') || (!options.prefer_gzip && !gzipped && !full_s3_resource.content_encoding )
end

#identical?Boolean

Returns:

  • (Boolean)

179
180
181
# File 'lib/middleman/s3_sync/resource.rb', line 179

def identical?
  status == :identical
end

#ignore!Object


156
157
158
159
160
161
162
163
164
165
# File 'lib/middleman/s3_sync/resource.rb', line 156

def ignore!
  if options.verbose
    reason = if redirect?
               :redirect
             elsif directory?
               :directory
             end
    say_status "#{ANSI.yellow{"Ignoring"}} #{remote_path} #{ reason ? ANSI.white {"(#{reason})" } : "" }"
  end
end

#local?Boolean

Returns:

  • (Boolean)

240
241
242
# File 'lib/middleman/s3_sync/resource.rb', line 240

def local?
  File.exist?(local_path) && resource
end

#local_contentObject


191
192
193
194
195
196
197
# File 'lib/middleman/s3_sync/resource.rb', line 191

def local_content
  if block_given?
    File.open(local_path) { |f| yield f.read }
  else
    File.read(local_path)
  end
end

#local_content_md5Object


303
304
305
306
307
308
309
310
311
# File 'lib/middleman/s3_sync/resource.rb', line 303

def local_content_md5
  @local_content_md5 ||= begin
    if File.exist?(original_path)
      Digest::MD5.hexdigest(File.read(original_path))
    else
      nil
    end
  end
end

#local_object_md5Object


299
300
301
# File 'lib/middleman/s3_sync/resource.rb', line 299

def local_object_md5
  @local_object_md5 ||= Digest::MD5.hexdigest(File.read(local_path))
end

#local_pathObject


97
98
99
100
101
102
103
104
# File 'lib/middleman/s3_sync/resource.rb', line 97

def local_path
  local_path = build_dir + '/' + path.gsub(/^#{options.prefix}/, '')
  if options.prefer_gzip && File.exist?(local_path + ".gz")
    @gzipped = true
    local_path += ".gz"
  end
  local_path
end

#metadata_match?Boolean

Returns:

  • (Boolean)

253
254
255
# File 'lib/middleman/s3_sync/resource.rb', line 253

def 
  redirect_match? && caching_policy_match?
end

#normalize_path(prefix, path) ⇒ Object


50
51
52
53
54
55
# File 'lib/middleman/s3_sync/resource.rb', line 50

def normalize_path(prefix, path)
  # Remove any trailing slash from prefix and leading slash from path
  prefix = prefix.chomp('/')
  path = path.sub(/^\//, '')
  "#{prefix}/#{path}"
end

#original_pathObject


313
314
315
# File 'lib/middleman/s3_sync/resource.rb', line 313

def original_path
  gzipped ? local_path.gsub(/\.gz$/, '') : local_path
end

#redirect?Boolean

Returns:

  • (Boolean)

248
249
250
251
# File 'lib/middleman/s3_sync/resource.rb', line 248

def redirect?
  (resource && resource.respond_to?(:redirect?) && resource.redirect?) || 
    (full_s3_resource && full_s3_resource.respond_to?(:website_redirect_location) && full_s3_resource.website_redirect_location)
end

#redirect_match?Boolean

Returns:

  • (Boolean)

257
258
259
260
261
262
263
# File 'lib/middleman/s3_sync/resource.rb', line 257

def redirect_match?
  if redirect?
    redirect_url == remote_redirect_url
  else
    true
  end
end

#redirect_urlObject


273
274
275
# File 'lib/middleman/s3_sync/resource.rb', line 273

def redirect_url
  resource.respond_to?(:target_url) ? resource.target_url : nil
end

#relative_pathObject


281
282
283
# File 'lib/middleman/s3_sync/resource.rb', line 281

def relative_path
  @relative_path ||= local_path.gsub(/#{build_dir}/, '')
end

#remote?Boolean

Returns:

  • (Boolean)

244
245
246
# File 'lib/middleman/s3_sync/resource.rb', line 244

def remote?
  !full_s3_resource.nil?
end

#remote_content_md5Object


293
294
295
296
297
# File 'lib/middleman/s3_sync/resource.rb', line 293

def remote_content_md5
  if full_s3_resource && full_s3_resource.
    full_s3_resource.[CONTENT_MD5_KEY]
  end
end

#remote_object_md5Object


285
286
287
# File 'lib/middleman/s3_sync/resource.rb', line 285

def remote_object_md5
  s3_resource.etag.gsub(/"/, '') if s3_resource.etag
end

#remote_pathObject Also known as: key


36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/middleman/s3_sync/resource.rb', line 36

def remote_path
  if s3_resource
    if s3_resource.respond_to?(:key)
      s3_resource.key.sub(/^\//, '')
    else
      # For HeadObjectOutput objects which don't have key method
      options.prefix ? normalize_path(options.prefix, path) : path.sub(/^\//, '')
    end
  else
    options.prefix ? normalize_path(options.prefix, path) : path.sub(/^\//, '')
  end.sub(/^\//, '')  # Ensure no leading slash
end

#remote_redirect_urlObject


269
270
271
# File 'lib/middleman/s3_sync/resource.rb', line 269

def remote_redirect_url
  full_s3_resource&.website_redirect_location
end

#s3_resourceObject


23
24
25
# File 'lib/middleman/s3_sync/resource.rb', line 23

def s3_resource
  @full_s3_resource || @partial_s3_resource
end

#shunned?Boolean

Returns:

  • (Boolean)

265
266
267
# File 'lib/middleman/s3_sync/resource.rb', line 265

def shunned?
  !!path[Regexp.union(options.ignore_paths)]
end

#statusObject


199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/middleman/s3_sync/resource.rb', line 199

def status
  @status ||= if shunned?
                :ignored
              elsif directory?
                if remote?
                  :deleted
                else
                  :ignored
                end
              elsif local? && remote?
                if options.force
                  :updated
                elsif not 
                  :updated
                elsif local_object_md5 == remote_object_md5
                  :identical
                else
                  if !gzipped
                    # we're not gzipped, object hashes being different indicates updated content
                    :updated
                  elsif !encoding_match? || local_content_md5 != remote_content_md5
                    # we're gzipped, so we checked the content MD5, and it also changed
                    :updated
                  else
                    # we're gzipped, the object hashes differ, but the content hashes are equal
                    # this means the gzipped bits changed while the original bits did not
                    # what's more, we spent a HEAD request to find out
                    :alternate_encoding
                  end
                end
              elsif local?
                :new
              elsif remote? && redirect?
                :ignored
              elsif remote?
                :deleted
              else
                :ignored
              end
end

#to_create?Boolean

Returns:

  • (Boolean)

171
172
173
# File 'lib/middleman/s3_sync/resource.rb', line 171

def to_create?
  status == :new
end

#to_delete?Boolean

Returns:

  • (Boolean)

167
168
169
# File 'lib/middleman/s3_sync/resource.rb', line 167

def to_delete?
  status == :deleted
end

#to_hObject Also known as: attributes


57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/middleman/s3_sync/resource.rb', line 57

def to_h
  attributes = {
    :key => key,
    :acl => options.acl,
    :content_type => content_type,
    CONTENT_MD5_KEY => local_content_md5
  }

  if caching_policy
    attributes[:cache_control] = caching_policy.cache_control
    attributes[:expires] = caching_policy.expires
  end

  if options.prefer_gzip && gzipped
    attributes[:content_encoding] = "gzip"
  end

  if options.reduced_redundancy_storage
    attributes[:storage_class] = 'REDUCED_REDUNDANCY'
  end

  if options.encryption
    attributes[:encryption] = 'AES256'
  end

  if redirect?
    attributes[REDIRECT_KEY] = redirect_url
  end

  attributes
end

#to_ignore?Boolean

Returns:

  • (Boolean)

187
188
189
# File 'lib/middleman/s3_sync/resource.rb', line 187

def to_ignore?
  status == :ignored || status == :alternate_encoding
end

#to_update?Boolean

Returns:

  • (Boolean)

183
184
185
# File 'lib/middleman/s3_sync/resource.rb', line 183

def to_update?
  status == :updated
end

#update!Object


90
91
92
93
94
95
# File 'lib/middleman/s3_sync/resource.rb', line 90

def update!
  say_status "#{ANSI.blue{"Updating"}} #{remote_path}#{ gzipped ? ANSI.white {' (gzipped)'} : ''}"
  unless options.dry_run
    upload!
  end
end

#upload!Object


118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/middleman/s3_sync/resource.rb', line 118

def upload!
  object = bucket.object(remote_path.sub(/^\//, ''))
  upload_options = {
    body: local_content,
    content_type: content_type,
    acl: options.acl
  }

  # Add metadata if present
  if local_content_md5
    upload_options[:metadata] = { CONTENT_MD5_KEY => local_content_md5 }
  end

  # Add redirect if present
  upload_options[:website_redirect_location] = redirect_url if redirect?

  # Add content encoding if present
  upload_options[:content_encoding] = "gzip" if options.prefer_gzip && gzipped

  # Add cache control and expires if present
  if caching_policy
    upload_options[:cache_control] = caching_policy.cache_control
    upload_options[:expires] = caching_policy.expires
  end

  # Add storage class if needed
  if options.reduced_redundancy_storage
    upload_options[:storage_class] = 'REDUCED_REDUNDANCY'
  end

  # Add encryption if needed
  if options.encryption
    upload_options[:server_side_encryption] = 'AES256'
  end

  object.put(upload_options)
end