configure-s3-website 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +1 -1
- data/README.md +44 -33
- data/bin/configure-s3-website +2 -24
- data/changelog.md +5 -0
- data/features/cassettes/cucumber_tags/create-cf-dist.yml +134 -0
- data/features/configure_bucket.feature +18 -11
- data/features/create_cloudfront_dist.feature +164 -0
- data/features/step_definitions/steps.rb +44 -7
- data/features/support/env.rb +4 -0
- data/features/support/sample_config_files/config_with_cloudfront_distribution_id.yml +4 -0
- data/features/support/sample_config_files/create_cf_dist.yml +3 -0
- data/features/support/vcr.rb +1 -0
- data/lib/configure-s3-website.rb +5 -0
- data/lib/configure-s3-website/cli.rb +35 -0
- data/lib/configure-s3-website/cloudfront_client.rb +117 -0
- data/lib/configure-s3-website/config_source/config_source.rb +9 -0
- data/lib/configure-s3-website/config_source/file_config_source.rb +19 -3
- data/lib/configure-s3-website/http_helper.rb +83 -0
- data/lib/configure-s3-website/runner.rb +17 -0
- data/lib/configure-s3-website/s3_client.rb +9 -68
- data/lib/configure-s3-website/version.rb +1 -1
- data/lib/configure-s3-website/xml_helper.rb +15 -0
- data/spec/config_source/file_config_source_spec.rb +22 -0
- data/spec/s3_client_spec.rb +2 -2
- data/spec/xml_helper_spec.rb +39 -0
- metadata +21 -6
- data/spec/config_extractor/file_config_extractor_spec.rb +0 -11
@@ -1,16 +1,18 @@
|
|
1
1
|
require 'rspec'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
When /^I run the configure-s3-website command$/ do
|
3
|
+
When /^I run the configure-s3-website command with parameters$/ do |table|
|
4
|
+
options, optparse = ConfigureS3Website::CLI.optparse_and_options
|
5
|
+
optparse.parse! args_array_from_cucumber_table(table)
|
6
|
+
@reset = create_reset_config_file_function options[:config_source].description
|
8
7
|
@console_output = capture_stdout {
|
9
|
-
|
10
|
-
ConfigureS3Website::S3Client.configure_website(config_source)
|
8
|
+
ConfigureS3Website::Runner.run(options, stub_stdin)
|
11
9
|
}
|
12
10
|
end
|
13
11
|
|
12
|
+
Given /^I answer 'yes' to 'do you want to use CloudFront'$/ do
|
13
|
+
@first_stdin_answer = 'y'
|
14
|
+
end
|
15
|
+
|
14
16
|
Then /^the output should be$/ do |expected_console_output|
|
15
17
|
@console_output.should eq(expected_console_output)
|
16
18
|
end
|
@@ -19,6 +21,41 @@ Then /^the output should include$/ do |expected_console_output|
|
|
19
21
|
@console_output.should include(expected_console_output)
|
20
22
|
end
|
21
23
|
|
24
|
+
def args_array_from_cucumber_table(table)
|
25
|
+
args = []
|
26
|
+
table.hashes.map do |entry|
|
27
|
+
{ entry[:option] => entry[:value] }
|
28
|
+
end.each do |opt|
|
29
|
+
args << opt.keys.first
|
30
|
+
args << opt.values.first if opt.values.first
|
31
|
+
end
|
32
|
+
args
|
33
|
+
end
|
34
|
+
|
35
|
+
def stub_stdin
|
36
|
+
stdin = stub('std_in')
|
37
|
+
stdin.stub(:gets).and_return {
|
38
|
+
first_stdin_answer
|
39
|
+
}
|
40
|
+
stdin
|
41
|
+
end
|
42
|
+
|
43
|
+
# A function for bringing back the original config file
|
44
|
+
# (in case we modified it during the test)
|
45
|
+
def create_reset_config_file_function(yaml_file_path)
|
46
|
+
original_contents = File.open(yaml_file_path, 'r').read
|
47
|
+
-> {
|
48
|
+
File.open(yaml_file_path, 'w') { |yaml_file|
|
49
|
+
yaml_file.puts(original_contents)
|
50
|
+
}
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
# The first prompt asks "do you want to create a CloudFront distro"
|
55
|
+
def first_stdin_answer
|
56
|
+
@first_stdin_answer || 'n'
|
57
|
+
end
|
58
|
+
|
22
59
|
module Kernel
|
23
60
|
require 'stringio'
|
24
61
|
|
data/features/support/env.rb
CHANGED
data/features/support/vcr.rb
CHANGED
data/lib/configure-s3-website.rb
CHANGED
@@ -1,4 +1,9 @@
|
|
1
1
|
require 'configure-s3-website/version'
|
2
2
|
require 'configure-s3-website/s3_client'
|
3
|
+
require 'configure-s3-website/cloudfront_client'
|
4
|
+
require 'configure-s3-website/xml_helper'
|
5
|
+
require 'configure-s3-website/http_helper'
|
6
|
+
require 'configure-s3-website/runner'
|
7
|
+
require 'configure-s3-website/cli'
|
3
8
|
require 'configure-s3-website/config_source/config_source'
|
4
9
|
require 'configure-s3-website/config_source/file_config_source'
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module ConfigureS3Website
|
2
|
+
class CLI
|
3
|
+
def self.optparse_and_options
|
4
|
+
options = {}
|
5
|
+
optparse = OptionParser.new do |opts|
|
6
|
+
opts.banner = banner
|
7
|
+
opts.on('-f', '--config-file FILE',
|
8
|
+
'Pick credentials and the S3 bucket name from a config file') do
|
9
|
+
|yaml_file_path|
|
10
|
+
options[:config_source] =
|
11
|
+
ConfigureS3Website::FileConfigSource.new yaml_file_path
|
12
|
+
end
|
13
|
+
opts.on('-v', '--verbose', 'Print more stuff') do
|
14
|
+
options[:verbose] = true
|
15
|
+
end
|
16
|
+
opts.on('--help', 'Display this screen') do
|
17
|
+
puts opts
|
18
|
+
exit
|
19
|
+
end
|
20
|
+
end
|
21
|
+
[options, optparse]
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def self.banner
|
27
|
+
%|Usage: #{File.basename(__FILE__)} arguments
|
28
|
+
|
29
|
+
Configure your S3 bucket to function as a web site
|
30
|
+
|
31
|
+
Arguments:
|
32
|
+
|
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require "rexml/document"
|
2
|
+
require "rexml/xpath"
|
3
|
+
|
4
|
+
module ConfigureS3Website
|
5
|
+
class CloudFrontClient
|
6
|
+
def self.create_distribution_if_user_agrees(options, standard_input)
|
7
|
+
puts 'Do you want to deliver your website via CloudFront, the CDN of Amazon? [y/N]'
|
8
|
+
case standard_input.gets.chomp
|
9
|
+
when /(y|Y)/ then do_create_distribution options
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def self.do_create_distribution(options)
|
16
|
+
config_source = options[:config_source]
|
17
|
+
response = HttpHelper.call_cloudfront_api(
|
18
|
+
path = '/2012-07-01/distribution',
|
19
|
+
method = Net::HTTP::Post,
|
20
|
+
body = (distribution_config_xml config_source),
|
21
|
+
config_source = config_source
|
22
|
+
)
|
23
|
+
response_xml = REXML::Document.new(response.body)
|
24
|
+
dist_id = REXML::XPath.first(response_xml, '/Distribution/Id').get_text
|
25
|
+
print_report_on_new_dist response_xml, dist_id, options
|
26
|
+
config_source.cloudfront_distribution_id = dist_id.to_s
|
27
|
+
puts " Added setting 'cloudfront_distribution_id: #{dist_id}' into #{config_source.description}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.print_report_on_new_dist(response_xml, dist_id, options)
|
31
|
+
config_source = options[:config_source]
|
32
|
+
domain_name = REXML::XPath.first(response_xml, '/Distribution/DomainName').get_text
|
33
|
+
puts " The distribution #{dist_id} at #{domain_name} now delivers the bucket #{config_source.s3_bucket_name}"
|
34
|
+
puts ' Please allow up to 15 minutes for the distribution to initialise'
|
35
|
+
puts ' For more information on the distribution, see https://console.aws.amazon.com/cloudfront'
|
36
|
+
if options[:verbose]
|
37
|
+
puts ' Below is the response from the CloudFront API:'
|
38
|
+
print_verbose(response_xml, left_padding = 4)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.print_verbose(response_xml, left_padding)
|
43
|
+
lines = []
|
44
|
+
response_xml.write(lines, 2)
|
45
|
+
padding = ""
|
46
|
+
left_padding.times { padding << " " }
|
47
|
+
puts lines.join().
|
48
|
+
gsub(/^/, "" + padding).
|
49
|
+
gsub(/\s$/, "")
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.distribution_config_xml(config_source, custom_cf_settings = {})
|
53
|
+
%|
|
54
|
+
<DistributionConfig xmlns="http://cloudfront.amazonaws.com/doc/2012-07-01/">
|
55
|
+
<Origins>
|
56
|
+
<Quantity>1</Quantity>
|
57
|
+
<Items>
|
58
|
+
<Origin>
|
59
|
+
<Id>#{origin_id config_source}</Id>
|
60
|
+
<DomainName>#{config_source.s3_bucket_name}.#{Endpoint.by_config_source(config_source).hostname}</DomainName>
|
61
|
+
<S3OriginConfig>
|
62
|
+
<OriginAccessIdentity></OriginAccessIdentity>
|
63
|
+
</S3OriginConfig>
|
64
|
+
</Origin>
|
65
|
+
</Items>
|
66
|
+
</Origins>
|
67
|
+
#{
|
68
|
+
XmlHelper.hash_to_api_xml(
|
69
|
+
default_cloudfront_settings(config_source).merge custom_cf_settings
|
70
|
+
)
|
71
|
+
}
|
72
|
+
</DistributionConfig>
|
73
|
+
|
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.default_cloudfront_settings(config_source)
|
77
|
+
{
|
78
|
+
'caller_reference' => 'configure-s3-website gem ' + Time.now.to_s,
|
79
|
+
'default_root_object' => 'index.html',
|
80
|
+
'logging' => {
|
81
|
+
'enabled' => 'false',
|
82
|
+
'include_cookies' => 'false',
|
83
|
+
'bucket' => '',
|
84
|
+
'prefix' => ''
|
85
|
+
},
|
86
|
+
'enabled' => 'true',
|
87
|
+
'comment' => 'Created by the configure-s3-website gem',
|
88
|
+
'aliases' => {
|
89
|
+
'quantity' => '0'
|
90
|
+
},
|
91
|
+
'default_cache_behavior' => {
|
92
|
+
'target_origin_id' => (origin_id config_source),
|
93
|
+
'trusted_signers' => {
|
94
|
+
'enabled' => 'false',
|
95
|
+
'quantity' => '0'
|
96
|
+
},
|
97
|
+
'forwarded_values' => {
|
98
|
+
'query_string' => 'true',
|
99
|
+
'cookies' => {
|
100
|
+
'forward' => 'all'
|
101
|
+
}
|
102
|
+
},
|
103
|
+
'viewer_protocol_policy' => 'allow-all',
|
104
|
+
'min_TTL' => (60 * 60 * 24)
|
105
|
+
},
|
106
|
+
'cache_behaviors' => {
|
107
|
+
'quantity' => '0'
|
108
|
+
},
|
109
|
+
'price_class' => 'PriceClass_All'
|
110
|
+
}
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.origin_id(config_source)
|
114
|
+
"#{config_source.s3_bucket_name}-S3-origin"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -1,5 +1,8 @@
|
|
1
1
|
module ConfigureS3Website
|
2
2
|
class ConfigSource
|
3
|
+
def description
|
4
|
+
end
|
5
|
+
|
3
6
|
def s3_access_key_id
|
4
7
|
end
|
5
8
|
|
@@ -14,5 +17,11 @@ module ConfigureS3Website
|
|
14
17
|
|
15
18
|
def routing_rules
|
16
19
|
end
|
20
|
+
|
21
|
+
def cloudfront_distribution_id
|
22
|
+
end
|
23
|
+
|
24
|
+
def cloudfront_distribution_id=(dist_id)
|
25
|
+
end
|
17
26
|
end
|
18
27
|
end
|
@@ -4,7 +4,12 @@ require 'erb'
|
|
4
4
|
module ConfigureS3Website
|
5
5
|
class FileConfigSource < ConfigSource
|
6
6
|
def initialize(yaml_file_path)
|
7
|
-
@
|
7
|
+
@yaml_file_path = yaml_file_path
|
8
|
+
@config = FileConfigSource.parse_config yaml_file_path
|
9
|
+
end
|
10
|
+
|
11
|
+
def description
|
12
|
+
@yaml_file_path
|
8
13
|
end
|
9
14
|
|
10
15
|
def s3_access_key_id
|
@@ -27,15 +32,26 @@ module ConfigureS3Website
|
|
27
32
|
@config['routing_rules']
|
28
33
|
end
|
29
34
|
|
35
|
+
def cloudfront_distribution_id
|
36
|
+
@config['cloudfront_distribution_id']
|
37
|
+
end
|
38
|
+
|
39
|
+
def cloudfront_distribution_id=(dist_id)
|
40
|
+
@config['cloudfront_distribution_id'] = dist_id
|
41
|
+
File.open(@yaml_file_path, 'w') do |yaml_file|
|
42
|
+
yaml_file.puts @config.to_yaml
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
30
46
|
private
|
31
47
|
|
32
|
-
def parse_config(yaml_file_path)
|
48
|
+
def self.parse_config(yaml_file_path)
|
33
49
|
config = YAML.load(ERB.new(File.read(yaml_file_path)).result)
|
34
50
|
validate_config(config, yaml_file_path)
|
35
51
|
config
|
36
52
|
end
|
37
53
|
|
38
|
-
def validate_config(config, yaml_file_path)
|
54
|
+
def self.validate_config(config, yaml_file_path)
|
39
55
|
required_keys = %w{s3_id s3_secret s3_bucket}
|
40
56
|
missing_keys = required_keys.reject do |key| config.keys.include?key end
|
41
57
|
unless missing_keys.empty?
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module ConfigureS3Website
|
2
|
+
class HttpHelper
|
3
|
+
def self.call_s3_api(path, method, body, config_source)
|
4
|
+
endpoint = Endpoint.by_config_source(config_source)
|
5
|
+
date = Time.now.utc.strftime("%a, %d %b %Y %H:%M:%S %Z")
|
6
|
+
digest = create_s3_digest(path, method, config_source, date)
|
7
|
+
self.call_api(
|
8
|
+
path, method, body, config_source, endpoint.hostname, digest, date
|
9
|
+
)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.call_cloudfront_api(path, method, body, config_source)
|
13
|
+
date = Time.now.utc.strftime("%a, %d %b %Y %H:%M:%S %Z")
|
14
|
+
digest = create_cloudfront_digest(config_source, date)
|
15
|
+
self.call_api(
|
16
|
+
path, method, body, config_source, 'cloudfront.amazonaws.com', digest, date
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def self.call_api(path, method, body, config_source, hostname, digest, date)
|
23
|
+
url = "https://#{hostname}#{path}"
|
24
|
+
uri = URI.parse(url)
|
25
|
+
req = method.new(uri.to_s)
|
26
|
+
req.initialize_http_header({
|
27
|
+
'Date' => date,
|
28
|
+
'Content-Type' => '',
|
29
|
+
'Content-Length' => body.length.to_s,
|
30
|
+
'Authorization' => "AWS %s:%s" % [config_source.s3_access_key_id, digest]
|
31
|
+
})
|
32
|
+
req.body = body
|
33
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
34
|
+
http.use_ssl = true
|
35
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
36
|
+
res = http.request(req)
|
37
|
+
if res.code.to_i.between? 200, 299
|
38
|
+
res
|
39
|
+
else
|
40
|
+
raise ConfigureS3Website::ErrorParser.create_error res.body
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.create_s3_digest(path, method, config_source, date)
|
45
|
+
digest = OpenSSL::Digest::Digest.new('sha1')
|
46
|
+
method_string = method.to_s.match(/Net::HTTP::(\w+)/)[1].upcase
|
47
|
+
can_string = "#{method_string}\n\n\n#{date}\n#{path}"
|
48
|
+
hmac = OpenSSL::HMAC.digest(digest, config_source.s3_secret_access_key, can_string)
|
49
|
+
signature = Base64.encode64(hmac).strip
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.create_cloudfront_digest(config_source, date)
|
53
|
+
digest = Base64.encode64(
|
54
|
+
OpenSSL::HMAC.digest(
|
55
|
+
OpenSSL::Digest::Digest.new('sha1'),
|
56
|
+
config_source.s3_secret_access_key,
|
57
|
+
date
|
58
|
+
)
|
59
|
+
).strip
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
module ConfigureS3Website
|
67
|
+
class ErrorParser
|
68
|
+
def self.create_error(amazon_error_xml)
|
69
|
+
error_code = amazon_error_xml.delete('\n').match(/<Code>(.*?)<\/Code>/)[1]
|
70
|
+
begin
|
71
|
+
Object.const_get("#{error_code}Error").new
|
72
|
+
rescue NameError
|
73
|
+
GenericError.new(amazon_error_xml)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class GenericError < StandardError
|
80
|
+
def initialize(error_message)
|
81
|
+
super("AWS API call failed:\n#{error_message}")
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module ConfigureS3Website
|
2
|
+
class Runner
|
3
|
+
def self.run(options, standard_input = STDIN)
|
4
|
+
S3Client.configure_website options
|
5
|
+
unless user_already_has_cf_configured options
|
6
|
+
CloudFrontClient.create_distribution_if_user_agrees options, standard_input
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def self.user_already_has_cf_configured(options)
|
13
|
+
config_source = options[:config_source]
|
14
|
+
config_source.cloudfront_distribution_id
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -6,7 +6,8 @@ require 'net/https'
|
|
6
6
|
|
7
7
|
module ConfigureS3Website
|
8
8
|
class S3Client
|
9
|
-
def self.configure_website(
|
9
|
+
def self.configure_website(options)
|
10
|
+
config_source = options[:config_source]
|
10
11
|
begin
|
11
12
|
enable_website_configuration(config_source)
|
12
13
|
make_bucket_readable_to_everyone(config_source)
|
@@ -30,7 +31,7 @@ module ConfigureS3Website
|
|
30
31
|
</ErrorDocument>
|
31
32
|
</WebsiteConfiguration>
|
32
33
|
|
|
33
|
-
call_s3_api(
|
34
|
+
HttpHelper.call_s3_api(
|
34
35
|
path = "/#{config_source.s3_bucket_name}/?website",
|
35
36
|
method = Net::HTTP::Put,
|
36
37
|
body = body,
|
@@ -50,7 +51,7 @@ module ConfigureS3Website
|
|
50
51
|
"Resource":["arn:aws:s3:::#{config_source.s3_bucket_name}/*"]
|
51
52
|
}]
|
52
53
|
}|
|
53
|
-
call_s3_api(
|
54
|
+
HttpHelper.call_s3_api(
|
54
55
|
path = "/#{config_source.s3_bucket_name}/?policy",
|
55
56
|
method = Net::HTTP::Put,
|
56
57
|
body = policy_json,
|
@@ -76,7 +77,7 @@ module ConfigureS3Website
|
|
76
77
|
body << %|
|
77
78
|
<RoutingRule>
|
78
79
|
|
|
79
|
-
body <<
|
80
|
+
body << XmlHelper.hash_to_api_xml(routing_rule, 7)
|
80
81
|
body << %|
|
81
82
|
</RoutingRule>
|
82
83
|
|
|
@@ -86,7 +87,7 @@ module ConfigureS3Website
|
|
86
87
|
</WebsiteConfiguration>
|
87
88
|
|
|
88
89
|
|
89
|
-
call_s3_api(
|
90
|
+
HttpHelper.call_s3_api(
|
90
91
|
path = "/#{config_source.s3_bucket_name}/?website",
|
91
92
|
method = Net::HTTP::Put,
|
92
93
|
body = body,
|
@@ -110,7 +111,7 @@ module ConfigureS3Website
|
|
110
111
|
|
|
111
112
|
end
|
112
113
|
|
113
|
-
call_s3_api(
|
114
|
+
HttpHelper.call_s3_api(
|
114
115
|
path = "/#{config_source.s3_bucket_name}",
|
115
116
|
method = Net::HTTP::Put,
|
116
117
|
body = body,
|
@@ -122,51 +123,6 @@ module ConfigureS3Website
|
|
122
123
|
endpoint.region
|
123
124
|
]
|
124
125
|
end
|
125
|
-
|
126
|
-
def self.call_s3_api(path, method, body, config_source)
|
127
|
-
endpoint = Endpoint.new(config_source.s3_endpoint || '')
|
128
|
-
date = Time.now.utc.strftime("%a, %d %b %Y %H:%M:%S %Z")
|
129
|
-
digest = create_digest(path, method, config_source, date)
|
130
|
-
url = "https://#{endpoint.hostname}#{path}"
|
131
|
-
uri = URI.parse(url)
|
132
|
-
req = method.new(uri.to_s)
|
133
|
-
req.initialize_http_header({
|
134
|
-
'Date' => date,
|
135
|
-
'Content-Type' => '',
|
136
|
-
'Content-Length' => body.length.to_s,
|
137
|
-
'Authorization' => "AWS %s:%s" % [config_source.s3_access_key_id, digest]
|
138
|
-
})
|
139
|
-
req.body = body
|
140
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
141
|
-
http.use_ssl = true
|
142
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
143
|
-
res = http.request(req)
|
144
|
-
if res.code.to_i.between? 200, 299
|
145
|
-
res
|
146
|
-
else
|
147
|
-
raise ConfigureS3Website::ErrorParser.create_error res.body
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
def self.create_digest(path, method, config_source, date)
|
152
|
-
digest = OpenSSL::Digest::Digest.new('sha1')
|
153
|
-
method_string = method.to_s.match(/Net::HTTP::(\w+)/)[1].upcase
|
154
|
-
can_string = "#{method_string}\n\n\n#{date}\n#{path}"
|
155
|
-
hmac = OpenSSL::HMAC.digest(digest, config_source.s3_secret_access_key, can_string)
|
156
|
-
signature = Base64.encode64(hmac).strip
|
157
|
-
end
|
158
|
-
|
159
|
-
def self.hash_to_api_xml(hash={}, indent=0)
|
160
|
-
"".tap do |body|
|
161
|
-
hash.each do |key, value|
|
162
|
-
key_name = key.sub(/^[a-z\d]*/) { $&.capitalize }.gsub(/(?:_|(\/))([a-z\d]*)/) { $2.capitalize }
|
163
|
-
value = value.is_a?(Hash) ? self.hash_to_api_xml(value, indent+1) : value
|
164
|
-
body << "\n"
|
165
|
-
body << " " * indent * 2 # 2-space indentation formatting for xml
|
166
|
-
body << "<#{key_name}>#{value}</#{key_name}>"
|
167
|
-
end
|
168
|
-
end
|
169
|
-
end
|
170
126
|
end
|
171
127
|
end
|
172
128
|
|
@@ -197,18 +153,9 @@ module ConfigureS3Website
|
|
197
153
|
'sa-east-1' => { :region => 'South America (Sao Paulo)', :endpoint => 's3-sa-east-1.amazonaws.com' }
|
198
154
|
}
|
199
155
|
end
|
200
|
-
end
|
201
|
-
end
|
202
156
|
|
203
|
-
|
204
|
-
|
205
|
-
def self.create_error(amazon_error_xml)
|
206
|
-
error_code = amazon_error_xml.delete('\n').match(/<Code>(.*?)<\/Code>/)[1]
|
207
|
-
begin
|
208
|
-
Object.const_get("#{error_code}Error").new
|
209
|
-
rescue NameError
|
210
|
-
GenericS3Error.new(amazon_error_xml)
|
211
|
-
end
|
157
|
+
def self.by_config_source(config_source)
|
158
|
+
endpoint = Endpoint.new(config_source.s3_endpoint || '')
|
212
159
|
end
|
213
160
|
end
|
214
161
|
end
|
@@ -218,9 +165,3 @@ end
|
|
218
165
|
|
219
166
|
class NoSuchBucketError < StandardError
|
220
167
|
end
|
221
|
-
|
222
|
-
class GenericS3Error < StandardError
|
223
|
-
def initialize(error_message)
|
224
|
-
super("AWS API call failed:\n#{error_message}")
|
225
|
-
end
|
226
|
-
end
|