s3_website 2.6.1 → 2.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|