src/main/scala/s3/website/CloudFront.scala in s3_website-2.8.3 vs src/main/scala/s3/website/CloudFront.scala in s3_website-2.8.4

- old
+ new

@@ -6,11 +6,11 @@ import com.amazonaws.services.cloudfront.model.{TooManyInvalidationsInProgressException, Paths, InvalidationBatch, CreateInvalidationRequest} import scala.collection.JavaConversions._ import scala.concurrent.duration._ import s3.website.S3.{SuccessfulDelete, PushSuccessReport, SuccessfulUpload} import com.amazonaws.auth.BasicAWSCredentials -import java.net.URI +import java.net.{URLEncoder, URI} import scala.concurrent.{ExecutionContextExecutor, Future} import s3.website.model.Config.awsCredentials object CloudFront { def invalidate(invalidationBatch: InvalidationBatch, distributionId: String, attempt: Attempt = 1) @@ -66,33 +66,38 @@ } def awsCloudFrontClient(config: Config) = new AmazonCloudFrontClient(awsCredentials(config)) def toInvalidationBatches(pushSuccessReports: Seq[PushSuccessReport])(implicit config: Config): Seq[InvalidationBatch] = { + def defaultPath(paths: Seq[String]): Option[String] = { + // This is how we support the Default Root Object @ CloudFront (http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DefaultRootObject.html) + // We could do this more accurately by fetching the distribution config (http://docs.aws.amazon.com/AmazonCloudFront/latest/APIReference/GetConfig.html) + // and reading the Default Root Object from there. + val containsPotentialDefaultRootObject = paths + .exists( + _ + .replaceFirst("^/", "") // S3 keys do not begin with a slash + .contains("/") == false // See if the S3 key is a top-level key (i.e., it is not within a directory) + ) + if (containsPotentialDefaultRootObject) Some("/") else None + } + val indexPath = config.cloudfront_invalidate_root collect { + case true if pushSuccessReports.nonEmpty => "/index.html" + } + val invalidationPaths: Seq[String] = { - def withDefaultPathIfNeeded(paths: Seq[String]) = { - // This is how we support the Default Root Object @ CloudFront (http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DefaultRootObject.html) - // We do this more accurately by fetching the distribution config (http://docs.aws.amazon.com/AmazonCloudFront/latest/APIReference/GetConfig.html) - // and reading the Default Root Object from there. - val containsPotentialDefaultRootObject = paths - .exists( - _ - .replaceFirst("^/", "") // S3 keys do not begin with a slash - .contains("/") == false // See if the S3 key is a top-level key (i.e., it is not within a directory) - ) - if (containsPotentialDefaultRootObject) paths :+ "/" else paths - } - def withIndexPathIfNeeded(paths: Seq[String]) = - if (config.cloudfront_invalidate_root.contains(true) && pushSuccessReports.nonEmpty) - paths :+ "/index.html" - else - paths val paths = pushSuccessReports - .filter(needsInvalidation) // Assume that redirect objects are never cached. + .filter(needsInvalidation) .map(toInvalidationPath) + .map(encodeUnsafeChars) .map(applyInvalidateRootSetting) - withIndexPathIfNeeded(withDefaultPathIfNeeded(paths)) + + val extraPathItems = defaultPath(paths) :: indexPath :: Nil collect { + case Some(path) => path + } + + paths ++ extraPathItems } invalidationPaths .grouped(1000) // CloudFront supports max 1000 invalidations in one request (http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Invalidation.html#InvalidationLimits) .map { batchKeys => @@ -107,20 +112,14 @@ if (config.cloudfront_invalidate_root.contains(true)) path.replaceFirst("/index.html$", "/") else path - def toInvalidationPath(report: PushSuccessReport) = { - def encodeUnsafeChars(path: String) = - new URI( - "http", - "cloudfront", // We want to use the encoder in the URI class. These must be passed in. - "/" + report.s3Key, // CloudFront keys have the slash in front - path - ).toURL.getPath // The URL class encodes the unsafe characters - val invalidationPath = "/" + report.s3Key // CloudFront keys have the slash in front - encodeUnsafeChars(invalidationPath) - } + def toInvalidationPath(report: PushSuccessReport) = "/" + report.s3Key + + def encodeUnsafeChars(invalidationPath: String) = + new URI("http", "cloudfront", invalidationPath, "").toURL.getPath + .replaceAll("'", URLEncoder.encode("'", "UTF-8")) // CloudFront does not accept ' in invalidation path def needsInvalidation: PartialFunction[PushSuccessReport, Boolean] = { case succ: SuccessfulUpload => succ.details.fold(_.uploadType, _.uploadType) == FileUpdate case SuccessfulDelete(_) => true