cloudfront-invalidator 0.1.2 → 0.2.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.
@@ -0,0 +1,24 @@
1
+ Usage
2
+ =====
3
+
4
+ invalidator = CloudfrontInvalidator.new(AWS_KEY, AWS_SECRET, CF_DISTRIBUTION_ID)
5
+ list = %w[
6
+ index.html
7
+ favicon.ico
8
+ ]
9
+ invalidator.invalidate(list) do |status,time| # Block is optional.
10
+ invalidator.list # Or invalidator.list_detail
11
+ puts "Complete after < #{time.to_f.ceil} seconds." if status == "Complete"
12
+ end
13
+
14
+ A command line utility is also included.
15
+
16
+ $ cloudfront-invalidator invalidate $AWS_KEY $AWS_SECRET $DISTRIBUTION_ID index.html favicon.ico
17
+ $ cloudfront-invalidator list $AWS_KEY $AWS_SECRET $DISTRIBUTION_ID
18
+ $ cloudfront-invalidator list_detail $AWS_KEY $AWS_SECRET $DISTRIBUTION_ID
19
+
20
+ Authors
21
+ =======
22
+
23
+ * Reid M Lynch
24
+ * Jacob Elder
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ begin
4
+ require '../lib/cloudfront-invalidator'
5
+ rescue LoadError
6
+ require 'rubygems'
7
+ require 'cloudfront-invalidator'
8
+ end
9
+
10
+ unless ARGV.length >= 4
11
+ warn "Usage: cloudfront-invalidator (invalidate|list|list_detail) aws_key aws_secret distribution_id [path ...]"
12
+ exit 1
13
+ end
14
+
15
+ verb = ARGV.shift
16
+ raise "Verb must be invalidate, list, or list_detail" unless %w{invalidate list list_detail}.include? verb
17
+ aws_key = ARGV.shift
18
+ aws_secret = ARGV.shift
19
+ distribution_id = ARGV.shift
20
+ paths = ARGV
21
+
22
+ invalidator = CloudfrontInvalidator.new(aws_key, aws_secret, distribution_id)
23
+ case verb
24
+ when 'invalidate'
25
+ print "Invalidating #{paths.size} objects"
26
+ invalidator.invalidate(paths) do |status,time|
27
+ print status == "Complete" ? "\nComplete after < #{time.to_f.ceil} seconds.\n" : "."
28
+ end
29
+ when 'list'
30
+ invalidator.list
31
+ when 'list_detail'
32
+ invalidator.list_detail
33
+ end
@@ -1,9 +1,15 @@
1
- require 'net/http'
1
+ require 'net/https'
2
2
  require 'base64'
3
- require 'hmac-sha1'
3
+ require 'rexml/document'
4
+ require 'hmac-sha1' # this is a gem
5
+
6
+ class CloudfrontInvalidator
7
+ API_VERSION = '2012-05-05'
8
+ BASE_URL = "https://cloudfront.amazonaws.com/#{API_VERSION}/distribution/"
9
+ DOC_URL = "http://cloudfront.amazonaws.com/doc/#{API_VERSION}/"
10
+ BACKOFF_LIMIT = 8192
11
+ BACKOFF_DELAY = 0.025
4
12
 
5
- class CloudfrontInvalidator
6
-
7
13
  def initialize(aws_key, aws_secret, cf_dist_id)
8
14
  @aws_key, @aws_secret, @cf_dist_id = aws_key, aws_secret, cf_dist_id
9
15
  end
@@ -13,23 +19,111 @@ class CloudfrontInvalidator
13
19
  k.start_with?('/') ? k : '/' + k
14
20
  end
15
21
 
16
- uri = URI.parse "https://cloudfront.amazonaws.com/2010-11-01/distribution/#{@cf_dist_id}/invalidation"
22
+ uri = URI.parse "#{BASE_URL}#{@cf_dist_id}/invalidation"
17
23
  http = Net::HTTP.new(uri.host, uri.port)
18
24
  http.use_ssl = true
19
25
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE
20
26
  body = xml_body(keys)
21
- resp = http.send_request 'POST', uri.path, body, headers
22
- puts resp.code
23
- puts resp.body
27
+
28
+ delay = 1
29
+ begin
30
+ resp = http.send_request 'POST', uri.path, body, headers
31
+ doc = REXML::Document.new resp.body
32
+
33
+ # Create and raise an exception for any error the API returns to us.
34
+ if resp.code.to_i != 201
35
+ error_code = doc.elements["ErrorResponse/Error/Code"].text
36
+ self.class.const_set(error_code,Class.new(StandardError)) unless self.class.const_defined?(error_code.to_sym)
37
+ raise self.class.const_get(error_code).new(doc.elements["ErrorResponse/Error/Message"].text)
38
+ end
39
+
40
+ # Handle the common case of too many in progress by waiting until the others finish.
41
+ rescue TooManyInvalidationsInProgress => e
42
+ sleep delay * BACKOFF_DELAY
43
+ delay *= 2 unless delay >= BACKOFF_LIMIT
44
+ STDERR.puts e.inspect
45
+ retry
46
+ end
47
+
48
+ # If we are passed a block, poll on the status of this invalidation with truncated exponential backoff.
49
+ if block_given?
50
+ invalidation_id = doc.elements["Invalidation/Id"].text
51
+ poll_invalidation(invalidation_id) do |status,time|
52
+ yield status, time
53
+ end
54
+ end
55
+ return resp
24
56
  end
