configure-s3-website 1.0.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.
- data/.gitignore +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +54 -0
- data/LICENSE +19 -0
- data/README.md +72 -0
- data/Rakefile +16 -0
- data/bin/configure-s3-website +34 -0
- data/configure-s3-website.gemspec +24 -0
- data/features/cassettes/cucumber_tags/bucket-does-not-exist.yml +159 -0
- data/features/cassettes/cucumber_tags/bucket-exists.yml +78 -0
- data/features/config_file.feature +57 -0
- data/features/configure_bucket.feature +24 -0
- data/features/step_definitions/steps.rb +29 -0
- data/features/support/env.rb +18 -0
- data/features/support/sample_config_files/s3_config_with_existing_bucket.yml +3 -0
- data/features/support/sample_config_files/s3_config_with_non-existing_bucket.yml +3 -0
- data/features/support/vcr.rb +11 -0
- data/lib/configure-s3-website.rb +4 -0
- data/lib/configure-s3-website/config_source/config_source.rb +12 -0
- data/lib/configure-s3-website/config_source/file_config_source.rb +38 -0
- data/lib/configure-s3-website/s3_client.rb +127 -0
- data/lib/configure-s3-website/version.rb +3 -0
- data/spec/config_extractor/file_config_extractor_spec.rb +11 -0
- data/spec/sample_files/_config_file_with_eruby.yml +3 -0
- metadata +193 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
configure-s3-website (0.0.1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: http://rubygems.org/
|
8
|
+
specs:
|
9
|
+
addressable (2.3.2)
|
10
|
+
aruba (0.4.11)
|
11
|
+
childprocess (>= 0.2.3)
|
12
|
+
cucumber (>= 1.1.1)
|
13
|
+
ffi (>= 1.0.11)
|
14
|
+
rspec (>= 2.7.0)
|
15
|
+
builder (3.1.4)
|
16
|
+
childprocess (0.3.6)
|
17
|
+
ffi (~> 1.0, >= 1.0.6)
|
18
|
+
crack (0.3.1)
|
19
|
+
cucumber (1.2.1)
|
20
|
+
builder (>= 2.1.2)
|
21
|
+
diff-lcs (>= 1.1.3)
|
22
|
+
gherkin (~> 2.11.0)
|
23
|
+
json (>= 1.4.6)
|
24
|
+
diff-lcs (1.1.3)
|
25
|
+
ffi (1.2.0)
|
26
|
+
gherkin (2.11.5)
|
27
|
+
json (>= 1.4.6)
|
28
|
+
json (1.7.5)
|
29
|
+
rake (0.9.6)
|
30
|
+
rspec (2.10.0)
|
31
|
+
rspec-core (~> 2.10.0)
|
32
|
+
rspec-expectations (~> 2.10.0)
|
33
|
+
rspec-mocks (~> 2.10.0)
|
34
|
+
rspec-core (2.10.1)
|
35
|
+
rspec-expectations (2.10.0)
|
36
|
+
diff-lcs (~> 1.1.3)
|
37
|
+
rspec-mocks (2.10.1)
|
38
|
+
vcr (2.3.0)
|
39
|
+
webmock (1.8.11)
|
40
|
+
addressable (>= 2.2.7)
|
41
|
+
crack (>= 0.1.7)
|
42
|
+
|
43
|
+
PLATFORMS
|
44
|
+
ruby
|
45
|
+
|
46
|
+
DEPENDENCIES
|
47
|
+
aruba (~> 0.4.0)
|
48
|
+
configure-s3-website!
|
49
|
+
cucumber (~> 1.2.0)
|
50
|
+
rake (~> 0.9.0)
|
51
|
+
rspec (~> 2.10.0)
|
52
|
+
rspec-expectations (~> 2.10.0)
|
53
|
+
vcr (~> 2.3.0)
|
54
|
+
webmock (~> 1.8.0)
|
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (C) 2012 Lauri lehmijoki
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
5
|
+
the Software without restriction, including without limitation the rights to
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
7
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
8
|
+
so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
19
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
# Configure-s3-website
|
2
|
+
|
3
|
+
[](http://travis-ci.org/laurilehmijoki/configure-s3-website)
|
4
|
+
[](http://badge.fury.io/rb/configure-s3-website)
|
5
|
+
|
6
|
+
Configure an AWS S3 bucket to function as a website. Easily from the
|
7
|
+
command-line interface.
|
8
|
+
|
9
|
+
The Ruby gem `configure-s3-website` can configure an S3 bucket to function as a
|
10
|
+
website. The bucket may or may not exist. If the bucket does not exist,
|
11
|
+
`configure-s3-website` will create it.
|
12
|
+
|
13
|
+
## Install
|
14
|
+
|
15
|
+
gem install configure-s3-website
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
Create a file that contains the S3 credentials and the name of the bucket:
|
20
|
+
|
21
|
+
```yaml
|
22
|
+
s3_id: your-aws-access-key
|
23
|
+
s3_secret: your-aws-secret-key
|
24
|
+
s3_bucket: name-of-your-bucket
|
25
|
+
```
|
26
|
+
|
27
|
+
Save the file (as *config.yml*, for example). Now you are ready to go. Run the
|
28
|
+
following command:
|
29
|
+
|
30
|
+
configure-s3-website --config-file config.yml
|
31
|
+
|
32
|
+
Congratulations! You now have an S3 bucket that can act as a website server for
|
33
|
+
you.
|
34
|
+
|
35
|
+
## How does `configure-s3-website` work?
|
36
|
+
|
37
|
+
It calls the [PUT Bucket
|
38
|
+
website](http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketPUTwebsite.html)
|
39
|
+
API with the following XML:
|
40
|
+
|
41
|
+
```xml
|
42
|
+
<WebsiteConfiguration xmlns='http://s3.amazonaws.com/doc/2006-03-01/'>
|
43
|
+
<IndexDocument>
|
44
|
+
<Suffix>index.html</Suffix>
|
45
|
+
</IndexDocument>
|
46
|
+
<ErrorDocument>
|
47
|
+
<Key>error.html</Key>
|
48
|
+
</ErrorDocument>
|
49
|
+
</WebsiteConfiguration>
|
50
|
+
```
|
51
|
+
|
52
|
+
Then **it makes all the objects on the bucket visible to the whole world** by
|
53
|
+
calling the [PUT Bucket
|
54
|
+
policy](http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketPUTpolicy.html)
|
55
|
+
API with the following JSON:
|
56
|
+
|
57
|
+
```json
|
58
|
+
{
|
59
|
+
"Version":"2008-10-17",
|
60
|
+
"Statement":[{
|
61
|
+
"Sid":"PublicReadForGetBucketObjects",
|
62
|
+
"Effect":"Allow",
|
63
|
+
"Principal": { "AWS": "*" },
|
64
|
+
"Action":["s3:GetObject"],
|
65
|
+
"Resource":["arn:aws:s3:::your-bucket-name/*"]
|
66
|
+
}]
|
67
|
+
}
|
68
|
+
```
|
69
|
+
|
70
|
+
## License
|
71
|
+
|
72
|
+
See file LICENSE.
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
desc "Build the project"
|
5
|
+
task :default => 'test'
|
6
|
+
|
7
|
+
desc "Run tests"
|
8
|
+
task :test do
|
9
|
+
sh "bundle exec rspec"
|
10
|
+
sh "bundle exec cucumber"
|
11
|
+
end
|
12
|
+
|
13
|
+
desc 'Run features tagged with @wip'
|
14
|
+
task 'cucumber:wip' do
|
15
|
+
sh "bundle exec cucumber --tags @wip"
|
16
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
|
4
|
+
|
5
|
+
require 'optparse'
|
6
|
+
require 'configure-s3-website'
|
7
|
+
|
8
|
+
options = {}
|
9
|
+
|
10
|
+
banner = <<END
|
11
|
+
Usage: #{File.basename(__FILE__)} arguments
|
12
|
+
|
13
|
+
Configure your S3 bucket to function as a web site
|
14
|
+
|
15
|
+
Arguments:
|
16
|
+
END
|
17
|
+
|
18
|
+
optparse = OptionParser.new do |opts|
|
19
|
+
opts.banner = banner
|
20
|
+
opts.on('-f', '--config-file FILE',
|
21
|
+
'Pick credentials and the S3 bucket name from a config file') do
|
22
|
+
|yaml_file_path|
|
23
|
+
options[:config_source] =
|
24
|
+
ConfigureS3Website::FileConfigSource.new yaml_file_path
|
25
|
+
end
|
26
|
+
opts.on('--help', 'Display this screen') do
|
27
|
+
puts opts
|
28
|
+
exit
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
optparse.parse!
|
33
|
+
|
34
|
+
ConfigureS3Website::S3Client.configure_website(options[:config_source])
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require File.join([File.dirname(__FILE__),'lib','configure-s3-website','version.rb'])
|
2
|
+
spec = Gem::Specification.new do |s|
|
3
|
+
s.name = 'configure-s3-website'
|
4
|
+
s.version = ConfigureS3Website::VERSION
|
5
|
+
s.author = 'Lauri Lehmijoki'
|
6
|
+
s.email = 'lauri.lehmijoki@iki.fi'
|
7
|
+
s.homepage = 'https://github.com/laurilehmijoki/configure-s3-website'
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.summary = 'Configure your S3 bucket to function as a web site'
|
10
|
+
s.bindir = 'bin'
|
11
|
+
|
12
|
+
s.add_development_dependency 'rspec', '~> 2.10.0'
|
13
|
+
s.add_development_dependency 'rspec-expectations', '~> 2.10.0'
|
14
|
+
s.add_development_dependency 'cucumber', '~> 1.2.0'
|
15
|
+
s.add_development_dependency 'aruba', '~> 0.4.0'
|
16
|
+
s.add_development_dependency 'rake', '~> 0.9.0'
|
17
|
+
s.add_development_dependency 'vcr', '~> 2.3.0'
|
18
|
+
s.add_development_dependency 'webmock', '~> 1.8.0'
|
19
|
+
|
20
|
+
s.files = `git ls-files`.split("\n")
|
21
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
22
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
23
|
+
s.require_paths = ["lib"]
|
24
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
---
|
2
|
+
http_interactions:
|
3
|
+
- request:
|
4
|
+
method: put
|
5
|
+
uri: https://s3.amazonaws.com/name-of-a-new-bucket/?website
|
6
|
+
body:
|
7
|
+
encoding: US-ASCII
|
8
|
+
string: ! "\n <WebsiteConfiguration xmlns='http://s3.amazonaws.com/doc/2006-03-01/'>\n
|
9
|
+
\ <IndexDocument>\n <Suffix>index.html</Suffix>\n </IndexDocument>\n
|
10
|
+
\ <ErrorDocument>\n <Key>error.html</Key>\n </ErrorDocument>\n
|
11
|
+
\ </WebsiteConfiguration>\n "
|
12
|
+
headers:
|
13
|
+
Date:
|
14
|
+
- Sun, 30 Dec 2012 19:59:34 EET
|
15
|
+
Content-Type:
|
16
|
+
- ''
|
17
|
+
Content-Length:
|
18
|
+
- '298'
|
19
|
+
Authorization:
|
20
|
+
- AWS foo
|
21
|
+
response:
|
22
|
+
status:
|
23
|
+
code: 404
|
24
|
+
message: Not Found
|
25
|
+
headers:
|
26
|
+
X-Amz-Request-Id:
|
27
|
+
- CD16DF6FF66B5922
|
28
|
+
X-Amz-Id-2:
|
29
|
+
- Xu30q0VM0aaYY091koYh8FIUDPB2KWOQLRPqnLulIK3ScIrMWgOwF7M2XRYIi5Vs
|
30
|
+
Content-Type:
|
31
|
+
- application/xml
|
32
|
+
Transfer-Encoding:
|
33
|
+
- chunked
|
34
|
+
Date:
|
35
|
+
- Sun, 30 Dec 2012 17:59:34 GMT
|
36
|
+
Connection:
|
37
|
+
- close
|
38
|
+
Server:
|
39
|
+
- AmazonS3
|
40
|
+
body:
|
41
|
+
encoding: US-ASCII
|
42
|
+
string: ! '<?xml version="1.0" encoding="UTF-8"?>
|
43
|
+
|
44
|
+
<Error><Code>NoSuchBucket</Code><Message>The specified bucket does not exist</Message><BucketName>name-of-a-new-bucket</BucketName><RequestId>CD16DF6FF66B5922</RequestId><HostId>Xu30q0VM0aaYY091koYh8FIUDPB2KWOQLRPqnLulIK3ScIrMWgOwF7M2XRYIi5Vs</HostId></Error>'
|
45
|
+
http_version:
|
46
|
+
recorded_at: Sun, 30 Dec 2012 17:59:37 GMT
|
47
|
+
- request:
|
48
|
+
method: put
|
49
|
+
uri: https://s3.amazonaws.com/name-of-a-new-bucket
|
50
|
+
body:
|
51
|
+
encoding: US-ASCII
|
52
|
+
string: ''
|
53
|
+
headers:
|
54
|
+
Date:
|
55
|
+
- Sun, 30 Dec 2012 19:59:37 EET
|
56
|
+
Content-Type:
|
57
|
+
- ''
|
58
|
+
Content-Length:
|
59
|
+
- '0'
|
60
|
+
Authorization:
|
61
|
+
- AWS foo
|
62
|
+
response:
|
63
|
+
status:
|
64
|
+
code: 200
|
65
|
+
message: OK
|
66
|
+
headers:
|
67
|
+
X-Amz-Id-2:
|
68
|
+
- 7HuGyurBPI/HV8DVtjtRufaMaLRJLU+vCch+BPuBAysYAlE7XS4Eavz7srhB2dTx
|
69
|
+
X-Amz-Request-Id:
|
70
|
+
- 431755651DECBBC5
|
71
|
+
Date:
|
72
|
+
- Sun, 30 Dec 2012 17:59:39 GMT
|
73
|
+
Location:
|
74
|
+
- /name-of-a-new-bucket
|
75
|
+
Content-Length:
|
76
|
+
- '0'
|
77
|
+
Server:
|
78
|
+
- AmazonS3
|
79
|
+
body:
|
80
|
+
encoding: US-ASCII
|
81
|
+
string: ''
|
82
|
+
http_version:
|
83
|
+
recorded_at: Sun, 30 Dec 2012 17:59:39 GMT
|
84
|
+
- request:
|
85
|
+
method: put
|
86
|
+
uri: https://s3.amazonaws.com/name-of-a-new-bucket/?website
|
87
|
+
body:
|
88
|
+
encoding: US-ASCII
|
89
|
+
string: ! "\n <WebsiteConfiguration xmlns='http://s3.amazonaws.com/doc/2006-03-01/'>\n
|
90
|
+
\ <IndexDocument>\n <Suffix>index.html</Suffix>\n </IndexDocument>\n
|
91
|
+
\ <ErrorDocument>\n <Key>error.html</Key>\n </ErrorDocument>\n
|
92
|
+
\ </WebsiteConfiguration>\n "
|
93
|
+
headers:
|
94
|
+
Date:
|
95
|
+
- Sun, 30 Dec 2012 19:59:39 EET
|
96
|
+
Content-Type:
|
97
|
+
- ''
|
98
|
+
Content-Length:
|
99
|
+
- '298'
|
100
|
+
Authorization:
|
101
|
+
- AWS foo
|
102
|
+
response:
|
103
|
+
status:
|
104
|
+
code: 200
|
105
|
+
message: OK
|
106
|
+
headers:
|
107
|
+
X-Amz-Id-2:
|
108
|
+
- XZE0T/tWFNuRi6/yu1O+O3A7fJ/j2oas8ZqK4p6rLbaRxevXe44K/pAOsrB381ei
|
109
|
+
X-Amz-Request-Id:
|
110
|
+
- 514396AE3C1BB2C1
|
111
|
+
Date:
|
112
|
+
- Sun, 30 Dec 2012 17:59:41 GMT
|
113
|
+
Content-Length:
|
114
|
+
- '0'
|
115
|
+
Server:
|
116
|
+
- AmazonS3
|
117
|
+
body:
|
118
|
+
encoding: US-ASCII
|
119
|
+
string: ''
|
120
|
+
http_version:
|
121
|
+
recorded_at: Sun, 30 Dec 2012 17:59:42 GMT
|
122
|
+
- request:
|
123
|
+
method: put
|
124
|
+
uri: https://s3.amazonaws.com/name-of-a-new-bucket/?policy
|
125
|
+
body:
|
126
|
+
encoding: US-ASCII
|
127
|
+
string: ! "{\n \"Version\":\"2008-10-17\",\n \"Statement\":[{\n
|
128
|
+
\ \"Sid\":\"PublicReadForGetBucketObjects\",\n \"Effect\":\"Allow\",\n
|
129
|
+
\ \"Principal\": { \"AWS\": \"*\" },\n \"Action\":[\"s3:GetObject\"],\n
|
130
|
+
\ \"Resource\":[\"arn:aws:s3:::name-of-a-new-bucket/*\"]\n }]\n
|
131
|
+
\ }"
|
132
|
+
headers:
|
133
|
+
Date:
|
134
|
+
- Sun, 30 Dec 2012 19:59:42 EET
|
135
|
+
Content-Type:
|
136
|
+
- ''
|
137
|
+
Content-Length:
|
138
|
+
- '289'
|
139
|
+
Authorization:
|
140
|
+
- AWS foo
|
141
|
+
response:
|
142
|
+
status:
|
143
|
+
code: 204
|
144
|
+
message: No Content
|
145
|
+
headers:
|
146
|
+
X-Amz-Id-2:
|
147
|
+
- z/XdtqiOW0ihAGadAXwCNCAuE0OLeFisy4TN5e/3OmEI72dQxQjfftyXFSNNwOQN
|
148
|
+
X-Amz-Request-Id:
|
149
|
+
- 82E8EBABB5D150C5
|
150
|
+
Date:
|
151
|
+
- Sun, 30 Dec 2012 17:59:44 GMT
|
152
|
+
Server:
|
153
|
+
- AmazonS3
|
154
|
+
body:
|
155
|
+
encoding: US-ASCII
|
156
|
+
string: ''
|
157
|
+
http_version:
|
158
|
+
recorded_at: Sun, 30 Dec 2012 17:59:44 GMT
|
159
|
+
recorded_with: VCR 2.3.0
|
@@ -0,0 +1,78 @@
|
|
1
|
+
---
|
2
|
+
http_interactions:
|
3
|
+
- request:
|
4
|
+
method: put
|
5
|
+
uri: https://s3.amazonaws.com/name-of-an-existing-bucket/?website
|
6
|
+
body:
|
7
|
+
encoding: US-ASCII
|
8
|
+
string: ! "\n <WebsiteConfiguration xmlns='http://s3.amazonaws.com/doc/2006-03-01/'>\n
|
9
|
+
\ <IndexDocument>\n <Suffix>index.html</Suffix>\n </IndexDocument>\n
|
10
|
+
\ <ErrorDocument>\n <Key>error.html</Key>\n </ErrorDocument>\n
|
11
|
+
\ </WebsiteConfiguration>\n "
|
12
|
+
headers:
|
13
|
+
Date:
|
14
|
+
- Sun, 30 Dec 2012 20:40:12 EET
|
15
|
+
Content-Type:
|
16
|
+
- ''
|
17
|
+
Content-Length:
|
18
|
+
- '298'
|
19
|
+
Authorization:
|
20
|
+
- AWS
|
21
|
+
response:
|
22
|
+
status:
|
23
|
+
code: 200
|
24
|
+
message: OK
|
25
|
+
headers:
|
26
|
+
X-Amz-Id-2:
|
27
|
+
- IjY5jr731On+Fs4U70tsMf5f6KmYMhe+zCgbrIRBIrVqlRflrm5o9gpPWLzhO9SG
|
28
|
+
X-Amz-Request-Id:
|
29
|
+
- 2675BEDE56DD5A89
|
30
|
+
Date:
|
31
|
+
- Sun, 30 Dec 2012 18:40:14 GMT
|
32
|
+
Content-Length:
|
33
|
+
- '0'
|
34
|
+
Server:
|
35
|
+
- AmazonS3
|
36
|
+
body:
|
37
|
+
encoding: US-ASCII
|
38
|
+
string: ''
|
39
|
+
http_version:
|
40
|
+
recorded_at: Sun, 30 Dec 2012 18:40:14 GMT
|
41
|
+
- request:
|
42
|
+
method: put
|
43
|
+
uri: https://s3.amazonaws.com/name-of-an-existing-bucket/?policy
|
44
|
+
body:
|
45
|
+
encoding: US-ASCII
|
46
|
+
string: ! "{\n \"Version\":\"2008-10-17\",\n \"Statement\":[{\n
|
47
|
+
\ \"Sid\":\"PublicReadForGetBucketObjects\",\n \"Effect\":\"Allow\",\n
|
48
|
+
\ \"Principal\": { \"AWS\": \"*\" },\n \"Action\":[\"s3:GetObject\"],\n
|
49
|
+
\ \"Resource\":[\"arn:aws:s3:::name-of-an-existing-bucket/*\"]\n }]\n
|
50
|
+
\ }"
|
51
|
+
headers:
|
52
|
+
Date:
|
53
|
+
- Sun, 30 Dec 2012 20:40:14 EET
|
54
|
+
Content-Type:
|
55
|
+
- ''
|
56
|
+
Content-Length:
|
57
|
+
- '295'
|
58
|
+
Authorization:
|
59
|
+
- AWS
|
60
|
+
response:
|
61
|
+
status:
|
62
|
+
code: 204
|
63
|
+
message: No Content
|
64
|
+
headers:
|
65
|
+
X-Amz-Id-2:
|
66
|
+
- i1+BrkVJ09WJjTMD4FrlD7WU1lDeE3FrTJHs+GPui8JoXcFW0dFWFjykJVnXA+4g
|
67
|
+
X-Amz-Request-Id:
|
68
|
+
- 3959D1657C7E30DC
|
69
|
+
Date:
|
70
|
+
- Sun, 30 Dec 2012 18:40:15 GMT
|
71
|
+
Server:
|
72
|
+
- AmazonS3
|
73
|
+
body:
|
74
|
+
encoding: US-ASCII
|
75
|
+
string: ''
|
76
|
+
http_version:
|
77
|
+
recorded_at: Sun, 30 Dec 2012 18:40:15 GMT
|
78
|
+
recorded_with: VCR 2.3.0
|
@@ -0,0 +1,57 @@
|
|
1
|
+
Feature: load credentials and the S3 bucket name from a file
|
2
|
+
|
3
|
+
As an AWS S3 user
|
4
|
+
Who has S3 credentials and the S3 bucket name in a YAML file
|
5
|
+
I would like to configure the bucket to function as a website
|
6
|
+
|
7
|
+
Scenario: config file is missing property "s3_id"
|
8
|
+
Given a directory named "website-project"
|
9
|
+
And a file named "website-project/s3_config.yml" with:
|
10
|
+
"""
|
11
|
+
id: key
|
12
|
+
s3_secret: SECRET
|
13
|
+
s3_bucket: my-bucket
|
14
|
+
"""
|
15
|
+
When I run `configure-s3-website --config-file website-project/s3_config.yml`
|
16
|
+
Then the output should contain:
|
17
|
+
"""
|
18
|
+
website-project/s3_config.yml does not contain the required key(s) s3_id
|
19
|
+
"""
|
20
|
+
|
21
|
+
Scenario: config file is missing property "s3_secret"
|
22
|
+
Given a directory named "website-project"
|
23
|
+
And a file named "website-project/s3_config.yml" with:
|
24
|
+
"""
|
25
|
+
s3_id: key
|
26
|
+
s3_bucket: my-bucket
|
27
|
+
"""
|
28
|
+
When I run `configure-s3-website --config-file website-project/s3_config.yml`
|
29
|
+
Then the output should contain:
|
30
|
+
"""
|
31
|
+
website-project/s3_config.yml does not contain the required key(s) s3_secret
|
32
|
+
"""
|
33
|
+
|
34
|
+
Scenario: config file is missing property "s3_bucket"
|
35
|
+
Given a directory named "website-project"
|
36
|
+
And a file named "website-project/s3_config.yml" with:
|
37
|
+
"""
|
38
|
+
s3_id: key
|
39
|
+
s3_secret: secret
|
40
|
+
"""
|
41
|
+
When I run `configure-s3-website --config-file website-project/s3_config.yml`
|
42
|
+
Then the output should contain:
|
43
|
+
"""
|
44
|
+
website-project/s3_config.yml does not contain the required key(s) s3_bucket
|
45
|
+
"""
|
46
|
+
|
47
|
+
Scenario: config file is missing properties "s3_bucket" and "s3_id"
|
48
|
+
Given a directory named "website-project"
|
49
|
+
And a file named "website-project/s3_config.yml" with:
|
50
|
+
"""
|
51
|
+
s3_secret: secret
|
52
|
+
"""
|
53
|
+
When I run `configure-s3-website --config-file website-project/s3_config.yml`
|
54
|
+
Then the output should contain:
|
55
|
+
"""
|
56
|
+
website-project/s3_config.yml does not contain the required key(s) s3_id, s3_bucket
|
57
|
+
"""
|
@@ -0,0 +1,24 @@
|
|
1
|
+
Feature: configure an S3 bucket to function as a website
|
2
|
+
|
3
|
+
@bucket-does-not-exist
|
4
|
+
Scenario: The bucket does not yet exist
|
5
|
+
Given my config file is in "features/support/sample_config_files/s3_config_with_non-existing_bucket.yml"
|
6
|
+
When I run the configure-s3-website command
|
7
|
+
Then the output should be
|
8
|
+
"""
|
9
|
+
Created bucket name-of-a-new-bucket
|
10
|
+
Bucket name-of-a-new-bucket now functions as a website
|
11
|
+
Bucket name-of-a-new-bucket is now readable to the whole world
|
12
|
+
|
13
|
+
"""
|
14
|
+
|
15
|
+
@bucket-exists
|
16
|
+
Scenario: The bucket already exists
|
17
|
+
Given my config file is in "features/support/sample_config_files/s3_config_with_existing_bucket.yml"
|
18
|
+
When I run the configure-s3-website command
|
19
|
+
Then the output should be
|
20
|
+
"""
|
21
|
+
Bucket name-of-an-existing-bucket now functions as a website
|
22
|
+
Bucket name-of-an-existing-bucket is now readable to the whole world
|
23
|
+
|
24
|
+
"""
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
|
3
|
+
Given /^my config file is in "(.*?)"$/ do |config_file_path|
|
4
|
+
@config_file_path = config_file_path
|
5
|
+
end
|
6
|
+
|
7
|
+
When /^I run the configure-s3-website command$/ do
|
8
|
+
@console_output = capture_stdout {
|
9
|
+
config_source = ConfigureS3Website::FileConfigSource.new(@config_file_path)
|
10
|
+
ConfigureS3Website::S3Client.configure_website(config_source)
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
Then /^the output should be$/ do |expected_console_output|
|
15
|
+
@console_output.should eq(expected_console_output)
|
16
|
+
end
|
17
|
+
|
18
|
+
module Kernel
|
19
|
+
require 'stringio'
|
20
|
+
|
21
|
+
def capture_stdout
|
22
|
+
out = StringIO.new
|
23
|
+
$stdout = out
|
24
|
+
yield
|
25
|
+
out.string
|
26
|
+
ensure
|
27
|
+
$stdout = STDOUT
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
|
4
|
+
Bundler.require
|
5
|
+
|
6
|
+
require 'aruba/cucumber'
|
7
|
+
require 'cucumber/rspec/doubles'
|
8
|
+
|
9
|
+
# Following from 'aruba/cucumber'
|
10
|
+
Before do
|
11
|
+
@__aruba_original_paths = (ENV['PATH'] || '').split(File::PATH_SEPARATOR)
|
12
|
+
ENV['PATH'] = ([File.expand_path('bin')] + @__aruba_original_paths).join(File::PATH_SEPARATOR)
|
13
|
+
end
|
14
|
+
|
15
|
+
After do
|
16
|
+
ENV['PATH'] = @__aruba_original_paths.join(File::PATH_SEPARATOR)
|
17
|
+
end
|
18
|
+
# End of following from 'aruba/cucumber'
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'erb'
|
3
|
+
|
4
|
+
module ConfigureS3Website
|
5
|
+
class FileConfigSource < ConfigSource
|
6
|
+
def initialize(yaml_file_path)
|
7
|
+
@config = parse_config yaml_file_path
|
8
|
+
end
|
9
|
+
|
10
|
+
def s3_access_key_id
|
11
|
+
@config['s3_id']
|
12
|
+
end
|
13
|
+
|
14
|
+
def s3_secret_access_key
|
15
|
+
@config['s3_secret']
|
16
|
+
end
|
17
|
+
|
18
|
+
def s3_bucket_name
|
19
|
+
@config['s3_bucket']
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def parse_config(yaml_file_path)
|
25
|
+
config = YAML.load(ERB.new(File.read(yaml_file_path)).result)
|
26
|
+
validate_config(config, yaml_file_path)
|
27
|
+
config
|
28
|
+
end
|
29
|
+
|
30
|
+
def validate_config(config, yaml_file_path)
|
31
|
+
required_keys = %w{s3_id s3_secret s3_bucket}
|
32
|
+
missing_keys = required_keys.reject do |key| config.keys.include?key end
|
33
|
+
unless missing_keys.empty?
|
34
|
+
raise "File #{yaml_file_path} does not contain the required key(s) #{missing_keys.join(', ')}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'openssl'
|
3
|
+
require 'digest/sha1'
|
4
|
+
require 'digest/md5'
|
5
|
+
require 'net/https'
|
6
|
+
|
7
|
+
module ConfigureS3Website
|
8
|
+
class S3Client
|
9
|
+
def self.configure_website(config_source)
|
10
|
+
begin
|
11
|
+
enable_website_configuration(config_source)
|
12
|
+
make_bucket_readable_to_everyone(config_source)
|
13
|
+
rescue NoSuchBucketError
|
14
|
+
create_bucket(config_source)
|
15
|
+
retry
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def self.enable_website_configuration(config_source)
|
22
|
+
body = %|
|
23
|
+
<WebsiteConfiguration xmlns='http://s3.amazonaws.com/doc/2006-03-01/'>
|
24
|
+
<IndexDocument>
|
25
|
+
<Suffix>index.html</Suffix>
|
26
|
+
</IndexDocument>
|
27
|
+
<ErrorDocument>
|
28
|
+
<Key>error.html</Key>
|
29
|
+
</ErrorDocument>
|
30
|
+
</WebsiteConfiguration>
|
31
|
+
|
|
32
|
+
call_s3_api(
|
33
|
+
path = "/#{config_source.s3_bucket_name}/?website",
|
34
|
+
method = Net::HTTP::Put,
|
35
|
+
body = body,
|
36
|
+
config_source = config_source
|
37
|
+
)
|
38
|
+
puts "Bucket #{config_source.s3_bucket_name} now functions as a website"
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.make_bucket_readable_to_everyone(config_source)
|
42
|
+
policy_json = %|{
|
43
|
+
"Version":"2008-10-17",
|
44
|
+
"Statement":[{
|
45
|
+
"Sid":"PublicReadForGetBucketObjects",
|
46
|
+
"Effect":"Allow",
|
47
|
+
"Principal": { "AWS": "*" },
|
48
|
+
"Action":["s3:GetObject"],
|
49
|
+
"Resource":["arn:aws:s3:::#{config_source.s3_bucket_name}/*"]
|
50
|
+
}]
|
51
|
+
}|
|
52
|
+
call_s3_api(
|
53
|
+
path = "/#{config_source.s3_bucket_name}/?policy",
|
54
|
+
method = Net::HTTP::Put,
|
55
|
+
body = policy_json,
|
56
|
+
config_source = config_source
|
57
|
+
)
|
58
|
+
puts "Bucket #{config_source.s3_bucket_name} is now readable to the whole world"
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.create_bucket(config_source)
|
62
|
+
call_s3_api(
|
63
|
+
path = "/#{config_source.s3_bucket_name}",
|
64
|
+
method = Net::HTTP::Put,
|
65
|
+
body = '',
|
66
|
+
config_source = config_source
|
67
|
+
)
|
68
|
+
puts "Created bucket #{config_source.s3_bucket_name}"
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.call_s3_api(path, method, body, config_source)
|
72
|
+
date = Time.now.strftime("%a, %d %b %Y %H:%M:%S %Z")
|
73
|
+
digest = create_digest(path, method, config_source, date)
|
74
|
+
url = "https://s3.amazonaws.com#{path}"
|
75
|
+
uri = URI.parse(url)
|
76
|
+
req = method.new(uri.to_s)
|
77
|
+
req.initialize_http_header({
|
78
|
+
'Date' => date,
|
79
|
+
'Content-Type' => '',
|
80
|
+
'Content-Length' => body.length.to_s,
|
81
|
+
'Authorization' => "AWS %s:%s" % [config_source.s3_access_key_id, digest]
|
82
|
+
})
|
83
|
+
req.body = body
|
84
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
85
|
+
http.use_ssl = true
|
86
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
87
|
+
res = http.request(req)
|
88
|
+
if res.code.to_i.between? 200, 299
|
89
|
+
res
|
90
|
+
else
|
91
|
+
raise ConfigureS3Website::ErrorParser.create_error res.body
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.create_digest(path, method, config_source, date)
|
96
|
+
digest = OpenSSL::Digest::Digest.new('sha1')
|
97
|
+
method_string = method.to_s.match(/Net::HTTP::(\w+)/)[1].upcase
|
98
|
+
can_string = "#{method_string}\n\n\n#{date}\n#{path}"
|
99
|
+
hmac = OpenSSL::HMAC.digest(digest, config_source.s3_secret_access_key, can_string)
|
100
|
+
signature = Base64.encode64(hmac).strip
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
module ConfigureS3Website
|
108
|
+
class ErrorParser
|
109
|
+
def self.create_error(amazon_error_xml)
|
110
|
+
error_code = amazon_error_xml.delete('\n').match(/<Code>(.*?)<\/Code>/)[1]
|
111
|
+
begin
|
112
|
+
Object.const_get("#{error_code}Error").new
|
113
|
+
rescue NameError
|
114
|
+
GenericS3Error.new(amazon_error_xml)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
class NoSuchBucketError < StandardError
|
121
|
+
end
|
122
|
+
|
123
|
+
class GenericS3Error < StandardError
|
124
|
+
def initialize(error_message)
|
125
|
+
super("AWS API call failed:\n#{error_message}")
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'configure-s3-website'
|
3
|
+
|
4
|
+
describe ConfigureS3Website::FileConfigSource do
|
5
|
+
it 'can parse files that contain eRuby code' do
|
6
|
+
extractor = ConfigureS3Website::FileConfigSource.new('spec/sample_files/_config_file_with_eruby.yml')
|
7
|
+
extractor.s3_access_key_id.should eq('hello world')
|
8
|
+
extractor.s3_secret_access_key.should eq('secret world')
|
9
|
+
extractor.s3_bucket_name.should eq('my-bucket')
|
10
|
+
end
|
11
|
+
end
|
metadata
ADDED
@@ -0,0 +1,193 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: configure-s3-website
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Lauri Lehmijoki
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-30 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.10.0
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 2.10.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rspec-expectations
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 2.10.0
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 2.10.0
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: cucumber
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 1.2.0
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.2.0
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: aruba
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 0.4.0
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 0.4.0
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: rake
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 0.9.0
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ~>
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 0.9.0
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: vcr
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ~>
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: 2.3.0
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ~>
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: 2.3.0
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: webmock
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ~>
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 1.8.0
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ~>
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: 1.8.0
|
126
|
+
description:
|
127
|
+
email: lauri.lehmijoki@iki.fi
|
128
|
+
executables:
|
129
|
+
- configure-s3-website
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files: []
|
132
|
+
files:
|
133
|
+
- .gitignore
|
134
|
+
- .travis.yml
|
135
|
+
- Gemfile
|
136
|
+
- Gemfile.lock
|
137
|
+
- LICENSE
|
138
|
+
- README.md
|
139
|
+
- Rakefile
|
140
|
+
- bin/configure-s3-website
|
141
|
+
- configure-s3-website.gemspec
|
142
|
+
- features/cassettes/cucumber_tags/bucket-does-not-exist.yml
|
143
|
+
- features/cassettes/cucumber_tags/bucket-exists.yml
|
144
|
+
- features/config_file.feature
|
145
|
+
- features/configure_bucket.feature
|
146
|
+
- features/step_definitions/steps.rb
|
147
|
+
- features/support/env.rb
|
148
|
+
- features/support/sample_config_files/s3_config_with_existing_bucket.yml
|
149
|
+
- features/support/sample_config_files/s3_config_with_non-existing_bucket.yml
|
150
|
+
- features/support/vcr.rb
|
151
|
+
- lib/configure-s3-website.rb
|
152
|
+
- lib/configure-s3-website/config_source/config_source.rb
|
153
|
+
- lib/configure-s3-website/config_source/file_config_source.rb
|
154
|
+
- lib/configure-s3-website/s3_client.rb
|
155
|
+
- lib/configure-s3-website/version.rb
|
156
|
+
- spec/config_extractor/file_config_extractor_spec.rb
|
157
|
+
- spec/sample_files/_config_file_with_eruby.yml
|
158
|
+
homepage: https://github.com/laurilehmijoki/configure-s3-website
|
159
|
+
licenses: []
|
160
|
+
post_install_message:
|
161
|
+
rdoc_options: []
|
162
|
+
require_paths:
|
163
|
+
- lib
|
164
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
165
|
+
none: false
|
166
|
+
requirements:
|
167
|
+
- - ! '>='
|
168
|
+
- !ruby/object:Gem::Version
|
169
|
+
version: '0'
|
170
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
171
|
+
none: false
|
172
|
+
requirements:
|
173
|
+
- - ! '>='
|
174
|
+
- !ruby/object:Gem::Version
|
175
|
+
version: '0'
|
176
|
+
requirements: []
|
177
|
+
rubyforge_project:
|
178
|
+
rubygems_version: 1.8.24
|
179
|
+
signing_key:
|
180
|
+
specification_version: 3
|
181
|
+
summary: Configure your S3 bucket to function as a web site
|
182
|
+
test_files:
|
183
|
+
- features/cassettes/cucumber_tags/bucket-does-not-exist.yml
|
184
|
+
- features/cassettes/cucumber_tags/bucket-exists.yml
|
185
|
+
- features/config_file.feature
|
186
|
+
- features/configure_bucket.feature
|
187
|
+
- features/step_definitions/steps.rb
|
188
|
+
- features/support/env.rb
|
189
|
+
- features/support/sample_config_files/s3_config_with_existing_bucket.yml
|
190
|
+
- features/support/sample_config_files/s3_config_with_non-existing_bucket.yml
|
191
|
+
- features/support/vcr.rb
|
192
|
+
- spec/config_extractor/file_config_extractor_spec.rb
|
193
|
+
- spec/sample_files/_config_file_with_eruby.yml
|