configure-s3-website 1.3.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -33,6 +33,10 @@ you.
33
33
 
34
34
  ### Deliver your website via CloudFront
35
35
 
36
+ This gem can create new CloudFront distributions and update existing ones.
37
+
38
+ #### Creating a new distribution
39
+
36
40
  `configure-s3-website` can create a CloudFront distribution for you. It will ask
37
41
  you whether you want to deliver your website via the CDN. If you answer yes,
38
42
  `configure-s3-website` will create a CloudFront distribution that has the
@@ -55,9 +59,102 @@ configuration file, `configure-s3-website` will not create a new distribution.
55
59
  Conversely, if you remove the `cloudfront_distribution_id` key from the file and
56
60
  run `configure-s3-website` again, it will create you a new distribution.
57
61
 
58
- If you want to, you can tune the distribution settings on the management console
62
+ ##### Creating a new distribution with custom settings
63
+
64
+ If the default settings do not suit you, you can create a new distribution with
65
+ your settings by adding `cloudfront_distribution_config` values into your config
66
+ file. For example:
67
+
68
+ ```yaml
69
+ cloudfront_distribution_config:
70
+ default_cache_behavior:
71
+ min_TTL: 600
72
+ default_root_object: index.json
73
+ ```
74
+
75
+ See the section below for more information about the valid values of the
76
+ `cloudfront_distribution_config` setting.
77
+
78
+ If you want to, you can look at the distribution settings on the management console
59
79
  at <https://console.aws.amazon.com/cloudfront>.
60
80
 
