capistrano-s3 2.3.0 → 3.0.0.pre

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.
@@ -1,106 +1,111 @@
1
- require 'aws-sdk'
2
- require 'mime/types'
3
- require 'fileutils'
1
+ # frozen_string_literal: true
2
+
3
+ require "aws-sdk"
4
+ require "mime/types"
5
+ require "fileutils"
6
+ require "capistrano/s3/mime_types"
7
+ require "yaml"
4
8
 
5
9
  module Capistrano
6
10
  module S3
7
11
  module Publisher
8
- LAST_PUBLISHED_FILE = '.last_published'
9
- LAST_INVALIDATION_FILE = '.last_invalidation'
12
+ LAST_PUBLISHED_FILE = ".last_published"
13
+ LAST_INVALIDATION_FILE = ".last_invalidation"
10
14
 
11
- def self.publish!(region, key, secret, bucket, deployment_path, target_path, distribution_id, invalidations, exclusions, only_gzip, extra_options, stage = 'default')
12
- deployment_path_absolute = File.expand_path(deployment_path, Dir.pwd)
13
- s3 = self.establish_s3_client_connection!(region, key, secret)
14
- updated = false
15
+ class << self
16
+ def publish!(region, key, secret, bucket, deployment_path, target_path, distribution_id,
17
+ invalidations, exclusions, only_gzip, extra_options, stage = "default")
18
+ deployment_path_absolute = File.expand_path(deployment_path, Dir.pwd)
19
+ s3_client = establish_s3_client_connection!(region, key, secret)
15
20
 
16
- self.files(deployment_path_absolute, exclusions).each do |file|
17
- if !File.directory?(file)
18
- next if self.published?(file, bucket, stage)
19
- next if only_gzip && self.has_gzipped_version?(file)
21
+ files(deployment_path_absolute, exclusions).each do |file|
22
+ next if File.directory?(file)
23
+ next if published?(file, bucket, stage)
24
+ next if only_gzip && gzipped_version?(file)
20
25
 
