configure-s3-website 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+ tmp
2
+ pkg
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.2
5
+ - 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
@@ -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.
@@ -0,0 +1,72 @@
1
+ # Configure-s3-website
2
+
3
+ [![Build Status](https://secure.travis-ci.org/laurilehmijoki/configure-s3-website.png)](http://travis-ci.org/laurilehmijoki/configure-s3-website)
4
+ [![Gem Version](https://fury-badge.herokuapp.com/rb/configure-s3-website.png)](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.
@@ -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,3 @@
1
+ s3_id: foo
2
+ s3_secret: foo
3
+ s3_bucket: name-of-an-existing-bucket
@@ -0,0 +1,3 @@
1
+ s3_id: foo
2
+ s3_secret: foo
3
+ s3_bucket: name-of-a-new-bucket
@@ -0,0 +1,11 @@
1
+ require 'vcr'
2
+
3
+ VCR.configure do |c|
4
+ c.hook_into :webmock
5
+ c.cassette_library_dir = 'features/cassettes'
6
+ end
7
+
8
+ VCR.cucumber_tags do |t|
9
+ t.tag '@bucket-does-not-exist'
10
+ t.tag '@bucket-exists'
11
+ end
@@ -0,0 +1,4 @@
1
+ require 'configure-s3-website/version'
2
+ require 'configure-s3-website/s3_client'
3
+ require 'configure-s3-website/config_source/config_source'
4
+ require 'configure-s3-website/config_source/file_config_source'
@@ -0,0 +1,12 @@
1
+ module ConfigureS3Website
2
+ class ConfigSource
3
+ def s3_access_key_id
4
+ end
5
+
6
+ def s3_secret_access_key
7
+ end
8
+
9
+ def s3_bucket_name
10
+ end
11
+ end
12
+ end
@@ -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,3 @@
1
+ module ConfigureS3Website
2
+ VERSION = '1.0.0'
3
+ 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
@@ -0,0 +1,3 @@
1
+ s3_id: <%= 'hello world' %>
2
+ s3_secret: <%= 'secret world' %>
3
+ s3_bucket: my-bucket
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