configure-s3-website 1.3.0 → 1.4.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/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