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.
- data/README.md +24 -0
- data/bin/cloudfront-invalidator +33 -0
- data/lib/cloudfront-invalidator.rb +111 -15
- metadata +11 -8
data/README.md
ADDED
@@ -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/
|
1
|
+
require 'net/https'
|
2
2
|
require 'base64'
|
3
|
-
require '
|
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 "
|
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
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
30
|
-
|
31
|
-
<
|
32
|
-
|
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.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
|
-
-
|
8
|
+
- Jacob Elder
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
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: &
|
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: *
|
24
|
+
version_requirements: *70238974779060
|
25
25
|
description:
|
26
|
-
email:
|
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.
|
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
|