s3_website_monadic 0.0.29 → 0.0.30
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/bin/s3_website_monadic +9 -18
- data/changelog.md +5 -2
- data/lib/s3_website/version.rb +1 -1
- data/lib/s3_website.rb +0 -3
- data/src/main/scala/s3/website/Push.scala +29 -18
- data/src/main/scala/s3/website/model/ssg.scala +33 -0
- data/src/main/scala/s3/website/package.scala +3 -0
- data/src/test/scala/s3/website/S3WebsiteSpec.scala +175 -160
- metadata +3 -5
- data/lib/s3_website/jekyll.rb +0 -5
- data/lib/s3_website/nanoc.rb +0 -5
- data/lib/s3_website/paths.rb +0 -40
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a489b6afb931e8384aa6d656acd8bf23a0c366be
|
4
|
+
data.tar.gz: 12234bf6f3689a83ad37a61c7029ce80900e2a67
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bbc33e009ccbe805fe72697433d59fd4c82691cc9edd3e95d4e8277fceb159715f8b0d4566fa2c1e85d092c20cab2eb37178e0cfde0db1fc2103682f90653a67
|
7
|
+
data.tar.gz: 6c8b915eab3220b1a7148d0912c267dfe7b05ac653367b033e83c9c6144d027d22d18ae2ec820314415b46433b0ec5335616a8f2cda730c46bab28577dbc8528
|
data/bin/s3_website_monadic
CHANGED
@@ -44,13 +44,11 @@ class Cli < Thor
|
|
44
44
|
option(
|
45
45
|
:site,
|
46
46
|
:type => :string,
|
47
|
-
:
|
48
|
-
:desc => "The directory where your website files are. When not defined, s3_website will look for the site in #{S3Website::Paths.site_paths.join(' or ')}."
|
47
|
+
:desc => "The directory where your website files are. When not defined, s3_website will look for the site in either _site or public/output."
|
49
48
|
)
|
50
49
|
option(
|
51
50
|
:config_dir,
|
52
51
|
:type => :string,
|
53
|
-
:default => Dir.pwd,
|
54
52
|
:desc => "The directory where your config file is. When not defined, s3_website will look in the current working directory."
|
55
53
|
)
|
56
54
|
option(
|
@@ -74,11 +72,10 @@ class Cli < Thor
|
|
74
72
|
call_dir = Dir.pwd
|
75
73
|
project_root = File.expand_path(File.dirname(__FILE__)+ '/..')
|
76
74
|
logger = Logger.new(options[:verbose])
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
}
|
75
|
+
# Find the jar
|
76
|
+
jar_file = resolve_jar project_root, logger
|
77
|
+
# Then run it
|
78
|
+
run_s3_website_jar(jar_file, call_dir, logger)
|
82
79
|
end
|
83
80
|
|
84
81
|
desc 'cfg SUBCOMMAND ...ARGS', 'Operate on the config file'
|
@@ -86,15 +83,7 @@ class Cli < Thor
|
|
86
83
|
end
|
87
84
|
|
88
85
|
def run_s3_website_jar(jar_file, call_dir, logger)
|
89
|
-
|
90
|
-
config_dir = File.expand_path options[:config_dir]
|
91
|
-
|
92
|
-
args = [
|
93
|
-
"--site=#{site_path}",
|
94
|
-
"--config-dir=#{config_dir}",
|
95
|
-
"#{'--verbose' if options[:verbose]}",
|
96
|
-
"#{'--dry-run' if options[:dry_run]}"
|
97
|
-
].join ' '
|
86
|
+
args = ARGV.join(' ').sub('push', '')
|
98
87
|
logger.debug_msg "Using #{jar_file}"
|
99
88
|
if system("java -cp #{jar_file} s3.website.Push #{args}")
|
100
89
|
exit 0
|
@@ -121,7 +110,9 @@ def resolve_jar(project_root, logger)
|
|
121
110
|
else
|
122
111
|
is_development = File.exists?(project_root + '/.git')
|
123
112
|
if is_development
|
124
|
-
|
113
|
+
Dir.chdir(project_root) {
|
114
|
+
system "./sbt assembly"
|
115
|
+
}
|
125
116
|
development_jar_path
|
126
117
|
else
|
127
118
|
download_jar(released_jar_lookup_paths, logger)
|
data/changelog.md
CHANGED
@@ -4,8 +4,11 @@ This project uses [Semantic Versioning](http://semver.org).
|
|
4
4
|
|
5
5
|
## 2.0.0
|
6
6
|
|
7
|
-
|
8
|
-
|
7
|
+
### New features
|
8
|
+
|
9
|
+
* Simulate deployments with `push --dry-run`
|
10
|
+
|
11
|
+
* Support CloudFront invalidations when the site contains over 3000 files
|
9
12
|
|
10
13
|
### Java is now required
|
11
14
|
|
data/lib/s3_website/version.rb
CHANGED
data/lib/s3_website.rb
CHANGED
@@ -25,6 +25,8 @@ import s3.website.CloudFront.CloudFrontSetting
|
|
25
25
|
import s3.website.S3.SuccessfulUpload
|
26
26
|
import s3.website.CloudFront.FailedInvalidation
|
27
27
|
import scala.Int
|
28
|
+
import java.io.File
|
29
|
+
import com.lexicalscope.jewel.cli.CliFactory.parseArguments
|
28
30
|
|
29
31
|
object Push {
|
30
32
|
|
@@ -185,35 +187,44 @@ object Push {
|
|
185
187
|
trait CliArgs {
|
186
188
|
import com.lexicalscope.jewel.cli.Option
|
187
189
|
|
188
|
-
@Option def site: String
|
189
|
-
@Option(longName = Array("config-dir")) def configDir: String
|
190
|
+
@Option(defaultToNull = true) def site: String
|
191
|
+
@Option(longName = Array("config-dir"), defaultToNull = true) def configDir: String
|
190
192
|
@Option def verbose: Boolean
|
191
193
|
@Option(longName = Array("dry-run")) def dryRun: Boolean
|
192
194
|
}
|
193
195
|
|
194
196
|
def main(args: Array[String]) {
|
195
|
-
val cliArgs =
|
197
|
+
implicit val cliArgs = parseArguments(classOf[CliArgs], args:_*)
|
196
198
|
implicit val s3Settings = S3Setting()
|
197
199
|
implicit val cloudFrontSettings = CloudFrontSetting()
|
200
|
+
implicit val workingDirectory = new File(System.getProperty("user.dir")).getAbsoluteFile
|
201
|
+
System exit push
|
202
|
+
}
|
203
|
+
|
204
|
+
def push(implicit cliArgs: CliArgs, s3Settings: S3Setting, cloudFrontSettings: CloudFrontSetting, workingDirectory: File): ExitCode = {
|
198
205
|
implicit val logger: Logger = new Logger(cliArgs.verbose)
|
199
206
|
implicit val pushMode = new PushMode {
|
200
207
|
def dryRun = cliArgs.dryRun
|
201
208
|
}
|
202
|
-
val errorOrPushStatus = push(siteInDirectory = cliArgs.site, withConfigDirectory = cliArgs.configDir)
|
203
|
-
errorOrPushStatus.left foreach (err => logger.fail(s"Could not load the site: ${err.reportMessage}"))
|
204
|
-
System exit errorOrPushStatus.fold(_ => 1, pushStatus => pushStatus)
|
205
|
-
}
|
206
209
|
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
.
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
210
|
+
val errorOrSiteDir: ErrorOrFile =
|
211
|
+
Option(cliArgs.site).fold(Ssg.findSiteDirectory(workingDirectory))(siteDirFromCli => Right(new File(siteDirFromCli)))
|
212
|
+
def errorOrSite(siteInDirectory: File): Either[ErrorReport, Site] =
|
213
|
+
loadSite(Option(cliArgs.configDir).getOrElse(workingDirectory.getPath) + "/s3_website.yml", siteInDirectory.getAbsolutePath)
|
214
|
+
|
215
|
+
val errorOrPushStatus = for {
|
216
|
+
siteInDirectory <- errorOrSiteDir.right
|
217
|
+
loadedSite <- errorOrSite(siteInDirectory).right
|
218
|
+
} yield {
|
219
|
+
implicit val site = loadedSite
|
220
|
+
val threadPool = newFixedThreadPool(site.config.concurrency_level)
|
221
|
+
implicit val executor = fromExecutor(threadPool)
|
222
|
+
val pushStatus = pushSite
|
223
|
+
threadPool.shutdownNow()
|
224
|
+
pushStatus
|
218
225
|
}
|
226
|
+
|
227
|
+
errorOrPushStatus.left foreach (err => logger.fail(s"Could not load the site: ${err.reportMessage}"))
|
228
|
+
errorOrPushStatus fold((err: ErrorReport) => 1, pushStatus => pushStatus)
|
229
|
+
}
|
219
230
|
}
|
@@ -0,0 +1,33 @@
|
|
1
|
+
package s3.website.model
|
2
|
+
|
3
|
+
import java.io.File
|
4
|
+
import s3.website.{ErrorOrFile, ErrorReport}
|
5
|
+
|
6
|
+
// ssg = static site generator
|
7
|
+
trait Ssg {
|
8
|
+
def outputDirectory: String
|
9
|
+
}
|
10
|
+
|
11
|
+
object Ssg {
|
12
|
+
val automaticallySupportedSiteGenerators = Jekyll :: Nanoc :: Nil
|
13
|
+
|
14
|
+
val notFoundErrorReport =
|
15
|
+
new ErrorReport {
|
16
|
+
def reportMessage =
|
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
|
19
|
+
}
|
20
|
+
|
21
|
+
def findSiteDirectory(workingDirectory: File): ErrorOrFile =
|
22
|
+
LocalFile.recursiveListFiles(workingDirectory).find { file =>
|
23
|
+
file.isDirectory && automaticallySupportedSiteGenerators.exists(_.outputDirectory == file.getName)
|
24
|
+
}.fold(Left(notFoundErrorReport): ErrorOrFile)(Right(_))
|
25
|
+
}
|
26
|
+
|
27
|
+
case object Jekyll extends Ssg {
|
28
|
+
def outputDirectory = "_site"
|
29
|
+
}
|
30
|
+
|
31
|
+
case object Nanoc extends Ssg {
|
32
|
+
def outputDirectory = "public/output"
|
33
|
+
}
|
@@ -6,6 +6,7 @@ import s3.website.Utils._
|
|
6
6
|
import s3.website.S3.{PushSuccessReport, PushFailureReport}
|
7
7
|
import com.amazonaws.AmazonServiceException
|
8
8
|
import s3.website.model.{Config, Site}
|
9
|
+
import java.io.File
|
9
10
|
|
10
11
|
package object website {
|
11
12
|
trait Report {
|
@@ -94,4 +95,6 @@ package object website {
|
|
94
95
|
}
|
95
96
|
|
96
97
|
implicit def site2Config(implicit site: Site): Config = site.config
|
98
|
+
|
99
|
+
type ErrorOrFile = Either[ErrorReport, File]
|
97
100
|
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
package s3.website
|
2
2
|
|
3
|
-
import org.specs2.mutable.{After, Specification}
|
3
|
+
import org.specs2.mutable.{BeforeAfter, After, Specification}
|
4
4
|
import s3.website.model._
|
5
5
|
import org.specs2.specification.Scope
|
6
6
|
import org.apache.commons.io.FileUtils
|
@@ -10,7 +10,6 @@ import org.mockito.{Mockito, Matchers, ArgumentCaptor}
|
|
10
10
|
import org.mockito.Mockito._
|
11
11
|
import com.amazonaws.services.s3.AmazonS3
|
12
12
|
import com.amazonaws.services.s3.model._
|
13
|
-
import scala.concurrent.ExecutionContext.Implicits.global
|
14
13
|
import scala.concurrent.duration._
|
15
14
|
import s3.website.S3.S3Setting
|
16
15
|
import scala.collection.JavaConversions._
|
@@ -22,25 +21,26 @@ import com.amazonaws.services.cloudfront.model.{CreateInvalidationResult, Create
|
|
22
21
|
import org.mockito.stubbing.Answer
|
23
22
|
import org.mockito.invocation.InvocationOnMock
|
24
23
|
import java.util.concurrent.atomic.AtomicInteger
|
25
|
-
import org.apache.commons.io.FileUtils.write
|
24
|
+
import org.apache.commons.io.FileUtils.{forceDelete, forceMkdir, write}
|
26
25
|
import scala.collection.mutable
|
26
|
+
import s3.website.Push.CliArgs
|
27
27
|
|
28
28
|
class S3WebsiteSpec extends Specification {
|
29
29
|
|
30
30
|
"gzip: true" should {
|
31
|
-
"update a gzipped S3 object if the contents has changed" in new
|
31
|
+
"update a gzipped S3 object if the contents has changed" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
32
32
|
config = "gzip: true"
|
33
33
|
setLocalFileWithContent(("styles.css", "<h1>hi again</h1>"))
|
34
34
|
setS3Files(S3File("styles.css", "1c5117e5839ad8fc00ce3c41296255a1" /* md5 of the gzip of the file contents */))
|
35
|
-
Push.
|
35
|
+
Push.push
|
36
36
|
sentPutObjectRequest.getKey must equalTo("styles.css")
|
37
37
|
}
|
38
38
|
|
39
|
-
"not update a gzipped S3 object if the contents has not changed" in new
|
39
|
+
"not update a gzipped S3 object if the contents has not changed" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
40
40
|
config = "gzip: true"
|
41
41
|
setLocalFileWithContent(("styles.css", "<h1>hi</h1>"))
|
42
42
|
setS3Files(S3File("styles.css", "1c5117e5839ad8fc00ce3c41296255a1" /* md5 of the gzip of the file contents */))
|
43
|
-
Push.
|
43
|
+
Push.push
|
44
44
|
noUploadsOccurred must beTrue
|
45
45
|
}
|
46
46
|
}
|
@@ -49,159 +49,159 @@ class S3WebsiteSpec extends Specification {
|
|
49
49
|
gzip:
|
50
50
|
- .xml
|
51
51
|
""" should {
|
52
|
-
"update a gzipped S3 object if the contents has changed" in new
|
52
|
+
"update a gzipped S3 object if the contents has changed" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
53
53
|
config = """
|
54
54
|
|gzip:
|
55
55
|
| - .xml
|
56
56
|
""".stripMargin
|
57
57
|
setLocalFileWithContent(("file.xml", "<h1>hi again</h1>"))
|
58
58
|
setS3Files(S3File("file.xml", "1c5117e5839ad8fc00ce3c41296255a1" /* md5 of the gzip of the file contents */))
|
59
|
-
Push.
|
59
|
+
Push.push
|
60
60
|
sentPutObjectRequest.getKey must equalTo("file.xml")
|
61
61
|
}
|
62
62
|
}
|
63
63
|
|
64
64
|
"push" should {
|
65
|
-
"not upload a file if it has not changed" in new
|
65
|
+
"not upload a file if it has not changed" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
66
66
|
setLocalFileWithContent(("index.html", "<div>hello</div>"))
|
67
67
|
setS3Files(S3File("index.html", md5Hex("<div>hello</div>")))
|
68
|
-
Push.
|
68
|
+
Push.push
|
69
69
|
noUploadsOccurred must beTrue
|
70
70
|
}
|
71
71
|
|
72
|
-
"update a file if it has changed" in new
|
72
|
+
"update a file if it has changed" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
73
73
|
setLocalFileWithContent(("index.html", "<h1>old text</h1>"))
|
74
74
|
setS3Files(S3File("index.html", md5Hex("<h1>new text</h1>")))
|
75
|
-
Push.
|
75
|
+
Push.push
|
76
76
|
sentPutObjectRequest.getKey must equalTo("index.html")
|
77
77
|
}
|
78
78
|
|
79
|
-
"create a file if does not exist on S3" in new
|
79
|
+
"create a file if does not exist on S3" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
80
80
|
setLocalFile("index.html")
|
81
|
-
Push.
|
81
|
+
Push.push
|
82
82
|
sentPutObjectRequest.getKey must equalTo("index.html")
|
83
83
|
}
|
84
84
|
|
85
|
-
"delete files that are on S3 but not on local file system" in new
|
85
|
+
"delete files that are on S3 but not on local file system" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
86
86
|
setS3Files(S3File("old.html", md5Hex("<h1>old text</h1>")))
|
87
|
-
Push.
|
87
|
+
Push.push
|
88
88
|
sentDelete must equalTo("old.html")
|
89
89
|
}
|
90
90
|
|
91
|
-
"try again if the upload fails" in new
|
91
|
+
"try again if the upload fails" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
92
92
|
setLocalFile("index.html")
|
93
93
|
uploadFailsAndThenSucceeds(howManyFailures = 5)
|
94
|
-
Push.
|
94
|
+
Push.push
|
95
95
|
verify(amazonS3Client, times(6)).putObject(Matchers.any(classOf[PutObjectRequest]))
|
96
96
|
}
|
97
97
|
|
98
|
-
"not try again if the upload fails on because of invalid credentials" in new
|
98
|
+
"not try again if the upload fails on because of invalid credentials" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
99
99
|
setLocalFile("index.html")
|
100
100
|
when(amazonS3Client.putObject(Matchers.any(classOf[PutObjectRequest]))).thenThrow {
|
101
101
|
val e = new AmazonServiceException("your credentials are incorrect")
|
102
102
|
e.setStatusCode(403)
|
103
103
|
e
|
104
104
|
}
|
105
|
-
Push.
|
105
|
+
Push.push
|
106
106
|
verify(amazonS3Client, times(1)).putObject(Matchers.any(classOf[PutObjectRequest]))
|
107
107
|
}
|
108
108
|
|
109
|
-
"try again if the delete fails" in new
|
109
|
+
"try again if the delete fails" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
110
110
|
setS3Files(S3File("old.html", md5Hex("<h1>old text</h1>")))
|
111
111
|
deleteFailsAndThenSucceeds(howManyFailures = 5)
|
112
|
-
Push.
|
112
|
+
Push.push
|
113
113
|
verify(amazonS3Client, times(6)).deleteObject(Matchers.anyString(), Matchers.anyString())
|
114
114
|
}
|
115
115
|
|
116
|
-
"try again if the object listing fails" in new
|
116
|
+
"try again if the object listing fails" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
117
117
|
setS3Files(S3File("old.html", md5Hex("<h1>old text</h1>")))
|
118
118
|
objectListingFailsAndThenSucceeds(howManyFailures = 5)
|
119
|
-
Push.
|
119
|
+
Push.push
|
120
120
|
verify(amazonS3Client, times(6)).listObjects(Matchers.any(classOf[ListObjectsRequest]))
|
121
121
|
}
|
122
122
|
}
|
123
123
|
|
124
124
|
"push with CloudFront" should {
|
125
|
-
"invalidate the updated CloudFront items" in new
|
125
|
+
"invalidate the updated CloudFront items" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
126
126
|
config = "cloudfront_distribution_id: EGM1J2JJX9Z"
|
127
127
|
setLocalFiles("css/test.css", "articles/index.html")
|
128
128
|
setOutdatedS3Keys("css/test.css", "articles/index.html")
|
129
|
-
Push.
|
129
|
+
Push.push
|
130
130
|
sentInvalidationRequest.getInvalidationBatch.getPaths.getItems.toSeq.sorted must equalTo(("/css/test.css" :: "/articles/index.html" :: Nil).sorted)
|
131
131
|
}
|
132
132
|
|
133
|
-
"not send CloudFront invalidation requests on new objects" in new
|
133
|
+
"not send CloudFront invalidation requests on new objects" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
134
134
|
config = "cloudfront_distribution_id: EGM1J2JJX9Z"
|
135
135
|
setLocalFile("newfile.js")
|
136
|
-
Push.
|
136
|
+
Push.push
|
137
137
|
noInvalidationsOccurred must beTrue
|
138
138
|
}
|
139
139
|
|
140
|
-
"not send CloudFront invalidation requests on redirect objects" in new
|
140
|
+
"not send CloudFront invalidation requests on redirect objects" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
141
141
|
config = """
|
142
142
|
|cloudfront_distribution_id: EGM1J2JJX9Z
|
143
143
|
|redirects:
|
144
144
|
| /index.php: index.html
|
145
145
|
""".stripMargin
|
146
|
-
Push.
|
146
|
+
Push.push
|
147
147
|
noInvalidationsOccurred must beTrue
|
148
148
|
}
|
149
149
|
|
150
|
-
"retry CloudFront responds with TooManyInvalidationsInProgressException" in new
|
150
|
+
"retry CloudFront responds with TooManyInvalidationsInProgressException" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
151
151
|
setTooManyInvalidationsInProgress(4)
|
152
152
|
config = "cloudfront_distribution_id: EGM1J2JJX9Z"
|
153
153
|
setLocalFile("test.css")
|
154
154
|
setOutdatedS3Keys("test.css")
|
155
|
-
Push.
|
155
|
+
Push.push must equalTo(0) // The retries should finally result in a success
|
156
156
|
sentInvalidationRequests.length must equalTo(4)
|
157
157
|
}
|
158
158
|
|
159
|
-
"retry if CloudFront is temporarily unreachable" in new
|
159
|
+
"retry if CloudFront is temporarily unreachable" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
160
160
|
invalidationsFailAndThenSucceed(5)
|
161
161
|
config = "cloudfront_distribution_id: EGM1J2JJX9Z"
|
162
162
|
setLocalFile("test.css")
|
163
163
|
setOutdatedS3Keys("test.css")
|
164
|
-
Push.
|
164
|
+
Push.push
|
165
165
|
sentInvalidationRequests.length must equalTo(6)
|
166
166
|
}
|
167
167
|
|
168
|
-
"encode unsafe characters in the keys" in new
|
168
|
+
"encode unsafe characters in the keys" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
169
169
|
config = "cloudfront_distribution_id: EGM1J2JJX9Z"
|
170
170
|
setLocalFile("articles/arnold's file.html")
|
171
171
|
setOutdatedS3Keys("articles/arnold's file.html")
|
172
|
-
Push.
|
172
|
+
Push.push
|
173
173
|
sentInvalidationRequest.getInvalidationBatch.getPaths.getItems.toSeq.sorted must equalTo(("/articles/arnold's%20file.html" :: Nil).sorted)
|
174
174
|
}
|
175
175
|
|
176
|
-
"invalidate the root object '/' if a top-level object is updated or deleted" in new
|
176
|
+
"invalidate the root object '/' if a top-level object is updated or deleted" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
177
177
|
config = "cloudfront_distribution_id: EGM1J2JJX9Z"
|
178
178
|
setLocalFile("maybe-index.html")
|
179
179
|
setOutdatedS3Keys("maybe-index.html")
|
180
|
-
Push.
|
180
|
+
Push.push
|
181
181
|
sentInvalidationRequest.getInvalidationBatch.getPaths.getItems.toSeq.sorted must equalTo(("/" :: "/maybe-index.html" :: Nil).sorted)
|
182
182
|
}
|
183
183
|
}
|
184
184
|
|
185
185
|
"cloudfront_invalidate_root: true" should {
|
186
|
-
"convert CloudFront invalidation paths with the '/index.html' suffix into '/'" in new
|
186
|
+
"convert CloudFront invalidation paths with the '/index.html' suffix into '/'" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
187
187
|
config = """
|
188
188
|
|cloudfront_distribution_id: EGM1J2JJX9Z
|
189
189
|
|cloudfront_invalidate_root: true
|
190
190
|
""".stripMargin
|
191
191
|
setLocalFile("articles/index.html")
|
192
192
|
setOutdatedS3Keys("articles/index.html")
|
193
|
-
Push.
|
193
|
+
Push.push
|
194
194
|
sentInvalidationRequest.getInvalidationBatch.getPaths.getItems.toSeq.sorted must equalTo(("/articles/" :: Nil).sorted)
|
195
195
|
}
|
196
196
|
}
|
197
197
|
|
198
198
|
"a site with over 1000 items" should {
|
199
|
-
"split the CloudFront invalidation requests into batches of 1000 items" in new
|
199
|
+
"split the CloudFront invalidation requests into batches of 1000 items" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
200
200
|
val files = (1 to 1002).map { i => s"lots-of-files/file-$i"}
|
201
201
|
config = "cloudfront_distribution_id: EGM1J2JJX9Z"
|
202
202
|
setLocalFiles(files:_*)
|
203
203
|
setOutdatedS3Keys(files:_*)
|
204
|
-
Push.
|
204
|
+
Push.push
|
205
205
|
sentInvalidationRequests.length must equalTo(2)
|
206
206
|
sentInvalidationRequests(0).getInvalidationBatch.getPaths.getItems.length must equalTo(1000)
|
207
207
|
sentInvalidationRequests(1).getInvalidationBatch.getPaths.getItems.length must equalTo(2)
|
@@ -209,73 +209,73 @@ class S3WebsiteSpec extends Specification {
|
|
209
209
|
}
|
210
210
|
|
211
211
|
"push exit status" should {
|
212
|
-
"be 0 all uploads succeed" in new
|
212
|
+
"be 0 all uploads succeed" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
213
213
|
setLocalFiles("file.txt")
|
214
|
-
Push.
|
214
|
+
Push.push must equalTo(0)
|
215
215
|
}
|
216
216
|
|
217
|
-
"be 1 if any of the uploads fails" in new
|
217
|
+
"be 1 if any of the uploads fails" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
218
218
|
setLocalFiles("file.txt")
|
219
219
|
when(amazonS3Client.putObject(Matchers.any(classOf[PutObjectRequest]))).thenThrow(new AmazonServiceException("AWS failed"))
|
220
|
-
Push.
|
220
|
+
Push.push must equalTo(1)
|
221
221
|
}
|
222
222
|
|
223
|
-
"be 1 if any of the redirects fails" in new
|
223
|
+
"be 1 if any of the redirects fails" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
224
224
|
config = """
|
225
225
|
|redirects:
|
226
226
|
| index.php: /index.html
|
227
227
|
""".stripMargin
|
228
228
|
when(amazonS3Client.putObject(Matchers.any(classOf[PutObjectRequest]))).thenThrow(new AmazonServiceException("AWS failed"))
|
229
|
-
Push.
|
229
|
+
Push.push must equalTo(1)
|
230
230
|
}
|
231
231
|
|
232
|
-
"be 0 if CloudFront invalidations and uploads succeed"in new
|
232
|
+
"be 0 if CloudFront invalidations and uploads succeed"in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
233
233
|
config = "cloudfront_distribution_id: EGM1J2JJX9Z"
|
234
234
|
setLocalFile("test.css")
|
235
235
|
setOutdatedS3Keys("test.css")
|
236
|
-
Push.
|
236
|
+
Push.push must equalTo(0)
|
237
237
|
}
|
238
238
|
|
239
|
-
"be 1 if CloudFront is unreachable or broken"in new
|
239
|
+
"be 1 if CloudFront is unreachable or broken"in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
240
240
|
setCloudFrontAsInternallyBroken()
|
241
241
|
config = "cloudfront_distribution_id: EGM1J2JJX9Z"
|
242
242
|
setLocalFile("test.css")
|
243
243
|
setOutdatedS3Keys("test.css")
|
244
|
-
Push.
|
244
|
+
Push.push must equalTo(1)
|
245
245
|
}
|
246
246
|
|
247
|
-
"be 0 if upload retry succeeds" in new
|
247
|
+
"be 0 if upload retry succeeds" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
248
248
|
setLocalFile("index.html")
|
249
249
|
uploadFailsAndThenSucceeds(howManyFailures = 1)
|
250
|
-
Push.
|
250
|
+
Push.push must equalTo(0)
|
251
251
|
}
|
252
252
|
|
253
|
-
"be 1 if delete retry fails" in new
|
253
|
+
"be 1 if delete retry fails" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
254
254
|
setLocalFile("index.html")
|
255
255
|
uploadFailsAndThenSucceeds(howManyFailures = 6)
|
256
|
-
Push.
|
256
|
+
Push.push must equalTo(1)
|
257
257
|
}
|
258
258
|
|
259
|
-
"be 1 if an object listing fails" in new
|
259
|
+
"be 1 if an object listing fails" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
260
260
|
setS3Files(S3File("old.html", md5Hex("<h1>old text</h1>")))
|
261
261
|
objectListingFailsAndThenSucceeds(howManyFailures = 6)
|
262
|
-
Push.
|
262
|
+
Push.push must equalTo(1)
|
263
263
|
}
|
264
264
|
}
|
265
265
|
|
266
266
|
"s3_website.yml file" should {
|
267
|
-
"never be uploaded" in new
|
267
|
+
"never be uploaded" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
268
268
|
setLocalFile("s3_website.yml")
|
269
|
-
Push.
|
269
|
+
Push.push
|
270
270
|
noUploadsOccurred must beTrue
|
271
271
|
}
|
272
272
|
}
|
273
273
|
|
274
274
|
"exclude_from_upload: string" should {
|
275
|
-
"result in matching files not being uploaded" in new
|
275
|
+
"result in matching files not being uploaded" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
276
276
|
config = "exclude_from_upload: .DS_.*?"
|
277
277
|
setLocalFile(".DS_Store")
|
278
|
-
Push.
|
278
|
+
Push.push
|
279
279
|
noUploadsOccurred must beTrue
|
280
280
|
}
|
281
281
|
}
|
@@ -285,23 +285,23 @@ class S3WebsiteSpec extends Specification {
|
|
285
285
|
- regex
|
286
286
|
- another_exclusion
|
287
287
|
""" should {
|
288
|
-
"result in matching files not being uploaded" in new
|
288
|
+
"result in matching files not being uploaded" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
289
289
|
config = """
|
290
290
|
|exclude_from_upload:
|
291
291
|
| - .DS_.*?
|
292
292
|
| - logs
|
293
293
|
""".stripMargin
|
294
294
|
setLocalFiles(".DS_Store", "logs/test.log")
|
295
|
-
Push.
|
295
|
+
Push.push
|
296
296
|
noUploadsOccurred must beTrue
|
297
297
|
}
|
298
298
|
}
|
299
299
|
|
300
300
|
"ignore_on_server: value" should {
|
301
|
-
"not delete the S3 objects that match the ignore value" in new
|
301
|
+
"not delete the S3 objects that match the ignore value" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
302
302
|
config = "ignore_on_server: logs"
|
303
303
|
setS3Files(S3File("logs/log.txt", ""))
|
304
|
-
Push.
|
304
|
+
Push.push
|
305
305
|
noDeletesOccurred must beTrue
|
306
306
|
}
|
307
307
|
}
|
@@ -311,224 +311,228 @@ class S3WebsiteSpec extends Specification {
|
|
311
311
|
- regex
|
312
312
|
- another_ignore
|
313
313
|
""" should {
|
314
|
-
"not delete the S3 objects that match the ignore value" in new
|
314
|
+
"not delete the S3 objects that match the ignore value" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
315
315
|
config = """
|
316
316
|
|ignore_on_server:
|
317
317
|
| - .*txt
|
318
318
|
""".stripMargin
|
319
319
|
setS3Files(S3File("logs/log.txt", ""))
|
320
|
-
Push.
|
320
|
+
Push.push
|
321
321
|
noDeletesOccurred must beTrue
|
322
322
|
}
|
323
323
|
}
|
324
324
|
|
325
325
|
"max-age in config" can {
|
326
|
-
"be applied to all files" in new
|
326
|
+
"be applied to all files" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
327
327
|
config = "max_age: 60"
|
328
328
|
setLocalFile("index.html")
|
329
|
-
Push.
|
329
|
+
Push.push
|
330
330
|
sentPutObjectRequest.getMetadata.getCacheControl must equalTo("max-age=60")
|
331
331
|
}
|
332
332
|
|
333
|
-
"be applied to files that match the glob" in new
|
333
|
+
"be applied to files that match the glob" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
334
334
|
config = """
|
335
335
|
|max_age:
|
336
336
|
| "*.html": 90
|
337
337
|
""".stripMargin
|
338
338
|
setLocalFile("index.html")
|
339
|
-
Push.
|
339
|
+
Push.push
|
340
340
|
sentPutObjectRequest.getMetadata.getCacheControl must equalTo("max-age=90")
|
341
341
|
}
|
342
342
|
|
343
|
-
"be applied to directories that match the glob" in new
|
343
|
+
"be applied to directories that match the glob" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
344
344
|
config = """
|
345
345
|
|max_age:
|
346
346
|
| "assets/**/*.js": 90
|
347
347
|
""".stripMargin
|
348
348
|
setLocalFile("assets/lib/jquery.js")
|
349
|
-
Push.
|
349
|
+
Push.push
|
350
350
|
sentPutObjectRequest.getMetadata.getCacheControl must equalTo("max-age=90")
|
351
351
|
}
|
352
352
|
|
353
|
-
"not be applied if the glob doesn't match" in new
|
353
|
+
"not be applied if the glob doesn't match" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
354
354
|
config = """
|
355
355
|
|max_age:
|
356
356
|
| "*.js": 90
|
357
357
|
""".stripMargin
|
358
358
|
setLocalFile("index.html")
|
359
|
-
Push.
|
359
|
+
Push.push
|
360
360
|
sentPutObjectRequest.getMetadata.getCacheControl must beNull
|
361
361
|
}
|
362
362
|
|
363
|
-
"be used to disable caching" in new
|
363
|
+
"be used to disable caching" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
364
364
|
config = "max_age: 0"
|
365
365
|
setLocalFile("index.html")
|
366
|
-
Push.
|
366
|
+
Push.push
|
367
367
|
sentPutObjectRequest.getMetadata.getCacheControl must equalTo("no-cache; max-age=0")
|
368
368
|
}
|
369
369
|
}
|
370
370
|
|
371
371
|
"max-age in config" should {
|
372
|
-
"respect the more specific glob" in new
|
372
|
+
"respect the more specific glob" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
373
373
|
config = """
|
374
374
|
|max_age:
|
375
375
|
| "assets/*": 150
|
376
376
|
| "assets/*.gif": 86400
|
377
377
|
""".stripMargin
|
378
378
|
setLocalFiles("assets/jquery.js", "assets/picture.gif")
|
379
|
-
Push.
|
379
|
+
Push.push
|
380
380
|
sentPutObjectRequests.find(_.getKey == "assets/jquery.js").get.getMetadata.getCacheControl must equalTo("max-age=150")
|
381
381
|
sentPutObjectRequests.find(_.getKey == "assets/picture.gif").get.getMetadata.getCacheControl must equalTo("max-age=86400")
|
382
382
|
}
|
383
383
|
}
|
384
384
|
|
385
385
|
"s3_reduced_redundancy: true in config" should {
|
386
|
-
"result in uploads being marked with reduced redundancy" in new
|
386
|
+
"result in uploads being marked with reduced redundancy" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
387
387
|
config = "s3_reduced_redundancy: true"
|
388
388
|
setLocalFile("file.exe")
|
389
|
-
Push.
|
389
|
+
Push.push
|
390
390
|
sentPutObjectRequest.getStorageClass must equalTo("REDUCED_REDUNDANCY")
|
391
391
|
}
|
392
392
|
}
|
393
393
|
|
394
394
|
"s3_reduced_redundancy: false in config" should {
|
395
|
-
"result in uploads being marked with the default storage class" in new
|
395
|
+
"result in uploads being marked with the default storage class" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
396
396
|
config = "s3_reduced_redundancy: false"
|
397
397
|
setLocalFile("file.exe")
|
398
|
-
Push.
|
398
|
+
Push.push
|
399
399
|
sentPutObjectRequest.getStorageClass must beNull
|
400
400
|
}
|
401
401
|
}
|
402
402
|
|
403
403
|
"redirect in config" should {
|
404
|
-
"result in a redirect instruction that is sent to AWS" in new
|
404
|
+
"result in a redirect instruction that is sent to AWS" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
405
405
|
config = """
|
406
406
|
|redirects:
|
407
407
|
| index.php: /index.html
|
408
408
|
""".stripMargin
|
409
|
-
Push.
|
409
|
+
Push.push
|
410
410
|
sentPutObjectRequest.getRedirectLocation must equalTo("/index.html")
|
411
411
|
}
|
412
412
|
|
413
|
-
"result in max-age=0 Cache-Control header on the object" in new
|
413
|
+
"result in max-age=0 Cache-Control header on the object" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
414
414
|
config = """
|
415
415
|
|redirects:
|
416
416
|
| index.php: /index.html
|
417
417
|
""".stripMargin
|
418
|
-
Push.
|
418
|
+
Push.push
|
419
419
|
sentPutObjectRequest.getMetadata.getCacheControl must equalTo("max-age=0, no-cache")
|
420
420
|
}
|
421
421
|
}
|
422
422
|
|
423
423
|
"redirect in config and an object on the S3 bucket" should {
|
424
|
-
"not result in the S3 object being deleted" in new
|
424
|
+
"not result in the S3 object being deleted" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
425
425
|
config = """
|
426
426
|
|redirects:
|
427
427
|
| index.php: /index.html
|
428
428
|
""".stripMargin
|
429
429
|
setLocalFile("index.php")
|
430
430
|
setS3Files(S3File("index.php", "md5"))
|
431
|
-
Push.
|
431
|
+
Push.push
|
432
432
|
noDeletesOccurred must beTrue
|
433
433
|
}
|
434
434
|
}
|
435
435
|
|
436
436
|
"dotfiles" should {
|
437
|
-
"be included in the pushed files" in new
|
437
|
+
"be included in the pushed files" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
438
438
|
setLocalFile(".vimrc")
|
439
|
-
Push.
|
439
|
+
Push.push
|
440
440
|
sentPutObjectRequest.getKey must equalTo(".vimrc")
|
441
441
|
}
|
442
442
|
}
|
443
443
|
|
444
444
|
"content type inference" should {
|
445
|
-
"add charset=utf-8 to all html documents" in new
|
445
|
+
"add charset=utf-8 to all html documents" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
446
446
|
setLocalFile("index.html")
|
447
|
-
Push.
|
447
|
+
Push.push
|
448
448
|
sentPutObjectRequest.getMetadata.getContentType must equalTo("text/html; charset=utf-8")
|
449
449
|
}
|
450
450
|
|
451
|
-
"add charset=utf-8 to all text documents" in new
|
451
|
+
"add charset=utf-8 to all text documents" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
452
452
|
setLocalFile("index.txt")
|
453
|
-
Push.
|
453
|
+
Push.push
|
454
454
|
sentPutObjectRequest.getMetadata.getContentType must equalTo("text/plain; charset=utf-8")
|
455
455
|
}
|
456
456
|
|
457
|
-
"add charset=utf-8 to all json documents" in new
|
457
|
+
"add charset=utf-8 to all json documents" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
458
458
|
setLocalFile("data.json")
|
459
|
-
Push.
|
459
|
+
Push.push
|
460
460
|
sentPutObjectRequest.getMetadata.getContentType must equalTo("application/json; charset=utf-8")
|
461
461
|
}
|
462
462
|
|
463
|
-
"resolve the content type from file contents" in new
|
463
|
+
"resolve the content type from file contents" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
464
464
|
setLocalFileWithContent(("index", "<html><body><h1>hi</h1></body></html>"))
|
465
|
-
Push.
|
465
|
+
Push.push
|
466
466
|
sentPutObjectRequest.getMetadata.getContentType must equalTo("text/html; charset=utf-8")
|
467
467
|
}
|
468
468
|
}
|
469
469
|
|
470
470
|
"ERB in config file" should {
|
471
|
-
"be evaluated" in new
|
471
|
+
"be evaluated" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
472
472
|
config = """
|
473
473
|
|redirects:
|
474
474
|
|<%= ('a'..'f').to_a.map do |t| ' '+t+ ': /'+t+'.html' end.join('\n')%>
|
475
475
|
""".stripMargin
|
476
|
-
Push.
|
476
|
+
Push.push
|
477
477
|
sentPutObjectRequests.length must equalTo(6)
|
478
478
|
sentPutObjectRequests.forall(_.getRedirectLocation != null) must beTrue
|
479
479
|
}
|
480
480
|
}
|
481
481
|
|
482
|
-
"logging" should {
|
483
|
-
"print the debug messages when --verbose is defined" in new EmptySite with VerboseLogger with MockAWS with DefaultRunMode {
|
484
|
-
Push.pushSite
|
485
|
-
logEntries must contain("[debg] Querying S3 files")
|
486
|
-
}
|
487
|
-
|
488
|
-
"not print the debug messages by default" in new EmptySite with NonVerboseLogger with MockAWS with DefaultRunMode {
|
489
|
-
Push.pushSite
|
490
|
-
logEntries.forall(_.contains("[debg]")) must beFalse
|
491
|
-
}
|
492
|
-
}
|
493
|
-
|
494
482
|
"dry run" should {
|
495
|
-
"not push updates" in new
|
483
|
+
"not push updates" in new AllInSameDirectory with EmptySite with MockAWS with DryRunMode {
|
496
484
|
setLocalFileWithContent(("index.html", "<div>new</div>"))
|
497
485
|
setS3Files(S3File("index.html", md5Hex("<div>old</div>")))
|
498
|
-
Push.
|
486
|
+
Push.push
|
499
487
|
noUploadsOccurred must beTrue
|
500
488
|
}
|
501
489
|
|
502
|
-
"not push redirects" in new
|
490
|
+
"not push redirects" in new AllInSameDirectory with EmptySite with MockAWS with DryRunMode {
|
503
491
|
config =
|
504
492
|
"""
|
505
493
|
|redirects:
|
506
494
|
| index.php: /index.html
|
507
495
|
""".stripMargin
|
508
|
-
Push.
|
496
|
+
Push.push
|
509
497
|
noUploadsOccurred must beTrue
|
510
498
|
}
|
511
499
|
|
512
|
-
"not push deletes" in new
|
500
|
+
"not push deletes" in new AllInSameDirectory with EmptySite with MockAWS with DryRunMode {
|
513
501
|
setS3Files(S3File("index.html", md5Hex("<div>old</div>")))
|
514
|
-
Push.
|
502
|
+
Push.push
|
515
503
|
noUploadsOccurred must beTrue
|
516
504
|
}
|
517
505
|
|
518
|
-
"not push new files" in new
|
506
|
+
"not push new files" in new AllInSameDirectory with EmptySite with MockAWS with DryRunMode {
|
519
507
|
setLocalFile("index.html")
|
520
|
-
Push.
|
508
|
+
Push.push
|
521
509
|
noUploadsOccurred must beTrue
|
522
510
|
}
|
523
511
|
|
524
|
-
"not invalidate files" in new
|
512
|
+
"not invalidate files" in new AllInSameDirectory with EmptySite with MockAWS with DryRunMode {
|
525
513
|
config = "cloudfront_invalidation_id: AABBCC"
|
526
514
|
setS3Files(S3File("index.html", md5Hex("<div>old</div>")))
|
527
|
-
Push.
|
515
|
+
Push.push
|
528
516
|
noInvalidationsOccurred must beTrue
|
529
517
|
}
|
530
518
|
}
|
531
519
|
|
520
|
+
"Jekyll site" should {
|
521
|
+
"be detected automatically" in new JekyllSite with EmptySite with MockAWS with DefaultRunMode {
|
522
|
+
setLocalFile("index.html")
|
523
|
+
Push.push
|
524
|
+
sentPutObjectRequests.length must equalTo(1)
|
525
|
+
}
|
526
|
+
}
|
527
|
+
|
528
|
+
"Nanoc site" should {
|
529
|
+
"be detected automatically" in new NanocSite with EmptySite with MockAWS with DefaultRunMode {
|
530
|
+
setLocalFile("index.html")
|
531
|
+
Push.push
|
532
|
+
sentPutObjectRequests.length must equalTo(1)
|
533
|
+
}
|
534
|
+
}
|
535
|
+
|
532
536
|
trait DefaultRunMode {
|
533
537
|
implicit def pushMode: PushMode = new PushMode {
|
534
538
|
def dryRun = false
|
@@ -542,23 +546,6 @@ class S3WebsiteSpec extends Specification {
|
|
542
546
|
}
|
543
547
|
|
544
548
|
trait MockAWS extends MockS3 with MockCloudFront with Scope
|
545
|
-
|
546
|
-
trait VerboseLogger extends LogCapturer {
|
547
|
-
implicit val logger: Logger = new Logger(verboseOutput = true, logMessage = captureAndPrint)
|
548
|
-
}
|
549
|
-
|
550
|
-
trait NonVerboseLogger extends LogCapturer {
|
551
|
-
implicit val logger: Logger = new Logger(verboseOutput = false, logMessage = captureAndPrint)
|
552
|
-
}
|
553
|
-
|
554
|
-
trait LogCapturer {
|
555
|
-
val logEntries: mutable.Buffer[String] = mutable.Buffer()
|
556
|
-
|
557
|
-
def captureAndPrint(msg: String) {
|
558
|
-
logEntries += msg.replaceAll("\u001B\\[[;\\d]*m", "") // Remove ANSI coloring
|
559
|
-
println(msg)
|
560
|
-
}
|
561
|
-
}
|
562
549
|
|
563
550
|
trait MockCloudFront extends MockAWSHelper {
|
564
551
|
val amazonCloudFrontClient = mock(classOf[AmazonCloudFront])
|
@@ -599,8 +586,6 @@ class S3WebsiteSpec extends Specification {
|
|
599
586
|
}).when(amazonCloudFrontClient).createInvalidation(Matchers.anyObject())
|
600
587
|
}
|
601
588
|
|
602
|
-
|
603
|
-
|
604
589
|
def setCloudFrontAsInternallyBroken() {
|
605
590
|
when(amazonCloudFrontClient.createInvalidation(Matchers.anyObject())).thenThrow(new AmazonServiceException("CloudFront is down"))
|
606
591
|
}
|
@@ -699,16 +684,40 @@ class S3WebsiteSpec extends Specification {
|
|
699
684
|
}
|
700
685
|
}
|
701
686
|
|
702
|
-
trait
|
703
|
-
val
|
704
|
-
|
687
|
+
trait Directories extends BeforeAfter {
|
688
|
+
implicit final val workingDirectory: File = new File(FileUtils.getTempDirectory, "s3_website_dir" + Random.nextLong())
|
689
|
+
val siteDirectory: File // Represents the --site=X option
|
690
|
+
val configDirectory: File = workingDirectory // Represents the --config-dir=X option
|
691
|
+
|
692
|
+
lazy val allDirectories = workingDirectory :: siteDirectory :: configDirectory :: Nil
|
693
|
+
|
694
|
+
def before {
|
695
|
+
allDirectories foreach forceMkdir
|
696
|
+
}
|
705
697
|
|
706
698
|
def after {
|
707
|
-
|
699
|
+
allDirectories foreach { dir =>
|
700
|
+
if (dir.exists) forceDelete(dir)
|
701
|
+
}
|
708
702
|
}
|
709
703
|
}
|
704
|
+
|
705
|
+
/**
|
706
|
+
* Represents the situation where the current working directory, site dir and config dir are in the same directory
|
707
|
+
*/
|
708
|
+
trait AllInSameDirectory extends Directories {
|
709
|
+
val siteDirectory = workingDirectory
|
710
|
+
}
|
711
|
+
|
712
|
+
trait JekyllSite extends Directories {
|
713
|
+
val siteDirectory = new File(workingDirectory, "_site")
|
714
|
+
}
|
715
|
+
|
716
|
+
trait NanocSite extends Directories {
|
717
|
+
val siteDirectory = new File(workingDirectory, "public/output")
|
718
|
+
}
|
710
719
|
|
711
|
-
trait EmptySite extends
|
720
|
+
trait EmptySite extends Directories {
|
712
721
|
type LocalFileWithContent = (String, String)
|
713
722
|
|
714
723
|
val localFilesWithContent: mutable.Buffer[LocalFileWithContent] = mutable.Buffer()
|
@@ -717,15 +726,15 @@ class S3WebsiteSpec extends Specification {
|
|
717
726
|
def setLocalFileWithContent(fileNameAndContent: LocalFileWithContent) = localFilesWithContent += fileNameAndContent
|
718
727
|
def setLocalFilesWithContent(fileNamesAndContent: LocalFileWithContent*) = fileNamesAndContent foreach setLocalFileWithContent
|
719
728
|
var config = ""
|
720
|
-
|
729
|
+
val baseConfig =
|
721
730
|
"""
|
722
731
|
|s3_id: foo
|
723
732
|
|s3_secret: bar
|
724
733
|
|s3_bucket: bucket
|
725
734
|
""".stripMargin
|
726
735
|
|
727
|
-
implicit lazy val
|
728
|
-
|
736
|
+
implicit lazy val cliArgs: CliArgs = siteWithFilesAndContent(config, localFilesWithContent)
|
737
|
+
def pushMode: PushMode // Represents the --dry-run switch
|
729
738
|
|
730
739
|
def buildSite(
|
731
740
|
config: String = "",
|
@@ -735,24 +744,30 @@ class S3WebsiteSpec extends Specification {
|
|
735
744
|
|s3_secret: bar
|
736
745
|
|s3_bucket: bucket
|
737
746
|
""".stripMargin
|
738
|
-
):
|
739
|
-
val configFile = new File(
|
747
|
+
): CliArgs = {
|
748
|
+
val configFile = new File(configDirectory, "s3_website.yml")
|
740
749
|
write(configFile,
|
741
750
|
s"""
|
742
751
|
|$baseConfig
|
743
752
|
|$config
|
744
753
|
""".stripMargin
|
745
754
|
)
|
746
|
-
|
747
|
-
|
748
|
-
|
755
|
+
new CliArgs {
|
756
|
+
override def verbose = true
|
757
|
+
|
758
|
+
override def dryRun = pushMode.dryRun
|
759
|
+
|
760
|
+
override def site = siteDirectory.getAbsolutePath
|
761
|
+
|
762
|
+
override def configDir = configDirectory.getAbsolutePath
|
763
|
+
}
|
749
764
|
}
|
750
765
|
|
751
|
-
def siteWithFilesAndContent(config: String = "", localFilesWithContent: Seq[LocalFileWithContent]):
|
766
|
+
def siteWithFilesAndContent(config: String = "", localFilesWithContent: Seq[LocalFileWithContent]): CliArgs = {
|
752
767
|
localFilesWithContent.foreach {
|
753
768
|
case (filePath, content) =>
|
754
|
-
val file = new File(
|
755
|
-
|
769
|
+
val file = new File(siteDirectory, filePath)
|
770
|
+
forceMkdir(file.getParentFile)
|
756
771
|
file.createNewFile()
|
757
772
|
write(file, content)
|
758
773
|
}
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: s3_website_monadic
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.30
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lauri Lehmijoki
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-05-
|
11
|
+
date: 2014-05-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -102,9 +102,6 @@ files:
|
|
102
102
|
- build.sbt
|
103
103
|
- changelog.md
|
104
104
|
- lib/s3_website.rb
|
105
|
-
- lib/s3_website/jekyll.rb
|
106
|
-
- lib/s3_website/nanoc.rb
|
107
|
-
- lib/s3_website/paths.rb
|
108
105
|
- lib/s3_website/version.rb
|
109
106
|
- project/assembly.sbt
|
110
107
|
- project/build.properties
|
@@ -124,6 +121,7 @@ files:
|
|
124
121
|
- src/main/scala/s3/website/model/Site.scala
|
125
122
|
- src/main/scala/s3/website/model/errors.scala
|
126
123
|
- src/main/scala/s3/website/model/push.scala
|
124
|
+
- src/main/scala/s3/website/model/ssg.scala
|
127
125
|
- src/main/scala/s3/website/package.scala
|
128
126
|
- src/test/scala/s3/website/S3WebsiteSpec.scala
|
129
127
|
homepage: https://github.com/laurilehmijoki/s3_website/tree/s3_website_monadic
|
data/lib/s3_website/jekyll.rb
DELETED
data/lib/s3_website/nanoc.rb
DELETED
data/lib/s3_website/paths.rb
DELETED
@@ -1,40 +0,0 @@
|
|
1
|
-
module S3Website
|
2
|
-
class Paths
|
3
|
-
def self.site_paths
|
4
|
-
[Nanoc::SITE_PATH, Jekyll::SITE_PATH]
|
5
|
-
end
|
6
|
-
|
7
|
-
def self.infer_site_path(candidate_path, pwd)
|
8
|
-
if candidate_path == 'infer automatically'
|
9
|
-
infer_automatically pwd
|
10
|
-
else
|
11
|
-
candidate_path_if_exists candidate_path
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
private
|
16
|
-
|
17
|
-
def self.candidate_path_if_exists(candidate_path)
|
18
|
-
raise NoWebsiteDirectoryFound.new(
|
19
|
-
"Can't find a website in " + candidate_path
|
20
|
-
) unless File.exists? candidate_path
|
21
|
-
candidate_path
|
22
|
-
end
|
23
|
-
|
24
|
-
def self.infer_automatically(pwd)
|
25
|
-
site_path = site_paths.
|
26
|
-
map do |site_path|
|
27
|
-
pwd + '/' + site_path
|
28
|
-
end.
|
29
|
-
find do |site_path|
|
30
|
-
File.exists? site_path
|
31
|
-
end
|
32
|
-
if site_path
|
33
|
-
site_path
|
34
|
-
else
|
35
|
-
puts "Could not find a website directory. Specify one with the --site parameter."
|
36
|
-
exit
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|