81
+ #### Updating an existing distribution
82
+
83
+ You can modify an existing CloudFront distribution by defining the id of the
84
+ distribution and the configs you wish to override the defaults with.
85
+
86
+ Let's say your config file contains the following fragment:
87
+
88
+ ```yaml
89
+ cloudfront_distribution_id: AXSAXSSE134
90
+ cloudfront_distribution_config:
91
+ default_cache_behavior:
92
+ min_TTL: 600
93
+ default_root_object: index.json
94
+ ```
95
+
96
+ When you invoke `configure-s3-website`, it will overwrite the default value of
97
+ *default_cache_behavior's* *min_TTL* as well as the default value of
98
+ *default_root_object* setting in the [default distribution configs](#default-distribution-configs).
99
+
100
+ This gem generates `<DistributionConfig>` of the CloudFront REST API. For
101
+ reference, see
102
+ <http://docs.aws.amazon.com/AmazonCloudFront/latest/APIReference/GetConfig.html#GetConfig_Responses>
103
+ (example) and
104
+ <https://cloudfront.amazonaws.com/doc/2012-07-01/AmazonCloudFrontCommon.xsd>
105
+ (XSD). In other words, When you call `configure-s3-website`, it will turn the values of your
106
+ `cloudfront_distribution_config` into XML, include them in the
107
+ `<DistributionConfig>` element and send them to the CloudFront REST API.
108
+
109
+ The YAML keys in the config file will be turned into CloudFront REST API XML
110
+ with the same logic as in [configuring redirects](#configuring-redirects).
111
+
112
+ Having the distribution settings in the config file is handy, because it allows
113
+ you to store most (in many cases all) website deployment settings in one file.
114
+
115
+ #### Default distribution configs
116
+
117
+ Below is the default CloudFront distribution config. It is built based on the
118
+ API version 2012-07-01 of [DistributionConfig Complex
119
+ Type](http://docs.aws.amazon.com/AmazonCloudFront/latest/APIReference/DistributionConfigDatatype.html).
120
+
121
+ ```ruby
122
+ {
123
+ 'caller_reference' => 'configure-s3-website gem [generated-timestamp]',
124
+ 'default_root_object' => 'index.html',
125
+ 'logging' => {
126
+ 'enabled' => 'false',
127
+ 'include_cookies' => 'false',
128
+ 'bucket' => '',
129
+ 'prefix' => ''
130
+ },
131
+ 'enabled' => 'true',
132
+ 'comment' => 'Created by the configure-s3-website gem',
133
+ 'aliases' => {
134
+ 'quantity' => '0'
135
+ },
136
+ 'default_cache_behavior' => {
137
+ 'target_origin_id' => '[generated-string]',
138
+ 'trusted_signers' => {
139
+ 'enabled' => 'false',
140
+ 'quantity' => '0'
141
+ },
142
+ 'forwarded_values' => {
143
+ 'query_string' => 'true',
144
+ 'cookies' => {
145
+ 'forward' => 'all'
146
+ }
147
+ },
148
+ 'viewer_protocol_policy' => 'allow-all',
149
+ 'min_TTL' => '86400'
150
+ },
151
+ 'cache_behaviors' => {
152
+ 'quantity' => '0'
153
+ },
154
+ 'price_class' => 'PriceClass_All'
155
+ }
156
+ ```
157
+
61
158
  ### Specifying a non-standard S3 endpoint
62
159
 
63
160
  By default, `configure-s3-website` creates the S3 website into the US Standard
@@ -109,8 +206,10 @@ the bucket. In brief, it does the following things:
109
206
  3. Make the bucket **readable to the whole world**
110
207
  4. Apply the redirect (a.k.a routing) rules on the bucket website
111
208
 
112
- In addition, if you instruct `configure-s3-website` to create a CloudFront
113
- distribution to you, it will call the [CloudFront POST Distribution](http://docs.aws.amazon.com/AmazonCloudFront/latest/APIReference/CreateDistribution.html) API.
209
+ When interacting with CloudFront, `configure-s3-website` uses the [POST
210
+ Distribution](http://docs.aws.amazon.com/AmazonCloudFront/latest/APIReference/CreateDistribution.html),
211
+ [GET
212
+ Distribution](http://docs.aws.amazon.com/AmazonCloudFront/latest/APIReference/GetDistribution.html) and [PUT Distribution Config](http://docs.aws.amazon.com/AmazonCloudFront/latest/APIReference/PutConfig.html) APIs.
114
213
 
115
214
  ## Development
116
215
 
@@ -2,6 +2,12 @@
2
2
 
3
3
  This project uses [Semantic Versioning](http://semver.org).
4
4
 
5
+ ## 1.4.0
6
+
7
+ * Allow the user to store his CloudFront settings in the config file
8
+ * Support updating configs of an existing CloudFront distribution
9
+ * Support creating of new distros with custom CloudFront configs
10
+
5
11
  ## 1.3.0
6
12
 
7
13
  * Create a CloudFront distro if the user wants to deliver his S3 website via the
@@ -9,6 +9,8 @@ spec = Gem::Specification.new do |s|
9
9
  s.summary = 'Configure your AWS S3 bucket to function as a web site'
10
10
  s.bindir = 'bin'
11
11
 
12
+ s.add_dependency 'deep_merge', '= 1.0.0'
13
+
12
14
  s.add_development_dependency 'rspec', '~> 2.10.0'
13
15
  s.add_development_dependency 'rspec-expectations', '~> 2.10.0'
14
16
  s.add_development_dependency 'cucumber', '~> 1.2.0'
@@ -0,0 +1,169 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: put
5
+ uri: https://s3.amazonaws.com/website-via-cf/?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
+ - Tue, 21 May 2013 12:19:26 UTC
15
+ Content-Type:
16
+ - ''
17
+ Content-Length:
18
+ - '298'
19
+ Authorization:
20
+ - AWS foo:foo
21
+ response:
22
+ status:
23
+ code: 200
24
+ message: OK
25
+ headers:
26
+ X-Amz-Id-2:
27
+ - GtZ029e+Hum8PqKTmknJf1cB2zfnh4ug71/wVX/uDySx4ETpPfU7YcoSj7nPkUeM
28
+ X-Amz-Request-Id:
29
+ - A12EA2F4AEDA0657
30
+ Date:
31
+ - Tue, 21 May 2013 12:19:33 GMT
32
+ Content-Length:
33
+ - '0'
34
+ Server:
35
+ - AmazonS3
36
+ body:
37
+ encoding: US-ASCII
38
+ string: ''
39
+ http_version:
40
+ recorded_at: Tue, 21 May 2013 12:19:28 GMT
41
+ - request:
42
+ method: put
43
+ uri: https://s3.amazonaws.com/website-via-cf/?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:::website-via-cf/*\"]\n }]\n }"
50
+ headers:
51
+ Date:
52
+ - Tue, 21 May 2013 12:19:28 UTC
53
+ Content-Type:
54
+ - ''
55
+ Content-Length:
56
+ - '283'
57
+ Authorization:
58
+ - AWS foo:foo
59
+ response:
60
+ status:
61
+ code: 204
62
+ message: No Content
63
+ headers:
64
+ X-Amz-Id-2:
65
+ - qWuZ40Adyj2wa4i/ywb3E5mjypu0HYJpQOuAhBXkQskqf7O3NBVTeZ2vQT2UzspU
66
+ X-Amz-Request-Id:
67
+ - E998426FB9B27996
68
+ Date:
69
+ - Tue, 21 May 2013 12:19:34 GMT
70
+ Server:
71
+ - AmazonS3
72
+ body:
73
+ encoding: US-ASCII
74
+ string: ''
75
+ http_version:
76
+ recorded_at: Tue, 21 May 2013 12:19:29 GMT
77
+ - request:
78
+ method: get
79
+ uri: https://cloudfront.amazonaws.com/2012-07-01/distribution/E13NX4HCPUP9BP/config
80
+ body:
81
+ encoding: US-ASCII
82
+ string: ''
83
+ headers:
84
+ Date:
85
+ - Tue, 21 May 2013 12:19:29 UTC
86
+ Content-Type:
87
+ - ''
88
+ Content-Length:
89
+ - '0'
90
+ Authorization:
91
+ - AWS foo:foo
92
+ response:
93
+ status:
94
+ code: 200
95
+ message: OK
96
+ headers:
97
+ X-Amzn-Requestid:
98
+ - b29153e6-c210-11e2-8f84-efb8518943a7
99
+ Etag:
100
+ - E234UVYUVB06TU
101
+ Content-Type:
102
+ - text/xml
103
+ Content-Length:
104
+ - '1150'
105
+ Date:
106
+ - Tue, 21 May 2013 12:19:33 GMT
107
+ body:
108
+ encoding: US-ASCII
109
+ string: ! '<?xml version="1.0"?>
110
+
111
+ <DistributionConfig xmlns="http://cloudfront.amazonaws.com/doc/2012-07-01/"><CallerReference>1369126181889</CallerReference><Aliases><Quantity>0</Quantity></Aliases><DefaultRootObject>index.json</DefaultRootObject><Origins><Quantity>1</Quantity><Items><Origin><Id>website-via-cf-S3-origin</Id><DomainName>website-via-cf.s3.amazonaws.com</DomainName><S3OriginConfig><OriginAccessIdentity></OriginAccessIdentity></S3OriginConfig></Origin></Items></Origins><DefaultCacheBehavior><TargetOriginId>website-via-cf-S3-origin</TargetOriginId><ForwardedValues><QueryString>true</QueryString><Cookies><Forward>all</Forward></Cookies></ForwardedValues><TrustedSigners><Enabled>false</Enabled><Quantity>0</Quantity></TrustedSigners><ViewerProtocolPolicy>allow-all</ViewerProtocolPolicy><MinTTL>3600</MinTTL></DefaultCacheBehavior><CacheBehaviors><Quantity>0</Quantity></CacheBehaviors><Comment>Created
112
+ by the configure-s3-website gem</Comment><Logging><Enabled>false</Enabled><IncludeCookies>false</IncludeCookies><Bucket></Bucket><Prefix></Prefix></Logging><PriceClass>PriceClass_All</PriceClass><Enabled>true</Enabled></DistributionConfig>'
113
+ http_version:
114
+ recorded_at: Tue, 21 May 2013 12:19:29 GMT
115
+ - request:
116
+ method: put
117
+ uri: https://cloudfront.amazonaws.com/2012-07-01/distribution/E13NX4HCPUP9BP/config
118
+ body:
119
+ encoding: US-ASCII
120
+ string: ! "\n <DistributionConfig xmlns=\"http://cloudfront.amazonaws.com/doc/2012-07-01/\">\n
121
+ \ <Origins>\n <Quantity>1</Quantity>\n <Items>\n <Origin>\n
122
+ \ <Id>website-via-cf-S3-origin</Id>\n <DomainName>website-via-cf.s3.amazonaws.com</DomainName>\n
123
+ \ <S3OriginConfig>\n <OriginAccessIdentity></OriginAccessIdentity>\n
124
+ \ </S3OriginConfig>\n </Origin>\n </Items>\n
125
+ \ </Origins>\n \n<CallerReference>1369126181889</CallerReference>\n<DefaultRootObject>index.json</DefaultRootObject>\n<Logging>\n
126
+ \ <Enabled>false</Enabled>\n <IncludeCookies>false</IncludeCookies>\n <Bucket></Bucket>\n
127
+ \ <Prefix></Prefix></Logging>\n<Enabled>true</Enabled>\n<Comment>Updated by
128
+ the configure-s3-website gem</Comment>\n<Aliases>\n <Quantity>0</Quantity></Aliases>\n<DefaultCacheBehavior>\n
129
+ \ <TargetOriginId>website-via-cf-S3-origin</TargetOriginId>\n <TrustedSigners>\n
130
+ \ <Enabled>false</Enabled>\n <Quantity>0</Quantity></TrustedSigners>\n
131
+ \ <ForwardedValues>\n <QueryString>true</QueryString>\n <Cookies>\n
132
+ \ <Forward>all</Forward></Cookies></ForwardedValues>\n <ViewerProtocolPolicy>allow-all</ViewerProtocolPolicy>\n
133
+ \ <MinTTL>3600</MinTTL></DefaultCacheBehavior>\n<CacheBehaviors>\n <Quantity>0</Quantity></CacheBehaviors>\n<PriceClass>PriceClass_All</PriceClass>\n
134
+ \ </DistributionConfig>\n "
135
+ headers:
136
+ Date:
137
+ - Tue, 21 May 2013 12:19:29 UTC
138
+ Content-Type:
139
+ - ''
140
+ Content-Length:
141
+ - '1381'
142
+ Authorization:
143
+ - AWS foo:foo
144
+ If-Match:
145
+ - E234UVYUVB06TU
146
+ response:
147
+ status:
148
+ code: 200
149
+ message: OK
150
+ headers:
151
+ X-Amzn-Requestid:
152
+ - b2e5dc3f-c210-11e2-83d0-6be74820cf4d
153
+ Etag:
154
+ - E257DGI0PKML9Q
155
+ Content-Type:
156
+ - text/xml
157
+ Content-Length:
158
+ - '1499'
159
+ Date:
160
+ - Tue, 21 May 2013 12:19:34 GMT
161
+ body:
162
+ encoding: US-ASCII
163
+ string: ! '<?xml version="1.0"?>
164
+
165
+ <Distribution xmlns="http://cloudfront.amazonaws.com/doc/2012-07-01/"><Id>E13NX4HCPUP9BP</Id><Status>InProgress</Status><LastModifiedTime>2013-05-21T12:19:35.353Z</LastModifiedTime><InProgressInvalidationBatches>0</InProgressInvalidationBatches><DomainName>d2u7lu18vsudvw.cloudfront.net</DomainName><ActiveTrustedSigners><Enabled>false</Enabled><Quantity>0</Quantity></ActiveTrustedSigners><DistributionConfig><CallerReference>1369126181889</CallerReference><Aliases><Quantity>0</Quantity></Aliases><DefaultRootObject>index.json</DefaultRootObject><Origins><Quantity>1</Quantity><Items><Origin><Id>website-via-cf-S3-origin</Id><DomainName>website-via-cf.s3.amazonaws.com</DomainName><S3OriginConfig><OriginAccessIdentity></OriginAccessIdentity></S3OriginConfig></Origin></Items></Origins><DefaultCacheBehavior><TargetOriginId>website-via-cf-S3-origin</TargetOriginId><ForwardedValues><QueryString>true</QueryString><Cookies><Forward>all</Forward></Cookies></ForwardedValues><TrustedSigners><Enabled>false</Enabled><Quantity>0</Quantity></TrustedSigners><ViewerProtocolPolicy>allow-all</ViewerProtocolPolicy><MinTTL>3600</MinTTL></DefaultCacheBehavior><CacheBehaviors><Quantity>0</Quantity></CacheBehaviors><Comment>Updated
166
+ by the configure-s3-website gem</Comment><Logging><Enabled>false</Enabled><IncludeCookies>false</IncludeCookies><Bucket></Bucket><Prefix></Prefix></Logging><PriceClass>PriceClass_All</PriceClass><Enabled>true</Enabled></DistributionConfig></Distribution>'
167
+ http_version:
168
+ recorded_at: Tue, 21 May 2013 12:19:30 GMT
169
+ recorded_with: VCR 2.3.0
@@ -1,4 +1,4 @@
1
- Feature: Create CloudFront distribution
1
+ Feature: Create a CloudFront distribution
2
2
 
3
3
  @create-cf-dist
4
4
  Scenario: The user wants to deliver his website via CloudFront
@@ -18,6 +18,31 @@ Feature: Create CloudFront distribution
18
18
  Added setting 'cloudfront_distribution_id: E45H2VN49KPDU' into features/support/sample_config_files/create_cf_dist.yml
19
19
 
20
20
  """
21
+ And the config file should contain the distribution id
22
+
23
+ @create-cf-dist
24
+ Scenario: The user wants create a CloudFront distribution with his own settings
25
+ Given I answer 'yes' to 'do you want to use CloudFront'
26
+ When I run the configure-s3-website command with parameters
27
+ | option | value |
28
+ | --config-file | features/support/sample_config_files/create_cf_dist_with_custom_configs.yml |
29
+ Then the output should be
30
+ """
31
+ Bucket website-via-cf now functions as a website
32
+ Bucket website-via-cf is now readable to the whole world
33
+ No redirects to configure for website-via-cf bucket
34
+ Do you want to deliver your website via CloudFront, the CDN of Amazon? [y/N]
35
+ The distribution E45H2VN49KPDU at d3feoe9t5ufu01.cloudfront.net now delivers the bucket website-via-cf
36
+ Please allow up to 15 minutes for the distribution to initialise
37
+ For more information on the distribution, see https://console.aws.amazon.com/cloudfront
38
+ Added setting 'cloudfront_distribution_id: E45H2VN49KPDU' into features/support/sample_config_files/create_cf_dist_with_custom_configs.yml
39
+ Applied custom distribution settings:
40
+ default_cache_behavior:
41
+ min_TTL: 600
42
+ default_root_object: index.json
43
+
44
+ """
45
+ And the config file should contain the distribution id
21
46
 
22
47
  @create-cf-dist
23
48
  Scenario: The user wants to deliver his website via CloudFront and see details on the new distribution
@@ -149,6 +174,7 @@ Feature: Create CloudFront distribution
149
174
  Added setting 'cloudfront_distribution_id: E45H2VN49KPDU' into features/support/sample_config_files/create_cf_dist.yml
150
175
 
151
176
  """
177
+ And the config file should contain the distribution id
152
178
 
153
179
  @bucket-exists
154
180
  Scenario: The user already has CloudFront configured in his configuration file
@@ -0,0 +1,19 @@
1
+ Feature: Modify an existing CloudFront distribution
2
+
3
+ @apply-configs-on-cf-dist
4
+ Scenario: The user wants to modify an existing CloudFront distribution
5
+ When I run the configure-s3-website command with parameters
6
+ | option | value |
7
+ | --config-file | features/support/sample_config_files/apply_configs_on_cf_dist.yml |
8
+ Then the output should be
9
+ """
10
+ Bucket website-via-cf now functions as a website
11
+ Bucket website-via-cf is now readable to the whole world
12
+ No redirects to configure for website-via-cf bucket
13
+ Detected an existing CloudFront distribution (id E13NX4HCPUP9BP) ...
14
+ Applied custom distribution settings:
15
+ default_cache_behavior:
16
+ min_TTL: 3600
17
+ default_root_object: index.json
18
+
19
+ """
@@ -3,7 +3,8 @@ require 'rspec'
3
3
  When /^I run the configure-s3-website command with parameters$/ do |table|
4
4
  options, optparse = ConfigureS3Website::CLI.optparse_and_options
5
5
  optparse.parse! args_array_from_cucumber_table(table)
6
- @reset = create_reset_config_file_function options[:config_source].description
6
+ @config_source = options[:config_source]
7
+ @reset = create_reset_config_file_function @config_source.description
7
8
  @console_output = capture_stdout {
8
9
  ConfigureS3Website::Runner.run(options, stub_stdin)
9
10
  }
@@ -21,6 +22,13 @@ Then /^the output should include$/ do |expected_console_output|
21
22
  @console_output.should include(expected_console_output)
22
23
  end
23
24
 
25
+ Then /^the config file should contain the distribution id$/ do
26
+ config_file_path = @config_source.description
27
+ File.open(config_file_path, 'r').read.should include(
28
+ "cloudfront_distribution_id: #{@config_source.cloudfront_distribution_id}"
29
+ )
30
+ end
31
+
24
32
  def args_array_from_cucumber_table(table)
25
33
  args = []
26
34
  table.hashes.map do |entry|
@@ -0,0 +1,8 @@
1
+ s3_id: foo
2
+ s3_secret: foo
3
+ s3_bucket: website-via-cf
4
+ cloudfront_distribution_id: E13NX4HCPUP9BP
5
+ cloudfront_distribution_config:
6
+ default_cache_behavior:
7
+ min_TTL: 3600
8
+ default_root_object: index.json
@@ -0,0 +1,7 @@
1
+ s3_id: foo
2
+ s3_secret: foo
3
+ s3_bucket: website-via-cf
4
+ cloudfront_distribution_config:
5
+ default_cache_behavior:
6
+ min_TTL: 600
7
+ default_root_object: index.json
@@ -11,4 +11,5 @@ VCR.cucumber_tags do |t|
11
11
  t.tag '@bucket-exists'
12
12
  t.tag '@redirects'
13
13
  t.tag '@create-cf-dist'
14
+ t.tag '@apply-configs-on-cf-dist'
14
15
  end
@@ -3,28 +3,82 @@ require "rexml/xpath"
3
3
 
4
4
  module ConfigureS3Website
5
5
  class CloudFrontClient
6
+ def self.apply_distribution_config(options)
7
+ config_source = options[:config_source]
8
+ puts "Detected an existing CloudFront distribution (id #{config_source.cloudfront_distribution_id}) ..."
9
+
10
+ # Get caller reference and ETag (will be required by the PUT config resource)
11
+ response = HttpHelper.call_cloudfront_api(
12
+ path = "/2012-07-01/distribution/#{config_source.cloudfront_distribution_id}/config",
13
+ method = Net::HTTP::Get,
14
+ body = '',
15
+ config_source
16
+ )
17
+ etag = response['ETag']
18
+ caller_reference = REXML::XPath.first(
19
+ REXML::Document.new(response.body),
20
+ '/DistributionConfig/CallerReference'
21
+ ).get_text.to_s
22
+
23
+ # Call the PUT config resource with the caller reference and ETag
24
+ custom_distribution_config = config_source.cloudfront_distribution_config || {}
25
+ custom_distribution_config_with_caller_ref = custom_distribution_config.merge({
26
+ 'caller_reference' => caller_reference,
27
+ 'comment' => 'Updated by the configure-s3-website gem'
28
+ })
29
+ HttpHelper.call_cloudfront_api(
30
+ path = "/2012-07-01/distribution/#{options[:config_source].cloudfront_distribution_id}/config",
31
+ method = Net::HTTP::Put,
32
+ body = distribution_config_xml(
33
+ config_source,
34
+ custom_distribution_config_with_caller_ref
35
+ ),
36
+ config_source,
37
+ headers = { 'If-Match' => etag }
38
+ )
39
+
40
+ # Report
41
+ unless custom_distribution_config.empty?
42
+ print_report_on_custom_distribution_config custom_distribution_config
43
+ end
44
+ end
45
+
6
46
  def self.create_distribution_if_user_agrees(options, standard_input)
7
47
  puts 'Do you want to deliver your website via CloudFront, the CDN of Amazon? [y/N]'
8
48
  case standard_input.gets.chomp
9
- when /(y|Y)/ then do_create_distribution options
49
+ when /(y|Y)/ then create_distribution options
10
50
  end
11
51
  end
12
52
 
13
53
  private
14
54
 
15
- def self.do_create_distribution(options)
55
+ def self.create_distribution(options)
16
56
  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
57
+ custom_distribution_config = config_source.cloudfront_distribution_config || {}
58
+ response_xml = REXML::Document.new(
59
+ HttpHelper.call_cloudfront_api(
60
+ '/2012-07-01/distribution',
61
+ Net::HTTP::Post,
62
+ distribution_config_xml(config_source, custom_distribution_config),
63
+ config_source
64
+ ).body
22
65
  )
23
- response_xml = REXML::Document.new(response.body)
24
66
  dist_id = REXML::XPath.first(response_xml, '/Distribution/Id').get_text
25
67
  print_report_on_new_dist response_xml, dist_id, options
26
68
  config_source.cloudfront_distribution_id = dist_id.to_s
27
69
  puts " Added setting 'cloudfront_distribution_id: #{dist_id}' into #{config_source.description}"
70
+ unless custom_distribution_config.empty?
71
+ print_report_on_custom_distribution_config custom_distribution_config
72
+ end
73
+ end
74
+
75
+ def self.print_report_on_custom_distribution_config(custom_distribution_config, left_padding = 4)
76
+ puts ' Applied custom distribution settings:'
77
+ puts custom_distribution_config.
78
+ to_yaml.
79
+ to_s.
80
+ gsub("---\n", '').
81
+ gsub(/^/, padding(left_padding))
28
82
  end
29
83
 
30
84
  def self.print_report_on_new_dist(response_xml, dist_id, options)
@@ -35,21 +89,19 @@ module ConfigureS3Website
35
89
  puts ' For more information on the distribution, see https://console.aws.amazon.com/cloudfront'
36
90
  if options[:verbose]
37
91
  puts ' Below is the response from the CloudFront API:'
38
- print_verbose(response_xml, left_padding = 4)
92
+ print_verbose_response_from_cloudfront(response_xml)
39
93
  end
40
94
  end
41
95
 
42
- def self.print_verbose(response_xml, left_padding)
96
+ def self.print_verbose_response_from_cloudfront(response_xml, left_padding = 4)
43
97
  lines = []
44
98
  response_xml.write(lines, 2)
45
- padding = ""
46
- left_padding.times { padding << " " }
47
99
  puts lines.join().
48
- gsub(/^/, "" + padding).
100
+ gsub(/^/, "" + padding(left_padding)).
49
101
  gsub(/\s$/, "")
50
102
  end
51
103
 
52
- def self.distribution_config_xml(config_source, custom_cf_settings = {})
104
+ def self.distribution_config_xml(config_source, custom_cf_settings)
53
105
  %|
54
106
  <DistributionConfig xmlns="http://cloudfront.amazonaws.com/doc/2012-07-01/">
55
107
  <Origins>
@@ -65,14 +117,19 @@ module ConfigureS3Website
65
117
  </Items>
66
118
  </Origins>
67
119
  #{
68
- XmlHelper.hash_to_api_xml(
69
- default_cloudfront_settings(config_source).merge custom_cf_settings
70
- )
120
+ require 'deep_merge'
121
+ settings = default_cloudfront_settings config_source
122
+ settings.deep_merge! custom_cf_settings
123
+ XmlHelper.hash_to_api_xml(settings)
71
124
  }
72
125
  </DistributionConfig>
73
126
  |
74
127
  end
75
128
 
129
+ # Changing these default settings probably necessitates a
130
+ # backward incompatible release.
131
+ #
132
+ # If you change these settings, remember to update also the README.md.
76
133
  def self.default_cloudfront_settings(config_source)
77
134
  {
78
135
  'caller_reference' => 'configure-s3-website gem ' + Time.now.to_s,
@@ -101,7 +158,7 @@ module ConfigureS3Website
101
158
  }
102
159
  },
103
160
  'viewer_protocol_policy' => 'allow-all',
104
- 'min_TTL' => (60 * 60 * 24)
161
+ 'min_TTL' => '86400'
105
162
  },
106
163
  'cache_behaviors' => {
107
164
  'quantity' => '0'
@@ -113,5 +170,11 @@ module ConfigureS3Website
113
170
  def self.origin_id(config_source)
114
171
  "#{config_source.s3_bucket_name}-S3-origin"
115
172
  end
173
+
174
+ def self.padding(amount)
175
+ padding = ''
176
+ amount.times { padding << " " }
177
+ padding
178
+ end
116
179
  end
117
180
  end
@@ -18,6 +18,9 @@ module ConfigureS3Website
18
18
  def routing_rules
19
19
  end
20
20
 
21
+ def cloudfront_distribution_config
22
+ end
23
+
21
24
  def cloudfront_distribution_id
22
25
  end
23
26
 
@@ -32,6 +32,10 @@ module ConfigureS3Website
32
32
  @config['routing_rules']
33
33
  end
34
34
 
35
+ def cloudfront_distribution_config
36
+ @config['cloudfront_distribution_config']
37
+ end
38
+
35
39
  def cloudfront_distribution_id
36
40
  @config['cloudfront_distribution_id']
37
41
  end
@@ -5,21 +5,34 @@ module ConfigureS3Website
5
5
  date = Time.now.utc.strftime("%a, %d %b %Y %H:%M:%S %Z")
6
6
  digest = create_s3_digest(path, method, config_source, date)
7
7
  self.call_api(
8
- path, method, body, config_source, endpoint.hostname, digest, date
8
+ path,
9
+ method,
10
+ body,
11
+ config_source,
12
+ endpoint.hostname,
13
+ digest,
14
+ date
9
15
  )
10
16
  end
11
17
 
12
- def self.call_cloudfront_api(path, method, body, config_source)
18
+ def self.call_cloudfront_api(path, method, body, config_source, headers = {})
13
19
  date = Time.now.utc.strftime("%a, %d %b %Y %H:%M:%S %Z")
14
20
  digest = create_cloudfront_digest(config_source, date)
15
21
  self.call_api(
16
- path, method, body, config_source, 'cloudfront.amazonaws.com', digest, date
22
+ path,
23
+ method,
24
+ body,
25
+ config_source,
26
+ 'cloudfront.amazonaws.com',
27
+ digest,
28
+ date,
29
+ headers
17
30
  )
18
31
  end
19
32
 
20
33
  private
21
34
 
22
- def self.call_api(path, method, body, config_source, hostname, digest, date)
35
+ def self.call_api(path, method, body, config_source, hostname, digest, date, additional_headers = {})
23
36
  url = "https://#{hostname}#{path}"
24
37
  uri = URI.parse(url)
25
38
  req = method.new(uri.to_s)
@@ -28,9 +41,10 @@ module ConfigureS3Website
28
41
  'Content-Type' => '',
29
42
  'Content-Length' => body.length.to_s,
30
43
  'Authorization' => "AWS %s:%s" % [config_source.s3_access_key_id, digest]
31
- })
44
+ }.merge(additional_headers))
32
45
  req.body = body
33
46
  http = Net::HTTP.new(uri.host, uri.port)
47
+ # http.set_debug_output $stderr
34
48
  http.use_ssl = true
35
49
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE
36
50
  res = http.request(req)
@@ -2,16 +2,30 @@ module ConfigureS3Website
2
2
  class Runner
3
3
  def self.run(options, standard_input = STDIN)
4
4
  S3Client.configure_website options
5
+ maybe_create_or_update_cloudfront options, standard_input
6
+ end
7
+
8
+ private
9
+
10
+ def self.maybe_create_or_update_cloudfront(options, standard_input)
5
11
  unless user_already_has_cf_configured options
6
12
  CloudFrontClient.create_distribution_if_user_agrees options, standard_input
13
+ return
14
+ end
15
+ if user_already_has_cf_configured(options) and user_has_custom_cf_dist_config(options)
16
+ CloudFrontClient.apply_distribution_config options
17
+ return
7
18
  end
8
19
  end
9
20
 
10
- private
11
-
12
21
  def self.user_already_has_cf_configured(options)
13
22
  config_source = options[:config_source]
14
23
  config_source.cloudfront_distribution_id
15
24
  end
25
+
26
+ def self.user_has_custom_cf_dist_config(options)
27
+ config_source = options[:config_source]
28
+ config_source.cloudfront_distribution_config
29
+ end
16
30
  end
17
31
  end
@@ -1,3 +1,3 @@
1
1
  module ConfigureS3Website
2
- VERSION = '1.3.0'
2
+ VERSION = '1.4.0'
3
3
  end
@@ -0,0 +1,71 @@
1
+ require 'rspec'
2
+ require 'configure-s3-website'
3
+ require "rexml/document"
4
+ require "rexml/xpath"
5
+
6
+ describe ConfigureS3Website::CloudFrontClient do
7
+ context '#distribution_config_xml' do
8
+ describe 'letting the user to override the default values' do
9
+ let(:config_source) {
10
+ mock = double('config_source')
11
+ mock.stub(:s3_bucket_name).and_return('test-bucket')
12
+ mock.stub(:s3_endpoint).and_return(nil)
13
+ mock
14
+ }
15
+
16
+ let(:custom_settings) {
17
+ { 'default_cache_behavior' => { 'min_TTL' => '987' } }
18
+ }
19
+
20
+ let(:distribution_config_xml) {
21
+ REXML::Document.new(
22
+ ConfigureS3Website::CloudFrontClient.send(
23
+ :distribution_config_xml,
24
+ config_source,
25
+ custom_settings
26
+ )
27
+ )
28
+ }
29
+
30
+ it 'allows the user to override default CloudFront settings' do
31
+ REXML::XPath.first(
32
+ distribution_config_xml,
33
+ '/DistributionConfig/DefaultCacheBehavior/MinTTL'
34
+ ).get_text.to_s.should eq('987')
35
+ end
36
+
37
+ it 'retains the default values that are not overriden' do
38
+ REXML::XPath.first(
39
+ distribution_config_xml,
40
+ '/DistributionConfig/DefaultCacheBehavior/ViewerProtocolPolicy'
41
+ ).get_text.to_s.should eq('allow-all')
42
+ end
43
+ end
44
+
45
+ describe 'inferring //Origins/Items/Origin/DomainName' do
46
+ let(:config_source) {
47
+ mock = double('config_source')
48
+ mock.stub(:s3_bucket_name).and_return('test-bucket')
49
+ mock.stub(:s3_endpoint).and_return('us-west-1')
50
+ mock
51
+ }
52
+
53
+ let(:distribution_config_xml) {
54
+ REXML::Document.new(
55
+ ConfigureS3Website::CloudFrontClient.send(
56
+ :distribution_config_xml,
57
+ config_source,
58
+ custom_distribution_config = {}
59
+ )
60
+ )
61
+ }
62
+
63
+ it 'honors the endpoint of the S3 bucket' do
64
+ REXML::XPath.first(
65
+ distribution_config_xml,
66
+ '/DistributionConfig/Origins/Items/Origin/DomainName'
67
+ ).get_text.to_s.should eq('test-bucket.s3-us-west-1.amazonaws.com')
68
+ end
69
+ end
70
+ end
71
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: configure-s3-website
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,8 +9,24 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-05-20 00:00:00.000000000 Z
12
+ date: 2013-05-21 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: deep_merge
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - '='
20
+ - !ruby/object:Gem::Version
21
+ version: 1.0.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - '='
28
+ - !ruby/object:Gem::Version
29
+ version: 1.0.0
14
30
  - !ruby/object:Gem::Dependency
15
31
  name: rspec
16
32
  requirement: !ruby/object:Gem::Requirement
@@ -139,6 +155,7 @@ files:
139
155
  - bin/configure-s3-website
140
156
  - changelog.md
141
157
  - configure-s3-website.gemspec
158
+ - features/cassettes/cucumber_tags/apply-configs-on-cf-dist.yml
142
159
  - features/cassettes/cucumber_tags/bucket-does-not-exist-in-tokyo.yml
143
160
  - features/cassettes/cucumber_tags/bucket-does-not-exist.yml
144
161
  - features/cassettes/cucumber_tags/bucket-exists.yml
@@ -147,10 +164,13 @@ files:
147
164
  - features/config_file.feature
148
165
  - features/configure_bucket.feature
149
166
  - features/create_cloudfront_dist.feature
167
+ - features/modify_cloudfront_dist.feature
150
168
  - features/step_definitions/steps.rb
151
169
  - features/support/env.rb
170
+ - features/support/sample_config_files/apply_configs_on_cf_dist.yml
152
171
  - features/support/sample_config_files/config_with_cloudfront_distribution_id.yml
153
172
  - features/support/sample_config_files/create_cf_dist.yml
173
+ - features/support/sample_config_files/create_cf_dist_with_custom_configs.yml
154
174
  - features/support/sample_config_files/endpoint_tokyo.yml
155
175
  - features/support/sample_config_files/redirects.yml
156
176
  - features/support/sample_config_files/s3_config_with_existing_bucket.yml
@@ -166,6 +186,7 @@ files:
166
186
  - lib/configure-s3-website/s3_client.rb
167
187
  - lib/configure-s3-website/version.rb
168
188
  - lib/configure-s3-website/xml_helper.rb
189
+ - spec/cloudfront_client_spec.rb
169
190
  - spec/config_source/file_config_source_spec.rb
170
191
  - spec/s3_client_spec.rb
171
192
  - spec/sample_files/_config_file.yml
@@ -186,7 +207,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
186
207
  version: '0'
187
208
  segments:
188
209
  - 0
189
- hash: -2014890720503017169
210
+ hash: 2506779264415997513
190
211
  required_rubygems_version: !ruby/object:Gem::Requirement
191
212
  none: false
192
213
  requirements:
@@ -195,7 +216,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
195
216
  version: '0'
196
217
  segments:
197
218
  - 0
198
- hash: -2014890720503017169
219
+ hash: 2506779264415997513
199
220
  requirements: []
200
221
  rubyforge_project:
201
222
  rubygems_version: 1.8.25
@@ -203,6 +224,7 @@ signing_key:
203
224
  specification_version: 3
204
225
  summary: Configure your AWS S3 bucket to function as a web site
205
226
  test_files:
227
+ - features/cassettes/cucumber_tags/apply-configs-on-cf-dist.yml
206
228
  - features/cassettes/cucumber_tags/bucket-does-not-exist-in-tokyo.yml
207
229
  - features/cassettes/cucumber_tags/bucket-does-not-exist.yml
208
230
  - features/cassettes/cucumber_tags/bucket-exists.yml
@@ -211,15 +233,19 @@ test_files:
211
233
  - features/config_file.feature
212
234
  - features/configure_bucket.feature
213
235
  - features/create_cloudfront_dist.feature
236
+ - features/modify_cloudfront_dist.feature
214
237
  - features/step_definitions/steps.rb
215
238
  - features/support/env.rb
239
+ - features/support/sample_config_files/apply_configs_on_cf_dist.yml
216
240
  - features/support/sample_config_files/config_with_cloudfront_distribution_id.yml
217
241
  - features/support/sample_config_files/create_cf_dist.yml
242
+ - features/support/sample_config_files/create_cf_dist_with_custom_configs.yml
218
243
  - features/support/sample_config_files/endpoint_tokyo.yml
219
244
  - features/support/sample_config_files/redirects.yml
220
245
  - features/support/sample_config_files/s3_config_with_existing_bucket.yml
221
246
  - features/support/sample_config_files/s3_config_with_non-existing_bucket.yml
222
247
  - features/support/vcr.rb
248
+ - spec/cloudfront_client_spec.rb
223
249
  - spec/config_source/file_config_source_spec.rb
224
250
  - spec/s3_client_spec.rb
225
251
  - spec/sample_files/_config_file.yml