s3_website 2.4.0 → 2.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b26682cbb78c10aa527d65993f265c3b97af2450
4
- data.tar.gz: 3bd33a3a6440de3a8f83b257828bc815e0e4e439
3
+ metadata.gz: d0ae62a6f1d23ff2c577665bf70c0f0ab7a4eb56
4
+ data.tar.gz: 012d1764cf766d1b166647c9fe109186622d92a0
5
5
  SHA512:
6
- metadata.gz: c102eab9efbb9d438329a32456c5ee55a11f096fe18e25767eb7029055d76c3176c1b7bd0c085fe1df8fcf19f54708fe30c8261bec947df8a299548e27dfedde
7
- data.tar.gz: 89fc0705033813863965ea64abec0ee1e3137f70bcf5fdfe3bda40380aeb062cd5d9a6c79a3d535ae1cd4ce4ba579388abc595239a1bdec83cf2c923c8259f8c
6
+ metadata.gz: 9801caa38458f53ca30095735cd051eed8e302370a4026d97980d09f3b2a90370ac0a21d5c8fe5c2717f6c57df5eb9f88e851d0ac106adcf34d2ee234719cf04
7
+ data.tar.gz: 3cf7599615e920cf6000ef4d2525013f74421b5ca759add184012df9d44ef4facdb3657d73c0d9a3c4526e3293e25fdd34869514edaeaaf730156142add4fdcf
@@ -1,3 +1,7 @@
1
1
  language: scala
2
2
  scala:
3
- 2.11.1
3
+ 2.11.2
4
+ jdk:
5
+ - openjdk6
6
+ - openjdk7
7
+ - oraclejdk8
data/README.md CHANGED
@@ -110,6 +110,9 @@ max_age:
110
110
 
111
111
  Place the configuration into the file `s3_website.yml`.
112
112
 
113
+ After changing the `max_age` setting, push with the `--force` option.
114
+ Force-pushing allows you to update the S3 object metadata of existing files.
115
+
113
116
  ### Gzip Compression
114
117
 
115
118
  If you choose, you can use compress certain file types before uploading them to
@@ -136,6 +139,8 @@ gzip:
136
139
  Remember that the extensions here are referring to the *compiled* extensions,
137
140
  not the pre-processed extensions.
138
141
 
142
+ After changing the `gzip` setting, push with the `--force` option.
143
+
139
144
  ### Using non-standard AWS regions
140
145
 
141
146
  By default, `s3_website` uses the US Standard Region. You can upload your
@@ -196,6 +201,9 @@ You can reduce the cost of hosting your blog on S3 by using Reduced Redundancy S
196
201
  * All objects uploaded after this change will use the Reduced Redundancy Storage.
197
202
  * If you want to change all of the files in the bucket, you can change them through the AWS console, or update the timestamp on the files before running `s3_website` again
198
203
 
204
+ After changing the `s3_reduced_redundancy` setting, push with the `--force`
205
+ option.
206
+
199
207
  ### How to use Cloudfront to deliver your blog
200
208
 
201
209
  It is easy to deliver your S3-based web site via Cloudfront, the CDN of Amazon.
@@ -78,6 +78,12 @@ class Cli < Thor
78
78
  :default => false,
79
79
  :desc => "Print verbose output"
80
80
  )
