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,177 @@
1
+ module S3Website
2
+ class Uploader
3
+ def self.run(site_dir, config, in_headless_mode = false)
4
+ puts "Deploying #{site_dir.sub(Dir.pwd + '/', '')}/* to #{config['s3_bucket']}"
5
+
6
+ s3_config = { :s3_endpoint => Endpoint.new(config['s3_endpoint']).hostname }
7
+ s3_id, s3_secret = config['s3_id'], config['s3_secret']
8
+ unless s3_id.nil? || s3_id == '' || s3_secret.nil? || s3_secret == ''
9
+ s3_config.merge! :access_key_id => s3_id, :secret_access_key => s3_secret
10
+ end
11
+
12
+ s3 = AWS::S3.new(s3_config)
13
+
14
+ new_files_count, changed_files_count, changed_files = upload_files(
15
+ s3, config, site_dir
16
+ )
17
+
18
+ redirects = config['redirects'] || {}
19
+ changed_redirects = setup_redirects redirects, config, s3
20
+
21
+ deleted_files = remove_superfluous_files(
22
+ s3,
23
+ config,
24
+ {
25
+ :s3_bucket => config['s3_bucket'],
26
+ :site_dir => site_dir,
27
+ :redirects => redirects,
28
+ :in_headless_mode => in_headless_mode,
29
+ :ignore_on_server => config["ignore_on_server"]
30
+ }
31
+ )
32
+
33
+ print_done_report config
34
+
35
+ [new_files_count, changed_files_count, deleted_files, changed_files, changed_redirects, deleted_files]
36
+ end
37
+
38
+ private
39
+
40
+ def self.print_done_report(config)
41
+ bucket_name = config['s3_bucket']
42
+ website_hostname_suffix = Endpoint.new(config['s3_endpoint']).website_hostname
43
+ website_hostname_with_bucket =
44
+ "%s.%s" % [bucket_name, website_hostname_suffix]
45
+ puts "Done! Go visit: http://#{website_hostname_with_bucket}/index.html"
46
+ end
47
+
48
+ def self.upload_files(s3, config, site_dir)
49
+ changed_files, new_files = DiffHelper.resolve_files_to_upload(
50
+ s3.buckets[config['s3_bucket']],
51
+ site_dir,
52
+ config
53
+ )
54
+ to_upload = changed_files + new_files
55
+ if to_upload.empty?
56
+ puts "No new or changed files to upload"
57
+ else
58
+ pre_upload_report = []
59
+ pre_upload_report << "Uploading"
60
+ pre_upload_report << "#{new_files.length} new" if new_files.length > 0
61
+ pre_upload_report << "and" if changed_files.length > 0 and new_files.length > 0
62
+ pre_upload_report << "#{changed_files.length} changed" if changed_files.length > 0
63
+ pre_upload_report << "file(s)"
64
+ puts pre_upload_report.join(' ')
65
+ upload_in_parallel_or_sequentially to_upload, s3, config, site_dir
66
+ end
67
+ [new_files.length, changed_files.length, changed_files]
68
+ end
69
+
70
+ def self.upload_in_parallel_or_sequentially(files_to_upload, s3, config, site_dir)
71
+ Parallelism.each_in_parallel_or_sequentially(files_to_upload, config) { |f|
72
+ upload_file(f, s3, config, site_dir)
73
+ }
74
+ end
75
+
76
+ def self.upload_file(file, s3, config, site_dir)
77
+ Retry.run_with_retry do
78
+ upload = Upload.new(file, s3, config, site_dir)
79
+
80
+ if upload.perform!
81
+ print "Upload #{upload.details}: Success!\n"
82
+ else
83
+ print "Upload #{upload.details}: FAILURE!\n"
84
+ end
85
+ end
86
+ end
87
+
88
+ def self.setup_redirects(redirects, config, s3)
89
+ operations = redirects.map do |path, target|
90
+ setup_redirect(path, target, s3, config)
91
+ end
92
+ performed_operations = operations.reject do |op|
93
+ op == :no_redirect_operation_performed
94
+ end
95
+ unless performed_operations.empty?
96
+ puts 'Creating new redirects ...'
97
+ end
98
+ performed_operations.each do |redirect_operation|
99
+ puts ' ' + redirect_operation[:report]
100
+ end
101
+ performed_operations.map do |redirect_operation|
102
+ redirect_operation[:path]
103
+ end
104
+ end
105
+
106
+ def self.setup_redirect(path, target, s3, config)
107
+ target = '/' + target unless target =~ %r{^(/|https?://)}
108
+ s3_object = s3.buckets[config['s3_bucket']].objects[path]
109
+
110
+ begin
111
+ current_head = s3_object.head
112
+ rescue AWS::S3::Errors::NoSuchKey
113
+ end
114
+
115
+ if current_head.nil? or current_head[:website_redirect_location] != target
116
+ s3_object.write('', :website_redirect_location => target)
117
+ {
118
+ :report => "Redirect #{path} to #{target}: Success!",
119
+ :path => path
120
+ }
121
+ else
122
+ :no_redirect_operation_performed
123
+ end
124
+ end
125
+
126
+ def self.remove_superfluous_files(s3, config, options)
127
+ s3_bucket_name = options.fetch(:s3_bucket)
128
+ site_dir = options.fetch(:site_dir)
129
+ in_headless_mode = options.fetch(:in_headless_mode)
130
+
131
+ remote_files = s3.buckets[s3_bucket_name].objects.map { |f| f.key }
132
+ local_files = load_all_local_files(site_dir) + options.fetch(:redirects).keys
133
+ files_to_delete = build_list_of_files_to_delete(remote_files, local_files, options[:ignore_on_server])
134
+
135
+ deleted_files = []
136
+ if in_headless_mode
137
+ files_to_delete.each { |s3_object_key|
138
+ delete_s3_object s3, s3_bucket_name, s3_object_key
139
+ deleted_files << s3_object_key
140
+ }
141
+ else
142
+ Keyboard.if_user_confirms_delete(files_to_delete, config) { |s3_object_key|
143
+ delete_s3_object s3, s3_bucket_name, s3_object_key
144
+ deleted_files << s3_object_key
145
+ }
146
+ end
147
+ deleted_files
148
+ end
149
+
150
+ def self.build_list_of_files_to_delete(remote_files, local_files, ignore_on_server = nil)
151
+ files_to_delete = remote_files - local_files
152
+ files_to_delete.reject { |file|
153
+ ignore_regexps(ignore_on_server).any? do |ignore_regexp|
154
+ Regexp.new(ignore_regexp).match file
155
+ end
156
+ }
157
+ end
158
+
159
+ def self.ignore_regexps(ignore_on_server)
160
+ ignore_regexps = ignore_on_server || "a_string_that_should_never_match_ever"
161
+ ignore_regexps.class == Array ? ignore_regexps : [ignore_regexps]
162
+ end
163
+
164
+ def self.delete_s3_object(s3, s3_bucket_name, s3_object_key)
165
+ Retry.run_with_retry do
166
+ s3.buckets[s3_bucket_name].objects[s3_object_key].delete
167
+ print "Delete #{s3_object_key}: Success!\n"
168
+ end
169
+ end
170
+
171
+ def self.load_all_local_files(site_dir)
172
+ Dir.glob(site_dir + '/**/*', File::FNM_DOTMATCH).
173
+ delete_if { |f| File.directory?(f) }.
174
+ map { |f| f.sub(site_dir + '/', '') }
175
+ end
176
+ end
177
+ end
data/lib/s3_website.rb ADDED
@@ -0,0 +1,34 @@
1
+ require 'rubygems'
2
+ require 'yaml'
3
+ require 'erubis'
4
+ require 'aws-sdk'
5
+ require 'simple-cloudfront-invalidator'
6
+ require 'filey-diff'
7
+ require 'mime/types'
8
+ require 'thor'
9
+
10
+ module S3Website
11
+ DEFAULT_GZIP_EXTENSIONS = %w(.html .css .js .svg .txt)
12
+ end
13
+
14
+ %w{
15
+ errors
16
+ upload
17
+ uploader
18
+ tasks
19
+ config_loader
20
+ retry
21
+ keyboard
22
+ diff_helper
23
+ endpoint
24
+ parallelism
25
+ jekyll
26
+ nanoc
27
+ paths
28
+ }.each do |file|
29
+ require File.dirname(__FILE__) + "/s3_website/#{file}"
30
+ end
31
+
32
+ %w{invalidator}.each do |file|
33
+ require File.dirname(__FILE__) + "/cloudfront/#{file}"
34
+ end
@@ -0,0 +1 @@
1
+ addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.11.2")
File without changes
@@ -0,0 +1 @@
1
+ addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.6.0")
Binary file
@@ -0,0 +1,56 @@
1
+ s3_id: YOUR_AWS_S3_ACCESS_KEY_ID
2
+ s3_secret: YOUR_AWS_S3_SECRET_ACCESS_KEY
3
+ s3_bucket: your.blog.bucket.com
4
+
5
+ # Below are examples of all the available configurations.
6
+ # See README for more detailed info on each of them.
7
+
8
+ # max_age:
9
+ # "assets/*": 6000
10
+ # "*": 300
11
+
12
+ # gzip:
13
+ # - .html
14
+ # - .css
15
+ # - .md
16
+ # gzip_zopfli: true
17
+
18
+ # See http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region for valid endpoints
19
+ # s3_endpoint: ap-northeast-1
20
+
21
+ # ignore_on_server: that_folder_of_stuff_i_dont_keep_locally
22
+
23
+ # exclude_from_upload:
24
+ # - those_folders_of_stuff
25
+ # - i_wouldnt_want_to_upload
26
+
27
+ # s3_reduced_redundancy: true
28
+
29
+ # cloudfront_distribution_id: your-dist-id
30
+
31
+ # cloudfront_distribution_config:
32
+ # default_cache_behavior:
33
+ # min_TTL: <%= 60 * 60 * 24 %>
34
+ # aliases:
35
+ # quantity: 1
36
+ # items:
37
+ # CNAME: your.website.com
38
+
39
+ # cloudfront_invalidate_root: true
40
+
41
+ # concurrency_level: 5
42
+
43
+ # redirects:
44
+ # index.php: /
45
+ # about.php: about.html
46
+ # music-files/promo.mp4: http://www.youtube.com/watch?v=dQw4w9WgXcQ
47
+
48
+ # routing_rules:
49
+ # - condition:
50
+ # key_prefix_equals: blog/some_path
51
+ # redirect:
52
+ # host_name: blog.example.com
53
+ # replace_key_prefix_with: some_new_path/
54
+ # http_redirect_code: 301
55
+
56
+ # extensionless_mime_type: text/html
@@ -0,0 +1,41 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "s3_website_monadic"
6
+ s.version = "0.0.1"
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = ["Lauri Lehmijoki"]
9
+ s.email = ["lauri.lehmijoki@iki.fi"]
10
+ s.homepage = "https://github.com/laurilehmijoki/s3_website"
11
+ s.summary = %q{Manage your S3 website}
12
+ s.description = %q{
13
+ Sync website files, set redirects, use HTTP performance optimisations, deliver via
14
+ CloudFront.
15
+ }
16
+ s.license = 'MIT'
17
+
18
+ s.default_executable = %q{s3_website}
19
+
20
+ s.add_dependency 'aws-sdk', '~> 1'
21
+ s.add_dependency 'filey-diff', '~> 1.4.3'
22
+ s.add_dependency 'simple-cloudfront-invalidator', '~> 1'
23
+ s.add_dependency 'erubis', '~> 2.7.0'
24
+ s.add_dependency 'mime-types', '~> 1'
25
+ s.add_dependency 'thor', '= 0.18.1'
26
+ s.add_dependency 'configure-s3-website', '= 1.5.5'
27
+ s.add_dependency 'zopfli', '~> 0.0.3'
28
+
29
+ s.add_development_dependency 'rspec', '2.14.0'
30
+ s.add_development_dependency 'rspec-expectations', '2.14.4'
31
+ s.add_development_dependency 'cucumber', '1.3.10'
32
+ s.add_development_dependency 'aruba', '0.5.3'
33
+ s.add_development_dependency 'rake', '10.1.1'
34
+ s.add_development_dependency 'vcr', '2.8.0'
35
+ s.add_development_dependency 'webmock', '1.16.1'
36
+
37
+ s.files = `git ls-files`.split("\n")
38
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
39
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
40
+ s.require_paths = ["lib"]
41
+ end
data/sbt ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bash
2
+
3
+ SBT_OPTS="-Xms512M -Xmx1536M -Xss1M -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256M"
4
+ java $SBT_OPTS -jar `dirname $0`/project/sbt-launch-0.13.2.jar "$@"
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ describe S3Website::Cloudfront::Invalidator do
4
+ let(:config) {{
5
+ 's3_id' => 'aws id',
6
+ 's3_secret' => 'aws secret',
7
+ 'cloudfront_distribution_id' => 'EFXX'
8
+ }}
9
+
10
+ describe 'default behaviour' do
11
+ it 'invalidates the root resource' do
12
+ invalidator = create_simple_cloudfront_invalidator(config)
13
+ invalidator.
14
+ should_receive(:invalidate).
15
+ with(['index.html', '']).
16
+ and_return(:text_report => 'report txt')
17
+
18
+ S3Website::Cloudfront::Invalidator.invalidate(config, ['index.html'])
19
+ end
20
+ end
21
+
22
+ context 'option cloudfront_invalidate_root = true' do
23
+ let(:config_with_root_invalidation) {
24
+ config.merge( {
25
+ 'cloudfront_invalidate_root' => true
26
+ })
27
+ }
28
+
29
+ it 'invalidates all root resources' do
30
+ invalidator = create_simple_cloudfront_invalidator(config_with_root_invalidation)
31
+ invalidator.
32
+ should_receive(:invalidate).
33
+ with(['article/', '']).
34
+ and_return(:text_report => 'report txt')
35
+
36
+ S3Website::Cloudfront::Invalidator.invalidate(config_with_root_invalidation, ['article/index.html'])
37
+ end
38
+ end
39
+
40
+ context 'the file name contains special characters' do
41
+ it 'encodes the file paths according to rfc1738' do
42
+ invalidator = create_simple_cloudfront_invalidator config
43
+ invalidator.
44
+ should_receive(:invalidate).
45
+ with(['article/arnold%27s%20file.html', '']).
46
+ and_return(:text_report => 'report txt')
47
+
48
+ S3Website::Cloudfront::Invalidator.invalidate(config, ["article/arnold's file.html"])
49
+ end
50
+ end
51
+
52
+ def create_simple_cloudfront_invalidator(config)
53
+ invalidator = double('invalidator')
54
+ SimpleCloudfrontInvalidator::CloudfrontClient.
55
+ should_receive(:new).
56
+ with(config['s3_id'], config['s3_secret'], config['cloudfront_distribution_id']).
57
+ and_return(invalidator)
58
+ invalidator
59
+ end
60
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe S3Website::ConfigLoader do
4
+ it 'supports eRuby syntax in s3_website.yml' do
5
+ config = S3Website::ConfigLoader.load_configuration('spec/sample_files/hyde_site/')
6
+ config['s3_id'].should eq('hello')
7
+ config['s3_secret'].should eq('world')
8
+ config['s3_bucket'].should eq('galaxy')
9
+ end
10
+
11
+ it 'does not define default endpoint' do
12
+ config = S3Website::ConfigLoader.load_configuration('spec/sample_files/hyde_site/')
13
+ config['s3_endpoint'].should be_nil
14
+ end
15
+
16
+ it 'reads the S3 endpoint setting from s3_website.yml' do
17
+ config = S3Website::ConfigLoader.load_configuration('spec/sample_files/tokyo_site')
18
+ config['s3_endpoint'].should eq('ap-northeast-1')
19
+ end
20
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+ require 'pp'
3
+
4
+ describe S3Website::Endpoint do
5
+
6
+ it 'uses the DEFAULT_LOCATION_CONSTRAINT constant to set the default location constraint' do
7
+ endpoint = S3Website::Endpoint.new
8
+ endpoint.location_constraint.should eq(S3Website::Endpoint::DEFAULT_LOCATION_CONSTRAINT)
9
+ end
10
+
11
+ it 'uses the "us-east-1" as the default location' do
12
+ S3Website::Endpoint::DEFAULT_LOCATION_CONSTRAINT.should eq('us-east-1')
13
+ end
14
+
15
+ it 'takes a valid location constraint as a constructor parameter' do
16
+ endpoint = S3Website::Endpoint.new('EU')
17
+ endpoint.location_constraint.should eq('EU')
18
+ end
19
+
20
+ it 'takes eu-west-1 as an alias for EU' do
21
+ endpoint = S3Website::Endpoint.new('eu-west-1')
22
+ endpoint.location_constraint.should eq('eu-west-1')
23
+ S3Website::Endpoint.new.location_constraints['EU'].should eq(S3Website::Endpoint.new.location_constraints['eu-west-1'])
24
+ end
25
+
26
+ it 'fails if the location constraint is invalid' do
27
+ expect {
28
+ S3Website::Endpoint.new('andromeda')
29
+ }.to raise_error
30
+ end
31
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'error reporting' do
4
+ it 'prints the class name of the error' do
5
+ S3Website::error_report(SocketError.new('network is down')).should include(
6
+ 'SocketError'
7
+ )
8
+ end
9
+
10
+ it 'prints the message of the error' do
11
+ S3Website::error_report(SocketError.new('network is down')).should eq(
12
+ 'network is down (SocketError)'
13
+ )
14
+ end
15
+
16
+ it "only prints the message if the error is an #{S3Website::S3WebsiteError}" do
17
+ S3Website::error_report(S3Website::NoWebsiteDirectoryFound.new()).should eq(
18
+ "I can't find any website. Are you in the right directory?"
19
+ )
20
+ end
21
+ end
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+
3
+ describe S3Website::Keyboard do
4
+ describe '.keep_or_delete' do
5
+ let(:s3_object_keys) { ['a', 'b', 'c'] }
6
+ let(:standard_input) { stub('std_in') }
7
+
8
+ it 'can delete only the first item' do
9
+ standard_input.stub(:gets).and_return("d", "K")
10
+ deleted_keys = call_keyboard(s3_object_keys, standard_input)
11
+ deleted_keys.should eq(['a'])
12
+ end
13
+
14
+ it 'can delete only the second item' do
15
+ standard_input.stub(:gets).and_return("k", "d", "k")
16
+ deleted_keys = call_keyboard(s3_object_keys, standard_input)
17
+ deleted_keys.should eq(['b'])
18
+ end
19
+
20
+ it 'can delete all but the first item' do
21
+ standard_input.stub(:gets).and_return("k", "D")
22
+ deleted_keys = call_keyboard(s3_object_keys, standard_input)
23
+ deleted_keys.should eq(['b', 'c'])
24
+ end
25
+
26
+ it 'can delete all s3 objects' do
27
+ standard_input.stub(:gets).and_return("D")
28
+ deleted_keys = call_keyboard(s3_object_keys, standard_input)
29
+ deleted_keys.should eq(['a', 'b', 'c'])
30
+ end
31
+
32
+ it 'can keep one s3 object' do
33
+ standard_input.stub(:gets).and_return("k", "d", "d")
34
+ deleted_keys = call_keyboard(s3_object_keys, standard_input)
35
+ deleted_keys.should eq(['b', 'c'])
36
+ end
37
+
38
+ it 'can keep all s3 objects' do
39
+ standard_input.stub(:gets).and_return("k", "k", "k")
40
+ deleted_keys = call_keyboard(s3_object_keys, standard_input)
41
+ deleted_keys.should eq([])
42
+ end
43
+
44
+ it 'can keep all s3 objects' do
45
+ standard_input.stub(:gets).and_return("K")
46
+ deleted_keys = call_keyboard(s3_object_keys, standard_input)
47
+ deleted_keys.should eq([])
48
+ end
49
+
50
+ def call_keyboard(s3_object_keys, standard_input)
51
+ deleted_keys = []
52
+ S3Website::Keyboard.if_user_confirms_delete(
53
+ s3_object_keys,
54
+ config = {},
55
+ standard_input
56
+ ) { |key|
57
+ deleted_keys << key
58
+ }
59
+ deleted_keys
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,81 @@
1
+ require 'spec_helper'
2
+
3
+ describe S3Website::Parallelism do
4
+ context 'user has disabled parallelism' do
5
+ let(:config) {
6
+ {}
7
+ }
8
+
9
+ before(:all) {
10
+ @original_disable_state = ENV['disable_parallel_processing']
11
+ ENV['disable_parallel_processing'] = 'true'
12
+ }
13
+
14
+ after(:all) {
15
+ ENV['disable_parallel_processing'] = @original_disable_state
16
+ }
17
+
18
+ it 'runs things sequentially' do
19
+ ints = (0..100).to_a
20
+ after_processing = []
21
+ S3Website::Parallelism.each_in_parallel_or_sequentially(ints, config) { |int|
22
+ after_processing << int
23
+ }
24
+ ints.should eq(after_processing)
25
+ end
26
+ end
27
+
28
+ context 'user has not disabled parallelism' do
29
+ let(:config) {
30
+ {}
31
+ }
32
+
33
+ before(:all) {
34
+ @original_disable_state = ENV['disable_parallel_processing']
35
+ ENV.delete 'disable_parallel_processing'
36
+ }
37
+
38
+ after(:all) {
39
+ ENV['disable_parallel_processing'] = @original_disable_state if @original_disable_state
40
+ }
41
+
42
+ it 'runs things in parallel' do
43
+ ints = (0..100).to_a
44
+ after_processing = []
45
+ S3Website::Parallelism.each_in_parallel_or_sequentially(ints, config) { |int|
46
+ after_processing << int
47
+ }
48
+ ints.should_not eq(after_processing) # Parallel processing introduces non-determinism
49
+ end
50
+ end
51
+
52
+ context 'limiting parallelism' do
53
+ shared_examples 'parallel processing' do |config|
54
+ let(:concurrency_level) {
55
+ config['concurrency_level'] || S3Website::Parallelism::DEFAULT_CONCURRENCY_LEVEL
56
+ }
57
+
58
+ before(:each) {
59
+ ints = (0..199).to_a
60
+ @after_processing = []
61
+ S3Website::Parallelism.each_in_parallel_or_sequentially(ints, config) { |int|
62
+ @after_processing << int
63
+ }
64
+ }
65
+
66
+ it "does at most <concurrency_level> operations in parallel" do
67
+ @after_processing.slice(0, concurrency_level).all? do |int|
68
+ int <= concurrency_level
69
+ end.should be true
70
+ @after_processing.slice(100, concurrency_level).all? do |int|
71
+ int >= 100 and int <= 100 + concurrency_level
72
+ end.should be true
73
+ end
74
+ end
75
+
76
+
77
+ include_examples 'parallel processing', config = {}
78
+
79
+ include_examples 'parallel processing', config = { 'concurrency_level' => 100 }
80
+ end
81
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe S3Website::Paths do
4
+ it 'recognises Jekyll and Nanoc site paths' do
5
+ S3Website::Paths.site_paths.should eq(['public/output', '_site'])
6
+ end
7
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ describe S3Website::Retry do
4
+ describe ".run_with_retry" do
5
+ it "retry the operation 4 times" do
6
+ retries = 0
7
+ begin
8
+ S3Website::Retry.run_with_retry(0.001) {
9
+ retries += 1
10
+ raise Exception
11
+ }
12
+ rescue
13
+ end
14
+ retries.should be(4)
15
+ end
16
+
17
+ it "throws an error if all retries fail" do
18
+ expect {
19
+ S3Website::Retry.run_with_retry(0.001) {
20
+ raise Exception
21
+ }
22
+ }.to raise_error(S3Website::RetryAttemptsExhaustedError)
23
+ end
24
+
25
+ it "re-runs the block if the block throws an error" do
26
+ retries = 0
27
+ S3Website::Retry.run_with_retry(0.001) {
28
+ retries += 1
29
+ raise Exception if retries < 2
30
+ }
31
+ retries.should be(2)
32
+ end
33
+ end
34
+ end