s3_website_monadic 0.0.14 → 0.0.15

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1e02998ffcdd53134cc43365b7d958d6e5ac84d8
4
- data.tar.gz: 06f5c5be34c990d4d8cef7fc03ed6993680a833c
3
+ metadata.gz: 68d01d8499eba5c41c9b42d0d6ead9ea5386590a
4
+ data.tar.gz: 5a6381dfa127b1f824c1ded97efe0cb390b179ec
5
5
  SHA512:
6
- metadata.gz: 8fd0bfa68863f8c6ac38bf2292deed7e6096e92625905dbf03e92af8c377e120d434e17c657361215ca692b6ba7c67d8efdf4a140b31ce8ba483916cfb5f00d0
7
- data.tar.gz: 0df2a5712cf8fcbb144f586158fe7a6560566907b1864fe6d604c4de2775c43215eb460702bcebffbcc579d283187d450e3c55c317d73472924fb6afcc6f9a4e
6
+ metadata.gz: 1e35433d2fb1ad9ce6f737dcfaba2e0b49ca02ab57ae1092e7370ede5e5b50292e19b50236dff44cf5d8ceb76460ccb25a5c1fd15b78e886954616f037ebdafc
7
+ data.tar.gz: 9bcc6e4226a57017089ddd4e65918a53eee9f3fd3bf3b2c7a2d93542de249fdb4e0c51b762875d2615168721cbcbb847aac31126d23605afe8e46ee7bedca72d
data/s3_website.gemspec CHANGED
@@ -3,7 +3,7 @@ $:.push File.expand_path("../lib", __FILE__)
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "s3_website_monadic"
6
- s.version = "0.0.14"
6
+ s.version = "0.0.15"
7
7
  s.platform = Gem::Platform::RUBY
8
8
  s.authors = ["Lauri Lehmijoki"]
9
9
  s.email = ["lauri.lehmijoki@iki.fi"]