25
-
57
+
58
+ def poll_invalidation(invalidation_id)
59
+ start = Time.now
60
+ delay = 1
61
+ loop do
62
+ doc = REXML::Document.new get_invalidation_detail_xml(invalidation_id)
63
+ status = doc.elements["Invalidation/Status"].text
64
+ yield status, Time.now - start
65
+ break if status != "InProgress"
66
+ sleep delay * BACKOFF_DELAY
67
+ delay *= 2 unless delay >= BACKOFF_LIMIT
68
+ end
69
+ end
70
+
71
+ def list(show_detail = false)
72
+ uri = URI.parse "#{BASE_URL}#{@cf_dist_id}/invalidation"
73
+ http = Net::HTTP.new(uri.host, uri.port)
74
+ http.use_ssl = true
75
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
76
+ resp = http.send_request 'GET', uri.path, '', headers
77
+
78
+ doc = REXML::Document.new resp.body
79
+ puts "MaxItems " + doc.elements["InvalidationList/MaxItems"].text + "; " + (doc.elements["InvalidationList/MaxItems"].text == "true" ? "truncated" : "not truncated")
80
+
81
+ doc.each_element("/InvalidationList/InvalidationSummary") do |summary|
82
+ invalidation_id = summary.elements["Id"].text
83
+ summary_text = "ID " + invalidation_id + ": " + summary.elements["Status"].text
84
+
85
+ if show_detail
86
+ detail_doc = REXML::Document.new get_invalidation_detail_xml(invalidation_id)
87
+ puts summary_text +
88
+ "; Created at: " +
89
+ detail_doc.elements["Invalidation/CreateTime"].text +
90
+ '; Caller reference: "' +
91
+ detail_doc.elements["Invalidation/InvalidationBatch/CallerReference"].text +
92
+ '"'
93
+ puts ' Invalidated URL paths:'
94
+
95
+ puts " " + detail_doc.elements.to_a('Invalidation/InvalidationBatch/Path').map { |path| path.text }.join(" ")
96
+ else
97
+ puts summary_text
98
+ end
99
+ end
100
+ end
101
+
102
+ def list_detail
103
+ list(true)
104
+ end
105
+
106
+ def get_invalidation_detail_xml(invalidation_id)
107
+ uri = URI.parse "#{BASE_URL}#{@cf_dist_id}/invalidation/#{invalidation_id}"
108
+ http = Net::HTTP.new(uri.host, uri.port)
109
+ http.use_ssl = true
110
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
111
+ resp = http.send_request 'GET', uri.path, '', headers
112
+ return resp.body
113
+ end
114
+
26
115
  def xml_body(keys)
27
116
  xml = <<XML
28
117
  <?xml version="1.0" encoding="UTF-8"?>
29
- <InvalidationBatch>
30
- #{keys.map{|k| "<Path>#{k}</Path>" }.join("\n ")}
31
- <CallerReference>CloudfrontInvalidator at #{Time.now}</CallerReference>"
32
- </InvalidationBatch>
118
+ <InvalidationBatch xmlns="#{DOC_URL}">
119
+ <Paths>
120
+ <Quantity>#{keys.size}</Quantity>
121
+ <Items>
122
+ #{keys.map{|k| "<Path>#{k}</Path>" }.join("\n ")}
123
+ </Items>
124
+ </Paths>
125
+ <CallerReference>#{self.class.to_s} on #{Socket.gethostname} at #{Time.now.to_i}</CallerReference>"
126
+ </InvalidationBatch>
33
127
  XML
34
128
  end
35
129
 
@@ -40,5 +134,7 @@ XML
40
134
  signature = Base64.encode64(digest.digest)
41
135
  {'Date' => date, 'Authorization' => "AWS #{@aws_key}:#{signature}"}
42
136
  end
43
-
44
- end
137
+
138
+ class TooManyInvalidationsInProgress < StandardError ; end
139
+
140
+ end
metadata CHANGED
@@ -1,19 +1,19 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cloudfront-invalidator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
8
- - Reid M Lynch
8
+ - Jacob Elder
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-06-16 00:00:00.000000000Z
12
+ date: 2012-05-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: ruby-hmac
16
- requirement: &987040 !ruby/object:Gem::Requirement
16
+ requirement: &70238974779060 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,14 +21,17 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *987040
24
+ version_requirements: *70238974779060
25
25
  description:
26
- email: reid.lynch@gmail.com
27
- executables: []
26
+ email: jacob.elder@gmail.com
27
+ executables:
28
+ - cloudfront-invalidator
28
29
  extensions: []
29
30
  extra_rdoc_files: []
30
31
  files:
31
32
  - lib/cloudfront-invalidator.rb
33
+ - README.md
34
+ - bin/cloudfront-invalidator
32
35
  homepage: http://github.com/reidiculous/cloudfront-invalidator
33
36
  licenses: []
34
37
  post_install_message:
@@ -49,7 +52,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
49
52
  version: '0'
50
53
  requirements: []
51
54
  rubyforge_project: cloudfront-invalidator
52
- rubygems_version: 1.8.5
55
+ rubygems_version: 1.8.11
53
56
  signing_key:
54
57
  specification_version: 3
55
58
  summary: Simple gem to invalidate a list of keys belonging to a Cloudfront distribution