s3_website_monadic 0.0.35 → 0.0.36
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -1
- data/README.md +19 -22
- data/additional-docs/development.md +21 -1
- data/bin/s3_website_monadic +57 -5
- data/build.sbt +1 -1
- data/changelog.md +15 -3
- data/lib/s3_website/version.rb +1 -1
- data/src/main/scala/s3/website/CloudFront.scala +2 -2
- data/src/main/scala/s3/website/S3.scala +2 -1
- data/src/main/scala/s3/website/model/Config.scala +17 -2
- data/src/main/scala/s3/website/model/Site.scala +2 -2
- data/src/test/scala/s3/website/S3WebsiteSpec.scala +10 -11
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e7c4690fe767a7e3acb2814561d70516adb70a37
|
4
|
+
data.tar.gz: 58348dcb010d4f4ba30132c26e7cc8008e97f4f9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ede3a36d8687fe7d9794405810b8425c01a7ce4c5b19c196d77ee7f1737fb2913d0d970767e3d04b0c5cfa4580ca14cd5f430d55b0a532d93523d44edca504e1
|
7
|
+
data.tar.gz: bf26d74d0b3179386e48fd12d42ae9d58a51f7bf94b4a7ad84014513736b461c5cf4b7f2345cecbe799c611d20ee68dbd7a03a2706d9d124fdb7949187b9a681
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -17,10 +17,6 @@
|
|
17
17
|
|
18
18
|
gem install s3_website_monadic
|
19
19
|
|
20
|
-
`s3_website_monadic` requires Ruby and Java. Here is documentation on installing Ruby:
|
21
|
-
<http://www.ruby-lang.org/en/downloads/>, and here is documentation for Java:
|
22
|
-
<http://www.oracle.com/technetwork/java/javase/downloads/index.html>.
|
23
|
-
|
24
20
|
## Usage
|
25
21
|
|
26
22
|
Here's how you can get started:
|
@@ -34,11 +30,6 @@ Here's how you can get started:
|
|
34
30
|
S3 website. If the bucket does not exist, the command will create it for you.
|
35
31
|
* Run `s3_website_monadic push` to push your website to S3. Congratulations! You are live.
|
36
32
|
|
37
|
-
**Important security note:** if the source code of your website is publicly
|
38
|
-
available, ensure that the `s3_website.yml` file is in the list of ignored files.
|
39
|
-
For git users this means that the file `.gitignore` should mention the
|
40
|
-
`s3_website.yml` file.
|
41
|
-
|
42
33
|
### For Jekyll users
|
43
34
|
|
44
35
|
S3_website will automatically discover your website in the *_site* directory.
|
@@ -334,7 +325,7 @@ If you experience the "too many open files" error, either increase the amount of
|
|
334
325
|
maximum open files (on Unix-like systems, see `man ulimit`) or decrease the
|
335
326
|
`concurrency_level` setting.
|
336
327
|
|
337
|
-
|
328
|
+
### Simulating deployments
|
338
329
|
|
339
330
|
You can simulate the `s3_website_monadic push` operation by adding the
|
340
331
|
`--dry-run` switch. The dry run mode will not apply any modifications on your S3
|
@@ -344,18 +335,31 @@ operation would actually do if run without the dry switch.
|
|
344
335
|
You can use the dry run mode if you are unsure what kind of effects the `push`
|
345
336
|
operation would cause to your live website.
|
346
337
|
|
338
|
+
## Migrating from v1 to v2
|
339
|
+
|
340
|
+
Please read the [release note](/changelog.md#200) on version 2. It contains
|
341
|
+
information on backward incompatible changes.
|
342
|
+
|
347
343
|
## Example configurations
|
348
344
|
|
349
345
|
See
|
350
346
|
<https://github.com/laurilehmijoki/s3_website/blob/master/additional-docs/example-configurations.md>.
|
351
347
|
|
348
|
+
## On security
|
349
|
+
|
350
|
+
If the source code of your website is publicly
|
351
|
+
available, ensure that the `s3_website.yml` file is in the list of ignored files.
|
352
|
+
For git users this means that the file `.gitignore` should mention the
|
353
|
+
`s3_website.yml` file.
|
354
|
+
|
355
|
+
If you use the .dotenv gem, ensure that you do not push the `.env` file to a
|
356
|
+
public git repository.
|
357
|
+
|
352
358
|
## Known issues
|
353
359
|
|
354
360
|
Please create an issue and send a pull request if you spot any.
|
355
361
|
|
356
|
-
##
|
357
|
-
|
358
|
-
### Versioning
|
362
|
+
## Versioning
|
359
363
|
|
360
364
|
s3_website_monadic uses [Semantic Versioning](http://semver.org).
|
361
365
|
|
@@ -363,10 +367,9 @@ In the spirit of semantic versioning, here is the definition of public API for
|
|
363
367
|
s3_website: Within a major version, s3_website_monadic will not break
|
364
368
|
backwards-compatibility of anything that is mentioned in this README file.
|
365
369
|
|
366
|
-
|
370
|
+
## Development
|
367
371
|
|
368
|
-
|
369
|
-
* Run all tests by invoking `rake test`
|
372
|
+
See [development](additional-docs/development.md).
|
370
373
|
|
371
374
|
### Contributing
|
372
375
|
|
@@ -382,12 +385,6 @@ If you are not sure how to test your pull request, you can ask the [gem owners
|
|
382
385
|
However, by including proper tests, you increase the chances of your pull
|
383
386
|
request being incorporated into future releases.
|
384
387
|
|
385
|
-
#### Checklist for new features
|
386
|
-
|
387
|
-
* Is it tested?
|
388
|
-
* Is it documented in README?
|
389
|
-
* Is it mentioned in `resources/configuration_file_template.yml`?
|
390
|
-
|
391
388
|
## License
|
392
389
|
|
393
390
|
MIT. See the LICENSE file for more information.
|
@@ -1,4 +1,23 @@
|
|
1
|
-
|
1
|
+
## Coding
|
2
|
+
|
3
|
+
Install a Scala editor. Intellij IDEA has great Scala support.
|
4
|
+
|
5
|
+
If you use IDEA, install the [Grep
|
6
|
+
Console](http://plugins.jetbrains.com/plugin/?idea&pluginId=7125) plugin. It
|
7
|
+
shows the ANSI colors in your IDEA console.
|
8
|
+
|
9
|
+
### Test runs with IDEA
|
10
|
+
|
11
|
+
1. Create a run profile: *Run* –> *Edit Configurations...*.
|
12
|
+
2. Add *Application*
|
13
|
+
3. Set *Main class* to `s3.website.Push`
|
14
|
+
4. Set *Program arguments* to `--site=/Users/you/yourtestsite/_site --config-dir=/Users/you/yourtestsite --verbose`
|
15
|
+
|
16
|
+
## Automated tests
|
17
|
+
|
18
|
+
./sbt test
|
19
|
+
|
20
|
+
## Test Linux distributions
|
2
21
|
|
3
22
|
Use Vagrant for testing the installation procedure on Linux.
|
4
23
|
|
@@ -7,3 +26,4 @@ Here's howto:
|
|
7
26
|
1. Install <https://www.vagrantup.com/downloads.html>
|
8
27
|
2. `cd vagrant && vagrant status`
|
9
28
|
3. launch with `vagrant up <name>` and ssh into with `vagrant ssh <name>`
|
29
|
+
4. test the latest release with `gem install s3_website && s3_website push`
|
data/bin/s3_website_monadic
CHANGED
@@ -83,6 +83,11 @@ class Cli < Thor
|
|
83
83
|
end
|
84
84
|
|
85
85
|
def run_s3_website_jar(jar_file, call_dir, logger)
|
86
|
+
java_installed = resolve_exit_status('which javas') or resolve_exit_status('javas -version')
|
87
|
+
unless java_installed
|
88
|
+
logger.info_msg "Cannot find Java. s3_website push is implemented in Scala, and it needs Java to run."
|
89
|
+
autoinstall_java_or_print_help_and_exit(logger)
|
90
|
+
end
|
86
91
|
args = ARGV.join(' ').sub('push', '')
|
87
92
|
logger.debug_msg "Using #{jar_file}"
|
88
93
|
if system("java -cp #{jar_file} s3.website.Push #{args}")
|
@@ -92,6 +97,53 @@ def run_s3_website_jar(jar_file, call_dir, logger)
|
|
92
97
|
end
|
93
98
|
end
|
94
99
|
|
100
|
+
def resolve_exit_status(cmd)
|
101
|
+
`#{cmd}`
|
102
|
+
cmd_succeeded = $? == 0
|
103
|
+
rescue
|
104
|
+
cmd_succeeded = false
|
105
|
+
end
|
106
|
+
|
107
|
+
def autoinstall_java_or_print_help_and_exit(logger)
|
108
|
+
@logger = logger
|
109
|
+
automatic_methods = [
|
110
|
+
{
|
111
|
+
:package_manager_lookup => 'which apt-get',
|
112
|
+
:install_command => 'sudo apt-get install --assume-yes openjdk-7-jre'
|
113
|
+
},
|
114
|
+
{
|
115
|
+
:package_manager_lookup => 'which yum',
|
116
|
+
:install_command => 'sudo yum install --assumeyes java-1.7.0-openjdk'
|
117
|
+
}
|
118
|
+
]
|
119
|
+
|
120
|
+
def print_manual_method_and_exit
|
121
|
+
@logger.info_msg 'Go to http://java.com, install Java and then try again.'
|
122
|
+
exit 1
|
123
|
+
end
|
124
|
+
|
125
|
+
manual_method_help =
|
126
|
+
|
127
|
+
automatic_method = automatic_methods.find { |automatic_method|
|
128
|
+
resolve_exit_status automatic_method.fetch(:package_manager_lookup)
|
129
|
+
}
|
130
|
+
if automatic_method
|
131
|
+
@logger.info_msg "Do you want me to install Java with the command `#{automatic_method.fetch(:install_command)}`? [Y/n]"
|
132
|
+
user_answer = $stdin.gets
|
133
|
+
if user_answer.chomp.downcase == 'y' or user_answer == "\n"
|
134
|
+
automatic_method_succeeded = system automatic_method.fetch(:install_command)
|
135
|
+
unless automatic_method_succeeded
|
136
|
+
@logger.fail_msg "Could not automatically install Java. Try setting it up manually:"
|
137
|
+
print_manual_method_and_exit
|
138
|
+
end
|
139
|
+
else
|
140
|
+
print_manual_method_and_exit
|
141
|
+
end
|
142
|
+
else
|
143
|
+
print_manual_method_and_exit
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
95
147
|
def resolve_jar(project_root, logger)
|
96
148
|
development_jar_path =
|
97
149
|
project_root + '/target/scala-2.11/s3_website.jar'
|
@@ -99,11 +151,11 @@ def resolve_jar(project_root, logger)
|
|
99
151
|
project_root + "/s3_website-#{S3Website::VERSION}.jar",
|
100
152
|
(ENV['TMPDIR'] || '/tmp') + "/s3_website-#{S3Website::VERSION}.jar"
|
101
153
|
]
|
102
|
-
found_jar = ([development_jar_path] + released_jar_lookup_paths)
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
154
|
+
found_jar = ([development_jar_path] + released_jar_lookup_paths).
|
155
|
+
select { |jar_path|
|
156
|
+
File.exists? jar_path
|
157
|
+
}.
|
158
|
+
first
|
107
159
|
jar_file =
|
108
160
|
if found_jar
|
109
161
|
found_jar
|
data/build.sbt
CHANGED
data/changelog.md
CHANGED
@@ -8,6 +8,11 @@ This project uses [Semantic Versioning](http://semver.org).
|
|
8
8
|
|
9
9
|
* Faster uploads for extra large sites
|
10
10
|
|
11
|
+
Use a local database for calculating diffs. This removes the need to read all
|
12
|
+
the files of the website, when you call the `s3_website push` command.
|
13
|
+
|
14
|
+
Use proper multithreading with JVM threads.
|
15
|
+
|
11
16
|
* Simulate deployments with `push --dry-run`
|
12
17
|
|
13
18
|
* Support CloudFront invalidations when the site contains over 3000 files
|
@@ -22,10 +27,16 @@ This project uses [Semantic Versioning](http://semver.org).
|
|
22
27
|
|
23
28
|
* Fault tolerance – do not crash if one of the uploads fails
|
24
29
|
|
30
|
+
Before, the push command crashed if something unexpected happened. From now
|
31
|
+
on, s3_website will run all the operations it can, and report errors in the
|
32
|
+
end.
|
33
|
+
|
25
34
|
### Java is now required
|
26
35
|
|
27
|
-
* The `push` command is now written in Scala
|
28
|
-
|
36
|
+
* The `push` command is now written in Scala
|
37
|
+
|
38
|
+
This means that you need Java 1.6 or above to run the command `s3_website
|
39
|
+
push`.
|
29
40
|
|
30
41
|
### Removed features
|
31
42
|
|
@@ -38,7 +49,8 @@ This project uses [Semantic Versioning](http://semver.org).
|
|
38
49
|
s3_website always deletes the files that are on the S3 bucket but not on the local file system.
|
39
50
|
Use the settings `ignore_on_server` and `exclude_from_upload` to control the retained files.
|
40
51
|
|
41
|
-
* You can no longer use this gem as a Ruby library
|
52
|
+
* You can no longer use this gem as a Ruby library. You can migrate by calling
|
53
|
+
the `s3_website push --site=x --config-dir=y` system command from your Ruby code.
|
42
54
|
|
43
55
|
* `gzip_zopfli: true`
|
44
56
|
|
data/lib/s3_website/version.rb
CHANGED
@@ -9,6 +9,7 @@ import s3.website.S3.{SuccessfulDelete, PushSuccessReport, SuccessfulUpload}
|
|
9
9
|
import com.amazonaws.auth.BasicAWSCredentials
|
10
10
|
import java.net.URI
|
11
11
|
import scala.concurrent.{ExecutionContextExecutor, Future}
|
12
|
+
import s3.website.model.Config.awsCredentials
|
12
13
|
|
13
14
|
object CloudFront {
|
14
15
|
def invalidate(invalidationBatch: InvalidationBatch, distributionId: String, attempt: Attempt = 1)
|
@@ -63,8 +64,7 @@ object CloudFront {
|
|
63
64
|
def reportMessage = s"Failed to invalidate the CloudFront distribution (${error.getMessage})"
|
64
65
|
}
|
65
66
|
|
66
|
-
def awsCloudFrontClient(config: Config) =
|
67
|
-
new AmazonCloudFrontClient(new BasicAWSCredentials(config.s3_id, config.s3_secret))
|
67
|
+
def awsCloudFrontClient(config: Config) = new AmazonCloudFrontClient(awsCredentials(config))
|
68
68
|
|
69
69
|
def toInvalidationBatches(pushSuccessReports: Seq[PushSuccessReport])(implicit config: Config): Seq[InvalidationBatch] = {
|
70
70
|
val invalidationPaths: Seq[String] = {
|
@@ -21,6 +21,7 @@ import scala.concurrent.duration.TimeUnit
|
|
21
21
|
import java.util.concurrent.TimeUnit.SECONDS
|
22
22
|
import s3.website.S3.SuccessfulUpload.humanizeUploadSpeed
|
23
23
|
import java.io.FileInputStream
|
24
|
+
import s3.website.model.Config.awsCredentials
|
24
25
|
|
25
26
|
object S3 {
|
26
27
|
|
@@ -105,7 +106,7 @@ object S3 {
|
|
105
106
|
None // We are not interested in tracking durations of PUT requests that don't contain data. Redirect is an example of such request.
|
106
107
|
}
|
107
108
|
|
108
|
-
def awsS3Client(config: Config) = new AmazonS3Client(
|
109
|
+
def awsS3Client(config: Config) = new AmazonS3Client(awsCredentials(config))
|
109
110
|
|
110
111
|
def resolveS3Files(nextMarker: Option[String] = None, alreadyResolved: Seq[S3File] = Nil, attempt: Attempt = 1)
|
111
112
|
(implicit config: Config, s3Settings: S3Setting, ec: ExecutionContextExecutor, logger: Logger, pushMode: PushMode):
|
@@ -4,10 +4,11 @@ import scala.util.{Failure, Try}
|
|
4
4
|
import scala.collection.JavaConversions._
|
5
5
|
import s3.website.Ruby.rubyRuntime
|
6
6
|
import s3.website.ErrorReport
|
7
|
+
import com.amazonaws.auth.{AWSCredentialsProvider, BasicAWSCredentials, DefaultAWSCredentialsProviderChain}
|
7
8
|
|
8
9
|
case class Config(
|
9
|
-
s3_id: String,
|
10
|
-
s3_secret: String,
|
10
|
+
s3_id: Option[String], // If undefined, use IAM Roles (http://docs.aws.amazon.com/AWSSdkDocsJava/latest/DeveloperGuide/java-dg-roles.html)
|
11
|
+
s3_secret: Option[String], // If undefined, use IAM Roles (http://docs.aws.amazon.com/AWSSdkDocsJava/latest/DeveloperGuide/java-dg-roles.html)
|
11
12
|
s3_bucket: String,
|
12
13
|
s3_endpoint: S3Endpoint,
|
13
14
|
max_age: Option[Either[Int, Map[String, Int]]],
|
@@ -23,6 +24,20 @@ case class Config(
|
|
23
24
|
)
|
24
25
|
|
25
26
|
object Config {
|
27
|
+
|
28
|
+
def awsCredentials(config: Config): AWSCredentialsProvider = {
|
29
|
+
val credentialsFromConfigFile = for {
|
30
|
+
s3_id <- config.s3_id
|
31
|
+
s3_secret <- config.s3_secret
|
32
|
+
} yield new BasicAWSCredentials(s3_id, s3_secret)
|
33
|
+
credentialsFromConfigFile.fold(new DefaultAWSCredentialsProviderChain: AWSCredentialsProvider)(credentials =>
|
34
|
+
new AWSCredentialsProvider {
|
35
|
+
def getCredentials = credentials
|
36
|
+
def refresh() = {}
|
37
|
+
}
|
38
|
+
)
|
39
|
+
}
|
40
|
+
|
26
41
|
def loadOptionalBooleanOrStringSeq(key: String)(implicit unsafeYaml: UnsafeYaml): Either[ErrorReport, Option[Either[Boolean, Seq[String]]]] = {
|
27
42
|
val yamlValue = for {
|
28
43
|
optionalValue <- loadOptionalValue(key)
|
@@ -31,8 +31,8 @@ object Site {
|
|
31
31
|
case Success(yamlObject) =>
|
32
32
|
implicit val unsafeYaml = UnsafeYaml(yamlObject)
|
33
33
|
val config: Either[ErrorReport, Config] = for {
|
34
|
-
s3_id <-
|
35
|
-
s3_secret <-
|
34
|
+
s3_id <- loadOptionalString("s3_id").right
|
35
|
+
s3_secret <- loadOptionalString("s3_secret").right
|
36
36
|
s3_bucket <- loadRequiredString("s3_bucket").right
|
37
37
|
s3_endpoint <- loadEndpoint.right
|
38
38
|
max_age <- loadMaxAge.right
|
@@ -591,10 +591,9 @@ class S3WebsiteSpec extends Specification {
|
|
591
591
|
}
|
592
592
|
|
593
593
|
"push locally unchanged files that are missing from S3" in new AllInSameDirectory with EmptySite with MockAWS with DefaultRunMode {
|
594
|
-
|
594
|
+
setLocalFileWithContent(("file.txt", "first run"))
|
595
595
|
push
|
596
|
-
|
597
|
-
setLocalFileWithContentAndLastModified(("file.txt", "contents", new Date(1400000000000L)))
|
596
|
+
removeAllFilesFromS3()
|
598
597
|
push
|
599
598
|
sentPutObjectRequests.length must equalTo(2) // Even though we use the local db, we should notice that someone else has deleted file.txt
|
600
599
|
}
|
@@ -702,6 +701,10 @@ class S3WebsiteSpec extends Specification {
|
|
702
701
|
}
|
703
702
|
}
|
704
703
|
|
704
|
+
def removeAllFilesFromS3() {
|
705
|
+
setS3Files(Nil: _*) // This corresponds to the situation where the S3 bucket is empty
|
706
|
+
}
|
707
|
+
|
705
708
|
def uploadFailsAndThenSucceeds(implicit howManyFailures: Int, callCount: AtomicInteger = new AtomicInteger(0)) {
|
706
709
|
doAnswer(temporaryFailure(classOf[PutObjectResult]))
|
707
710
|
.when(amazonS3Client)
|
@@ -795,15 +798,12 @@ class S3WebsiteSpec extends Specification {
|
|
795
798
|
|
796
799
|
trait EmptySite extends Directories {
|
797
800
|
type LocalFileWithContent = (String, String)
|
798
|
-
type LocalFileWithContentAndLastModified = (String, String, Date)
|
799
801
|
|
800
|
-
val localFilesWithContent: mutable.Set[
|
802
|
+
val localFilesWithContent: mutable.Set[LocalFileWithContent] = mutable.Set()
|
801
803
|
def setLocalFile(fileName: String) = setLocalFileWithContent((fileName, ""))
|
802
804
|
def setLocalFiles(fileNames: String*) = fileNames foreach setLocalFile
|
803
805
|
def setLocalFileWithContent(fileNameAndContent: LocalFileWithContent) =
|
804
|
-
|
805
|
-
def setLocalFileWithContentAndLastModified(fileNameAndContentAndLastModified: LocalFileWithContentAndLastModified) =
|
806
|
-
localFilesWithContent += fileNameAndContentAndLastModified
|
806
|
+
localFilesWithContent += fileNameAndContent
|
807
807
|
def setLocalFilesWithContent(fileNamesAndContent: LocalFileWithContent*) = fileNamesAndContent foreach setLocalFileWithContent
|
808
808
|
var config = ""
|
809
809
|
val baseConfig =
|
@@ -816,14 +816,13 @@ class S3WebsiteSpec extends Specification {
|
|
816
816
|
implicit def cliArgs: CliArgs = siteWithFilesAndContent(config, localFilesWithContent)
|
817
817
|
def pushMode: PushMode // Represents the --dry-run switch
|
818
818
|
|
819
|
-
private def siteWithFilesAndContent(config: String = "", localFilesWithContent: mutable.Set[
|
819
|
+
private def siteWithFilesAndContent(config: String = "", localFilesWithContent: mutable.Set[LocalFileWithContent]): CliArgs = {
|
820
820
|
localFilesWithContent.foreach {
|
821
|
-
case (filePath, content
|
821
|
+
case (filePath, content) =>
|
822
822
|
val file = new File(siteDirectory, filePath)
|
823
823
|
forceMkdir(file.getParentFile)
|
824
824
|
file.createNewFile()
|
825
825
|
write(file, content)
|
826
|
-
file.setLastModified(lastModified.getTime)
|
827
826
|
}
|
828
827
|
localFilesWithContent.clear() // Once we've persisted the changes on the disk, clear the queue. I.e., keep the state where it should be – on the disk.
|
829
828
|
buildCliArgs(config)
|
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.36
|
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-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|