@@ -61,7 +61,7 @@ object CloudFront {
61
61
  type CloudFrontClientProvider = (Config) => AmazonCloudFront
62
62
 
63
63
  case class SuccessfulInvalidation(invalidatedItemsCount: Int) extends SuccessReport {
64
- def reportMessage = s"Invalidated ${countToString(invalidatedItemsCount, "item")} on CloudFront"
64
+ def reportMessage = s"Invalidated ${invalidatedItemsCount ofType "item"} on CloudFront"
65
65
  }
66
66
 
67
67
  case class FailedInvalidation(error: Throwable) extends FailureReport {
@@ -71,18 +71,36 @@ object CloudFront {
71
71
  def awsCloudFrontClient(config: Config) =
72
72
  new AmazonCloudFrontClient(new BasicAWSCredentials(config.s3_id, config.s3_secret))
73
73
 
74
- def toInvalidationBatches(pushSuccessReports: Seq[PushSuccessReport])(implicit config: Config): Seq[InvalidationBatch] =
75
- pushSuccessReports
76
- .filter(needsInvalidation) // Assume that redirect objects are never cached.
77
- .map(toInvalidationPath)
78
- .map (applyInvalidateRootSetting)
74
+ def toInvalidationBatches(pushSuccessReports: Seq[PushSuccessReport])(implicit config: Config): Seq[InvalidationBatch] = {
75
+ val invalidationPaths: Seq[String] = {
76
+ def withDefaultPathIfNeeded(paths: Seq[String]) = {
77
+ // This is how we support the Default Root Object @ CloudFront (http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DefaultRootObject.html)
78
+ // We do this more accurately by fetching the distribution config (http://docs.aws.amazon.com/AmazonCloudFront/latest/APIReference/GetConfig.html)
79
+ // and reading the Default Root Object from there.
80
+ val containsPotentialDefaultRootObject = paths
81
+ .exists(
82
+ _
83
+ .replaceFirst("^/", "") // S3 keys do not begin with a slash
84
+ .contains("/") == false // See if the S3 key is a top-level key (i.e., it is not within a directory)
85
+ )
86
+ if (containsPotentialDefaultRootObject) paths :+ "/" else paths
87
+ }
88
+ val paths = pushSuccessReports
89
+ .filter(needsInvalidation) // Assume that redirect objects are never cached.
90
+ .map(toInvalidationPath)
91
+ .map (applyInvalidateRootSetting)
92
+ withDefaultPathIfNeeded(paths)
93
+ }
94
+
95
+ invalidationPaths
79
96
  .grouped(1000) // CloudFront supports max 1000 invalidations in one request (http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Invalidation.html#InvalidationLimits)
80
97
  .map { batchKeys =>
81
98
  new InvalidationBatch() withPaths
82
99
  (new Paths() withItems batchKeys withQuantity batchKeys.size) withCallerReference
83
- s"s3_website gem ${System.currentTimeMillis()}"
100
+ s"s3_website gem ${System.currentTimeMillis()}"
84
101
  }
85
102
  .toSeq
103
+ }
86
104
 
87
105
  def applyInvalidateRootSetting(path: String)(implicit config: Config) =
88
106
  if (config.cloudfront_invalidate_root.exists(_ == true))
@@ -156,11 +156,11 @@ object Push {
156
156
  "There was nothing to push."
157
157
  case PushCounts(updates, newFiles, failures, redirects, deletes) =>
158
158
  val reportClauses: scala.collection.mutable.ArrayBuffer[String] = ArrayBuffer()
159
- if (updates > 0) reportClauses += s"Updated ${countToString(updates, "file")}."
160
- if (newFiles > 0) reportClauses += s"Created ${countToString(newFiles, "file")}."
161
- if (failures > 0) reportClauses += s"${countToString(failures, "operation")} failed." // This includes both failed uploads and deletes.
162
- if (redirects > 0) reportClauses += s"Applied ${countToString(redirects, "redirect")}."
163
- if (deletes > 0) reportClauses += s"Deleted ${countToString(deletes, "file")}."
159
+ if (updates > 0) reportClauses += s"Updated ${updates ofType "file"}."
160
+ if (newFiles > 0) reportClauses += s"Created ${newFiles ofType "file"}."
161
+ if (failures > 0) reportClauses += s"${failures ofType "operation"} failed." // This includes both failed uploads and deletes.
162
+ if (redirects > 0) reportClauses += s"Applied ${redirects ofType "redirect"}."
163
+ if (deletes > 0) reportClauses += s"Deleted ${deletes ofType "file"}."
164
164
  reportClauses.mkString(" ")
165
165
  }
166
166
 
@@ -51,8 +51,12 @@ package object website {
51
51
  httpStatusCode.exists(c => c >= 400 && c < 500)
52
52
  }
53
53
 
54
- def countToString(count: Int, singular: String) = {
55
- def plural = s"${singular}s"
56
- s"$count ${if (count > 1) plural else singular}"
54
+ implicit class NumReport(val num: Int) extends AnyVal {
55
+ def ofType(itemType: String) = countToString(num, itemType)
56
+
57
+ private def countToString(count: Int, singular: String) = {
58
+ def plural = s"${singular}s"
59
+ s"$count ${if (count > 1) plural else singular}"
60
+ }
57
61
  }
58
62
  }
@@ -136,11 +136,11 @@ class S3WebsiteSpec extends Specification {
136
136
  "invalidate the updated CloudFront items" in new SiteDirectory with MockAWS {
137
137
  implicit val site = siteWithFiles(
138
138
  config = defaultConfig.copy(cloudfront_distribution_id = Some("EGM1J2JJX9Z")),
139
- localFiles = "test.css" :: "articles/index.html" :: Nil
139
+ localFiles = "css/test.css" :: "articles/index.html" :: Nil
140
140
  )
141
- setOutdatedS3Keys("test.css", "articles/index.html")
141
+ setOutdatedS3Keys("css/test.css", "articles/index.html")
142
142
  Push.pushSite
143
- sentInvalidationRequest.getInvalidationBatch.getPaths.getItems.toSeq.sorted must equalTo(("/test.css" :: "/articles/index.html" :: Nil).sorted)
143
+ sentInvalidationRequest.getInvalidationBatch.getPaths.getItems.toSeq.sorted must equalTo(("/css/test.css" :: "/articles/index.html" :: Nil).sorted)
144
144
  }
145
145
 
146
146
  "not send CloudFront invalidation requests on new objects" in new SiteDirectory with MockAWS {
@@ -191,23 +191,37 @@ class S3WebsiteSpec extends Specification {
191
191
  Push.pushSite
192
192
  sentInvalidationRequest.getInvalidationBatch.getPaths.getItems.toSeq.sorted must equalTo(("/articles/arnold's%20file.html" :: Nil).sorted)
193
193
  }
194
+
195
+ /*
196
+ * Because CloudFront supports Default Root Objects (http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DefaultRootObject.html),
197
+ * we have to guess
198
+ */
199
+ "invalidate the root object '/' if a top-level object is updated or deleted" in new SiteDirectory with MockAWS {
200
+ implicit val site = siteWithFiles(
201
+ config = defaultConfig.copy(cloudfront_distribution_id = Some("EGM1J2JJX9Z")),
202
+ localFiles = "maybe-index.html" :: Nil
203
+ )
204
+ setOutdatedS3Keys("maybe-index.html")
205
+ Push.pushSite
206
+ sentInvalidationRequest.getInvalidationBatch.getPaths.getItems.toSeq.sorted must equalTo(("/" :: "/maybe-index.html" :: Nil).sorted)
207
+ }
194
208
  }
195
209
 
196
210
  "cloudfront_invalidate_root: true" should {
197
211
  "convert CloudFront invalidation paths with the '/index.html' suffix into '/'" in new SiteDirectory with MockAWS {
198
212
  implicit val site = siteWithFiles(
199
213
  config = defaultConfig.copy(cloudfront_distribution_id = Some("EGM1J2JJX9Z"), cloudfront_invalidate_root = Some(true)),
200
- localFiles = "index.html" :: "articles/index.html" :: Nil
214
+ localFiles = "articles/index.html" :: Nil
201
215
  )
202
- setOutdatedS3Keys("index.html", "articles/index.html")
216
+ setOutdatedS3Keys("articles/index.html")
203
217
  Push.pushSite
204
- sentInvalidationRequest.getInvalidationBatch.getPaths.getItems.toSeq.sorted must equalTo(("/" :: "/articles/" :: Nil).sorted)
218
+ sentInvalidationRequest.getInvalidationBatch.getPaths.getItems.toSeq.sorted must equalTo(("/articles/" :: Nil).sorted)
205
219
  }
206
220
  }
207
221
 
208
222
  "a site with over 1000 items" should {
209
223
  "split the CloudFront invalidation requests into batches of 1000 items" in new SiteDirectory with MockAWS {
210
- val files = (1 to 1002).map { i => s"file-$i"}
224
+ val files = (1 to 1002).map { i => s"lots-of-files/file-$i"}
211
225
  implicit val site = siteWithFiles(
212
226
  config = defaultConfig.copy(cloudfront_distribution_id = Some("EGM1J2JJX9Z")),
213
227
  localFiles = files
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: s3_website_monadic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.14
4
+ version: 0.0.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lauri Lehmijoki
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-08 00:00:00.000000000 Z
11
+ date: 2014-05-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk