configure-s3-website 1.2.0 → 1.3.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/.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
|