s3_website 2.1.16 → 2.2.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 +8 -12
- data/bin/s3_website +0 -2
- data/build.sbt +1 -3
- data/changelog.md +10 -0
- data/lib/s3_website/version.rb +1 -1
- data/resources/configuration_file_template.yml +2 -0
- data/src/main/scala/s3/website/ByteHelper.scala +18 -0
- data/src/main/scala/s3/website/Push.scala +5 -9
- data/src/main/scala/s3/website/model/Config.scala +9 -2
- data/src/main/scala/s3/website/model/Site.scala +45 -12
- data/src/main/scala/s3/website/model/push.scala +1 -1
- data/src/main/scala/s3/website/model/ssg.scala +4 -10
- data/src/test/scala/s3/website/S3WebsiteSpec.scala +164 -116
- metadata +3 -3
- data/src/main/java/s3/website/ByteHelper.java +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a0db818cde92ba72ae2bca269f676c18e63277ac
|
4
|
+
data.tar.gz: 20315d8fde0ef16cad751cf104f45eea931612d0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0229a6d300c77da0ea0e01d151994c26fd7cd62fd84e64fdf6c58e7b1ee087b5a6f29bd8667ec7432efd3a6dfbf058facfb07aa90e764840be78c32aeb2c4fef
|
7
|
+
data.tar.gz: 03fd97df2738bd7a8e0b09f37e759d7de45b3075932bf828cdda04eae2667eb7f547b8563a4bb2b80e2d4c8a1acbbd36c892bc0fd4b9f8522dbd013febcc0abe
|
data/README.md
CHANGED
@@ -33,23 +33,19 @@ Here's how you can get started:
|
|
33
33
|
S3 website. If the bucket does not exist, the command will create it for you.
|
34
34
|
* Run `s3_website push` to push your website to S3. Congratulations! You are live.
|
35
35
|
|
36
|
-
###
|
36
|
+
### Specifying the location of your website
|
37
37
|
|
38
|
-
S3_website will automatically discover
|
38
|
+
S3_website will automatically discover websites in the *_site* and
|
39
|
+
*public/output* directories.
|
39
40
|
|
40
|
-
|
41
|
+
If your website is not in either of those directories, you can
|
42
|
+
point the location of your website in two ways:
|
41
43
|
|
42
|
-
|
43
|
-
|
44
|
-
### For others
|
45
|
-
|
46
|
-
It's a good idea to store the `s3_website.yml` file in your project's root.
|
47
|
-
Let's say the contents you wish to upload to your S3 website bucket are in
|
48
|
-
*my_website_output*. You can upload the contents of that directory with
|
49
|
-
`s3_website push --site my_website_output`.
|
44
|
+
1. Add the line `site: path-to-your-website` into the `s3_website.yml` file
|
45
|
+
2. Or, use the `--site=path-to-your-site` command-line argument
|
50
46
|
|
51
47
|
If you want to store the `s3_website.yml` file in a directory other than
|
52
|
-
the project's root you can specify the directory
|
48
|
+
the project's root you can specify the directory like so:
|
53
49
|
`s3_website push --config-dir config`.
|
54
50
|
|
55
51
|
### Using environment variables
|
data/bin/s3_website
CHANGED
data/build.sbt
CHANGED
@@ -4,7 +4,7 @@ name := "s3_website"
|
|
4
4
|
|
5
5
|
version := "0.0.1"
|
6
6
|
|
7
|
-
scalaVersion := "2.11.
|
7
|
+
scalaVersion := "2.11.2"
|
8
8
|
|
9
9
|
scalacOptions += "-feature"
|
10
10
|
|
@@ -14,8 +14,6 @@ scalacOptions += "-language:postfixOps"
|
|
14
14
|
|
15
15
|
scalacOptions += "-target:jvm-1.6"
|
16
16
|
|
17
|
-
javacOptions in Compile ++= Seq("-source", "1.6", "-target", "1.6")
|
18
|
-
|
19
17
|
libraryDependencies += "org.yaml" % "snakeyaml" % "1.13"
|
20
18
|
|
21
19
|
libraryDependencies += "org.jruby" % "jruby" % "1.7.11"
|
data/changelog.md
CHANGED
@@ -2,6 +2,16 @@
|
|
2
2
|
|
3
3
|
This project uses [Semantic Versioning](http://semver.org).
|
4
4
|
|
5
|
+
## 2.2.0
|
6
|
+
|
7
|
+
* Specify the location of the website in the *s3_website.yml* file
|
8
|
+
|
9
|
+
Just add the setting `site: path-to-your-website` into the file.
|
10
|
+
|
11
|
+
* Fix Nanoc auto detection
|
12
|
+
|
13
|
+
Previously, a website in *public/output* was not automatically detected.
|
14
|
+
|
5
15
|
## 2.1.16
|
6
16
|
|
7
17
|
* Support non-US-ASCII files when using `ignore_on_server`
|
data/lib/s3_website/version.rb
CHANGED
@@ -0,0 +1,18 @@
|
|
1
|
+
package s3.website
|
2
|
+
|
3
|
+
object ByteHelper {
|
4
|
+
|
5
|
+
// Adapted from http://stackoverflow.com/a/3758880/219947
|
6
|
+
def humanReadableByteCount(bytes: Long): String = {
|
7
|
+
val si: Boolean = true
|
8
|
+
val unit: Int = if (si) 1000 else 1024
|
9
|
+
if (bytes < unit) {
|
10
|
+
bytes + " B"
|
11
|
+
} else {
|
12
|
+
val exp: Int = (Math.log(bytes) / Math.log(unit)).asInstanceOf[Int]
|
13
|
+
val pre: String = (if (si) "kMGTPE" else "KMGTPE").charAt(exp - 1) + (if (si) "" else "i")
|
14
|
+
val formatArgs = (bytes / Math.pow(unit, exp)).asInstanceOf[AnyRef] :: pre :: Nil
|
15
|
+
String.format("%.1f %sB", formatArgs.toArray:_*)
|
16
|
+
}
|
17
|
+
}
|
18
|
+
}
|
@@ -1,5 +1,6 @@
|
|
1
1
|
package s3.website
|
2
2
|
|
3
|
+
import s3.website.model.Config.S3_website_yml
|
3
4
|
import s3.website.model.Site._
|
4
5
|
import scala.concurrent.{ExecutionContextExecutor, Future, Await}
|
5
6
|
import scala.concurrent.duration._
|
@@ -21,7 +22,6 @@ import s3.website.S3.S3Setting
|
|
21
22
|
import s3.website.CloudFront.CloudFrontSetting
|
22
23
|
import s3.website.S3.SuccessfulUpload
|
23
24
|
import s3.website.CloudFront.FailedInvalidation
|
24
|
-
import scala.Int
|
25
25
|
import java.io.File
|
26
26
|
import com.lexicalscope.jewel.cli.CliFactory.parseArguments
|
27
27
|
import s3.website.ByteHelper.humanReadableByteCount
|
@@ -52,15 +52,11 @@ object Push {
|
|
52
52
|
def dryRun = cliArgs.dryRun
|
53
53
|
}
|
54
54
|
|
55
|
-
val
|
56
|
-
Option(cliArgs.site).fold(Ssg.findSiteDirectory(workingDirectory))(siteDirFromCli => Right(new File(siteDirFromCli)))
|
57
|
-
def errorOrSite(siteInDirectory: File): Either[ErrorReport, Site] =
|
58
|
-
loadSite(Option(cliArgs.configDir).getOrElse(workingDirectory.getPath) + "/s3_website.yml", siteInDirectory.getAbsolutePath)
|
55
|
+
implicit val yamlConfig = S3_website_yml(new File(Option(cliArgs.configDir).getOrElse(workingDirectory.getPath) + "/s3_website.yml"))
|
59
56
|
|
60
|
-
val errorOrPushStatus = for
|
61
|
-
|
62
|
-
|
63
|
-
} yield {
|
57
|
+
val errorOrPushStatus = for (
|
58
|
+
loadedSite <- loadSite.right
|
59
|
+
) yield {
|
64
60
|
implicit val site = loadedSite
|
65
61
|
val threadPool = newFixedThreadPool(site.config.concurrency_level)
|
66
62
|
implicit val executor = fromExecutor(threadPool)
|
@@ -1,5 +1,7 @@
|
|
1
1
|
package s3.website.model
|
2
2
|
|
3
|
+
import java.io.File
|
4
|
+
|
3
5
|
import scala.util.{Failure, Try}
|
4
6
|
import scala.collection.JavaConversions._
|
5
7
|
import s3.website.Ruby.rubyRuntime
|
@@ -11,6 +13,7 @@ case class Config(
|
|
11
13
|
s3_secret: Option[String], // If undefined, use IAM Roles (http://docs.aws.amazon.com/AWSSdkDocsJava/latest/DeveloperGuide/java-dg-roles.html)
|
12
14
|
s3_bucket: String,
|
13
15
|
s3_endpoint: S3Endpoint,
|
16
|
+
site: Option[String],
|
14
17
|
max_age: Option[Either[Int, Map[String, Int]]],
|
15
18
|
gzip: Option[Either[Boolean, Seq[String]]],
|
16
19
|
gzip_zopfli: Option[Boolean],
|
@@ -154,7 +157,7 @@ object Config {
|
|
154
157
|
unsafeYaml.yamlObject.asInstanceOf[java.util.Map[String, _]].toMap get key
|
155
158
|
}
|
156
159
|
|
157
|
-
def erbEval(erbString: String,
|
160
|
+
def erbEval(erbString: String, yamlConfig: S3_website_yml): Try[String] = Try {
|
158
161
|
val erbStringWithoutComments = erbString.replaceAll("^\\s*#.*", "")
|
159
162
|
rubyRuntime.evalScriptlet(
|
160
163
|
s"""|# encoding: utf-8
|
@@ -167,9 +170,13 @@ object Config {
|
|
167
170
|
""".stripMargin
|
168
171
|
).asJavaString()
|
169
172
|
} match {
|
170
|
-
case Failure(err) => Failure(new RuntimeException(s"Failed to parse ERB in $
|
173
|
+
case Failure(err) => Failure(new RuntimeException(s"Failed to parse ERB in $yamlConfig:\n${err.getMessage}"))
|
171
174
|
case x => x
|
172
175
|
}
|
173
176
|
|
174
177
|
case class UnsafeYaml(yamlObject: AnyRef)
|
178
|
+
|
179
|
+
case class S3_website_yml(file: File) {
|
180
|
+
override def toString = file.getPath
|
181
|
+
}
|
175
182
|
}
|
@@ -1,6 +1,9 @@
|
|
1
1
|
package s3.website.model
|
2
2
|
|
3
3
|
import java.io.File
|
4
|
+
import s3.website.Push.CliArgs
|
5
|
+
import s3.website.model.Ssg.autodetectSiteDir
|
6
|
+
|
4
7
|
import scala.util.Try
|
5
8
|
import org.yaml.snakeyaml.Yaml
|
6
9
|
import s3.website.model.Config._
|
@@ -11,8 +14,8 @@ import scala.util.Failure
|
|
11
14
|
import s3.website.model.Config.UnsafeYaml
|
12
15
|
import scala.util.Success
|
13
16
|
|
14
|
-
case class Site(rootDirectory:
|
15
|
-
def resolveS3Key(file: File) = file.getAbsolutePath.replace(rootDirectory, "").replaceFirst("^/", "")
|
17
|
+
case class Site(rootDirectory: File, config: Config) {
|
18
|
+
def resolveS3Key(file: File) = file.getAbsolutePath.replace(rootDirectory.getAbsolutePath, "").replaceFirst("^/", "")
|
16
19
|
|
17
20
|
def resolveFile(s3File: S3File): File = resolveFile(s3File.s3Key)
|
18
21
|
|
@@ -20,21 +23,22 @@ case class Site(rootDirectory: String, config: Config) {
|
|
20
23
|
}
|
21
24
|
|
22
25
|
object Site {
|
23
|
-
def
|
24
|
-
(implicit logger: Logger): Either[ErrorReport, Site] = {
|
26
|
+
def parseConfig(implicit logger: Logger, yamlConfig: S3_website_yml): Either[ErrorReport, Config] = {
|
25
27
|
val yamlObjectTry = for {
|
26
|
-
yamlString <- Try(fromFile(
|
27
|
-
yamlWithErbEvaluated <- erbEval(yamlString,
|
28
|
+
yamlString <- Try(fromFile(yamlConfig.file).mkString)
|
29
|
+
yamlWithErbEvaluated <- erbEval(yamlString, yamlConfig)
|
28
30
|
yamlObject <- Try(new Yaml() load yamlWithErbEvaluated)
|
29
31
|
} yield yamlObject
|
32
|
+
|
30
33
|
yamlObjectTry match {
|
31
34
|
case Success(yamlObject) =>
|
32
35
|
implicit val unsafeYaml = UnsafeYaml(yamlObject)
|
33
|
-
|
36
|
+
for {
|
34
37
|
s3_id <- loadOptionalString("s3_id").right
|
35
38
|
s3_secret <- loadOptionalString("s3_secret").right
|
36
39
|
s3_bucket <- loadRequiredString("s3_bucket").right
|
37
40
|
s3_endpoint <- loadEndpoint.right
|
41
|
+
site <- loadOptionalString("site").right
|
38
42
|
max_age <- loadMaxAge.right
|
39
43
|
gzip <- loadOptionalBooleanOrStringSeq("gzip").right
|
40
44
|
gzip_zopfli <- loadOptionalBoolean("gzip_zopfli").right
|
@@ -49,16 +53,17 @@ object Site {
|
|
49
53
|
} yield {
|
50
54
|
gzip_zopfli.foreach(_ => logger.info(
|
51
55
|
"""|Zopfli is not currently supported. Falling back to regular gzip.
|
52
|
-
|
53
|
-
|
56
|
+
|If you find a stable Java implementation for zopfli, please send an email to lauri.lehmijoki@iki.fi about it."""
|
57
|
+
.stripMargin))
|
54
58
|
extensionless_mime_type.foreach(_ => logger.info(
|
55
|
-
s"Ignoring the extensionless_mime_type setting in $
|
59
|
+
s"Ignoring the extensionless_mime_type setting in $yamlConfig. Counting on Apache Tika to infer correct mime types.")
|
56
60
|
)
|
57
61
|
Config(
|
58
62
|
s3_id,
|
59
63
|
s3_secret,
|
60
64
|
s3_bucket,
|
61
65
|
s3_endpoint getOrElse S3Endpoint.defaultEndpoint,
|
66
|
+
site,
|
62
67
|
max_age,
|
63
68
|
gzip,
|
64
69
|
gzip_zopfli,
|
@@ -71,10 +76,38 @@ object Site {
|
|
71
76
|
concurrency_level.fold(20)(_ max 20) // At minimum, run 20 concurrent operations
|
72
77
|
)
|
73
78
|
}
|
74
|
-
|
75
|
-
config.right.map(Site(siteRootDirectory, _))
|
76
79
|
case Failure(error) =>
|
77
80
|
Left(ErrorReport(error))
|
78
81
|
}
|
79
82
|
}
|
83
|
+
|
84
|
+
def loadSite(implicit yamlConfig: S3_website_yml, cliArgs: CliArgs, workingDirectory: File, logger: Logger): Either[ErrorReport, Site] =
|
85
|
+
parseConfig.right.flatMap { cfg =>
|
86
|
+
implicit val config: Config = cfg
|
87
|
+
val errorOrSiteDir = resolveSiteDir.fold(Left(ErrorReport(noSiteFound)): Either[ErrorReport, File])(Right(_))
|
88
|
+
errorOrSiteDir.right.map(Site(_, config))
|
89
|
+
}
|
90
|
+
|
91
|
+
val noSiteFound =
|
92
|
+
"""|Could not find a website.
|
93
|
+
|Either use the --site=DIR command-line argument or define the location of the site in s3_website.yml.
|
94
|
+
|
|
95
|
+
|Here's an example of how you can define the site directory in s3_website.yml:
|
96
|
+
| site: dist/website""".stripMargin
|
97
|
+
|
98
|
+
def resolveSiteDir(implicit yamlConfig: S3_website_yml, config: Config, cliArgs: CliArgs, workingDirectory: File): Option[File] = {
|
99
|
+
val siteFromAutoDetect = autodetectSiteDir(workingDirectory)
|
100
|
+
val siteFromCliArgs = Option(cliArgs.site).map(new File(_))
|
101
|
+
|
102
|
+
siteFromCliArgs orElse siteFromConfig orElse siteFromAutoDetect
|
103
|
+
}
|
104
|
+
|
105
|
+
def siteFromConfig(implicit yamlConfig: S3_website_yml, config: Config, workingDirectory: File): Option[File] =
|
106
|
+
config
|
107
|
+
.site
|
108
|
+
.map(new File(_))
|
109
|
+
.map { siteDir =>
|
110
|
+
if (siteDir.isAbsolute) siteDir
|
111
|
+
else new File(yamlConfig.file.getParentFile, siteDir.getPath)
|
112
|
+
}
|
80
113
|
}
|
@@ -142,7 +142,7 @@ object Files {
|
|
142
142
|
if (doNotUpload) logger.debug(s"Excluded $s3Key from upload")
|
143
143
|
doNotUpload
|
144
144
|
}
|
145
|
-
recursiveListFiles(
|
145
|
+
recursiveListFiles(site.rootDirectory)
|
146
146
|
.filterNot(_.isDirectory)
|
147
147
|
.filterNot(f => excludeFromUpload(site.resolveS3Key(f)))
|
148
148
|
}
|
@@ -1,6 +1,7 @@
|
|
1
1
|
package s3.website.model
|
2
2
|
|
3
3
|
import java.io.File
|
4
|
+
import s3.website.model.Files.recursiveListFiles
|
4
5
|
import s3.website.{ErrorOrFile, ErrorReport}
|
5
6
|
|
6
7
|
// ssg = static site generator
|
@@ -11,17 +12,10 @@ trait Ssg {
|
|
11
12
|
object Ssg {
|
12
13
|
val automaticallySupportedSiteGenerators = Jekyll :: Nanoc :: Nil
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
"""|Could not find a website in any of the pre-defined directories.
|
18
|
-
|Specify the website location with the --site=path argument and try again.""".stripMargin
|
15
|
+
def autodetectSiteDir(workingDirectory: File): Option[File] =
|
16
|
+
recursiveListFiles(workingDirectory).find { file =>
|
17
|
+
file.isDirectory && automaticallySupportedSiteGenerators.exists(ssg => file.getAbsolutePath.endsWith(ssg.outputDirectory))
|
19
18
|
}
|
20
|
-
|
21
|
-
def findSiteDirectory(workingDirectory: File): ErrorOrFile =
|
22
|
-
Files.recursiveListFiles(workingDirectory).find { file =>
|
23
|
-
file.isDirectory && automaticallySupportedSiteGenerators.exists(_.outputDirectory == file.getName)
|
24
|
-
}.fold(Left(notFoundErrorReport): ErrorOrFile)(Right(_))
|
25
19
|
}
|
26
20
|
|
27
21
|
case object Jekyll extends Ssg {
|
@@ -1,40 +1,37 @@
|
|
1
1
|
package s3.website
|
2
2
|
|
3
|
-
import org.specs2.mutable.{BeforeAfter, After, Specification}
|
4
|
-
import s3.website.model._
|
5
|
-
import org.specs2.specification.Scope
|
6
|
-
import org.apache.commons.io.FileUtils
|
7
3
|
import java.io.File
|
8
|
-
import
|
9
|
-
|
10
|
-
import
|
4
|
+
import java.util.concurrent.atomic.AtomicInteger
|
5
|
+
|
6
|
+
import com.amazonaws.AmazonServiceException
|
7
|
+
import com.amazonaws.services.cloudfront.AmazonCloudFront
|
8
|
+
import com.amazonaws.services.cloudfront.model.{CreateInvalidationRequest, CreateInvalidationResult, TooManyInvalidationsInProgressException}
|
11
9
|
import com.amazonaws.services.s3.AmazonS3
|
12
10
|
import com.amazonaws.services.s3.model._
|
13
|
-
import scala.concurrent.duration._
|
14
|
-
import s3.website.S3.S3Setting
|
15
|
-
import scala.collection.JavaConversions._
|
16
|
-
import com.amazonaws.AmazonServiceException
|
17
11
|
import org.apache.commons.codec.digest.DigestUtils._
|
18
|
-
import
|
19
|
-
import com.amazonaws.services.cloudfront.AmazonCloudFront
|
20
|
-
import com.amazonaws.services.cloudfront.model.{CreateInvalidationResult, CreateInvalidationRequest, TooManyInvalidationsInProgressException}
|
21
|
-
import org.mockito.stubbing.Answer
|
22
|
-
import org.mockito.invocation.InvocationOnMock
|
23
|
-
import java.util.concurrent.atomic.AtomicInteger
|
12
|
+
import org.apache.commons.io.FileUtils
|
24
13
|
import org.apache.commons.io.FileUtils._
|
25
|
-
import
|
26
|
-
import
|
27
|
-
import
|
28
|
-
import
|
29
|
-
import org.
|
30
|
-
import
|
14
|
+
import org.mockito.Mockito._
|
15
|
+
import org.mockito.invocation.InvocationOnMock
|
16
|
+
import org.mockito.stubbing.Answer
|
17
|
+
import org.mockito.{ArgumentCaptor, Matchers, Mockito}
|
18
|
+
import org.specs2.mutable.{BeforeAfter, Specification}
|
19
|
+
import org.specs2.specification.Scope
|
31
20
|
import s3.website.CloudFront.CloudFrontSetting
|
21
|
+
import s3.website.Push.{CliArgs}
|
32
22
|
import s3.website.S3.S3Setting
|
23
|
+
import s3.website.model.Config.S3_website_yml
|
24
|
+
import s3.website.model.Ssg.automaticallySupportedSiteGenerators
|
25
|
+
import s3.website.model._
|
26
|
+
|
27
|
+
import scala.collection.JavaConversions._
|
28
|
+
import scala.concurrent.duration._
|
29
|
+
import scala.util.Random
|
33
30
|
|
34
31
|
class S3WebsiteSpec extends Specification {
|
35
32
|
|
36
33
|
"gzip: true" should {
|
37
|
-
"update a gzipped S3 object if the contents has changed" in new
|
34
|
+
"update a gzipped S3 object if the contents has changed" in new BasicSetup {
|
38
35
|
config = "gzip: true"
|
39
36
|
setLocalFileWithContent(("styles.css", "<h1>hi again</h1>"))
|
40
37
|
setS3Files(S3File("styles.css", "1c5117e5839ad8fc00ce3c41296255a1" /* md5 of the gzip of the file contents */))
|
@@ -42,7 +39,7 @@ class S3WebsiteSpec extends Specification {
|
|
42
39
|
sentPutObjectRequest.getKey must equalTo("styles.css")
|
43
40
|
}
|
44
41
|
|
45
|
-
"not update a gzipped S3 object if the contents has not changed" in new
|
42
|
+
"not update a gzipped S3 object if the contents has not changed" in new BasicSetup {
|
46
43
|
config = "gzip: true"
|
47
44
|
setLocalFileWithContent(("styles.css", "<h1>hi</h1>"))
|
48
45
|
setS3Files(S3File("styles.css", "1c5117e5839ad8fc00ce3c41296255a1" /* md5 of the gzip of the file contents */))
|
@@ -55,7 +52,7 @@ class S3WebsiteSpec extends Specification {
|
|
55
52
|
gzip:
|
56
53
|
- .xml
|
57
54
|
""" should {
|
58
|
-
"update a gzipped S3 object if the contents has changed" in new
|
55
|
+
"update a gzipped S3 object if the contents has changed" in new BasicSetup {
|
59
56
|
config = """
|
60
57
|
|gzip:
|
61
58
|
| - .xml
|
@@ -68,40 +65,40 @@ class S3WebsiteSpec extends Specification {
|
|
68
65
|
}
|
69
66
|
|
70
67
|
"push" should {
|
71
|
-
"not upload a file if it has not changed" in new
|
68
|
+
"not upload a file if it has not changed" in new BasicSetup {
|
72
69
|
setLocalFileWithContent(("index.html", "<div>hello</div>"))
|
73
70
|
setS3Files(S3File("index.html", md5Hex("<div>hello</div>")))
|
74
71
|
push
|
75
72
|
noUploadsOccurred must beTrue
|
76
73
|
}
|
77
74
|
|
78
|
-
"update a file if it has changed" in new
|
75
|
+
"update a file if it has changed" in new BasicSetup {
|
79
76
|
setLocalFileWithContent(("index.html", "<h1>old text</h1>"))
|
80
77
|
setS3Files(S3File("index.html", md5Hex("<h1>new text</h1>")))
|
81
78
|
push
|
82
79
|
sentPutObjectRequest.getKey must equalTo("index.html")
|
83
80
|
}
|
84
81
|
|
85
|
-
"create a file if does not exist on S3" in new
|
82
|
+
"create a file if does not exist on S3" in new BasicSetup {
|
86
83
|
setLocalFile("index.html")
|
87
84
|
push
|
88
85
|
sentPutObjectRequest.getKey must equalTo("index.html")
|
89
86
|
}
|
90
87
|
|
91
|
-
"delete files that are on S3 but not on local file system" in new
|
88
|
+
"delete files that are on S3 but not on local file system" in new BasicSetup {
|
92
89
|
setS3Files(S3File("old.html", md5Hex("<h1>old text</h1>")))
|
93
90
|
push
|
94
91
|
sentDelete must equalTo("old.html")
|
95
92
|
}
|
96
93
|
|
97
|
-
"try again if the upload fails" in new
|
94
|
+
"try again if the upload fails" in new BasicSetup {
|
98
95
|
setLocalFile("index.html")
|
99
96
|
uploadFailsAndThenSucceeds(howManyFailures = 5)
|
100
97
|
push
|
101
98
|
verify(amazonS3Client, times(6)).putObject(Matchers.any(classOf[PutObjectRequest]))
|
102
99
|
}
|
103
100
|
|
104
|
-
"not try again if the upload fails on because of invalid credentials" in new
|
101
|
+
"not try again if the upload fails on because of invalid credentials" in new BasicSetup {
|
105
102
|
setLocalFile("index.html")
|
106
103
|
when(amazonS3Client.putObject(Matchers.any(classOf[PutObjectRequest]))).thenThrow {
|
107
104
|
val e = new AmazonServiceException("your credentials are incorrect")
|
@@ -112,7 +109,7 @@ class S3WebsiteSpec extends Specification {
|
|
112
109
|
verify(amazonS3Client, times(1)).putObject(Matchers.any(classOf[PutObjectRequest]))
|
113
110
|
}
|
114
111
|
|
115
|
-
"try again if the request times out" in new
|
112
|
+
"try again if the request times out" in new BasicSetup {
|
116
113
|
var attempt = 0
|
117
114
|
when(amazonS3Client putObject Matchers.any(classOf[PutObjectRequest])) thenAnswer new Answer[PutObjectResult] {
|
118
115
|
def answer(invocation: InvocationOnMock) = {
|
@@ -132,14 +129,14 @@ class S3WebsiteSpec extends Specification {
|
|
132
129
|
verify(amazonS3Client, times(2)).putObject(Matchers.any(classOf[PutObjectRequest]))
|
133
130
|
}
|
134
131
|
|
135
|
-
"try again if the delete fails" in new
|
132
|
+
"try again if the delete fails" in new BasicSetup {
|
136
133
|
setS3Files(S3File("old.html", md5Hex("<h1>old text</h1>")))
|
137
134
|
deleteFailsAndThenSucceeds(howManyFailures = 5)
|
138
135
|
push
|
139
136
|
verify(amazonS3Client, times(6)).deleteObject(Matchers.anyString(), Matchers.anyString())
|
140
137
|
}
|
141
138
|
|
142
|
-
"try again if the object listing fails" in new
|
139
|
+
"try again if the object listing fails" in new BasicSetup {
|
143
140
|
setS3Files(S3File("old.html", md5Hex("<h1>old text</h1>")))
|
144
141
|
objectListingFailsAndThenSucceeds(howManyFailures = 5)
|
145
142
|
push
|
@@ -148,7 +145,7 @@ class S3WebsiteSpec extends Specification {
|
|
148
145
|
}
|
149
146
|
|
150
147
|
"push with CloudFront" should {
|
151
|
-
"invalidate the updated CloudFront items" in new
|
148
|
+
"invalidate the updated CloudFront items" in new BasicSetup {
|
152
149
|
config = "cloudfront_distribution_id: EGM1J2JJX9Z"
|
153
150
|
setLocalFiles("css/test.css", "articles/index.html")
|
154
151
|
setOutdatedS3Keys("css/test.css", "articles/index.html")
|
@@ -156,14 +153,14 @@ class S3WebsiteSpec extends Specification {
|
|
156
153
|
sentInvalidationRequest.getInvalidationBatch.getPaths.getItems.toSeq.sorted must equalTo(("/css/test.css" :: "/articles/index.html" :: Nil).sorted)
|
157
154
|
}
|
158
155
|
|
159
|
-
"not send CloudFront invalidation requests on new objects" in new
|
156
|
+
"not send CloudFront invalidation requests on new objects" in new BasicSetup {
|
160
157
|
config = "cloudfront_distribution_id: EGM1J2JJX9Z"
|
161
158
|
setLocalFile("newfile.js")
|
162
159
|
push
|
163
160
|
noInvalidationsOccurred must beTrue
|
164
161
|
}
|
165
162
|
|
166
|
-
"not send CloudFront invalidation requests on redirect objects" in new
|
163
|
+
"not send CloudFront invalidation requests on redirect objects" in new BasicSetup {
|
167
164
|
config = """
|
168
165
|
|cloudfront_distribution_id: EGM1J2JJX9Z
|
169
166
|
|redirects:
|
@@ -173,7 +170,7 @@ class S3WebsiteSpec extends Specification {
|
|
173
170
|
noInvalidationsOccurred must beTrue
|
174
171
|
}
|
175
172
|
|
176
|
-
"retry CloudFront responds with TooManyInvalidationsInProgressException" in new
|
173
|
+
"retry CloudFront responds with TooManyInvalidationsInProgressException" in new BasicSetup {
|
177
174
|
setTooManyInvalidationsInProgress(4)
|
178
175
|
config = "cloudfront_distribution_id: EGM1J2JJX9Z"
|
179
176
|
setLocalFile("test.css")
|
@@ -182,7 +179,7 @@ class S3WebsiteSpec extends Specification {
|
|
182
179
|
sentInvalidationRequests.length must equalTo(4)
|
183
180
|
}
|
184
181
|
|
185
|
-
"retry if CloudFront is temporarily unreachable" in new
|
182
|
+
"retry if CloudFront is temporarily unreachable" in new BasicSetup {
|
186
183
|
invalidationsFailAndThenSucceed(5)
|
187
184
|
config = "cloudfront_distribution_id: EGM1J2JJX9Z"
|
188
185
|
setLocalFile("test.css")
|
@@ -191,7 +188,7 @@ class S3WebsiteSpec extends Specification {
|
|
191
188
|
sentInvalidationRequests.length must equalTo(6)
|
192
189
|
}
|
193
190
|
|
194
|
-
"encode unsafe characters in the keys" in new
|
191
|
+
"encode unsafe characters in the keys" in new BasicSetup {
|
195
192
|
config = "cloudfront_distribution_id: EGM1J2JJX9Z"
|
196
193
|
setLocalFile("articles/arnold's file.html")
|
197
194
|
setOutdatedS3Keys("articles/arnold's file.html")
|
@@ -199,7 +196,7 @@ class S3WebsiteSpec extends Specification {
|
|
199
196
|
sentInvalidationRequest.getInvalidationBatch.getPaths.getItems.toSeq.sorted must equalTo(("/articles/arnold's%20file.html" :: Nil).sorted)
|
200
197
|
}
|
201
198
|
|
202
|
-
"invalidate the root object '/' if a top-level object is updated or deleted" in new
|
199
|
+
"invalidate the root object '/' if a top-level object is updated or deleted" in new BasicSetup {
|
203
200
|
config = "cloudfront_distribution_id: EGM1J2JJX9Z"
|
204
201
|
setLocalFile("maybe-index.html")
|
205
202
|
setOutdatedS3Keys("maybe-index.html")
|
@@ -209,7 +206,7 @@ class S3WebsiteSpec extends Specification {
|
|
209
206
|
}
|
210
207
|
|
211
208
|
"cloudfront_invalidate_root: true" should {
|
212
|
-
"convert CloudFront invalidation paths with the '/index.html' suffix into '/'" in new
|
209
|
+
"convert CloudFront invalidation paths with the '/index.html' suffix into '/'" in new BasicSetup {
|
213
210
|
config = """
|
214
211
|
|cloudfront_distribution_id: EGM1J2JJX9Z
|
215
212
|
|cloudfront_invalidate_root: true
|
@@ -222,7 +219,7 @@ class S3WebsiteSpec extends Specification {
|
|
222
219
|
}
|
223
220
|
|
224
221
|
"a site with over 1000 items" should {
|
225
|
-
"split the CloudFront invalidation requests into batches of 1000 items" in new
|
222
|
+
"split the CloudFront invalidation requests into batches of 1000 items" in new BasicSetup {
|
226
223
|
val files = (1 to 1002).map { i => s"lots-of-files/file-$i"}
|
227
224
|
config = "cloudfront_distribution_id: EGM1J2JJX9Z"
|
228
225
|
setLocalFiles(files:_*)
|
@@ -235,18 +232,18 @@ class S3WebsiteSpec extends Specification {
|
|
235
232
|
}
|
236
233
|
|
237
234
|
"push exit status" should {
|
238
|
-
"be 0 all uploads succeed" in new
|
235
|
+
"be 0 all uploads succeed" in new BasicSetup {
|
239
236
|
setLocalFiles("file.txt")
|
240
237
|
push must equalTo(0)
|
241
238
|
}
|
242
239
|
|
243
|
-
"be 1 if any of the uploads fails" in new
|
240
|
+
"be 1 if any of the uploads fails" in new BasicSetup {
|
244
241
|
setLocalFiles("file.txt")
|
245
242
|
when(amazonS3Client.putObject(Matchers.any(classOf[PutObjectRequest]))).thenThrow(new AmazonServiceException("AWS failed"))
|
246
243
|
push must equalTo(1)
|
247
244
|
}
|
248
245
|
|
249
|
-
"be 1 if any of the redirects fails" in new
|
246
|
+
"be 1 if any of the redirects fails" in new BasicSetup {
|
250
247
|
config = """
|
251
248
|
|redirects:
|
252
249
|
| index.php: /index.html
|
@@ -255,14 +252,14 @@ class S3WebsiteSpec extends Specification {
|
|
255
252
|
push must equalTo(1)
|
256
253
|
}
|
257
254
|
|
258
|
-
"be 0 if CloudFront invalidations and uploads succeed"in new
|
255
|
+
"be 0 if CloudFront invalidations and uploads succeed"in new BasicSetup {
|
259
256
|
config = "cloudfront_distribution_id: EGM1J2JJX9Z"
|
260
257
|
setLocalFile("test.css")
|
261
258
|
setOutdatedS3Keys("test.css")
|
262
259
|
push must equalTo(0)
|
263
260
|
}
|
264
261
|
|
265
|
-
"be 1 if CloudFront is unreachable or broken"in new
|
262
|
+
"be 1 if CloudFront is unreachable or broken"in new BasicSetup {
|
266
263
|
setCloudFrontAsInternallyBroken()
|
267
264
|
config = "cloudfront_distribution_id: EGM1J2JJX9Z"
|
268
265
|
setLocalFile("test.css")
|
@@ -270,19 +267,19 @@ class S3WebsiteSpec extends Specification {
|
|
270
267
|
push must equalTo(1)
|
271
268
|
}
|
272
269
|
|
273
|
-
"be 0 if upload retry succeeds" in new
|
270
|
+
"be 0 if upload retry succeeds" in new BasicSetup {
|
274
271
|
setLocalFile("index.html")
|
275
272
|
uploadFailsAndThenSucceeds(howManyFailures = 1)
|
276
273
|
push must equalTo(0)
|
277
274
|
}
|
278
275
|
|
279
|
-
"be 1 if delete retry fails" in new
|
276
|
+
"be 1 if delete retry fails" in new BasicSetup {
|
280
277
|
setLocalFile("index.html")
|
281
278
|
uploadFailsAndThenSucceeds(howManyFailures = 6)
|
282
279
|
push must equalTo(1)
|
283
280
|
}
|
284
281
|
|
285
|
-
"be 1 if an object listing fails" in new
|
282
|
+
"be 1 if an object listing fails" in new BasicSetup {
|
286
283
|
setS3Files(S3File("old.html", md5Hex("<h1>old text</h1>")))
|
287
284
|
objectListingFailsAndThenSucceeds(howManyFailures = 6)
|
288
285
|
push must equalTo(1)
|
@@ -290,7 +287,7 @@ class S3WebsiteSpec extends Specification {
|
|
290
287
|
}
|
291
288
|
|
292
289
|
"s3_website.yml file" should {
|
293
|
-
"never be uploaded" in new
|
290
|
+
"never be uploaded" in new BasicSetup {
|
294
291
|
setLocalFile("s3_website.yml")
|
295
292
|
push
|
296
293
|
noUploadsOccurred must beTrue
|
@@ -298,7 +295,7 @@ class S3WebsiteSpec extends Specification {
|
|
298
295
|
}
|
299
296
|
|
300
297
|
".env file" should { // The .env file is the https://github.com/bkeepers/dotenv file
|
301
|
-
"never be uploaded" in new
|
298
|
+
"never be uploaded" in new BasicSetup {
|
302
299
|
setLocalFile(".env")
|
303
300
|
push
|
304
301
|
noUploadsOccurred must beTrue
|
@@ -306,7 +303,7 @@ class S3WebsiteSpec extends Specification {
|
|
306
303
|
}
|
307
304
|
|
308
305
|
"exclude_from_upload: string" should {
|
309
|
-
"result in matching files not being uploaded" in new
|
306
|
+
"result in matching files not being uploaded" in new BasicSetup {
|
310
307
|
config = "exclude_from_upload: .DS_.*?"
|
311
308
|
setLocalFile(".DS_Store")
|
312
309
|
push
|
@@ -319,7 +316,7 @@ class S3WebsiteSpec extends Specification {
|
|
319
316
|
- regex
|
320
317
|
- another_exclusion
|
321
318
|
""" should {
|
322
|
-
"result in matching files not being uploaded" in new
|
319
|
+
"result in matching files not being uploaded" in new BasicSetup {
|
323
320
|
config = """
|
324
321
|
|exclude_from_upload:
|
325
322
|
| - .DS_.*?
|
@@ -332,14 +329,14 @@ class S3WebsiteSpec extends Specification {
|
|
332
329
|
}
|
333
330
|
|
334
331
|
"ignore_on_server: value" should {
|
335
|
-
"not delete the S3 objects that match the ignore value" in new
|
332
|
+
"not delete the S3 objects that match the ignore value" in new BasicSetup {
|
336
333
|
config = "ignore_on_server: logs"
|
337
334
|
setS3Files(S3File("logs/log.txt", ""))
|
338
335
|
push
|
339
336
|
noDeletesOccurred must beTrue
|
340
337
|
}
|
341
338
|
|
342
|
-
"support non-US-ASCII files" in new
|
339
|
+
"support non-US-ASCII files" in new BasicSetup {
|
343
340
|
setS3Files(S3File("tags/笔记/test.html", ""))
|
344
341
|
config = "ignore_on_server: tags/笔记/test.html"
|
345
342
|
push
|
@@ -352,7 +349,7 @@ class S3WebsiteSpec extends Specification {
|
|
352
349
|
- regex
|
353
350
|
- another_ignore
|
354
351
|
""" should {
|
355
|
-
"not delete the S3 objects that match the ignore value" in new
|
352
|
+
"not delete the S3 objects that match the ignore value" in new BasicSetup {
|
356
353
|
config = """
|
357
354
|
|ignore_on_server:
|
358
355
|
| - .*txt
|
@@ -362,7 +359,7 @@ class S3WebsiteSpec extends Specification {
|
|
362
359
|
noDeletesOccurred must beTrue
|
363
360
|
}
|
364
361
|
|
365
|
-
"support non-US-ASCII files"
|
362
|
+
"support non-US-ASCII files" in new BasicSetup {
|
366
363
|
setS3Files(S3File("tags/笔记/test.html", ""))
|
367
364
|
config = """
|
368
365
|
|ignore_on_server:
|
@@ -373,15 +370,51 @@ class S3WebsiteSpec extends Specification {
|
|
373
370
|
}
|
374
371
|
}
|
375
372
|
|
373
|
+
"site in config" should {
|
374
|
+
"let the user deploy a site from a custom location" in new CustomSiteDirectory with EmptySite with MockAWS with DefaultRunMode {
|
375
|
+
config = s"site: $siteDirectory"
|
376
|
+
setLocalFile(".vimrc")
|
377
|
+
|
378
|
+
new File(siteDirectory, ".vimrc").exists() must beTrue // Sanity check
|
379
|
+
siteDirectory must not equalTo workingDirectory // Sanity check
|
380
|
+
|
381
|
+
push
|
382
|
+
sentPutObjectRequest.getKey must equalTo(".vimrc")
|
383
|
+
}
|
384
|
+
|
385
|
+
"not override the --site command-line switch" in new BasicSetup {
|
386
|
+
config = s"site: dir-that-does-not-exist"
|
387
|
+
setLocalFile(".vimrc") // This creates a file in the directory into which the --site CLI arg points
|
388
|
+
push
|
389
|
+
sentPutObjectRequest.getKey must equalTo(".vimrc")
|
390
|
+
}
|
391
|
+
|
392
|
+
automaticallySupportedSiteGenerators foreach { siteGenerator =>
|
393
|
+
"override an automatically detected site" in new CustomSiteDirectory with EmptySite with MockAWS with DefaultRunMode {
|
394
|
+
addContentToAutomaticallyDetectedSite(workingDirectory)
|
395
|
+
config = s"site: $siteDirectory"
|
396
|
+
setLocalFile(".vimrc") // Add content to the custom site directory
|
397
|
+
push
|
398
|
+
sentPutObjectRequest.getKey must equalTo(".vimrc")
|
399
|
+
}
|
400
|
+
|
401
|
+
def addContentToAutomaticallyDetectedSite(workingDirectory: File) {
|
402
|
+
val automaticallyDetectedSiteDir = new File(workingDirectory, siteGenerator.outputDirectory)
|
403
|
+
automaticallyDetectedSiteDir.mkdirs()
|
404
|
+
write(new File(automaticallyDetectedSiteDir, ".bashrc"), "echo hello")
|
405
|
+
}
|
406
|
+
}
|
407
|
+
}
|
408
|
+
|
376
409
|
"max-age in config" can {
|
377
|
-
"be applied to all files" in new
|
410
|
+
"be applied to all files" in new BasicSetup {
|
378
411
|
config = "max_age: 60"
|
379
412
|
setLocalFile("index.html")
|
380
413
|
push
|
381
414
|
sentPutObjectRequest.getMetadata.getCacheControl must equalTo("max-age=60")
|
382
415
|
}
|
383
416
|
|
384
|
-
"be applied to files that match the glob" in new
|
417
|
+
"be applied to files that match the glob" in new BasicSetup {
|
385
418
|
config = """
|
386
419
|
|max_age:
|
387
420
|
| "*.html": 90
|
@@ -391,7 +424,7 @@ class S3WebsiteSpec extends Specification {
|
|
391
424
|
sentPutObjectRequest.getMetadata.getCacheControl must equalTo("max-age=90")
|
392
425
|
}
|
393
426
|
|
394
|
-
"be applied to directories that match the glob" in new
|
427
|
+
"be applied to directories that match the glob" in new BasicSetup {
|
395
428
|
config = """
|
396
429
|
|max_age:
|
397
430
|
| "assets/**/*.js": 90
|
@@ -401,7 +434,7 @@ class S3WebsiteSpec extends Specification {
|
|
401
434
|
sentPutObjectRequest.getMetadata.getCacheControl must equalTo("max-age=90")
|
402
435
|
}
|
403
436
|
|
404
|
-
"not be applied if the glob doesn't match" in new
|
437
|
+
"not be applied if the glob doesn't match" in new BasicSetup {
|
405
438
|
config = """
|
406
439
|
|max_age:
|
407
440
|
| "*.js": 90
|
@@ -411,14 +444,14 @@ class S3WebsiteSpec extends Specification {
|
|
411
444
|
sentPutObjectRequest.getMetadata.getCacheControl must beNull
|
412
445
|
}
|
413
446
|
|
414
|
-
"be used to disable caching" in new
|
447
|
+
"be used to disable caching" in new BasicSetup {
|
415
448
|
config = "max_age: 0"
|
416
449
|
setLocalFile("index.html")
|
417
450
|
push
|
418
451
|
sentPutObjectRequest.getMetadata.getCacheControl must equalTo("no-cache; max-age=0")
|
419
452
|
}
|
420
453
|
|
421
|
-
"support non-US-ASCII directory names" in new
|
454
|
+
"support non-US-ASCII directory names" in new BasicSetup {
|
422
455
|
config = """
|
423
456
|
|max_age:
|
424
457
|
| "*": 21600
|
@@ -429,7 +462,7 @@ class S3WebsiteSpec extends Specification {
|
|
429
462
|
}
|
430
463
|
|
431
464
|
"max-age in config" should {
|
432
|
-
"respect the more specific glob" in new
|
465
|
+
"respect the more specific glob" in new BasicSetup {
|
433
466
|
config = """
|
434
467
|
|max_age:
|
435
468
|
| "assets/*": 150
|
@@ -443,7 +476,7 @@ class S3WebsiteSpec extends Specification {
|
|
443
476
|
}
|
444
477
|
|
445
478
|
"s3_reduced_redundancy: true in config" should {
|
446
|
-
"result in uploads being marked with reduced redundancy" in new
|
479
|
+
"result in uploads being marked with reduced redundancy" in new BasicSetup {
|
447
480
|
config = "s3_reduced_redundancy: true"
|
448
481
|
setLocalFile("file.exe")
|
449
482
|
push
|
@@ -452,7 +485,7 @@ class S3WebsiteSpec extends Specification {
|
|
452
485
|
}
|
453
486
|
|
454
487
|
"s3_reduced_redundancy: false in config" should {
|
455
|
-
"result in uploads being marked with the default storage class" in new
|
488
|
+
"result in uploads being marked with the default storage class" in new BasicSetup {
|
456
489
|
config = "s3_reduced_redundancy: false"
|
457
490
|
setLocalFile("file.exe")
|
458
491
|
push
|
@@ -461,7 +494,7 @@ class S3WebsiteSpec extends Specification {
|
|
461
494
|
}
|
462
495
|
|
463
496
|
"redirect in config" should {
|
464
|
-
"result in a redirect instruction that is sent to AWS" in new
|
497
|
+
"result in a redirect instruction that is sent to AWS" in new BasicSetup {
|
465
498
|
config = """
|
466
499
|
|redirects:
|
467
500
|
| index.php: /index.html
|
@@ -470,7 +503,7 @@ class S3WebsiteSpec extends Specification {
|
|
470
503
|
sentPutObjectRequest.getRedirectLocation must equalTo("/index.html")
|
471
504
|
}
|
472
505
|
|
473
|
-
"add slash to the redirect target" in new
|
506
|
+
"add slash to the redirect target" in new BasicSetup {
|
474
507
|
config = """
|
475
508
|
|redirects:
|
476
509
|
| index.php: index.html
|
@@ -479,7 +512,7 @@ class S3WebsiteSpec extends Specification {
|
|
479
512
|
sentPutObjectRequest.getRedirectLocation must equalTo("/index.html")
|
480
513
|
}
|
481
514
|
|
482
|
-
"support external redirects" in new
|
515
|
+
"support external redirects" in new BasicSetup {
|
483
516
|
config = """
|
484
517
|
|redirects:
|
485
518
|
| index.php: http://www.youtube.com/watch?v=dQw4w9WgXcQ
|
@@ -488,7 +521,7 @@ class S3WebsiteSpec extends Specification {
|
|
488
521
|
sentPutObjectRequest.getRedirectLocation must equalTo("http://www.youtube.com/watch?v=dQw4w9WgXcQ")
|
489
522
|
}
|
490
523
|
|
491
|
-
"support external redirects that point to an HTTPS target" in new
|
524
|
+
"support external redirects that point to an HTTPS target" in new BasicSetup {
|
492
525
|
config = """
|
493
526
|
|redirects:
|
494
527
|
| index.php: https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
@@ -497,7 +530,7 @@ class S3WebsiteSpec extends Specification {
|
|
497
530
|
sentPutObjectRequest.getRedirectLocation must equalTo("https://www.youtube.com/watch?v=dQw4w9WgXcQ")
|
498
531
|
}
|
499
532
|
|
500
|
-
"result in max-age=0 Cache-Control header on the object" in new
|
533
|
+
"result in max-age=0 Cache-Control header on the object" in new BasicSetup {
|
501
534
|
config = """
|
502
535
|
|redirects:
|
503
536
|
| index.php: /index.html
|
@@ -508,7 +541,7 @@ class S3WebsiteSpec extends Specification {
|
|
508
541
|
}
|
509
542
|
|
510
543
|
"redirect in config and an object on the S3 bucket" should {
|
511
|
-
"not result in the S3 object being deleted" in new
|
544
|
+
"not result in the S3 object being deleted" in new BasicSetup {
|
512
545
|
config = """
|
513
546
|
|redirects:
|
514
547
|
| index.php: /index.html
|
@@ -521,7 +554,7 @@ class S3WebsiteSpec extends Specification {
|
|
521
554
|
}
|
522
555
|
|
523
556
|
"dotfiles" should {
|
524
|
-
"be included in the pushed files" in new
|
557
|
+
"be included in the pushed files" in new BasicSetup {
|
525
558
|
setLocalFile(".vimrc")
|
526
559
|
push
|
527
560
|
sentPutObjectRequest.getKey must equalTo(".vimrc")
|
@@ -529,25 +562,25 @@ class S3WebsiteSpec extends Specification {
|
|
529
562
|
}
|
530
563
|
|
531
564
|
"content type inference" should {
|
532
|
-
"add charset=utf-8 to all html documents" in new
|
565
|
+
"add charset=utf-8 to all html documents" in new BasicSetup {
|
533
566
|
setLocalFile("index.html")
|
534
567
|
push
|
535
568
|
sentPutObjectRequest.getMetadata.getContentType must equalTo("text/html; charset=utf-8")
|
536
569
|
}
|
537
570
|
|
538
|
-
"add charset=utf-8 to all text documents" in new
|
571
|
+
"add charset=utf-8 to all text documents" in new BasicSetup {
|
539
572
|
setLocalFile("index.txt")
|
540
573
|
push
|
541
574
|
sentPutObjectRequest.getMetadata.getContentType must equalTo("text/plain; charset=utf-8")
|
542
575
|
}
|
543
576
|
|
544
|
-
"add charset=utf-8 to all json documents" in new
|
577
|
+
"add charset=utf-8 to all json documents" in new BasicSetup {
|
545
578
|
setLocalFile("data.json")
|
546
579
|
push
|
547
580
|
sentPutObjectRequest.getMetadata.getContentType must equalTo("application/json; charset=utf-8")
|
548
581
|
}
|
549
582
|
|
550
|
-
"resolve the content type from file contents" in new
|
583
|
+
"resolve the content type from file contents" in new BasicSetup {
|
551
584
|
setLocalFileWithContent(("index", "<html><body><h1>hi</h1></body></html>"))
|
552
585
|
push
|
553
586
|
sentPutObjectRequest.getMetadata.getContentType must equalTo("text/html; charset=utf-8")
|
@@ -555,7 +588,7 @@ class S3WebsiteSpec extends Specification {
|
|
555
588
|
}
|
556
589
|
|
557
590
|
"ERB in config file" should {
|
558
|
-
"be evaluated" in new
|
591
|
+
"be evaluated" in new BasicSetup {
|
559
592
|
config = """
|
560
593
|
|redirects:
|
561
594
|
|<%= ('a'..'f').to_a.map do |t| ' '+t+ ': /'+t+'.html' end.join('\n')%>
|
@@ -567,14 +600,14 @@ class S3WebsiteSpec extends Specification {
|
|
567
600
|
}
|
568
601
|
|
569
602
|
"dry run" should {
|
570
|
-
"not push updates" in new
|
603
|
+
"not push updates" in new SiteLocationFromCliArg with EmptySite with MockAWS with DryRunMode {
|
571
604
|
setLocalFileWithContent(("index.html", "<div>new</div>"))
|
572
605
|
setS3Files(S3File("index.html", md5Hex("<div>old</div>")))
|
573
606
|
push
|
574
607
|
noUploadsOccurred must beTrue
|
575
608
|
}
|
576
609
|
|
577
|
-
"not push redirects" in new
|
610
|
+
"not push redirects" in new SiteLocationFromCliArg with EmptySite with MockAWS with DryRunMode {
|
578
611
|
config =
|
579
612
|
"""
|
580
613
|
|redirects:
|
@@ -584,19 +617,19 @@ class S3WebsiteSpec extends Specification {
|
|
584
617
|
noUploadsOccurred must beTrue
|
585
618
|
}
|
586
619
|
|
587
|
-
"not push deletes" in new
|
620
|
+
"not push deletes" in new SiteLocationFromCliArg with EmptySite with MockAWS with DryRunMode {
|
588
621
|
setS3Files(S3File("index.html", md5Hex("<div>old</div>")))
|
589
622
|
push
|
590
623
|
noUploadsOccurred must beTrue
|
591
624
|
}
|
592
625
|
|
593
|
-
"not push new files" in new
|
626
|
+
"not push new files" in new SiteLocationFromCliArg with EmptySite with MockAWS with DryRunMode {
|
594
627
|
setLocalFile("index.html")
|
595
628
|
push
|
596
629
|
noUploadsOccurred must beTrue
|
597
630
|
}
|
598
|
-
|
599
|
-
"not invalidate files" in new
|
631
|
+
|
632
|
+
"not invalidate files" in new SiteLocationFromCliArg with EmptySite with MockAWS with DryRunMode {
|
600
633
|
config = "cloudfront_invalidation_id: AABBCC"
|
601
634
|
setS3Files(S3File("index.html", md5Hex("<div>old</div>")))
|
602
635
|
push
|
@@ -619,6 +652,8 @@ class S3WebsiteSpec extends Specification {
|
|
619
652
|
sentPutObjectRequests.length must equalTo(1)
|
620
653
|
}
|
621
654
|
}
|
655
|
+
|
656
|
+
trait BasicSetup extends SiteLocationFromCliArg with EmptySite with MockAWS with DefaultRunMode
|
622
657
|
|
623
658
|
trait DefaultRunMode {
|
624
659
|
implicit def pushMode: PushMode = new PushMode {
|
@@ -633,7 +668,7 @@ class S3WebsiteSpec extends Specification {
|
|
633
668
|
}
|
634
669
|
|
635
670
|
trait MockAWS extends MockS3 with MockCloudFront with Scope
|
636
|
-
|
671
|
+
|
637
672
|
trait MockCloudFront extends MockAWSHelper {
|
638
673
|
val amazonCloudFrontClient = mock(classOf[AmazonCloudFront])
|
639
674
|
implicit val cfSettings: CloudFrontSetting = CloudFrontSetting(
|
@@ -677,7 +712,7 @@ class S3WebsiteSpec extends Specification {
|
|
677
712
|
when(amazonCloudFrontClient.createInvalidation(Matchers.anyObject())).thenThrow(new AmazonServiceException("CloudFront is down"))
|
678
713
|
}
|
679
714
|
}
|
680
|
-
|
715
|
+
|
681
716
|
trait MockS3 extends MockAWSHelper {
|
682
717
|
val amazonS3Client = mock(classOf[AmazonS3])
|
683
718
|
implicit val s3Settings: S3Setting = S3Setting(
|
@@ -754,7 +789,7 @@ class S3WebsiteSpec extends Specification {
|
|
754
789
|
true // Mockito is based on exceptions
|
755
790
|
}
|
756
791
|
|
757
|
-
type S3Key = String
|
792
|
+
type S3Key = String
|
758
793
|
}
|
759
794
|
|
760
795
|
trait MockAWSHelper {
|
@@ -770,8 +805,10 @@ class S3WebsiteSpec extends Specification {
|
|
770
805
|
}
|
771
806
|
|
772
807
|
trait Directories extends BeforeAfter {
|
773
|
-
|
774
|
-
val
|
808
|
+
def randomDir() = new File(FileUtils.getTempDirectory, "s3_website_dir" + Random.nextLong())
|
809
|
+
implicit final val workingDirectory: File = randomDir()
|
810
|
+
implicit def yamlConfig: S3_website_yml = S3_website_yml(new File(workingDirectory, "s3_website.yml"))
|
811
|
+
val siteDirectory: File
|
775
812
|
val configDirectory: File = workingDirectory // Represents the --config-dir=X option
|
776
813
|
|
777
814
|
def before {
|
@@ -785,22 +822,28 @@ class S3WebsiteSpec extends Specification {
|
|
785
822
|
}
|
786
823
|
}
|
787
824
|
|
788
|
-
|
789
|
-
* Represents the situation where the current working directory, site dir and config dir are in the same directory
|
790
|
-
*/
|
791
|
-
trait AllInSameDirectory extends Directories {
|
825
|
+
trait SiteLocationFromCliArg extends Directories {
|
792
826
|
val siteDirectory = workingDirectory
|
827
|
+
val siteDirFromCLIArg = true
|
793
828
|
}
|
794
829
|
|
795
830
|
trait JekyllSite extends Directories {
|
796
831
|
val siteDirectory = new File(workingDirectory, "_site")
|
832
|
+
val siteDirFromCLIArg = false
|
797
833
|
}
|
798
834
|
|
799
835
|
trait NanocSite extends Directories {
|
800
836
|
val siteDirectory = new File(workingDirectory, "public/output")
|
837
|
+
val siteDirFromCLIArg = false
|
801
838
|
}
|
802
|
-
|
839
|
+
|
840
|
+
trait CustomSiteDirectory extends Directories {
|
841
|
+
val siteDirectory = randomDir()
|
842
|
+
val siteDirFromCLIArg = false
|
843
|
+
}
|
844
|
+
|
803
845
|
trait EmptySite extends Directories {
|
846
|
+
val siteDirFromCLIArg: Boolean
|
804
847
|
type LocalFileWithContent = (String, String)
|
805
848
|
|
806
849
|
def setLocalFile(fileName: String) = setLocalFileWithContent((fileName, ""))
|
@@ -812,6 +855,7 @@ class S3WebsiteSpec extends Specification {
|
|
812
855
|
file.createNewFile()
|
813
856
|
write(file, fileNameAndContent._2)
|
814
857
|
}
|
858
|
+
|
815
859
|
var config = ""
|
816
860
|
val baseConfig =
|
817
861
|
"""
|
@@ -820,34 +864,38 @@ class S3WebsiteSpec extends Specification {
|
|
820
864
|
|s3_bucket: bucket
|
821
865
|
""".stripMargin
|
822
866
|
|
823
|
-
implicit def
|
824
|
-
|
825
|
-
|
826
|
-
private def buildCliArgs(
|
827
|
-
config: String = "",
|
828
|
-
baseConfig: String =
|
829
|
-
"""
|
830
|
-
|s3_id: foo
|
831
|
-
|s3_secret: bar
|
832
|
-
|s3_bucket: bucket
|
833
|
-
""".stripMargin
|
834
|
-
): CliArgs = {
|
835
|
-
val configFile = new File(configDirectory, "s3_website.yml")
|
836
|
-
write(configFile,
|
867
|
+
implicit def configString: ConfigString =
|
868
|
+
ConfigString(
|
837
869
|
s"""
|
838
870
|
|$baseConfig
|
839
871
|
|$config
|
840
872
|
""".stripMargin
|
841
873
|
)
|
874
|
+
|
875
|
+
def pushMode: PushMode // Represents the --dry-run switch
|
876
|
+
|
877
|
+
implicit def cliArgs: CliArgs =
|
842
878
|
new CliArgs {
|
843
|
-
|
879
|
+
def verbose = true
|
844
880
|
|
845
|
-
|
881
|
+
def dryRun = pushMode.dryRun
|
846
882
|
|
847
|
-
|
883
|
+
def site = if (siteDirFromCLIArg) siteDirectory.getAbsolutePath else null
|
848
884
|
|
849
|
-
|
885
|
+
def configDir = configDirectory.getAbsolutePath
|
850
886
|
}
|
851
|
-
}
|
852
887
|
}
|
888
|
+
|
889
|
+
def push(implicit
|
890
|
+
emptyYamlConfig: S3_website_yml,
|
891
|
+
configString: ConfigString,
|
892
|
+
cliArgs: CliArgs,
|
893
|
+
s3Settings: S3Setting,
|
894
|
+
cloudFrontSettings: CloudFrontSetting,
|
895
|
+
workingDirectory: File) = {
|
896
|
+
write(emptyYamlConfig.file, configString.yaml) // Write the yaml config lazily, so that the tests can override the default yaml config
|
897
|
+
Push.push
|
898
|
+
}
|
899
|
+
|
900
|
+
case class ConfigString(yaml: String)
|
853
901
|
}
|
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.2.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-07-
|
11
|
+
date: 2014-07-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -126,8 +126,8 @@ files:
|
|
126
126
|
- resources/s3_website.jar.md5
|
127
127
|
- s3_website.gemspec
|
128
128
|
- sbt
|
129
|
-
- src/main/java/s3/website/ByteHelper.java
|
130
129
|
- src/main/resources/log4j.properties
|
130
|
+
- src/main/scala/s3/website/ByteHelper.scala
|
131
131
|
- src/main/scala/s3/website/CloudFront.scala
|
132
132
|
- src/main/scala/s3/website/Diff.scala
|
133
133
|
- src/main/scala/s3/website/Logger.scala
|
@@ -1,14 +0,0 @@
|
|
1
|
-
package s3.website;
|
2
|
-
|
3
|
-
public class ByteHelper {
|
4
|
-
|
5
|
-
// Adapted from http://stackoverflow.com/a/3758880/219947
|
6
|
-
public static String humanReadableByteCount(long bytes) {
|
7
|
-
boolean si = true;
|
8
|
-
int unit = si ? 1000 : 1024;
|
9
|
-
if (bytes < unit) return bytes + " B";
|
10
|
-
int exp = (int) (Math.log(bytes) / Math.log(unit));
|
11
|
-
String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp-1) + (si ? "" : "i");
|
12
|
-
return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
|
13
|
-
}
|
14
|
-
}
|