jelder_cloudfront-invalidator 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,18 @@
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
@@ -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
@@ -0,0 +1,140 @@
1
+ require 'net/https'
2
+ require 'base64'
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
12
+
13
+ def initialize(aws_key, aws_secret, cf_dist_id)
14
+ @aws_key, @aws_secret, @cf_dist_id = aws_key, aws_secret, cf_dist_id
15
+ end
16
+
17
+ def invalidate(*keys)
18
+ keys = keys.flatten.map do |k|
19
+ k.start_with?('/') ? k : '/' + k
20
+ end
21
+
22
+ uri = URI.parse "#{BASE_URL}#{@cf_dist_id}/invalidation"
23
+ http = Net::HTTP.new(uri.host, uri.port)
24
+ http.use_ssl = true
25
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
26
+ body = xml_body(keys)
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
56
+ end
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
+
115
+ def xml_body(keys)
116
+ xml = <<XML
117
+ <?xml version="1.0" encoding="UTF-8"?>
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>
127
+ XML
128
+ end
129
+
130
+ def headers
131
+ date = Time.now.strftime('%a, %d %b %Y %H:%M:%S %Z')
132
+ digest = HMAC::SHA1.new(@aws_secret)
133
+ digest << date
134
+ signature = Base64.encode64(digest.digest)
135
+ {'Date' => date, 'Authorization' => "AWS #{@aws_key}:#{signature}"}
136
+ end
137
+
138
+ class TooManyInvalidationsInProgress < StandardError ; end
139
+
140
+ end
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jelder_cloudfront-invalidator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jacob Elder
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-05-24 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: ruby-hmac
16
+ requirement: &70328224035140 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70328224035140
25
+ description: Ruby library and command line utility included.
26
+ email: jacob.elder@gmail.com
27
+ executables:
28
+ - cloudfront-invalidator
29
+ extensions: []
30
+ extra_rdoc_files: []
31
+ files:
32
+ - lib/cloudfront-invalidator.rb
33
+ - README.md
34
+ - bin/cloudfront-invalidator
35
+ homepage: http://github.com/jelder/cloudfront-invalidator
36
+ licenses: []
37
+ post_install_message:
38
+ rdoc_options: []
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ none: false
49
+ requirements:
50
+ - - ! '>='
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ requirements: []
54
+ rubyforge_project: jelder_cloudfront-invalidator
55
+ rubygems_version: 1.8.11
56
+ signing_key:
57
+ specification_version: 3
58
+ summary: Manage Amazon CloudFront invalidation requests
59
+ test_files: []