s3_website_monadic 0.0.1

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.
Files changed (151) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.travis.yml +3 -0
  4. data/Gemfile +3 -0
  5. data/LICENSE +42 -0
  6. data/README.md +451 -0
  7. data/Rakefile +24 -0
  8. data/additional-docs/example-configurations.md +62 -0
  9. data/additional-docs/setting-up-aws-credentials.md +51 -0
  10. data/assembly.sbt +3 -0
  11. data/bin/s3_website +80 -0
  12. data/build.sbt +33 -0
  13. data/changelog.md +215 -0
  14. data/features/as-library.feature +29 -0
  15. data/features/cassettes/cucumber_tags/create-redirect.yml +384 -0
  16. data/features/cassettes/cucumber_tags/empty-bucket.yml +89 -0
  17. data/features/cassettes/cucumber_tags/new-and-changed-files.yml +303 -0
  18. data/features/cassettes/cucumber_tags/new-files-for-sydney.yml +211 -0
  19. data/features/cassettes/cucumber_tags/new-files.yml +355 -0
  20. data/features/cassettes/cucumber_tags/no-new-or-changed-files.yml +359 -0
  21. data/features/cassettes/cucumber_tags/one-file-to-delete.yml +390 -0
  22. data/features/cassettes/cucumber_tags/only-changed-files.yml +411 -0
  23. data/features/cassettes/cucumber_tags/s3-and-cloudfront-after-deleting-a-file.yml +434 -0
  24. data/features/cassettes/cucumber_tags/s3-and-cloudfront-when-updating-a-file.yml +435 -0
  25. data/features/cassettes/cucumber_tags/s3-and-cloudfront.yml +290 -0
  26. data/features/cloudfront.feature +54 -0
  27. data/features/command-line-help.feature +54 -0
  28. data/features/delete.feature +19 -0
  29. data/features/error_reporting.feature +24 -0
  30. data/features/instructions-for-new-user.feature +154 -0
  31. data/features/jekyll-support.feature +20 -0
  32. data/features/nanoc-support.feature +20 -0
  33. data/features/push.feature +115 -0
  34. data/features/redirects.feature +14 -0
  35. data/features/security.feature +15 -0
  36. data/features/step_definitions/steps.rb +86 -0
  37. data/features/support/env.rb +26 -0
  38. data/features/support/test_site_dirs/cdn-powered.blog.fi/_site/css/styles.css +3 -0
  39. data/features/support/test_site_dirs/cdn-powered.blog.fi/_site/index.html +5 -0
  40. data/features/support/test_site_dirs/cdn-powered.blog.fi/s3_website.yml +4 -0
  41. data/features/support/test_site_dirs/cdn-powered.when-deleted-a-file.blog.fi/_site/index.html +10 -0
  42. data/features/support/test_site_dirs/cdn-powered.when-deleted-a-file.blog.fi/s3_website.yml +5 -0
  43. data/features/support/test_site_dirs/cdn-powered.with-one-change.blog.fi/_site/css/styles.css +3 -0
  44. data/features/support/test_site_dirs/cdn-powered.with-one-change.blog.fi/_site/index.html +10 -0
  45. data/features/support/test_site_dirs/cdn-powered.with-one-change.blog.fi/s3_website.yml +4 -0
  46. data/features/support/test_site_dirs/create-redirects/_site/.gitkeep +0 -0
  47. data/features/support/test_site_dirs/create-redirects/s3_website.yml +6 -0
  48. data/features/support/test_site_dirs/ignored-files.com/_site/css/styles.css +4 -0
  49. data/features/support/test_site_dirs/ignored-files.com/_site/index.html +8 -0
  50. data/features/support/test_site_dirs/ignored-files.com/s3_website.yml +5 -0
  51. data/features/support/test_site_dirs/index-and-assets.blog.fi/_site/assets/picture.gif +0 -0
  52. data/features/support/test_site_dirs/index-and-assets.blog.fi/_site/css/styles.css +3 -0
  53. data/features/support/test_site_dirs/index-and-assets.blog.fi/_site/index.html +5 -0
  54. data/features/support/test_site_dirs/index-and-assets.blog.fi/s3_website.yml +3 -0
  55. data/features/support/test_site_dirs/jekyllrb.com/_site/css/styles.css +3 -0
  56. data/features/support/test_site_dirs/jekyllrb.com/_site/index.html +5 -0
  57. data/features/support/test_site_dirs/jekyllrb.com/s3_website.yml +3 -0
  58. data/features/support/test_site_dirs/my.blog-with-clean-urls.com/_site/css/styles.css +3 -0
  59. data/features/support/test_site_dirs/my.blog-with-clean-urls.com/_site/index +5 -0
  60. data/features/support/test_site_dirs/my.blog-with-clean-urls.com/s3_website.yml +3 -0
  61. data/features/support/test_site_dirs/my.blog.com/_site/css/styles.css +3 -0
  62. data/features/support/test_site_dirs/my.blog.com/_site/index.html +5 -0
  63. data/features/support/test_site_dirs/my.blog.com/s3_website.yml +3 -0
  64. data/features/support/test_site_dirs/my.sydney.blog.au/_site/css/styles.css +3 -0
  65. data/features/support/test_site_dirs/my.sydney.blog.au/_site/index.html +5 -0
  66. data/features/support/test_site_dirs/my.sydney.blog.au/s3_website.yml +4 -0
  67. data/features/support/test_site_dirs/nanoc.ws/public/output/css/styles.css +3 -0
  68. data/features/support/test_site_dirs/nanoc.ws/public/output/index.html +5 -0
  69. data/features/support/test_site_dirs/nanoc.ws/s3_website.yml +3 -0
  70. data/features/support/test_site_dirs/new-and-changed-files.com/_site/css/styles.css +4 -0
  71. data/features/support/test_site_dirs/new-and-changed-files.com/_site/index.html +8 -0
  72. data/features/support/test_site_dirs/new-and-changed-files.com/s3_website.yml +3 -0
  73. data/features/support/test_site_dirs/no-new-or-changed-files.com/_site/css/styles.css +3 -0
  74. data/features/support/test_site_dirs/no-new-or-changed-files.com/_site/index.html +5 -0
  75. data/features/support/test_site_dirs/no-new-or-changed-files.com/s3_website.yml +3 -0
  76. data/features/support/test_site_dirs/only-changed-files.com/_site/css/styles.css +3 -0
  77. data/features/support/test_site_dirs/only-changed-files.com/_site/index.html +9 -0
  78. data/features/support/test_site_dirs/only-changed-files.com/s3_website.yml +3 -0
  79. data/features/support/test_site_dirs/site-that-contains-s3-website-file.com/_site/s3_website.yml +3 -0
  80. data/features/support/test_site_dirs/site-that-contains-s3-website-file.com/s3_website.yml +3 -0
  81. data/features/support/test_site_dirs/site-with-text-doc.com/_site/file.txt +1 -0
  82. data/features/support/test_site_dirs/site.with.css-maxage.com/_site/css/styles.css +3 -0
  83. data/features/support/test_site_dirs/site.with.css-maxage.com/_site/index.html +5 -0
  84. data/features/support/test_site_dirs/site.with.css-maxage.com/s3_website.yml +5 -0
  85. data/features/support/test_site_dirs/site.with.gzipped-and-max-aged-content.com/_site/css/styles.css +3 -0
  86. data/features/support/test_site_dirs/site.with.gzipped-and-max-aged-content.com/_site/index.html +5 -0
  87. data/features/support/test_site_dirs/site.with.gzipped-and-max-aged-content.com/s3_website.yml +5 -0
  88. data/features/support/test_site_dirs/site.with.gzipped-html.com/_site/css/styles.css +3 -0
  89. data/features/support/test_site_dirs/site.with.gzipped-html.com/_site/index.html +5 -0
  90. data/features/support/test_site_dirs/site.with.gzipped-html.com/s3_website.yml +5 -0
  91. data/features/support/test_site_dirs/site.with.maxage.com/_site/css/styles.css +3 -0
  92. data/features/support/test_site_dirs/site.with.maxage.com/_site/index.html +5 -0
  93. data/features/support/test_site_dirs/site.with.maxage.com/s3_website.yml +4 -0
  94. data/features/support/test_site_dirs/unpublish-a-post.com/_site/css/styles.css +3 -0
  95. data/features/support/test_site_dirs/unpublish-a-post.com/s3_website.yml +3 -0
  96. data/features/support/vcr.rb +20 -0
  97. data/features/website-performance.feature +57 -0
  98. data/lib/cloudfront/invalidator.rb +37 -0
  99. data/lib/s3_website/config_loader.rb +55 -0
  100. data/lib/s3_website/diff_helper.rb +113 -0
  101. data/lib/s3_website/endpoint.rb +37 -0
  102. data/lib/s3_website/errors.rb +42 -0
  103. data/lib/s3_website/jekyll.rb +5 -0
  104. data/lib/s3_website/keyboard.rb +27 -0
  105. data/lib/s3_website/nanoc.rb +5 -0
  106. data/lib/s3_website/parallelism.rb +25 -0
  107. data/lib/s3_website/paths.rb +39 -0
  108. data/lib/s3_website/retry.rb +19 -0
  109. data/lib/s3_website/tasks.rb +36 -0
  110. data/lib/s3_website/upload.rb +137 -0
  111. data/lib/s3_website/uploader.rb +177 -0
  112. data/lib/s3_website.rb +34 -0
  113. data/project/assembly.sbt +1 -0
  114. data/project/build.properties +0 -0
  115. data/project/plugins.sbt +1 -0
  116. data/project/sbt-launch-0.13.2.jar +0 -0
  117. data/resources/configuration_file_template.yml +56 -0
  118. data/s3_website.gemspec +41 -0
  119. data/sbt +4 -0
  120. data/spec/lib/cloudfront/invalidator_spec.rb +60 -0
  121. data/spec/lib/config_loader_spec.rb +20 -0
  122. data/spec/lib/endpoint_spec.rb +31 -0
  123. data/spec/lib/error_spec.rb +21 -0
  124. data/spec/lib/keyboard_spec.rb +62 -0
  125. data/spec/lib/parallelism_spec.rb +81 -0
  126. data/spec/lib/paths_spec.rb +7 -0
  127. data/spec/lib/retry_spec.rb +34 -0
  128. data/spec/lib/upload_spec.rb +303 -0
  129. data/spec/lib/uploader_spec.rb +37 -0
  130. data/spec/sample_files/hyde_site/_site/.vimrc +5 -0
  131. data/spec/sample_files/hyde_site/_site/css/styles.css +3 -0
  132. data/spec/sample_files/hyde_site/_site/index.html +1 -0
  133. data/spec/sample_files/hyde_site/s3_website.yml +3 -0
  134. data/spec/sample_files/tokyo_site/_site/.vimrc +5 -0
  135. data/spec/sample_files/tokyo_site/_site/css/styles.css +3 -0
  136. data/spec/sample_files/tokyo_site/_site/index.html +1 -0
  137. data/spec/sample_files/tokyo_site/s3_website.yml +4 -0
  138. data/spec/spec_helper.rb +1 -0
  139. data/src/main/scala/s3/website/CloudFront.scala +96 -0
  140. data/src/main/scala/s3/website/Diff.scala +42 -0
  141. data/src/main/scala/s3/website/Implicits.scala +7 -0
  142. data/src/main/scala/s3/website/Push.scala +191 -0
  143. data/src/main/scala/s3/website/Ruby.scala +12 -0
  144. data/src/main/scala/s3/website/S3.scala +139 -0
  145. data/src/main/scala/s3/website/model/Config.scala +152 -0
  146. data/src/main/scala/s3/website/model/S3Endpoint.scala +22 -0
  147. data/src/main/scala/s3/website/model/Site.scala +68 -0
  148. data/src/main/scala/s3/website/model/errors.scala +11 -0
  149. data/src/main/scala/s3/website/model/push.scala +192 -0
  150. data/src/test/scala/s3/website/S3WebsiteSpec.scala +445 -0
  151. metadata +508 -0
