Class: Middleman::S3Sync::Resource

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

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.



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

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



26
27
28
# File 'lib/middleman/s3_sync/resource.rb', line 26

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)


156
157
158
# File 'lib/middleman/s3_sync/resource.rb', line 156

def alternate_encoding?
  status == :alternate_encoding
end

#caching_policyObject



303
304
305
# File 'lib/middleman/s3_sync/resource.rb', line 303

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

#caching_policy_match?Boolean

Returns:

  • (Boolean)


307
308
309
310
311
312
313
# File 'lib/middleman/s3_sync/resource.rb', line 307

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



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

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

#destroy!Object



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

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

#directory?Boolean

Returns:

  • (Boolean)


258
259
260
# File 'lib/middleman/s3_sync/resource.rb', line 258

def directory?
  File.directory?(local_path)
end

#encoding_match?Boolean

Returns:

  • (Boolean)


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

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)


160
161
162
# File 'lib/middleman/s3_sync/resource.rb', line 160

def identical?
  status == :identical
end

#ignore!Object



137
138
139
140
141
142
143
144
145
146
# File 'lib/middleman/s3_sync/resource.rb', line 137

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)


221
222
223
# File 'lib/middleman/s3_sync/resource.rb', line 221

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

#local_contentObject



172
173
174
175
176
177
178
# File 'lib/middleman/s3_sync/resource.rb', line 172

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

#local_content_md5Object



284
285
286
287
288
289
290
291
292
# File 'lib/middleman/s3_sync/resource.rb', line 284

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



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

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

#local_pathObject



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

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)


234
235
236
# File 'lib/middleman/s3_sync/resource.rb', line 234

def 
  redirect_match? && caching_policy_match?
end

#normalize_path(prefix, path) ⇒ Object



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

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



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

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

#redirect?Boolean

Returns:

  • (Boolean)


229
230
231
232
# File 'lib/middleman/s3_sync/resource.rb', line 229

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)


238
239
240
241
242
243
244
# File 'lib/middleman/s3_sync/resource.rb', line 238

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

#redirect_urlObject



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

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

#relative_pathObject



262
263
264
# File 'lib/middleman/s3_sync/resource.rb', line 262

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

#remote?Boolean

Returns:

  • (Boolean)


225
226
227
# File 'lib/middleman/s3_sync/resource.rb', line 225

def remote?
  !full_s3_resource.nil?
end

#remote_content_md5Object



274
275
276
277
278
# File 'lib/middleman/s3_sync/resource.rb', line 274

def remote_content_md5
  if full_s3_resource && full_s3_resource.
    full_s3_resource.['content-md5']
  end
end

#remote_object_md5Object



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

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

#remote_pathObject Also known as: key



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

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



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

def remote_redirect_url
  full_s3_resource&.website_redirect_location
end

#s3_resourceObject



21
22
23
# File 'lib/middleman/s3_sync/resource.rb', line 21

def s3_resource
  @full_s3_resource || @partial_s3_resource
end

#shunned?Boolean

Returns:

  • (Boolean)


246
247
248
# File 'lib/middleman/s3_sync/resource.rb', line 246

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

#statusObject



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/middleman/s3_sync/resource.rb', line 180

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)


152
153
154
# File 'lib/middleman/s3_sync/resource.rb', line 152

def to_create?
  status == :new
end

#to_delete?Boolean

Returns:

  • (Boolean)


148
149
150
# File 'lib/middleman/s3_sync/resource.rb', line 148

def to_delete?
  status == :deleted
end

#to_hObject Also known as: attributes



55
56
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
# File 'lib/middleman/s3_sync/resource.rb', line 55

def to_h
  attributes = {
    :key => key,
    :content_type => content_type,
    'content-md5' => local_content_md5
  }
  # Only add ACL if enabled (not for buckets with ACLs disabled)
  attributes[:acl] = options.acl if options.acl_enabled?

  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['website-redirect-location'] = redirect_url
  end

  attributes
end

#to_ignore?Boolean

Returns:

  • (Boolean)


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

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

#to_update?Boolean

Returns:

  • (Boolean)


164
165
166
# File 'lib/middleman/s3_sync/resource.rb', line 164

def to_update?
  status == :updated
end

#update!Object



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

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

#upload!Object



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/middleman/s3_sync/resource.rb', line 117

def upload!
  object = bucket.object(remote_path.sub(/^\//, ''))
  upload_options = build_upload_options

  begin
    object.put(upload_options)
  rescue Aws::S3::Errors::AccessControlListNotSupported => e
    # Bucket has ACLs disabled - retry without ACL
    if upload_options.key?(:acl)
      say_status "#{ANSI.yellow{"Note"}} Bucket does not support ACLs, retrying without ACL parameter"
      # Automatically disable ACLs for this bucket going forward
      options.acl = ''
      upload_options.delete(:acl)
      retry
    else
      raise e
    end
  end
end