81
+ option(
82
+ :force,
83
+ :type => :boolean,
84
+ :default => false,
85
+ :desc => "Skip diff calculation and push all the files. This option is useful when you need to update metadata on the S3 objects."
86
+ )
81
87
  option(
82
88
  :dry_run,
83
89
  :type => :boolean,
@@ -2,6 +2,13 @@
2
2
 
3
3
  This project uses [Semantic Versioning](http://semver.org).
4
4
 
5
+ ## 2.5.0
6
+
7
+ * Add `push --force` option
8
+
9
+ When the user pushes with force, s3_website skips the diff. This is helpful for the
10
+ users who wish to update the S3 object metadata.
11
+
5
12
  ## 2.4.0
6
13
 
7
14
  * Add `ignore_on_server: _DELETE_NOTHING_ON_THE_S3_BUCKET_` for the sake of convenience
@@ -1,3 +1,3 @@
1
1
  module S3Website
2
- VERSION = '2.4.0'
2
+ VERSION = '2.5.0'
3
3
  end
@@ -14,9 +14,9 @@ import s3.website.model.Config.awsCredentials
14
14
 
15
15
  object CloudFront {
16
16
  def invalidate(invalidationBatch: InvalidationBatch, distributionId: String, attempt: Attempt = 1)
17
- (implicit ec: ExecutionContextExecutor, cloudFrontSettings: CloudFrontSetting, config: Config, logger: Logger, pushMode: PushMode): InvalidationResult =
17
+ (implicit ec: ExecutionContextExecutor, cloudFrontSettings: CloudFrontSetting, config: Config, logger: Logger, pushOptions: PushOptions): InvalidationResult =
18
18
  Future {
19
- if (!pushMode.dryRun) cloudFront createInvalidation new CreateInvalidationRequest(distributionId, invalidationBatch)
19
+ if (!pushOptions.dryRun) cloudFront createInvalidation new CreateInvalidationRequest(distributionId, invalidationBatch)
20
20
  val result = SuccessfulInvalidation(invalidationBatch.getPaths.getItems.size())
21
21
  logger.debug(invalidationBatch.getPaths.getItems.map(item => s"${Invalidated.renderVerb} $item") mkString "\n")
22
22
  logger.info(result)
@@ -27,7 +27,7 @@ object CloudFront {
27
27
  ))
28
28
 
29
29
  def tooManyInvalidationsRetry(invalidationBatch: InvalidationBatch, distributionId: String, attempt: Attempt)
30
- (implicit ec: ExecutionContextExecutor, logger: Logger, cloudFrontSettings: CloudFrontSetting, config: Config, pushMode: PushMode):
30
+ (implicit ec: ExecutionContextExecutor, logger: Logger, cloudFrontSettings: CloudFrontSetting, config: Config, pushOptions: PushOptions):
31
31
  PartialFunction[Throwable, InvalidationResult] = {
32
32
  case e: TooManyInvalidationsInProgressException =>
33
33
  val duration: Duration = Duration(
@@ -57,7 +57,7 @@ object CloudFront {
57
57
 
58
58
  type CloudFrontClientProvider = (Config) => AmazonCloudFront
59
59
 
60
- case class SuccessfulInvalidation(invalidatedItemsCount: Int)(implicit pushMode: PushMode) extends SuccessReport {
60
+ case class SuccessfulInvalidation(invalidatedItemsCount: Int)(implicit pushOptions: PushOptions) extends SuccessReport {
61
61
  def reportMessage = s"${Invalidated.renderVerb} ${invalidatedItemsCount ofType "item"} on CloudFront"
62
62
  }
63
63
 
@@ -5,7 +5,7 @@ import s3.website.model.Site._
5
5
  import scala.concurrent.{ExecutionContextExecutor, Future, Await}
6
6
  import scala.concurrent.duration._
7
7
  import scala.language.postfixOps
8
- import s3.website.Diff.{resolveDeletes, resolveDiff}
8
+ import s3.website.UploadHelper.{resolveDeletes, resolveUploads}
9
9
  import s3.website.S3._
10
10
  import scala.concurrent.ExecutionContext.fromExecutor
11
11
  import java.util.concurrent.Executors.newFixedThreadPool
@@ -44,12 +44,14 @@ object Push {
44
44
  @Option(longName = Array("config-dir"), defaultToNull = true) def configDir: String
45
45
  @Option def verbose: Boolean
46
46
  @Option(longName = Array("dry-run")) def dryRun: Boolean
47
+ @Option(longName = Array("force")) def force: Boolean
47
48
  }
48
49
 
49
50
  def push(implicit cliArgs: CliArgs, s3Settings: S3Setting, cloudFrontSettings: CloudFrontSetting, workingDirectory: File): ExitCode = {
50
51
  implicit val logger: Logger = new Logger(cliArgs.verbose)
51
- implicit val pushMode = new PushMode {
52
+ implicit val pushOptions = new PushOptions {
52
53
  def dryRun = cliArgs.dryRun
54
+ def force = cliArgs.force
53
55
  }
54
56
 
55
57
  implicit val yamlConfig = S3_website_yml(new File(Option(cliArgs.configDir).getOrElse(workingDirectory.getPath) + "/s3_website.yml"))
@@ -75,7 +77,7 @@ object Push {
75
77
  s3Settings: S3Setting,
76
78
  cloudFrontSettings: CloudFrontSetting,
77
79
  logger: Logger,
78
- pushMode: PushMode
80
+ pushOptions: PushOptions
79
81
  ): ExitCode = {
80
82
  logger.info(s"${Deploy.renderVerb} ${site.rootDirectory}/* to ${site.config.s3_bucket}")
81
83
  val redirects = Redirect.resolveRedirects
@@ -83,7 +85,7 @@ object Push {
83
85
  val redirectReports: PushReports = redirects.map(S3 uploadRedirect _) map (Right(_))
84
86
 
85
87
  val pushReports: Future[PushReports] = for {
86
- errorOrUploads: Either[ErrorReport, Seq[Upload]] <- resolveDiff(s3FilesFuture)
88
+ errorOrUploads: Either[ErrorReport, Seq[Upload]] <- resolveUploads(s3FilesFuture)
87
89
  } yield {
88
90
  val uploadReports: PushReports = errorOrUploads.fold(
89
91
  error => Left(error) :: Nil,
@@ -108,7 +110,7 @@ object Push {
108
110
 
109
111
  def invalidateCloudFrontItems
110
112
  (finishedPushOperations: FinishedPushOperations)
111
- (implicit config: Config, cloudFrontSettings: CloudFrontSetting, ec: ExecutionContextExecutor, logger: Logger, pushMode: PushMode):
113
+ (implicit config: Config, cloudFrontSettings: CloudFrontSetting, ec: ExecutionContextExecutor, logger: Logger, pushOptions: PushOptions):
112
114
  Option[InvalidationSucceeded] =
113
115
  config.cloudfront_distribution_id.map { distributionId =>
114
116
  val pushSuccessReports =
@@ -141,7 +143,7 @@ object Push {
141
143
  type InvalidationSucceeded = Boolean
142
144
 
143
145
  def afterPushFinished(finishedPushOps: FinishedPushOperations, invalidationSucceeded: Option[Boolean])
144
- (implicit config: Config, logger: Logger, pushMode: PushMode): ExitCode = {
146
+ (implicit config: Config, logger: Logger, pushOptions: PushOptions): ExitCode = {
145
147
  val pushCounts = resolvePushCounts(finishedPushOps)
146
148
  logger.info(s"Summary: ${pushCountsToString(pushCounts)}")
147
149
  val pushOpExitCode = finishedPushOps.foldLeft(0) { (memo, finishedUpload) =>
@@ -158,7 +160,7 @@ object Push {
158
160
  val exitCode = (pushOpExitCode + cloudFrontInvalidationExitCode) min 1
159
161
 
160
162
  exitCode match {
161
- case 0 if !pushMode.dryRun && pushCounts.thereWasSomethingToPush =>
163
+ case 0 if !pushOptions.dryRun && pushCounts.thereWasSomethingToPush =>
162
164
  logger.info(s"Successfully pushed the website to http://${config.s3_bucket}.${config.s3_endpoint.s3WebsiteHostname}")
163
165
  case 1 =>
164
166
  logger.fail(s"Failed to push the website to http://${config.s3_bucket}.${config.s3_endpoint.s3WebsiteHostname}")
@@ -191,7 +193,7 @@ object Push {
191
193
  )
192
194
  }
193
195
 
194
- def pushCountsToString(pushCounts: PushCounts)(implicit pushMode: PushMode): String =
196
+ def pushCountsToString(pushCounts: PushCounts)(implicit pushOptions: PushOptions): String =
195
197
  pushCounts match {
196
198
  case PushCounts(updates, newFiles, failures, redirects, deletes, _, _)
197
199
  if updates == 0 && newFiles == 0 && failures == 0 && redirects == 0 && deletes == 0 =>
@@ -18,20 +18,20 @@ import scala.util.Try
18
18
  object S3 {
19
19
 
20
20
  def uploadRedirect(redirect: Redirect, a: Attempt = 1)
21
- (implicit config: Config, s3Settings: S3Setting, pushMode: PushMode, executor: ExecutionContextExecutor, logger: Logger) =
21
+ (implicit config: Config, s3Settings: S3Setting, pushOptions: PushOptions, executor: ExecutionContextExecutor, logger: Logger) =
22
22
  uploadToS3(Right(redirect))
23
23
 
24
24
  def uploadFile(up: Upload, a: Attempt = 1)
25
- (implicit config: Config, s3Settings: S3Setting, pushMode: PushMode, executor: ExecutionContextExecutor, logger: Logger) =
25
+ (implicit config: Config, s3Settings: S3Setting, pushOptions: PushOptions, executor: ExecutionContextExecutor, logger: Logger) =
26
26
  uploadToS3(Left(up))
27
27
 
28
28
  def uploadToS3(source: Either[Upload, Redirect], a: Attempt = 1)
29
- (implicit config: Config, s3Settings: S3Setting, pushMode: PushMode, executor: ExecutionContextExecutor, logger: Logger):
29
+ (implicit config: Config, s3Settings: S3Setting, pushOptions: PushOptions, executor: ExecutionContextExecutor, logger: Logger):
30
30
  Future[Either[FailedUpload, SuccessfulUpload]] =
31
31
  Future {
32
32
  val putObjectRequest = toPutObjectRequest(source).get
33
33
  val uploadDuration =
34
- if (pushMode.dryRun) None
34
+ if (pushOptions.dryRun) None
35
35
  else Some(recordUploadDuration(putObjectRequest, s3Settings.s3Client(config) putObject putObjectRequest))
36
36
  val report = SuccessfulUpload(
37
37
  source.fold(_.s3Key, _.s3Key),
@@ -49,10 +49,10 @@ object S3 {
49
49
  )
50
50
 
51
51
  def delete(s3Key: S3Key, a: Attempt = 1)
52
- (implicit config: Config, s3Settings: S3Setting, pushMode: PushMode, executor: ExecutionContextExecutor, logger: Logger):
52
+ (implicit config: Config, s3Settings: S3Setting, pushOptions: PushOptions, executor: ExecutionContextExecutor, logger: Logger):
53
53
  Future[Either[FailedDelete, SuccessfulDelete]] =
54
54
  Future {
55
- if (!pushMode.dryRun) s3Settings.s3Client(config) deleteObject(config.s3_bucket, s3Key)
55
+ if (!pushOptions.dryRun) s3Settings.s3Client(config) deleteObject(config.s3_bucket, s3Key)
56
56
  val report = SuccessfulDelete(s3Key)
57
57
  logger.info(report)
58
58
  Right(report)
@@ -110,7 +110,7 @@ object S3 {
110
110
  def awsS3Client(config: Config) = new AmazonS3Client(awsCredentials(config))
111
111
 
112
112
  def resolveS3Files(nextMarker: Option[String] = None, alreadyResolved: Seq[S3File] = Nil, attempt: Attempt = 1)
113
- (implicit config: Config, s3Settings: S3Setting, ec: ExecutionContextExecutor, logger: Logger, pushMode: PushMode):
113
+ (implicit config: Config, s3Settings: S3Setting, ec: ExecutionContextExecutor, logger: Logger, pushOptions: PushOptions):
114
114
  Future[Either[ErrorReport, Seq[S3File]]] = Future {
115
115
  logger.debug(nextMarker.fold
116
116
  ("Querying S3 files")
@@ -152,7 +152,7 @@ object S3 {
152
152
  case class SuccessfulUpload(s3Key: S3Key,
153
153
  details: Either[SuccessfulNewOrCreatedDetails, SuccessfulRedirectDetails],
154
154
  putObjectRequest: PutObjectRequest)
155
- (implicit pushMode: PushMode, logger: Logger) extends PushSuccessReport {
155
+ (implicit pushOptions: PushOptions, logger: Logger) extends PushSuccessReport {
156
156
  def reportMessage =
157
157
  details.fold(
158
158
  newOrCreatedDetails => s"${newOrCreatedDetails.uploadType.pushAction} $s3Key ($reportDetails)",
@@ -205,7 +205,7 @@ object S3 {
205
205
  }
206
206
  }
207
207
 
208
- case class SuccessfulDelete(s3Key: String)(implicit pushMode: PushMode) extends PushSuccessReport {
208
+ case class SuccessfulDelete(s3Key: String)(implicit pushOptions: PushOptions) extends PushSuccessReport {
209
209
  def reportMessage = s"${Deleted.renderVerb} $s3Key"
210
210
  }
211
211
 
@@ -1,27 +1,31 @@
1
1
  package s3.website
2
2
 
3
+ import s3.website.model.Files.listSiteFiles
3
4
  import s3.website.model._
4
5
  import s3.website.Ruby.rubyRegexMatches
5
6
  import scala.concurrent.{ExecutionContextExecutor, Future}
6
7
  import scala.util.{Failure, Success, Try}
7
8
  import java.io.File
8
9
 
9
- object Diff {
10
+ object UploadHelper {
10
11
 
11
12
  type FutureUploads = Future[Either[ErrorReport, Seq[Upload]]]
12
13
 
13
- def resolveDiff(s3FilesFuture: Future[Either[ErrorReport, Seq[S3File]]])
14
- (implicit site: Site, logger: Logger, executor: ExecutionContextExecutor): FutureUploads =
15
- resolveDiffAgainstGetBucketResponse(s3FilesFuture)
14
+ def resolveUploads(s3FilesFuture: Future[Either[ErrorReport, Seq[S3File]]])
15
+ (implicit site: Site, pushOptions: PushOptions, logger: Logger, executor: ExecutionContextExecutor): FutureUploads =
16
+ resolveUploadsAgainstGetBucketResponse(s3FilesFuture)
16
17
 
17
- private def resolveDiffAgainstGetBucketResponse(s3FilesFuture: Future[Either[ErrorReport, Seq[S3File]]])
18
- (implicit site: Site, logger: Logger, executor: ExecutionContextExecutor): FutureUploads =
18
+ private def resolveUploadsAgainstGetBucketResponse(s3FilesFuture: Future[Either[ErrorReport, Seq[S3File]]])
19
+ (implicit site: Site,
20
+ pushOptions: PushOptions,
21
+ logger: Logger,
22
+ executor: ExecutionContextExecutor): FutureUploads =
19
23
  s3FilesFuture.map { errorOrS3Files =>
20
24
  errorOrS3Files.right.flatMap { s3Files =>
21
25
  Try {
22
26
  val s3KeyIndex = s3Files.map(_.s3Key).toSet
23
27
  val s3Md5Index = s3Files.map(_.md5).toSet
24
- val siteFiles = Files.listSiteFiles
28
+ val siteFiles = listSiteFiles
25
29
  val existsOnS3 = (f: File) => s3KeyIndex contains site.resolveS3Key(f)
26
30
  val isChangedOnS3 = (upload: Upload) => !(s3Md5Index contains upload.md5.get)
27
31
  val newUploads = siteFiles collect {
@@ -29,7 +33,7 @@ object Diff {
29
33
  }
30
34
  val changedUploads = siteFiles collect {
31
35
  case file if existsOnS3(file) => Upload(file, FileUpdate)
32
- } filter isChangedOnS3
36
+ } filter (if (pushOptions.force) selectAllFiles else isChangedOnS3)
33
37
  newUploads ++ changedUploads
34
38
  } match {
35
39
  case Success(ok) => Right(ok)
@@ -38,13 +42,15 @@ object Diff {
38
42
  }
39
43
  }
40
44
 
45
+ val selectAllFiles = (upload: Upload) => true
46
+
41
47
  def resolveDeletes(s3Files: Future[Either[ErrorReport, Seq[S3File]]], redirects: Seq[Redirect])
42
48
  (implicit site: Site, logger: Logger, executor: ExecutionContextExecutor): Future[Either[ErrorReport, Seq[S3Key]]] =
43
49
  if (site.config.ignore_on_server.contains(Left(DELETE_NOTHING_MAGIC_WORD))) {
44
50
  logger.debug(s"Ignoring all files on the bucket, since the setting $DELETE_NOTHING_MAGIC_WORD is on.")
45
51
  Future(Right(Nil))
46
52
  } else {
47
- val localS3Keys = Files.listSiteFiles.map(site resolveS3Key)
53
+ val localS3Keys = listSiteFiles.map(site resolveS3Key)
48
54
 
49
55
  s3Files map { s3Files: Either[ErrorReport, Seq[S3File]] =>
50
56
  for {
@@ -40,8 +40,16 @@ package object website {
40
40
  def retryTimeUnit: TimeUnit
41
41
  }
42
42
 
43
- trait PushMode {
43
+ trait PushOptions {
44
+ /**
45
+ * @return true if the CLI option --dry-run is on
46
+ */
44
47
  def dryRun: Boolean
48
+
49
+ /**
50
+ * @return true if the CLI option --force is on
51
+ */
52
+ def force: Boolean
45
53
  }
46
54
 
47
55
  type S3Key = String
@@ -51,8 +59,8 @@ package object website {
51
59
  trait PushAction {
52
60
  def actionName = getClass.getSimpleName.replace("$", "") // case object class names contain the '$' char
53
61
 
54
- def renderVerb(implicit pushMode: PushMode): String =
55
- if (pushMode.dryRun)
62
+ def renderVerb(implicit pushOptions: PushOptions): String =
63
+ if (pushOptions.dryRun)
56
64
  s"Would have ${actionName.toLowerCase}"
57
65
  else
58
66
  s"$actionName"
@@ -65,15 +73,15 @@ package object website {
65
73
  case object Invalidated extends PushAction
66
74
  case object Applied extends PushAction
67
75
  case object PushNothing extends PushAction {
68
- override def renderVerb(implicit pushMode: PushMode) =
69
- if (pushMode.dryRun)
76
+ override def renderVerb(implicit pushOptions: PushOptions) =
77
+ if (pushOptions.dryRun)
70
78
  s"Would have pushed nothing"
71
79
  else
72
80
  s"There was nothing to push"
73
81
  }
74
82
  case object Deploy extends PushAction {
75
- override def renderVerb(implicit pushMode: PushMode) =
76
- if (pushMode.dryRun)
83
+ override def renderVerb(implicit pushOptions: PushOptions) =
84
+ if (pushOptions.dryRun)
77
85
  s"Simulating the deployment of"
78
86
  else
79
87
  s"Deploying"
@@ -18,7 +18,7 @@ import org.mockito.{ArgumentCaptor, Matchers, Mockito}
18
18
  import org.specs2.mutable.{BeforeAfter, Specification}
19
19
  import org.specs2.specification.Scope
20
20
  import s3.website.CloudFront.CloudFrontSetting
21
- import s3.website.Diff.DELETE_NOTHING_MAGIC_WORD
21
+ import s3.website.UploadHelper.DELETE_NOTHING_MAGIC_WORD
22
22
  import s3.website.Push.{CliArgs}
23
23
  import s3.website.S3.S3Setting
24
24
  import s3.website.model.Config.S3_website_yml
@@ -611,15 +611,24 @@ class S3WebsiteSpec extends Specification {
611
611
  }
612
612
  }
613
613
 
614
+ "push --force" should {
615
+ "push all the files whether they have changed or not" in new ForcePush {
616
+ setLocalFileWithContent(("index.html", "<h1>hi</h1>"))
617
+ setS3Files(S3File("index.html", "1c5117e5839ad8fc00ce3c41296255a1" /* md5 of the gzip of the file contents */))
618
+ push
619
+ sentPutObjectRequest.getKey must equalTo("index.html")
620
+ }
621
+ }
622
+
614
623
  "dry run" should {
615
- "not push updates" in new SiteLocationFromCliArg with EmptySite with MockAWS with DryRunMode {
624
+ "not push updates" in new DryRun {
616
625
  setLocalFileWithContent(("index.html", "<div>new</div>"))
617
626
  setS3Files(S3File("index.html", md5Hex("<div>old</div>")))
618
627
  push
619
628
  noUploadsOccurred must beTrue
620
629
  }
621
630
 
622
- "not push redirects" in new SiteLocationFromCliArg with EmptySite with MockAWS with DryRunMode {
631
+ "not push redirects" in new DryRun {
623
632
  config =
624
633
  """
625
634
  |redirects:
@@ -629,19 +638,19 @@ class S3WebsiteSpec extends Specification {
629
638
  noUploadsOccurred must beTrue
630
639
  }
631
640
 
632
- "not push deletes" in new SiteLocationFromCliArg with EmptySite with MockAWS with DryRunMode {
641
+ "not push deletes" in new DryRun {
633
642
  setS3Files(S3File("index.html", md5Hex("<div>old</div>")))
634
643
  push
635
644
  noUploadsOccurred must beTrue
636
645
  }
637
646
 
638
- "not push new files" in new SiteLocationFromCliArg with EmptySite with MockAWS with DryRunMode {
647
+ "not push new files" in new DryRun {
639
648
  setLocalFile("index.html")
640
649
  push
641
650
  noUploadsOccurred must beTrue
642
651
  }
643
652
 
644
- "not invalidate files" in new SiteLocationFromCliArg with EmptySite with MockAWS with DryRunMode {
653
+ "not invalidate files" in new DryRun {
645
654
  config = "cloudfront_invalidation_id: AABBCC"
646
655
  setS3Files(S3File("index.html", md5Hex("<div>old</div>")))
647
656
  push
@@ -667,18 +676,32 @@ class S3WebsiteSpec extends Specification {
667
676
 
668
677
  trait BasicSetup extends SiteLocationFromCliArg with EmptySite with MockAWS with DefaultRunMode
669
678
 
679
+ trait ForcePush extends SiteLocationFromCliArg with EmptySite with MockAWS with ForcePushMode
680
+
681
+ trait DryRun extends SiteLocationFromCliArg with EmptySite with MockAWS with DryRunMode
682
+
670
683
  trait DefaultRunMode {
671
- implicit def pushMode: PushMode = new PushMode {
684
+ implicit def pushOptions: PushOptions = new PushOptions {
672
685
  def dryRun = false
686
+ def force = false
673
687
  }
674
688
  }
675
689
 
676
690
  trait DryRunMode {
677
- implicit def pushMode: PushMode = new PushMode {
691
+ implicit def pushOptions: PushOptions = new PushOptions {
678
692
  def dryRun = true
693
+ def force = false
679
694
  }
680
695
  }
681
696
 
697
+ trait ForcePushMode {
698
+ implicit def pushOptions: PushOptions = new PushOptions {
699
+ def dryRun = false
700
+ def force = true
701
+ }
702
+ }
703
+
704
+
682
705
  trait MockAWS extends MockS3 with MockCloudFront with Scope
683
706
 
684
707
  trait MockCloudFront extends MockAWSHelper {
@@ -884,17 +907,19 @@ class S3WebsiteSpec extends Specification {
884
907
  """.stripMargin
885
908
  )
886
909
 
887
- def pushMode: PushMode // Represents the --dry-run switch
910
+ def pushOptions: PushOptions
888
911
 
889
912
  implicit def cliArgs: CliArgs =
890
913
  new CliArgs {
891
914
  def verbose = true
892
915
 
893
- def dryRun = pushMode.dryRun
916
+ def dryRun = pushOptions.dryRun
894
917
 
895
918
  def site = if (siteDirFromCLIArg) siteDirectory.getAbsolutePath else null
896
919
 
897
920
  def configDir = configDirectory.getAbsolutePath
921
+
922
+ def force = pushOptions.force
898
923
  }
899
924
  }
900
925
 
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.0
4
+ version: 2.5.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-10-14 00:00:00.000000000 Z
11
+ date: 2014-10-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -129,11 +129,11 @@ files:
129
129
  - src/main/resources/log4j.properties
130
130
  - src/main/scala/s3/website/ByteHelper.scala
131
131
  - src/main/scala/s3/website/CloudFront.scala
132
- - src/main/scala/s3/website/Diff.scala
133
132
  - src/main/scala/s3/website/Logger.scala
134
133
  - src/main/scala/s3/website/Push.scala
135
134
  - src/main/scala/s3/website/Ruby.scala
136
135
  - src/main/scala/s3/website/S3.scala
136
+ - src/main/scala/s3/website/UploadHelper.scala
137
137
  - src/main/scala/s3/website/model/Config.scala
138
138
  - src/main/scala/s3/website/model/S3Endpoint.scala
139
139
  - src/main/scala/s3/website/model/Site.scala