cloudfront-invalidator 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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