@@ -0,0 +1,303 @@
1
+ require 'spec_helper'
2
+
3
+ describe S3Website::Upload do
4
+ describe 'uploading blacklisted files' do
5
+ let(:blacklisted_files) {
6
+ [ 's3_website.yml' ]
7
+ }
8
+ it 'should fail if the upload file is s3_website.yml' do
9
+ blacklisted_files.each do |blacklisted_file|
10
+ expect {
11
+ S3Website::Upload.new blacklisted_file, mock(), {}, mock()
12
+ }.to raise_error "May not upload #{blacklisted_file}, because it's blacklisted"
13
+ end
14
+ end
15
+
16
+ it 'should fail to upload configured blacklisted files' do
17
+ config = { 'exclude_from_upload' => 'vendor' }
18
+
19
+ expect {
20
+ S3Website::Upload.new "vendor/jquery/development.js", mock(), config, mock()
21
+ }.to raise_error "May not upload vendor/jquery/development.js, because it's blacklisted"
22
+ end
23
+
24
+ context 'the uploaded file matches a value in the exclude_from_upload setting' do
25
+ it 'should fail to upload any configured blacklisted files' do
26
+ config = { 'exclude_from_upload' => ['vendor', 'tests'] }
27
+
28
+ expect {
29
+ S3Website::Upload.new "vendor/jquery/development.js", mock(), config, mock()
30
+ }.to raise_error "May not upload vendor/jquery/development.js, because it's blacklisted"
31
+
32
+ expect {
33
+ S3Website::Upload.new "tests/spec_helper.js", mock(), config, mock()
34
+ }.to raise_error "May not upload tests/spec_helper.js, because it's blacklisted"
35
+ end
36
+
37
+ it 'supports regexes in the exclude_from_upload setting' do
38
+ config = { 'exclude_from_upload' => 'test.*' }
39
+
40
+ expect {
41
+ S3Website::Upload.new "tests/spec_helper.js", mock(), config, mock()
42
+ }.to raise_error "May not upload tests/spec_helper.js, because it's blacklisted"
43
+ end
44
+ end
45
+ end
46
+
47
+ describe 'reduced redundancy setting' do
48
+ let(:config) {
49
+ { 's3_reduced_redundancy' => true }
50
+ }
51
+
52
+ it 'allows storing a file under the Reduced Redundancy Storage' do
53
+ should_upload(
54
+ file = 'index.html',
55
+ site = 'features/support/test_site_dirs/my.blog.com/_site', config) { |s3_object|
56
+ s3_object.should_receive(:write).with(
57
+ anything(),
58
+ include(:reduced_redundancy => true)
59
+ )
60
+ }
61
+ end
62
+ end
63
+
64
+ describe 'content type resolving' do
65
+ it 'adds the content type of the uploaded CSS file into the S3 object' do
66
+ should_upload(
67
+ file = 'css/styles.css',
68
+ site = 'features/support/test_site_dirs/my.blog.com/_site') { |s3_object|
69
+ s3_object.should_receive(:write).with(
70
+ anything(),
71
+ include(:content_type => 'text/css; charset=utf-8')
72
+ )
73
+ }
74
+ end
75
+
76
+ it 'adds the content type of the uploaded HTML file into the S3 object' do
77
+ should_upload(
78
+ file = 'index.html',
79
+ site = 'features/support/test_site_dirs/my.blog.com/_site') { |s3_object|
80
+ s3_object.should_receive(:write).with(
81
+ anything(),
82
+ include(:content_type => 'text/html; charset=utf-8')
83
+ )
84
+ }
85
+ end
86
+
87
+ describe 'encoding of text documents' do
88
+ it 'should mark all text documents as utf-8' do
89
+ should_upload(
90
+ file = 'file.txt',
91
+ site = 'features/support/test_site_dirs/site-with-text-doc.com/_site') { |s3_object|
92
+ s3_object.should_receive(:write).with(
93
+ anything(),
94
+ include(:content_type => 'text/plain; charset=utf-8')
95
+ )
96
+ }
97
+ end
98
+ end
99
+
100
+ context 'the user specifies a mime-type for extensionless files' do
101
+ let(:config) {{
102
+ 'extensionless_mime_type' => "text/html"
103
+ }}
104
+
105
+ it 'adds the content type of the uploaded extensionless file into the S3 object' do
106
+ should_upload(
107
+ file = 'index',
108
+ site = 'features/support/test_site_dirs/my.blog-with-clean-urls.com/_site',
109
+ config) { |s3_object|
110
+ s3_object.should_receive(:write).with(
111
+ anything(),
112
+ include(:content_type => 'text/html; charset=utf-8')
113
+ )
114
+ }
115
+ end
116
+ end
117
+ end
118
+
119
+ describe 'gzip compression' do
120
+ let(:config){
121
+ {
122
+ 's3_reduced_redundancy' => false,
123
+ 'gzip' => true
124
+ }
125
+ }
126
+
127
+ subject{ S3Website::Upload.new("index.html", mock(), config, 'features/support/test_site_dirs/my.blog.com/_site') }
128
+
129
+ describe '#gzip?' do
130
+ it 'should be false if the config does not specify gzip' do
131
+ config.delete 'gzip'
132
+ subject.should_not be_gzip
133
+ end
134
+
135
+ it 'should be false if gzip is true but does not match a default extension' do
136
+ subject.stub(:path).and_return("index.bork")
137
+ subject.should_not be_gzip
138
+ end
139
+
140
+ it 'should be true if gzip is true and file extension matches' do
141
+ subject.should be_gzip
142
+ end
143
+
144
+ it 'should be true if gzip is true and file extension matches custom supplied' do
145
+ config['gzip'] = %w(.bork)
146
+ subject.stub(:path).and_return('index.bork')
147
+ subject.should be_gzip
148
+ end
149
+ end
150
+
151
+ describe '#gzipped_file' do
152
+ it 'should return a gzipped version of the file' do
153
+ gz = Zlib::GzipReader.new(subject.send(:gzipped_file))
154
+ gz.read.should == File.read('features/support/test_site_dirs/my.blog.com/_site/index.html')
155
+ end
156
+ end
157
+ end
158
+
159
+ describe 'gzip zopfli' do
160
+ let(:config){
161
+ {
162
+ 's3_reduced_redundancy' => false,
163
+ 'gzip' => true,
164
+ 'gzip_zopfli' => true
165
+ }
166
+ }
167
+
168
+ subject{ S3Website::Upload.new("index.html", mock(), config, 'features/support/test_site_dirs/my.blog.com/_site') }
169
+
170
+ # Zopfli should be compatible with the gzip format
171
+ describe '#gzipped_file' do
172
+ it 'should return a gzipped version of the file' do
173
+ gz = Zlib::GzipReader.new(subject.send(:gzipped_file))
174
+ gz.read.should == File.read('features/support/test_site_dirs/my.blog.com/_site/index.html')
175
+ end
176
+ end
177
+ end
178
+
179
+ describe 'cache control' do
180
+ let(:config){
181
+ {
182
+ 's3_reduced_redundancy' => false,
183
+ 'max_age' => 300
184
+ }
185
+ }
186
+
187
+ let(:subject) {
188
+ S3Website::Upload.new(
189
+ "index.html",
190
+ mock(),
191
+ config,
192
+ 'features/support/test_site_dirs/my.blog.com/_site'
193
+ )
194
+ }
195
+
196
+ describe '#cache_control?' do
197
+ it 'should be false if max_age is missing' do
198
+ config.delete 'max_age'
199
+ subject.should_not be_cache_control
200
+ end
201
+
202
+ it 'should be true if max_age is present' do
203
+ subject.should be_cache_control
204
+ end
205
+
206
+ it 'should be true if max_age is a hash' do
207
+ config['max_age'] = {'*' => 300}
208
+ subject.should be_cache_control
209
+ end
210
+ end
211
+
212
+ context 'the user specifies max-age as zero' do
213
+ let(:config) {{
214
+ 'max_age' => 0
215
+ }}
216
+
217
+ it 'includes the no-cache declaration in the cache-control metadata' do
218
+ subject.send(:upload_options)[:cache_control].should == 'no-cache, max-age=0'
219
+ end
220
+ end
221
+
222
+ describe '#max_age' do
223
+ it 'should be the universal value if one is set' do
224
+ subject.send(:max_age).should == 300
225
+ end
226
+
227
+ it 'should be the file-specific value if one is set' do
228
+ config['max_age'] = {'*index.html' => 500}
229
+ subject.send(:max_age).should == 500
230
+ end
231
+
232
+ it 'should be zero if no file-specific value hit' do
233
+ config['max_age'] = {'*.js' => 500}
234
+ subject.send(:max_age).should == 0
235
+ end
236
+
237
+ context 'overriding the more general setting with the more specific' do
238
+ let(:config){
239
+ {
240
+ 's3_reduced_redundancy' => false,
241
+ 'max_age' => {
242
+ '**' => 150,
243
+ 'assets/**' => 86400
244
+ }
245
+ }
246
+ }
247
+
248
+ it 'respects the most specific max-age selector' do
249
+ subject = S3Website::Upload.new(
250
+ 'assets/picture.gif',
251
+ mock(),
252
+ config,
253
+ 'features/support/test_site_dirs/index-and-assets.blog.fi/_site'
254
+ )
255
+ subject.send(:max_age).should == 86400
256
+ end
257
+
258
+ it 'respects the most specific max-age selector' do
259
+ subject = S3Website::Upload.new(
260
+ 'index.html',
261
+ mock(),
262
+ config,
263
+ 'features/support/test_site_dirs/index-and-assets.blog.fi/_site'
264
+ )
265
+ subject.send(:max_age).should == 150
266
+ end
267
+ end
268
+ end
269
+ end
270
+
271
+ def should_upload(file_to_upload, site_dir, config = {})
272
+ def create_verifying_s3_client(file_to_upload, &block)
273
+ def create_objects(file_to_upload, &block)
274
+ def create_html_s3_object(file_to_upload, &block)
275
+ s3_object = stub('s3_object')
276
+ yield s3_object
277
+ s3_object
278
+ end
279
+ objects = {}
280
+ objects[file_to_upload] = create_html_s3_object(file_to_upload, &block)
281
+ objects
282
+ end
283
+ def create_bucket(file_to_upload, &block)
284
+ bucket = stub('bucket')
285
+ bucket.stub(:objects => create_objects(file_to_upload, &block))
286
+ bucket
287
+ end
288
+ buckets = stub('buckets')
289
+ buckets.stub(:[] => create_bucket(file_to_upload, &block))
290
+ s3 = stub('s3')
291
+ s3.stub(:buckets => buckets)
292
+ s3
293
+ end
294
+
295
+ s3_client = create_verifying_s3_client(file_to_upload) do |s3_object|
296
+ yield s3_object
297
+ end
298
+ S3Website::Upload.new(file_to_upload,
299
+ s3_client,
300
+ config,
301
+ site_dir).perform!
302
+ end
303
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe S3Website::Uploader do
4
+ context '#load_all_local_files' do
5
+ let(:files) {
6
+ S3Website::Uploader.send(:load_all_local_files,
7
+ 'spec/sample_files/hyde_site/_site')
8
+ }
9
+
10
+ it 'loads regular files' do
11
+ files.should include('css/styles.css')
12
+ files.should include('index.html')
13
+ end
14
+
15
+ it 'loads also dotfiles' do
16
+ files.should include('.vimrc')
17
+ end
18
+ end
19
+
20
+ context "honoring the ignore_on_server setting" do
21
+ it "ignores files which match a regular expression" do
22
+ files_to_delete = S3Website::Uploader.build_list_of_files_to_delete(["a", "b", "ignored"], ["a"], "ignored")
23
+ files_to_delete.should eq ["b"]
24
+ end
25
+
26
+ it "let's the user specify the regexes in a list" do
27
+ files_to_delete = S3Website::Uploader.build_list_of_files_to_delete(["a", "b", "ignored"], ["a"], ["ignored"])
28
+ files_to_delete.should eq ["b"]
29
+ end
30
+
31
+
32
+ it "does not ignore when you don't provide an ignored regex" do
33
+ files_to_delete = S3Website::Uploader.build_list_of_files_to_delete(["a", "b", "ignored"], ["a"])
34
+ files_to_delete.should eq ["b", "ignored"]
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,5 @@
1
+ " Disable the arrow keys in the normal mode – using them there only slows you down. Use hkjl instead.
2
+ map <Left> :echo "no!"<cr>
3
+ map <Right> :echo "no!"<cr>
4
+ map <Up> :echo "no!"<cr>
5
+ map <Down> :echo "no!"<cr>
@@ -0,0 +1,3 @@
1
+ body {
2
+
3
+ }
@@ -0,0 +1 @@
1
+ <div>Hello world</div>
@@ -0,0 +1,3 @@
1
+ s3_id: <%= 'hello' %>
2
+ s3_secret: <%= 'world' %>
3
+ s3_bucket: galaxy
@@ -0,0 +1,5 @@
1
+ " Disable the arrow keys in the normal mode – using them there only slows you down. Use hkjl instead.
2
+ map <Left> :echo "no!"<cr>
3
+ map <Right> :echo "no!"<cr>
4
+ map <Up> :echo "no!"<cr>
5
+ map <Down> :echo "no!"<cr>
@@ -0,0 +1,3 @@
1
+ body {
2
+
3
+ }
@@ -0,0 +1 @@
1
+ <div>Hello world</div>
@@ -0,0 +1,4 @@
1
+ s3_id: <%= 'hello' %>
2
+ s3_secret: <%= 'world' %>
3
+ s3_bucket: galaxy
4
+ s3_endpoint: ap-northeast-1
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + "/../lib/s3_website.rb"
@@ -0,0 +1,96 @@
1
+ package s3.website
2
+
3
+ import s3.website.model.{Redirect, Config}
4
+ import com.amazonaws.services.cloudfront.{AmazonCloudFrontClient, AmazonCloudFront}
5
+ import s3.website.CloudFront.{FailedInvalidation, SuccessfulInvalidation, CloudFrontClientProvider}
6
+ import scala.util.{Failure, Success, Try}
7
+ import com.amazonaws.services.cloudfront.model.{TooManyInvalidationsInProgressException, Paths, InvalidationBatch, CreateInvalidationRequest}
8
+ import scala.collection.JavaConversions._
9
+ import scala.concurrent.duration._
10
+ import s3.website.S3.{SuccessfulUpload, PushSuccessReport}
11
+ import com.amazonaws.auth.BasicAWSCredentials
12
+
13
+ class CloudFront(implicit cfClient: CloudFrontClientProvider, sleepUnit: TimeUnit) {
14
+
15
+ def invalidate(invalidationBatch: InvalidationBatch, distributionId: String)(implicit config: Config): InvalidationResult = {
16
+ def tryInvalidate(implicit attempt: Int = 1): Try[SuccessfulInvalidation] =
17
+ Try {
18
+ val invalidationReq = new CreateInvalidationRequest(distributionId, invalidationBatch)
19
+ cfClient(config).createInvalidation(invalidationReq)
20
+ val result = SuccessfulInvalidation(invalidationBatch.getPaths.getItems.size())
21
+ println(s"Invalidated ${result.invalidatedItemsCount} item(s) on the CloudFront distribution $distributionId.")
22
+ result
23
+ } recoverWith {
24
+ case e: TooManyInvalidationsInProgressException =>
25
+ implicit val duration: Duration = Duration(
26
+ (fibs drop attempt).head min 15, /* AWS docs way that invalidations complete in 15 minutes */
27
+ sleepUnit
28
+ )
29
+ println(maxInvalidationsExceededInfo)
30
+ Thread.sleep(duration.toMillis)
31
+ tryInvalidate(attempt + 1)
32
+ }
33
+
34
+ tryInvalidate() match {
35
+ case Success(res) =>
36
+ Right(res)
37
+ case Failure(err) =>
38
+ println(s"Failed to invalidate the CloudFront distribution $distributionId (${err.getMessage})")
39
+ Left(FailedInvalidation())
40
+ }
41
+ }
42
+
43
+ def maxInvalidationsExceededInfo(implicit sleepDuration: Duration, attempt: Int) = {
44
+ val basicInfo = s"The maximum amount of CloudFront invalidations has exceeded. Trying again in $sleepDuration, please wait."
45
+ val extendedInfo =
46
+ s"""|$basicInfo
47
+ | For more information, see http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Invalidation.html#InvalidationLimits"""
48
+ .stripMargin
49
+ if (attempt == 1)
50
+ extendedInfo
51
+ else
52
+ basicInfo
53
+ }
54
+
55
+ type InvalidationResult = Either[FailedInvalidation, SuccessfulInvalidation]
56
+
57
+ lazy val fibs: Stream[Int] = 0 #:: 1 #:: fibs.zip(fibs.tail).map { n => n._1 + n._2 }
58
+ }
59
+
60
+ object CloudFront {
61
+
62
+ type CloudFrontClientProvider = (Config) => AmazonCloudFront
63
+
64
+ case class SuccessfulInvalidation(invalidatedItemsCount: Int)
65
+
66
+ case class FailedInvalidation()
67
+
68
+ def awsCloudFrontClient(config: Config) =
69
+ new AmazonCloudFrontClient(new BasicAWSCredentials(config.s3_id, config.s3_secret))
70
+
71
+ def toInvalidationBatches(pushSuccessReports: Seq[PushSuccessReport])(implicit config: Config): Seq[InvalidationBatch] =
72
+ pushSuccessReports
73
+ .filterNot(isRedirect) // Assume that redirect objects are never cached.
74
+ .map("/" + _.s3Key) // CloudFront keys always have the slash in front
75
+ .map { path =>
76
+ if (config.cloudfront_invalidate_root.exists(_ == true))
77
+ path.replaceFirst("/index.html$", "/")
78
+ else
79
+ path
80
+ }
81
+ .grouped(1000) // CloudFront supports max 1000 invalidations in one request (http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Invalidation.html#InvalidationLimits)
82
+ .map { batchKeys =>
83
+ new InvalidationBatch() withPaths
84
+ (new Paths() withItems batchKeys withQuantity batchKeys.size) withCallerReference
85
+ s"s3_website gem ${System.currentTimeMillis()}"
86
+ }
87
+ .toSeq
88
+
89
+ def isRedirect: PartialFunction[PushSuccessReport, Boolean] = {
90
+ case SuccessfulUpload(upload) => upload.uploadType match {
91
+ case Redirect => true
92
+ case _ => false
93
+ }
94
+ case _ => false
95
+ }
96
+ }
@@ -0,0 +1,42 @@
1
+ package s3.website
2
+
3
+ import s3.website.model._
4
+ import s3.website.Ruby.rubyRegexMatches
5
+
6
+ object Diff {
7
+
8
+ def resolveDeletes(localFiles: Seq[LocalFile], s3Files: Seq[S3File], redirects: Seq[Upload with UploadTypeResolved])(implicit config: Config): Seq[S3File] = {
9
+ val keysNotToBeDeleted: Set[String] = (localFiles ++ redirects).map(_.s3Key).toSet
10
+ s3Files.filterNot { s3File =>
11
+ val ignoreOnServer = config.ignore_on_server.exists(_.fold(
12
+ (ignoreRegex: String) => rubyRegexMatches(s3File.s3Key, ignoreRegex),
13
+ (ignoreRegexes: Seq[String]) => ignoreRegexes.exists(rubyRegexMatches(s3File.s3Key, _))
14
+ ))
15
+ keysNotToBeDeleted.exists(_ == s3File.s3Key) || ignoreOnServer
16
+ }
17
+ }
18
+
19
+ def resolveUploads(localFiles: Seq[LocalFile], s3Files: Seq[S3File])(implicit config: Config):
20
+ Stream[Either[Error, Upload with UploadTypeResolved]] = {
21
+ val remoteS3KeysIndex = s3Files.map(_.s3Key).toSet
22
+ val remoteMd5Index = s3Files.map(_.md5).toSet
23
+ localFiles
24
+ .toStream // Load lazily, because the MD5 computation for the local file requires us to read the whole file
25
+ .map(resolveUploadSource)
26
+ .collect {
27
+ case errorOrUpload if errorOrUpload.right.exists(isNewUpload(remoteS3KeysIndex)) =>
28
+ for (upload <- errorOrUpload.right) yield upload withUploadType NewFile
29
+ case errorOrUpload if errorOrUpload.right.exists(isUpdate(remoteS3KeysIndex, remoteMd5Index)) =>
30
+ for (upload <- errorOrUpload.right) yield upload withUploadType Update
31
+ }
32
+ }
33
+
34
+ def isNewUpload(remoteS3KeysIndex: Set[String])(u: Upload) = !remoteS3KeysIndex.exists(_ == u.s3Key)
35
+
36
+ def isUpdate(remoteS3KeysIndex: Set[String], remoteMd5Index: Set[String])(u: Upload) =
37
+ remoteS3KeysIndex.exists(_ == u.s3Key) && !remoteMd5Index.exists(remoteMd5 => u.essence.right.exists(_.md5 == remoteMd5))
38
+
39
+ def resolveUploadSource(localFile: LocalFile)(implicit config: Config): Either[Error, Upload] =
40
+ for (upload <- LocalFile.toUpload(localFile).right)
41
+ yield upload
42
+ }
@@ -0,0 +1,7 @@
1
+ package s3.website
2
+
3
+ import s3.website.model.{Site, Config}
4
+
5
+ object Implicits {
6
+ implicit def site2Config(implicit site: Site): Config = site.config
7
+ }