21
- path = self.base_file_path(deployment_path_absolute, file)
22
- path.gsub!(/^\//, "") # Remove preceding slash for S3
26
+ path = base_file_path(deployment_path_absolute, file)
27
+ path.gsub!(%r{^/}, "") # Remove preceding slash for S3
23
28
 
24
- self.put_object(s3, bucket, target_path, path, file, only_gzip, extra_options)
29
+ put_object(s3_client, bucket, target_path, path, file, only_gzip, extra_options)
25
30
  end
26
- end
27
31
 
28
- # invalidate CloudFront distribution if needed
29
- if distribution_id && !invalidations.empty?
30
- cf = self.establish_cf_client_connection!(region, key, secret)
31
-
32
- response = cf.create_invalidation({
33
- :distribution_id => distribution_id,
34
- :invalidation_batch => {
35
- :paths => {
36
- :quantity => invalidations.count,
37
- :items => invalidations.map do |path|
38
- File.join('/', self.add_prefix(path, prefix: target_path))
39
- end
40
- },
41
- :caller_reference => SecureRandom.hex
42
- }
43
- })
44
-
45
- if response && response.successful?
46
- File.open(LAST_INVALIDATION_FILE, 'w') { |file| file.write(response[:invalidation][:id]) }
32
+ # invalidate CloudFront distribution if needed
33
+ if distribution_id && !invalidations.empty?
34
+ cf = establish_cf_client_connection!(region, key, secret)
35
+
36
+ response = cf.create_invalidation(
37
+ distribution_id: distribution_id,
38
+ invalidation_batch: {
39
+ paths: {
40
+ quantity: invalidations.count,
41
+ items: invalidations.map do |path|
42
+ File.join("/", add_prefix(path, prefix: target_path))
43
+ end
44
+ },
45
+ caller_reference: SecureRandom.hex
46
+ }
47
+ )
48
+
49
+ if response&.successful?
50
+ File.write(LAST_INVALIDATION_FILE, response[:invalidation][:id])
51
+ end
47
52
  end
48
- end
49
53
 
50
- self.published_to!(bucket, stage)
51
- end
54
+ published_to!(bucket, stage)
55
+ end
52
56
 
53
- def self.clear!(region, key, secret, bucket, stage = 'default')
54
- s3 = self.establish_s3_connection!(region, key, secret)
55
- s3.buckets[bucket].clear!
57
+ def clear!(region, key, secret, bucket, stage = "default")
58
+ s3 = establish_s3_connection!(region, key, secret)
59
+ s3.buckets[bucket].clear!
56
60
 
57
- self.clear_published!(bucket, stage)
58
- FileUtils.rm(LAST_INVALIDATION_FILE)
59
- end
61
+ clear_published!(bucket, stage)
62
+ FileUtils.rm(LAST_INVALIDATION_FILE)
63
+ end
60
64
 
61
- def self.check_invalidation(region, key, secret, distribution_id, stage = 'default')
62
- last_invalidation_id = File.read(LAST_INVALIDATION_FILE).strip
65
+ def check_invalidation(region, key, secret, distribution_id, _stage = "default")
66
+ last_invalidation_id = File.read(LAST_INVALIDATION_FILE).strip
63
67
 
64
- cf = self.establish_cf_client_connection!(region, key, secret)
65
- cf.wait_until(:invalidation_completed, distribution_id: distribution_id, id: last_invalidation_id) do |w|
66
- w.max_attempts = nil
67
- w.delay = 30
68
+ cf = establish_cf_client_connection!(region, key, secret)
69
+ cf.wait_until(:invalidation_completed, distribution_id: distribution_id,
70
+ id: last_invalidation_id) do |w|
71
+ w.max_attempts = nil
72
+ w.delay = 30
73
+ end
68
74
  end
69
- end
70
75
 
71
- private
76
+ private
72
77
 
73
78
  # Establishes the connection to Amazon S3
74
- def self.establish_connection!(klass, region, key, secret)
79
+ def establish_connection!(klass, region, key, secret)
75
80
  # Send logging to STDOUT
76
81
  Aws.config[:logger] = ::Logger.new(STDOUT)
77
82
  Aws.config[:log_formatter] = Aws::Log::Formatter.colored
78
83
  klass.new(
79
- :region => region,
80
- :access_key_id => key,
81
- :secret_access_key => secret
84
+ region: region,
85
+ access_key_id: key,
86
+ secret_access_key: secret
82
87
  )
83
88
  end
84
89
 
85
- def self.establish_cf_client_connection!(region, key, secret)
86
- self.establish_connection!(Aws::CloudFront::Client, region, key, secret)
90
+ def establish_cf_client_connection!(region, key, secret)
91
+ establish_connection!(Aws::CloudFront::Client, region, key, secret)
87
92
  end
88
93
 
89
- def self.establish_s3_client_connection!(region, key, secret)
90
- self.establish_connection!(Aws::S3::Client, region, key, secret)
94
+ def establish_s3_client_connection!(region, key, secret)
95
+ establish_connection!(Aws::S3::Client, region, key, secret)
91
96
  end
92
97
 
93
- def self.establish_s3_connection!(region, key, secret)
94
- self.establish_connection!(Aws::S3, region, key, secret)
98
+ def establish_s3_connection!(region, key, secret)
99
+ establish_connection!(Aws::S3, region, key, secret)
95
100
  end
96
101
 
97
- def self.base_file_path(root, file)
102
+ def base_file_path(root, file)
98
103
  file.gsub(root, "")
99
104
  end
100
105
 
101
- def self.files(deployment_path, exclusions)
106
+ def files(deployment_path, exclusions)
102
107
  globbed_paths = Dir.glob(
103
- File.join(deployment_path, '**', '*'),
108
+ File.join(deployment_path, "**", "*"),
104
109
  File::FNM_DOTMATCH # Else Unix-like hidden files will be ignored
105
110
  )
106
111
 
@@ -111,106 +116,122 @@ module Capistrano
111
116
  globbed_paths - excluded_paths
112
117
  end
113
118
 
114
- def self.last_published
115
- if File.exists? LAST_PUBLISHED_FILE
119
+ def last_published
120
+ if File.exist? LAST_PUBLISHED_FILE
116
121
  YAML.load_file(LAST_PUBLISHED_FILE) || {}
117
122
  else
118
123
  {}
119
124
  end
120
125
  end
121
126
 
122
- def self.published_to!(bucket, stage)
123
- current_publish = self.last_published
127
+ def published_to!(bucket, stage)
128
+ current_publish = last_published
124
129
  current_publish["#{bucket}::#{stage}"] = Time.now.iso8601
125
130
  File.write(LAST_PUBLISHED_FILE, current_publish.to_yaml)
126
131
  end
127
132
 
128
- def self.clear_published!(bucket, stage)
129
- current_publish = self.last_published
133
+ def clear_published!(bucket, stage)
134
+ current_publish = last_published
130
135
  current_publish["#{bucket}::#{stage}"] = nil
131
136
  File.write(LAST_PUBLISHED_FILE, current_publish.to_yaml)
132
137
  end
133
138
 
134
- def self.published?(file, bucket, stage)
135
- return false unless last_publish_time = self.last_published["#{bucket}::#{stage}"]
139
+ def published?(file, bucket, stage)
140
+ return false unless (last_publish_time = last_published["#{bucket}::#{stage}"])
141
+
136
142
  File.mtime(file) < Time.parse(last_publish_time)
137
143
  end
138
144
 
139
- def self.put_object(s3, bucket, target_path, path, file, only_gzip, extra_options)
145
+ def put_object(s3_client, bucket, target_path, path, file, only_gzip, extra_options)
146
+ prefer_cf_mime_types = extra_options[:prefer_cf_mime_types] || false
147
+
140
148
  base_name = File.basename(file)
141
- mime_type = mime_type_for_file(base_name)
149
+ mime_type = mime_type_for_file(base_name, prefer_cf_mime_types)
142
150
  options = {
143
- :bucket => bucket,
144
- :key => self.add_prefix(path, prefix: target_path),
145
- :body => open(file),
146
- :acl => 'public-read',
151
+ bucket: bucket,
152
+ key: add_prefix(path, prefix: target_path),
153
+ body: File.read(file),
154
+ acl: "public-read"
147
155
  }
148
156
 
149
157
  options.merge!(build_redirect_hash(path, extra_options[:redirect]))
150
158
  options.merge!(extra_options[:write] || {})
151
159
 
160
+ object_write_options = extra_options[:object_write] || {}
161
+ object_write_options.each do |pattern, object_options|
162
+ options.merge!(object_options) if File.fnmatch(pattern, options[:key])
163
+ end
164
+
152
165
  if mime_type
153
166
  options.merge!(build_content_type_hash(mime_type))
154
167
 
155
168
  if mime_type.sub_type == "gzip"
156
169
  options.merge!(build_gzip_content_encoding_hash)
157
- options.merge!(build_gzip_content_type_hash(file, mime_type))
170
+ options.merge!(build_gzip_content_type_hash(file, mime_type, prefer_cf_mime_types))
158
171
 
159
172
  # upload as original file name
160
- options.merge!(key: self.add_prefix(self.orig_name(path), prefix: target_path)) if only_gzip
173
+ options[:key] = add_prefix(orig_name(path), prefix: target_path) if only_gzip
161
174
  end
162
175
  end
163
176
 
164
- s3.put_object(options)
177
+ s3_client.put_object(options)
165
178
  end
166
179
 
167
- def self.build_redirect_hash(path, redirect_options)
180
+ def build_redirect_hash(path, redirect_options)
168
181
  return {} unless redirect_options && redirect_options[path]
169
182
 
170
- { :website_redirect_location => redirect_options[path] }
183
+ { website_redirect_location: redirect_options[path] }
171
184
  end
172
185
 
173
- def self.build_content_type_hash(mime_type)
174
- { :content_type => mime_type.content_type }
186
+ def build_content_type_hash(mime_type)
187
+ { content_type: mime_type.content_type }
175
188
  end
176
189
 
177
- def self.build_gzip_content_encoding_hash
178
- { :content_encoding => "gzip" }
190
+ def build_gzip_content_encoding_hash
191
+ { content_encoding: "gzip" }
179
192
  end
180
193
 
181
- def self.has_gzipped_version?(file)
182
- File.exist?(self.gzip_name(file))
194
+ def gzipped_version?(file)
195
+ File.exist?(gzip_name(file))
183
196
  end
184
197
 
185
- def self.build_gzip_content_type_hash(file, mime_type)
186
- orig_name = self.orig_name(file)
187
- orig_mime = mime_type_for_file(orig_name)
198
+ def build_gzip_content_type_hash(file, _mime_type, prefer_cf_mime_types)
199
+ orig_name = orig_name(file)
200
+ orig_mime = mime_type_for_file(orig_name, prefer_cf_mime_types)
188
201
 
189
202
  return {} unless orig_mime && File.exist?(orig_name)
190
203
 
191
- { :content_type => orig_mime.content_type }
204
+ { content_type: orig_mime.content_type }
192
205
  end
193
206
 
194
- def self.mime_type_for_file(file)
195
- type = MIME::Types.type_for(file)
196
- (type && !type.empty?) ? type[0] : nil
207
+ def mime_type_for_file(file, prefer_cf_mime_types)
208
+ types = MIME::Types.type_for(file)
209
+
210
+ if prefer_cf_mime_types
211
+ intersection = types & Capistrano::S3::MIMETypes::CF_MIME_TYPES
212
+
213
+ types = intersection unless intersection.empty?
214
+ end
215
+
216
+ types.first
197
217
  end
198
218
 
199
- def self.gzip_name(file)
219
+ def gzip_name(file)
200
220
  "#{file}.gz"
201
221
  end
202
222
 
203
- def self.orig_name(file)
223
+ def orig_name(file)
204
224
  file.sub(/\.gz$/, "")
205
225
  end
206
226
 
207
- def self.add_prefix(path, prefix:)
227
+ def add_prefix(path, prefix:)
208
228
  if prefix.empty?
209
229
  path
210
230
  else
211
231
  File.join(prefix, path)
212
232
  end
213
233
  end
234
+ end
214
235
  end
215
236
  end
216
237
  end
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Capistrano
2
4
  module S3
3
- VERSION = "2.3.0"
5
+ VERSION = "3.0.0.pre"
4
6
  end
5
7
  end
data/lib/capistrano/s3.rb CHANGED
@@ -1,10 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "capistrano"
2
4
  require "capistrano/s3/publisher"
3
5
  require "capistrano/s3/version"
4
6
  require "capistrano/s3/defaults"
7
+ require "capistrano/s3/mime_types"
5
8
 
6
- if Gem::Specification.find_by_name('capistrano').version >= Gem::Version.new('3.0.0')
7
- load File.expand_path('../tasks/capistrano_3.rb', __FILE__)
9
+ if Gem::Specification.find_by_name("capistrano").version >= Gem::Version.new("3.0.0")
10
+ load File.expand_path("tasks/capistrano_3.rb", __dir__)
8
11
  else
9
- require_relative 'tasks/capistrano_2'
12
+ require_relative "tasks/capistrano_2"
10
13
  end
@@ -1,7 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Capistrano
4
+ # rubocop:disable Metrics/BlockLength
2
5
  Configuration.instance(true).load do
3
6
  def _cset(name, *args, &block)
4
- set(name, *args, &block) if !exists?(name)
7
+ set(name, *args, &block) unless exists?(name)
5
8
  end
6
9
 
7
10
  Capistrano::S3::Defaults.populate(self, :_cset)
@@ -16,14 +19,21 @@ module Capistrano
16
19
 
17
20
  desc "Waits until the last CloudFront invalidation batch is completed"
18
21
  task :wait_for_invalidation do
19
- S3::Publisher.check_invalidation(region, access_key_id, secret_access_key, distribution_id)
22
+ S3::Publisher.check_invalidation(region, access_key_id, secret_access_key,
23
+ distribution_id)
20
24
  end
21
25
 
22
26
  desc "Upload files to the bucket in the current state"
23
27
  task :upload_files do
24
- extra_options = { :write => bucket_write_options, :redirect => redirect_options }
28
+ extra_options = {
29
+ write: bucket_write_options,
30
+ redirect: redirect_options,
31
+ object_write: object_write_options,
32
+ prefer_cf_mime_types: prefer_cf_mime_types
33
+ }
25
34
  S3::Publisher.publish!(region, access_key_id, secret_access_key,
26
- bucket, deployment_path, target_path, distribution_id, invalidations, exclusions, only_gzip, extra_options)
35
+ bucket, deployment_path, target_path, distribution_id,
36
+ invalidations, exclusions, only_gzip, extra_options)
27
37
  end
