s3_website 2.4.0 → 2.5.0

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