s3_website 2.6.1 → 2.7.0
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 +4 -4
- data/README.md +14 -0
- data/changelog.md +10 -0
- data/lib/s3_website/version.rb +1 -1
- data/resources/s3_website.jar.md5 +1 -1
- data/src/main/scala/s3/website/Push.scala +34 -20
- data/src/main/scala/s3/website/model/Config.scala +2 -1
- data/src/main/scala/s3/website/model/Site.scala +3 -1
- data/src/main/scala/s3/website/model/push.scala +33 -7
- data/src/test/scala/s3/website/S3WebsiteSpec.scala +85 -30
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 778dcd186e3206eaf5e03252a3926747dd8d7ba1
|
4
|
+
data.tar.gz: 74dd5fbc8caa9f73115e9fcba347301cf99eeb9d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1cd34d83b87621b4cb05204c651a6af6c1fda88e2d7c0edfbfafa9d782217099650d7a3309d48c76b960328c0406f97df530861840a1e57bc10c859b17de5c23
|
7
|
+
data.tar.gz: 0f9260d64f41f661334de67f665b817245d15938abb3582443ab3f2b13915f17927a101c2c1581586196cd47203ef1e9147e54d5ef1fcf9e0ca9161268c4dbbd
|
data/README.md
CHANGED
@@ -313,6 +313,20 @@ For more information on configuring redirects, see the documentation of the
|
|
313
313
|
gem, which comes as a transitive dependency of the `s3_website` gem. (The
|
314
314
|
command `s3_website cfg apply` internally calls the `configure-s3-website` gem.)
|
315
315
|
|
316
|
+
#### On skipping application of redirects
|
317
|
+
|
318
|
+
If your website has a lot of redirects, you may find the following setting
|
319
|
+
helpful:
|
320
|
+
|
321
|
+
treat_zero_length_objects_as_redirects: true
|
322
|
+
|
323
|
+
The setting allows `s3_website push` to infer whether a redirect exists or not.
|
324
|
+
You will experience faster `push` performance when this setting is `true`.
|
325
|
+
However, if this setting is enabled and you modify the `redirects` setting in
|
326
|
+
*s3_website.yml*, use `push --force` to apply the modified values.
|
327
|
+
|
328
|
+
For backward-compatibility reasons, this setting is `false` by default.
|
329
|
+
|
316
330
|
### Specifying custom concurrency level
|
317
331
|
|
318
332
|
By default, `s3_website` does 3 operations in parallel. An operation can be an
|
data/changelog.md
CHANGED
@@ -2,6 +2,16 @@
|
|
2
2
|
|
3
3
|
This project uses [Semantic Versioning](http://semver.org).
|
4
4
|
|
5
|
+
## 2.7.0
|
6
|
+
|
7
|
+
* Add setting `treat_zero_length_objects_as_redirects`
|
8
|
+
|
9
|
+
Before, `s3_website push` always uploaded one zero-length object for each
|
10
|
+
configured redirect. From now on, you can instruct s3\_website to treat
|
11
|
+
zero-length S3 objects as existing redirects. If you have a large amount of
|
12
|
+
redirects on your site, you may find that this feature decreases the duration
|
13
|
+
of `s3_website push`.
|
14
|
+
|
5
15
|
## 2.6.1
|
6
16
|
|
7
17
|
* Always invalidate the object */index.html* if the setting `cloudfront_invalidate_root` is on
|
data/lib/s3_website/version.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
ddc9ee41507905b63f16ab01634e9e5a
|
@@ -1,11 +1,12 @@
|
|
1
1
|
package s3.website
|
2
2
|
|
3
3
|
import s3.website.model.Config.S3_website_yml
|
4
|
+
import s3.website.model.Redirect.{Redirects, resolveRedirects}
|
4
5
|
import s3.website.model.Site._
|
5
6
|
import scala.concurrent.{ExecutionContextExecutor, Future, Await}
|
6
7
|
import scala.concurrent.duration._
|
7
8
|
import scala.language.postfixOps
|
8
|
-
import s3.website.UploadHelper.{resolveDeletes, resolveUploads}
|
9
|
+
import s3.website.UploadHelper.{FutureUploads, resolveDeletes, resolveUploads}
|
9
10
|
import s3.website.S3._
|
10
11
|
import scala.concurrent.ExecutionContext.fromExecutor
|
11
12
|
import java.util.concurrent.Executors.newFixedThreadPool
|
@@ -80,29 +81,42 @@ object Push {
|
|
80
81
|
pushOptions: PushOptions
|
81
82
|
): ExitCode = {
|
82
83
|
logger.info(s"${Deploy.renderVerb} ${site.rootDirectory}/* to ${site.config.s3_bucket}")
|
83
|
-
val redirects = Redirect.resolveRedirects
|
84
84
|
val s3FilesFuture = resolveS3Files()
|
85
|
-
val
|
86
|
-
|
87
|
-
|
88
|
-
|
85
|
+
val redirectsFuture: Redirects = resolveRedirects(s3FilesFuture)
|
86
|
+
val redirectReports: Future[Either[ErrorReport, Seq[Future[PushErrorOrSuccess]]]] =
|
87
|
+
redirectsFuture.map { (errOrRedirects: Either[ErrorReport, Seq[Redirect]]) =>
|
88
|
+
errOrRedirects.right.map(_.filter(_.needsUpload).map(S3 uploadRedirect _))
|
89
|
+
}
|
90
|
+
|
91
|
+
val uploadFutures: FutureUploads = resolveUploads(s3FilesFuture)
|
92
|
+
val uploadReports: Future[Either[ErrorReport, Seq[Future[PushErrorOrSuccess]]]] = for {
|
93
|
+
errorOrUploads: Either[ErrorReport, Seq[Upload]] <- uploadFutures
|
94
|
+
} yield errorOrUploads.right.map(_.map(S3 uploadFile _))
|
95
|
+
|
96
|
+
val deleteReports: Future[Either[ErrorReport, Seq[Future[PushErrorOrSuccess]]]] = for {
|
97
|
+
errorOrUploads: Either[ErrorReport, Seq[Upload]] <- uploadFutures
|
89
98
|
} yield {
|
90
|
-
val
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
}
|
95
|
-
)
|
96
|
-
val deleteReports =
|
97
|
-
Await.result(resolveDeletes(s3FilesFuture, redirects), 1 day).right.map { keysToDelete =>
|
98
|
-
keysToDelete map (S3 delete _)
|
99
|
-
}.fold(
|
100
|
-
error => Left(error) :: Nil,
|
101
|
-
(pushResults: Seq[Future[PushErrorOrSuccess]]) => pushResults map (Right(_))
|
99
|
+
val errorsOrDeleteReports = redirectsFuture.flatMap { (errOrRedirects: Either[ErrorReport, Seq[Redirect]]) =>
|
100
|
+
errOrRedirects.fold(
|
101
|
+
err => Future(Left(err)),
|
102
|
+
redirects => resolveDeletes(s3FilesFuture, redirects)
|
102
103
|
)
|
103
|
-
|
104
|
+
}.map { (deletes: Either[ErrorReport, Seq[S3Key]]) =>
|
105
|
+
deletes.right.map(keysToDelete => keysToDelete.map(S3 delete _))
|
106
|
+
}
|
107
|
+
Await.result(errorsOrDeleteReports, 1 day)
|
108
|
+
}
|
109
|
+
val allReports = Future.sequence(redirectReports :: uploadReports :: deleteReports :: Nil) map { reports =>
|
110
|
+
reports.foldLeft(Nil: PushReports) { (memo, report: Either[ErrorReport, Seq[Future[PushErrorOrSuccess]]]) =>
|
111
|
+
report match {
|
112
|
+
case Left(err) =>
|
113
|
+
memo :+ Left(err)
|
114
|
+
case Right(pushResults: Seq[Future[PushErrorOrSuccess]]) =>
|
115
|
+
memo ++ pushResults.map(Right(_))
|
116
|
+
}
|
117
|
+
}
|
104
118
|
}
|
105
|
-
val finishedPushOps = awaitForResults(Await.result(
|
119
|
+
val finishedPushOps = awaitForResults(Await.result(allReports, 1 day))
|
106
120
|
val invalidationSucceeded = invalidateCloudFrontItems(finishedPushOps)
|
107
121
|
|
108
122
|
afterPushFinished(finishedPushOps, invalidationSucceeded)
|
@@ -23,7 +23,8 @@ case class Config(
|
|
23
23
|
cloudfront_distribution_id: Option[String],
|
24
24
|
cloudfront_invalidate_root: Option[Boolean],
|
25
25
|
redirects: Option[Map[String, String]],
|
26
|
-
concurrency_level: Int
|
26
|
+
concurrency_level: Int,
|
27
|
+
treat_zero_length_objects_as_redirects: Option[Boolean]
|
27
28
|
)
|
28
29
|
|
29
30
|
object Config {
|
@@ -50,6 +50,7 @@ object Site {
|
|
50
50
|
cloudfront_invalidate_root <- loadOptionalBoolean("cloudfront_invalidate_root").right
|
51
51
|
concurrency_level <- loadOptionalInt("concurrency_level").right
|
52
52
|
redirects <- loadRedirects.right
|
53
|
+
treat_zero_length_objects_as_redirects <- loadOptionalBoolean("treat_zero_length_objects_as_redirects").right
|
53
54
|
} yield {
|
54
55
|
gzip_zopfli.foreach(_ => logger.info(
|
55
56
|
"""|Zopfli is not currently supported. Falling back to regular gzip.
|
@@ -73,7 +74,8 @@ object Site {
|
|
73
74
|
cloudfront_distribution_id,
|
74
75
|
cloudfront_invalidate_root,
|
75
76
|
redirects,
|
76
|
-
concurrency_level.fold(20)(_ max 20)
|
77
|
+
concurrency_level.fold(20)(_ max 20),
|
78
|
+
treat_zero_length_objects_as_redirects
|
77
79
|
)
|
78
80
|
}
|
79
81
|
case Failure(error) =>
|
@@ -11,6 +11,7 @@ import s3.website.model.Upload.tika
|
|
11
11
|
import s3.website.model.Encoding.encodingOnS3
|
12
12
|
import java.io.File.createTempFile
|
13
13
|
import org.apache.commons.io.IOUtils.copy
|
14
|
+
import scala.concurrent.{ExecutionContextExecutor, Future}
|
14
15
|
import scala.util.Try
|
15
16
|
|
16
17
|
object Encoding {
|
@@ -148,17 +149,39 @@ object Files {
|
|
148
149
|
}
|
149
150
|
}
|
150
151
|
|
151
|
-
case class Redirect(s3Key: String, redirectTarget: String) {
|
152
|
+
case class Redirect(s3Key: String, redirectTarget: String, needsUpload: Boolean) {
|
152
153
|
def uploadType = RedirectFile
|
153
154
|
}
|
154
155
|
|
156
|
+
private case class RedirectSetting(source: String, target: String)
|
157
|
+
|
155
158
|
object Redirect {
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
+
type Redirects = Future[Either[ErrorReport, Seq[Redirect]]]
|
160
|
+
|
161
|
+
def resolveRedirects(s3FileFutures: Future[Either[ErrorReport, Seq[S3File]]])
|
162
|
+
(implicit config: Config, executor: ExecutionContextExecutor, pushOptions: PushOptions): Redirects = {
|
163
|
+
val redirectSettings = config.redirects.fold(Nil: Seq[RedirectSetting]) { sourcesToTargets =>
|
164
|
+
sourcesToTargets.foldLeft(Seq(): Seq[RedirectSetting]) {
|
159
165
|
(redirects, sourceToTarget) =>
|
160
|
-
redirects :+
|
166
|
+
redirects :+ RedirectSetting(sourceToTarget._1, applySlashIfNeeded(sourceToTarget._2))
|
161
167
|
}
|
168
|
+
}
|
169
|
+
def redirectsWithExistsOnS3Info =
|
170
|
+
s3FileFutures.map(_.right.map { s3Files =>
|
171
|
+
val existingRedirectKeys = s3Files.filter(_.size == 0).map(_.s3Key).toSet
|
172
|
+
redirectSettings.map(redirectSetting =>
|
173
|
+
Redirect(redirectSetting, needsUpload = !existingRedirectKeys.contains(redirectSetting.source))
|
174
|
+
)
|
175
|
+
})
|
176
|
+
val uploadOnlyMissingRedirects =
|
177
|
+
config.treat_zero_length_objects_as_redirects.contains(true) && !pushOptions.force
|
178
|
+
val allConfiguredRedirects = Future(Right(redirectSettings.map(redirectSetting =>
|
179
|
+
Redirect(redirectSetting, needsUpload = true)
|
180
|
+
)))
|
181
|
+
if (uploadOnlyMissingRedirects)
|
182
|
+
redirectsWithExistsOnS3Info
|
183
|
+
else
|
184
|
+
allConfiguredRedirects
|
162
185
|
}
|
163
186
|
|
164
187
|
private def applySlashIfNeeded(redirectTarget: String) = {
|
@@ -169,10 +192,13 @@ object Redirect {
|
|
169
192
|
else
|
170
193
|
"/" + redirectTarget // let the user have redirect settings like "index.php: index.html" in s3_website.ml
|
171
194
|
}
|
195
|
+
|
196
|
+
def apply(redirectSetting: RedirectSetting, needsUpload: Boolean): Redirect =
|
197
|
+
Redirect(redirectSetting.source, redirectSetting.target, needsUpload)
|
172
198
|
}
|
173
199
|
|
174
|
-
case class S3File(s3Key: String, md5: MD5)
|
200
|
+
case class S3File(s3Key: String, md5: MD5, size: Long)
|
175
201
|
|
176
202
|
object S3File {
|
177
|
-
def apply(summary: S3ObjectSummary): S3File = S3File(summary.getKey, summary.getETag)
|
203
|
+
def apply(summary: S3ObjectSummary): S3File = S3File(summary.getKey, summary.getETag, summary.getSize)
|
178
204
|
}
|
@@ -35,7 +35,7 @@ class S3WebsiteSpec extends Specification {
|
|
35
35
|
"update a gzipped S3 object if the contents has changed" in new BasicSetup {
|
36
36
|
config = "gzip: true"
|
37
37
|
setLocalFileWithContent(("styles.css", "<h1>hi again</h1>"))
|
38
|
-
|
38
|
+
setS3File("styles.css", "1c5117e5839ad8fc00ce3c41296255a1" /* md5 of the gzip of the file contents */)
|
39
39
|
push
|
40
40
|
sentPutObjectRequest.getKey must equalTo("styles.css")
|
41
41
|
}
|
@@ -43,7 +43,7 @@ class S3WebsiteSpec extends Specification {
|
|
43
43
|
"not update a gzipped S3 object if the contents has not changed" in new BasicSetup {
|
44
44
|
config = "gzip: true"
|
45
45
|
setLocalFileWithContent(("styles.css", "<h1>hi</h1>"))
|
46
|
-
|
46
|
+
setS3File("styles.css", "1c5117e5839ad8fc00ce3c41296255a1" /* md5 of the gzip of the file contents */)
|
47
47
|
push
|
48
48
|
noUploadsOccurred must beTrue
|
49
49
|
}
|
@@ -59,7 +59,7 @@ class S3WebsiteSpec extends Specification {
|
|
59
59
|
| - .xml
|
60
60
|
""".stripMargin
|
61
61
|
setLocalFileWithContent(("file.xml", "<h1>hi again</h1>"))
|
62
|
-
|
62
|
+
setS3File("file.xml", "1c5117e5839ad8fc00ce3c41296255a1" /* md5 of the gzip of the file contents */)
|
63
63
|
push
|
64
64
|
sentPutObjectRequest.getKey must equalTo("file.xml")
|
65
65
|
}
|
@@ -68,14 +68,14 @@ class S3WebsiteSpec extends Specification {
|
|
68
68
|
"push" should {
|
69
69
|
"not upload a file if it has not changed" in new BasicSetup {
|
70
70
|
setLocalFileWithContent(("index.html", "<div>hello</div>"))
|
71
|
-
|
71
|
+
setS3File("index.html", md5Hex("<div>hello</div>"))
|
72
72
|
push
|
73
73
|
noUploadsOccurred must beTrue
|
74
74
|
}
|
75
75
|
|
76
76
|
"update a file if it has changed" in new BasicSetup {
|
77
77
|
setLocalFileWithContent(("index.html", "<h1>old text</h1>"))
|
78
|
-
|
78
|
+
setS3File("index.html", md5Hex("<h1>new text</h1>"))
|
79
79
|
push
|
80
80
|
sentPutObjectRequest.getKey must equalTo("index.html")
|
81
81
|
}
|
@@ -87,7 +87,7 @@ class S3WebsiteSpec extends Specification {
|
|
87
87
|
}
|
88
88
|
|
89
89
|
"delete files that are on S3 but not on local file system" in new BasicSetup {
|
90
|
-
|
90
|
+
setS3File("old.html", md5Hex("<h1>old text</h1>"))
|
91
91
|
push
|
92
92
|
sentDelete must equalTo("old.html")
|
93
93
|
}
|
@@ -131,14 +131,14 @@ class S3WebsiteSpec extends Specification {
|
|
131
131
|
}
|
132
132
|
|
133
133
|
"try again if the delete fails" in new BasicSetup {
|
134
|
-
|
134
|
+
setS3File("old.html", md5Hex("<h1>old text</h1>"))
|
135
135
|
deleteFailsAndThenSucceeds(howManyFailures = 5)
|
136
136
|
push
|
137
137
|
verify(amazonS3Client, times(6)).deleteObject(Matchers.anyString(), Matchers.anyString())
|
138
138
|
}
|
139
139
|
|
140
140
|
"try again if the object listing fails" in new BasicSetup {
|
141
|
-
|
141
|
+
setS3File("old.html", md5Hex("<h1>old text</h1>"))
|
142
142
|
objectListingFailsAndThenSucceeds(howManyFailures = 5)
|
143
143
|
push
|
144
144
|
verify(amazonS3Client, times(6)).listObjects(Matchers.any(classOf[ListObjectsRequest]))
|
@@ -292,7 +292,7 @@ class S3WebsiteSpec extends Specification {
|
|
292
292
|
}
|
293
293
|
|
294
294
|
"be 1 if an object listing fails" in new BasicSetup {
|
295
|
-
|
295
|
+
setS3File("old.html", md5Hex("<h1>old text</h1>"))
|
296
296
|
objectListingFailsAndThenSucceeds(howManyFailures = 6)
|
297
297
|
push must equalTo(1)
|
298
298
|
}
|
@@ -343,13 +343,13 @@ class S3WebsiteSpec extends Specification {
|
|
343
343
|
"ignore_on_server: value" should {
|
344
344
|
"not delete the S3 objects that match the ignore value" in new BasicSetup {
|
345
345
|
config = "ignore_on_server: logs"
|
346
|
-
|
346
|
+
setS3File("logs/log.txt")
|
347
347
|
push
|
348
348
|
noDeletesOccurred must beTrue
|
349
349
|
}
|
350
350
|
|
351
351
|
"support non-US-ASCII files" in new BasicSetup {
|
352
|
-
|
352
|
+
setS3File("tags/笔记/test.html", "")
|
353
353
|
config = "ignore_on_server: tags/笔记/test.html"
|
354
354
|
push
|
355
355
|
noDeletesOccurred must beTrue
|
@@ -361,7 +361,7 @@ class S3WebsiteSpec extends Specification {
|
|
361
361
|
config = s"""
|
362
362
|
|ignore_on_server: $DELETE_NOTHING_MAGIC_WORD
|
363
363
|
""".stripMargin
|
364
|
-
|
364
|
+
setS3File("file.txt")
|
365
365
|
push
|
366
366
|
noDeletesOccurred
|
367
367
|
}
|
@@ -377,13 +377,13 @@ class S3WebsiteSpec extends Specification {
|
|
377
377
|
|ignore_on_server:
|
378
378
|
| - .*txt
|
379
379
|
""".stripMargin
|
380
|
-
|
380
|
+
setS3File("logs/log.txt", "")
|
381
381
|
push
|
382
382
|
noDeletesOccurred must beTrue
|
383
383
|
}
|
384
384
|
|
385
385
|
"support non-US-ASCII files" in new BasicSetup {
|
386
|
-
|
386
|
+
setS3File("tags/笔记/test.html", "")
|
387
387
|
config = """
|
388
388
|
|ignore_on_server:
|
389
389
|
| - tags/笔记/test.html
|
@@ -570,7 +570,7 @@ class S3WebsiteSpec extends Specification {
|
|
570
570
|
| index.php: /index.html
|
571
571
|
""".stripMargin
|
572
572
|
setLocalFile("index.php")
|
573
|
-
|
573
|
+
setS3File("index.php", "md5")
|
574
574
|
push
|
575
575
|
noDeletesOccurred must beTrue
|
576
576
|
}
|
@@ -625,7 +625,7 @@ class S3WebsiteSpec extends Specification {
|
|
625
625
|
"push --force" should {
|
626
626
|
"push all the files whether they have changed or not" in new ForcePush {
|
627
627
|
setLocalFileWithContent(("index.html", "<h1>hi</h1>"))
|
628
|
-
|
628
|
+
setS3File("index.html", "1c5117e5839ad8fc00ce3c41296255a1" /* md5 of the gzip of the file contents */)
|
629
629
|
push
|
630
630
|
sentPutObjectRequest.getKey must equalTo("index.html")
|
631
631
|
}
|
@@ -634,7 +634,7 @@ class S3WebsiteSpec extends Specification {
|
|
634
634
|
"dry run" should {
|
635
635
|
"not push updates" in new DryRun {
|
636
636
|
setLocalFileWithContent(("index.html", "<div>new</div>"))
|
637
|
-
|
637
|
+
setS3File("index.html", md5Hex("<div>old</div>"))
|
638
638
|
push
|
639
639
|
noUploadsOccurred must beTrue
|
640
640
|
}
|
@@ -650,7 +650,7 @@ class S3WebsiteSpec extends Specification {
|
|
650
650
|
}
|
651
651
|
|
652
652
|
"not push deletes" in new DryRun {
|
653
|
-
|
653
|
+
setS3File("index.html", md5Hex("<div>old</div>"))
|
654
654
|
push
|
655
655
|
noUploadsOccurred must beTrue
|
656
656
|
}
|
@@ -663,7 +663,7 @@ class S3WebsiteSpec extends Specification {
|
|
663
663
|
|
664
664
|
"not invalidate files" in new DryRun {
|
665
665
|
config = "cloudfront_invalidation_id: AABBCC"
|
666
|
-
|
666
|
+
setS3File("index.html", md5Hex("<div>old</div>"))
|
667
667
|
push
|
668
668
|
noInvalidationsOccurred must beTrue
|
669
669
|
}
|
@@ -684,6 +684,55 @@ class S3WebsiteSpec extends Specification {
|
|
684
684
|
sentPutObjectRequests.length must equalTo(1)
|
685
685
|
}
|
686
686
|
}
|
687
|
+
|
688
|
+
"the setting treat_zero_length_objects_as_redirects" should {
|
689
|
+
"skip application of redirect on a zero-length S3 object" in new BasicSetup {
|
690
|
+
config =
|
691
|
+
"""
|
692
|
+
|treat_zero_length_objects_as_redirects: true
|
693
|
+
|redirects:
|
694
|
+
| index.php: /index.html
|
695
|
+
""".stripMargin
|
696
|
+
setRedirectObject("index.php")
|
697
|
+
push
|
698
|
+
noUploadsOccurred
|
699
|
+
}
|
700
|
+
|
701
|
+
"not delete the redirect objects on the bucket" in new BasicSetup {
|
702
|
+
config =
|
703
|
+
"""
|
704
|
+
|treat_zero_length_objects_as_redirects: true
|
705
|
+
|redirects:
|
706
|
+
| index.php: /index.html
|
707
|
+
""".stripMargin
|
708
|
+
setRedirectObject("index.php")
|
709
|
+
push
|
710
|
+
noDeletesOccurred
|
711
|
+
}
|
712
|
+
|
713
|
+
"apply redirects that are missing from S3" in new BasicSetup {
|
714
|
+
config =
|
715
|
+
"""
|
716
|
+
|treat_zero_length_objects_as_redirects: true
|
717
|
+
|redirects:
|
718
|
+
| index.php: /index.html
|
719
|
+
""".stripMargin
|
720
|
+
push
|
721
|
+
sentPutObjectRequest.getRedirectLocation must equalTo("/index.html")
|
722
|
+
}
|
723
|
+
|
724
|
+
"apply all redirects when the user invokes `push --force`" in new ForcePush {
|
725
|
+
config =
|
726
|
+
"""
|
727
|
+
|treat_zero_length_objects_as_redirects: true
|
728
|
+
|redirects:
|
729
|
+
| index.php: /index.html
|
730
|
+
""".stripMargin
|
731
|
+
setRedirectObject("index.php")
|
732
|
+
push
|
733
|
+
sentPutObjectRequest.getRedirectLocation must equalTo("/index.html")
|
734
|
+
}
|
735
|
+
}
|
687
736
|
|
688
737
|
trait BasicSetup extends SiteLocationFromCliArg with EmptySite with MockAWS with DefaultRunMode
|
689
738
|
|
@@ -768,22 +817,28 @@ class S3WebsiteSpec extends Specification {
|
|
768
817
|
val s3ObjectListing = new ObjectListing
|
769
818
|
when(amazonS3Client.listObjects(Matchers.any(classOf[ListObjectsRequest]))).thenReturn(s3ObjectListing)
|
770
819
|
|
820
|
+
// Simulate the situation where the file on S3 is outdated (as compared to the local file)
|
771
821
|
def setOutdatedS3Keys(s3Keys: String*) {
|
772
|
-
s3Keys
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
822
|
+
s3Keys.map(setS3File(_))
|
823
|
+
}
|
824
|
+
|
825
|
+
def setRedirectObject(s3Key: String) {
|
826
|
+
setS3File(s3Key, size = 0)
|
827
|
+
}
|
828
|
+
|
829
|
+
def setS3File(s3Key: String, md5: String = "", size: Int = 10) {
|
830
|
+
s3ObjectListing.getObjectSummaries.add({
|
831
|
+
val summary = new S3ObjectSummary
|
832
|
+
summary.setETag(md5)
|
833
|
+
summary.setKey(s3Key)
|
834
|
+
summary.setSize(size)
|
835
|
+
summary
|
836
|
+
})
|
777
837
|
}
|
778
838
|
|
779
839
|
def setS3Files(s3Files: S3File*) {
|
780
840
|
s3Files.foreach { s3File =>
|
781
|
-
|
782
|
-
val summary = new S3ObjectSummary
|
783
|
-
summary.setETag(s3File.md5)
|
784
|
-
summary.setKey(s3File.s3Key)
|
785
|
-
summary
|
786
|
-
})
|
841
|
+
setS3File(s3File.s3Key, s3File.md5)
|
787
842
|
}
|
788
843
|
}
|
789
844
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: s3_website
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.7.0
|
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-11-
|
11
|
+
date: 2014-11-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|