28
38
  end
29
39
 
@@ -31,7 +41,10 @@ module Capistrano
31
41
  s3.upload_files
32
42
  end
33
43
 
34
- task :restart do; end
44
+ task :restart do
45
+ # @todo remove?
46
+ end
35
47
  end
36
48
  end
49
+ # rubocop:enable Metrics/BlockLength
37
50
  end
@@ -1,26 +1,42 @@
1
+ # frozen_string_literal: true
2
+
1
3
  namespace :load do
2
4
  task :defaults do
3
5
  Capistrano::S3::Defaults.populate(self, :set)
4
6
  end
5
7
  end
6
8
 
9
+ # rubocop:disable Metrics/BlockLength
7
10
  namespace :deploy do
8
11
  namespace :s3 do
9
12
  desc "Empties bucket of all files. Caution when using this command, as it cannot be undone!"
10
13
  task :empty do
11
- Capistrano::S3::Publisher.clear!(fetch(:region), fetch(:access_key_id), fetch(:secret_access_key), fetch(:bucket), fetch(:stage))
14
+ Capistrano::S3::Publisher.clear!(fetch(:region), fetch(:access_key_id),
15
+ fetch(:secret_access_key), fetch(:bucket), fetch(:stage))
12
16
  end
13
17
 
14
18
  desc "Waits until the last CloudFront invalidation batch is completed"
15
19
  task :wait_for_invalidation do
16
- Capistrano::S3::Publisher.check_invalidation(fetch(:region), fetch(:access_key_id), fetch(:secret_access_key), fetch(:distribution_id), fetch(:stage))
20
+ Capistrano::S3::Publisher.check_invalidation(fetch(:region), fetch(:access_key_id),
21
+ fetch(:secret_access_key),
22
+ fetch(:distribution_id),
23
+ fetch(:stage))
17
24
  end
18
25
 
19
26
  desc "Upload files to the bucket in the current state"
20
27
  task :upload_files do
21
- extra_options = { :write => fetch(:bucket_write_options), :redirect => fetch(:redirect_options) }
22
- Capistrano::S3::Publisher.publish!(fetch(:region), fetch(:access_key_id), fetch(:secret_access_key),
23
- fetch(:bucket), fetch(:deployment_path), fetch(:target_path), fetch(:distribution_id), fetch(:invalidations), fetch(:exclusions), fetch(:only_gzip), extra_options, fetch(:stage))
28
+ extra_options = {
29
+ write: fetch(:bucket_write_options),
30
+ redirect: fetch(:redirect_options),
31
+ object_write: fetch(:object_write_options),
32
+ prefer_cf_mime_types: fetch(:prefer_cf_mime_types)
33
+ }
34
+ Capistrano::S3::Publisher.publish!(fetch(:region), fetch(:access_key_id),
35
+ fetch(:secret_access_key), fetch(:bucket),
36
+ fetch(:deployment_path), fetch(:target_path),
37
+ fetch(:distribution_id), fetch(:invalidations),
38
+ fetch(:exclusions), fetch(:only_gzip), extra_options,
39
+ fetch(:stage))
24
40
  end
25
41
  end
26
42
 
@@ -28,3 +44,4 @@ namespace :deploy do
28
44
  invoke("deploy:s3:upload_files")
29
45
  end
30
46
  end
47
+ # rubocop:enable Metrics/BlockLength