middleman-cloudfront 0.2.1 → 0.3.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.
- checksums.yaml +5 -5
- data/.travis.yml +7 -5
- data/README.md +3 -1
- data/Rakefile +0 -6
- data/lib/middleman-cloudfront.rb +3 -6
- data/lib/middleman-cloudfront/commands/invalidate.rb +146 -0
- data/lib/middleman-cloudfront/extension.rb +19 -24
- data/lib/middleman-cloudfront/version.rb +1 -1
- data/middleman-cloudfront.gemspec +4 -13
- data/spec/lib/middleman-cloudfront/invalidate_spec.rb +89 -0
- metadata +27 -72
- data/features/support/env.rb +0 -6
- data/lib/middleman-cloudfront/commands.rb +0 -122
- data/spec/lib/middleman-cloudfront/commands_spec.rb +0 -82
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 45630e9002202a7bb7480ca73b84675937a5068864f7c238dd2c477899cf9184
|
|
4
|
+
data.tar.gz: 33c8b3c56d298a6f5ea6ff9c3382867b6e684d74617e1de255b90cb5b1744aa9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a09550094d9d1bc154e36fc2ba60beede436c967c93e698f6bd2d99038dd1ea001825689d31e38b4b72cb61bc5869e97d2c8dfe0e644d48a7723753802a7e1d7
|
|
7
|
+
data.tar.gz: dac269c7465ae90b45767a485550906f72eaef4417bfe3fbb4c6f9658578e9cbe245700911ef2164af039c2ffd66fdfaf367451522e9d427702acdc89367f650
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Middleman CloudFront [](https://travis-ci.org/andrusha/middleman-cloudfront) [](https://gemnasium.com/andrusha/middleman-cloudfront) [](https://codeclimate.com/github/andrusha/middleman-cloudfront)
|
|
2
2
|
A deploying tool for middleman which allows you to interact with Amazon CloudFront.
|
|
3
3
|
Some of its features are:
|
|
4
4
|
|
|
@@ -115,3 +115,5 @@ after_s3_sync do |files_by_status|
|
|
|
115
115
|
invalidate files_by_status[:updated]
|
|
116
116
|
end
|
|
117
117
|
```
|
|
118
|
+
|
|
119
|
+
NOTE: The `after_s3_sync` hook only works with middleman-s3_sync v3.x and below. It has been [removed in v4.0](https://github.com/fredjean/middleman-s3_sync/blob/master/Changelog.md#v400).
|
data/Rakefile
CHANGED
|
@@ -3,12 +3,6 @@ require 'rspec/core/rake_task'
|
|
|
3
3
|
require 'bundler'
|
|
4
4
|
Bundler::GemHelper.install_tasks
|
|
5
5
|
|
|
6
|
-
require 'cucumber/rake/task'
|
|
7
|
-
|
|
8
|
-
Cucumber::Rake::Task.new(:cucumber, 'Run features that should pass') do |t|
|
|
9
|
-
t.cucumber_opts = "--color --tags ~@wip --strict --format #{ENV['CUCUMBER_FORMAT'] || 'Fivemat'}"
|
|
10
|
-
end
|
|
11
|
-
|
|
12
6
|
RSpec::Core::RakeTask.new(:spec)
|
|
13
7
|
|
|
14
8
|
task :default => :spec
|
data/lib/middleman-cloudfront.rb
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
require 'middleman-core
|
|
2
|
-
require 'middleman-cloudfront/
|
|
1
|
+
require 'pathname' # for some reason, had to require this because middleman-core 4.1.7 did not.
|
|
2
|
+
require 'middleman-cloudfront/extension'
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
require "middleman-cloudfront/extension"
|
|
6
|
-
::Middleman::CloudFront
|
|
7
|
-
end
|
|
4
|
+
Middleman::Extensions.register :cloudfront, Middleman::CloudFront::Extension
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
require 'middleman-cli'
|
|
2
|
+
require 'middleman-cloudfront/extension'
|
|
3
|
+
require 'fog/aws'
|
|
4
|
+
|
|
5
|
+
module Middleman
|
|
6
|
+
module Cli
|
|
7
|
+
module CloudFront
|
|
8
|
+
# This class provides an "invalidate" command for the middleman CLI.
|
|
9
|
+
class Invalidate < ::Thor::Group
|
|
10
|
+
include Thor::Actions
|
|
11
|
+
|
|
12
|
+
INVALIDATION_LIMIT = 1000
|
|
13
|
+
INDEX_REGEX = /
|
|
14
|
+
\A
|
|
15
|
+
(.*\/)?
|
|
16
|
+
index\.html
|
|
17
|
+
\z
|
|
18
|
+
/ix
|
|
19
|
+
|
|
20
|
+
check_unknown_options!
|
|
21
|
+
|
|
22
|
+
def self.exit_on_failure?
|
|
23
|
+
true
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def invalidate(options = nil, files = nil)
|
|
27
|
+
|
|
28
|
+
# If called via commandline, discover config (from bin/middleman)
|
|
29
|
+
if options.nil?
|
|
30
|
+
app = Middleman::Application.new do
|
|
31
|
+
config[:mode] = :config
|
|
32
|
+
config[:exit_before_ready] = true
|
|
33
|
+
config[:watcher_disable] = true
|
|
34
|
+
config[:disable_sitemap] = true
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Get the options from the cloudfront extension
|
|
38
|
+
extension = app.extensions[:cloudfront]
|
|
39
|
+
unless extension.nil?
|
|
40
|
+
options = extension.options
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
if options.nil?
|
|
45
|
+
configuration_usage
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
[:distribution_id, :filter].each do |key|
|
|
49
|
+
raise StandardError, "Configuration key #{key} is missing." if options.public_send(key).nil?
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
puts '## Invalidating files on CloudFront'
|
|
53
|
+
|
|
54
|
+
fog_options = {
|
|
55
|
+
:provider => 'AWS'
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
fog_options.merge!(
|
|
59
|
+
if options.access_key_id && options.secret_access_key
|
|
60
|
+
{
|
|
61
|
+
:aws_access_key_id => options.access_key_id,
|
|
62
|
+
:aws_secret_access_key => options.secret_access_key
|
|
63
|
+
}
|
|
64
|
+
else
|
|
65
|
+
{ :use_iam_profile => true }
|
|
66
|
+
end
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
cdn = Fog::CDN.new(fog_options)
|
|
70
|
+
|
|
71
|
+
distribution = cdn.distributions.get(options.distribution_id)
|
|
72
|
+
|
|
73
|
+
raise StandardError, "Cannot access Distribution with id #{options.distribution_id}." if distribution.nil?
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# CloudFront limits the amount of files which can be invalidated by one request to 1000.
|
|
77
|
+
# If there are more than 1000 files to invalidate, do so sequentially and wait until each validation is ready.
|
|
78
|
+
# If there are max 1000 files, create the invalidation and return immediately.
|
|
79
|
+
files = normalize_files(files || list_files(options.filter))
|
|
80
|
+
return if files.empty?
|
|
81
|
+
|
|
82
|
+
if files.count <= INVALIDATION_LIMIT
|
|
83
|
+
puts "Invalidating #{files.count} files. It might take 10 to 15 minutes until all files are invalidated."
|
|
84
|
+
puts 'Please check the AWS Management Console to see the status of the invalidation.'
|
|
85
|
+
invalidation = distribution.invalidations.create(:paths => files)
|
|
86
|
+
raise StandardError, %(Invalidation status is #{invalidation.status}. Expected "InProgress") unless invalidation.status == 'InProgress'
|
|
87
|
+
else
|
|
88
|
+
slices = files.each_slice(INVALIDATION_LIMIT)
|
|
89
|
+
puts "Invalidating #{files.count} files in #{slices.count} batch(es). It might take 10 to 15 minutes per batch until all files are invalidated."
|
|
90
|
+
slices.each_with_index do |slice, i|
|
|
91
|
+
puts "Invalidating batch #{i + 1}..."
|
|
92
|
+
invalidation = distribution.invalidations.create(:paths => slice)
|
|
93
|
+
invalidation.wait_for { ready? } unless i == slices.count - 1
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
protected
|
|
99
|
+
|
|
100
|
+
def configuration_usage
|
|
101
|
+
raise Error, <<-TEXT
|
|
102
|
+
ERROR: You need to activate the cloudfront extension in config.rb.
|
|
103
|
+
|
|
104
|
+
The example configuration is:
|
|
105
|
+
activate :cloudfront do |cf|
|
|
106
|
+
cf.access_key_id = 'I'
|
|
107
|
+
cf.secret_access_key = 'love'
|
|
108
|
+
cf.distribution_id = 'cats'
|
|
109
|
+
# cf.filter = /\.html/i # default /.*/
|
|
110
|
+
# cf.after_build = true # default is false
|
|
111
|
+
end
|
|
112
|
+
TEXT
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def list_files(filter)
|
|
116
|
+
Dir.chdir('build/') do
|
|
117
|
+
Dir.glob('**/*', File::FNM_DOTMATCH).tap do |files|
|
|
118
|
+
# Remove directories
|
|
119
|
+
files.reject! { |f| File.directory?(f) }
|
|
120
|
+
|
|
121
|
+
# Remove files that do not match filter
|
|
122
|
+
files.reject! { |f| f !~ filter }
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def normalize_files(files)
|
|
128
|
+
# Add directories since they have to be invalidated
|
|
129
|
+
# as well if :directory_indexes is active
|
|
130
|
+
files += files.grep(INDEX_REGEX).map do |file|
|
|
131
|
+
file == 'index.html' ? '/' : File.dirname(file) << '/'
|
|
132
|
+
end.uniq
|
|
133
|
+
|
|
134
|
+
# URI encode and add leading slash
|
|
135
|
+
files.map { |f| URI::encode(f.start_with?('/') ? f : "/#{f}") }
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Add to CLI
|
|
139
|
+
Base.register(self, 'invalidate', 'invalidate', 'Invalidate a cloudfront distribution.')
|
|
140
|
+
|
|
141
|
+
# Map "inv" to "invalidate"
|
|
142
|
+
Base.map('inv' => 'invalidate')
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
@@ -1,36 +1,31 @@
|
|
|
1
1
|
require 'middleman-core'
|
|
2
|
+
require 'middleman-cloudfront/commands/invalidate'
|
|
2
3
|
|
|
3
4
|
module Middleman
|
|
4
5
|
module CloudFront
|
|
5
|
-
class
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
class Extension < Middleman::Extension
|
|
7
|
+
# @param [Symbol] key The name of the option
|
|
8
|
+
# @param [Object] default The default value for the option
|
|
9
|
+
# @param [String] description A human-readable description of what the option does
|
|
10
|
+
option :access_key_id, nil, 'Access key id'
|
|
11
|
+
option :secret_access_key, nil, 'Secret access key'
|
|
12
|
+
option :distribution_id, nil, 'Distribution id'
|
|
13
|
+
option :filter, /.*/, 'Filter files to be invalidated'
|
|
14
|
+
option :after_build, false, 'Invalidate after build'
|
|
15
|
+
|
|
16
|
+
def initialize(app, options_hash={}, &block)
|
|
17
|
+
super
|
|
10
18
|
end
|
|
11
19
|
|
|
12
|
-
def
|
|
13
|
-
|
|
14
|
-
yield @@cloudfront_options if block_given?
|
|
15
|
-
|
|
16
|
-
app.after_build do
|
|
17
|
-
::Middleman::Cli::CloudFront.new.invalidate(@@cloudfront_options) if @@cloudfront_options.after_build
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
app.send :include, Helpers
|
|
20
|
+
def after_build
|
|
21
|
+
Middleman::Cli::CloudFront::Invalidate.new.invalidate(options) if options.after_build
|
|
21
22
|
end
|
|
22
|
-
alias :included :registered
|
|
23
|
-
end
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def invalidate(files = nil)
|
|
31
|
-
::Middleman::Cli::CloudFront.new.invalidate(cloudfront_options, files)
|
|
24
|
+
helpers do
|
|
25
|
+
def invalidate(files = nil)
|
|
26
|
+
Middleman::Cli::CloudFront::Invalidate.new.invalidate(options, files)
|
|
27
|
+
end
|
|
32
28
|
end
|
|
33
29
|
end
|
|
34
|
-
|
|
35
30
|
end
|
|
36
31
|
end
|
|
@@ -15,25 +15,16 @@ Gem::Specification.new do |s|
|
|
|
15
15
|
s.summary = %q{Invalidate CloudFront cache after deployment to S3}
|
|
16
16
|
s.description = %q{Adds ability to invalidate a specific set of files in your CloudFront cache}
|
|
17
17
|
|
|
18
|
-
s.rubyforge_project = "middleman-cloudfront"
|
|
19
|
-
|
|
20
18
|
s.files = `git ls-files -z`.split("\0")
|
|
21
19
|
s.test_files = `git ls-files -z -- {fixtures,features}/*`.split("\0")
|
|
22
20
|
s.require_paths = ["lib"]
|
|
23
21
|
|
|
24
|
-
s.
|
|
22
|
+
s.required_ruby_version = '>= 2.0.0'
|
|
25
23
|
|
|
26
|
-
s.add_development_dependency 'cucumber', '~> 1.3'
|
|
27
|
-
s.add_development_dependency 'aruba', '~> 0.5'
|
|
28
|
-
s.add_development_dependency 'fivemat', '~> 1.3'
|
|
29
|
-
s.add_development_dependency 'simplecov', '~> 0.8'
|
|
30
24
|
s.add_development_dependency 'rake', '>= 0.9.0'
|
|
31
25
|
s.add_development_dependency 'rspec', '~> 3.0'
|
|
32
26
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
else
|
|
37
|
-
s.add_dependency 'middleman-core', '~> 3.0'
|
|
38
|
-
end
|
|
27
|
+
s.add_dependency 'fog-aws', '>= 0.1.1'
|
|
28
|
+
s.add_dependency 'middleman-core', '>= 3.0'
|
|
29
|
+
s.add_dependency 'middleman-cli', '>= 3.0'
|
|
39
30
|
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
require 'fog/aws/models/cdn/distributions'
|
|
4
|
+
|
|
5
|
+
describe Middleman::Cli::CloudFront::Invalidate do
|
|
6
|
+
INVALIDATION_LIMIT = Middleman::Cli::CloudFront::Invalidate::INVALIDATION_LIMIT
|
|
7
|
+
let(:cloudfront) { described_class.new }
|
|
8
|
+
let(:options) do
|
|
9
|
+
config = Middleman::Configuration::ConfigurationManager.new
|
|
10
|
+
config.access_key_id ='access_key_id_123'
|
|
11
|
+
config.secret_access_key = 'secret_access_key_123'
|
|
12
|
+
config.distribution_id = 'distribution_id_123'
|
|
13
|
+
config.filter = 'filter_123'
|
|
14
|
+
config.after_build = 'after_build_123'
|
|
15
|
+
config
|
|
16
|
+
end
|
|
17
|
+
let(:distribution) { double('distribution', invalidations: double('invalidations')) }
|
|
18
|
+
|
|
19
|
+
before do
|
|
20
|
+
allow_any_instance_of(Fog::CDN::AWS::Distributions).to receive(:get).and_return(distribution)
|
|
21
|
+
allow(distribution.invalidations).to receive(:create) do
|
|
22
|
+
double('invalidation', status: 'InProgress', wait_for: -> {})
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it 'gets the correct distribution' do
|
|
27
|
+
allow(cloudfront).to receive(:list_files).and_return([])
|
|
28
|
+
expect_any_instance_of(Fog::CDN::AWS::Distributions).to receive(:get).with('distribution_id_123')
|
|
29
|
+
cloudfront.invalidate(options)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'normalizes paths' do
|
|
33
|
+
files = %w(file directory/index.html)
|
|
34
|
+
normalized_files = %w(/file /directory/index.html /directory/)
|
|
35
|
+
allow(cloudfront).to receive(:list_files).and_return(files)
|
|
36
|
+
expect(distribution.invalidations).to receive(:create).once.with(paths: normalized_files)
|
|
37
|
+
cloudfront.invalidate(options)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
context 'when the amount of files to invalidate is under the limit' do
|
|
41
|
+
it 'divides them up in packages and creates one invalidation per package' do
|
|
42
|
+
files = (1..INVALIDATION_LIMIT).map { |i| "/file_#{i}" }
|
|
43
|
+
allow(cloudfront).to receive(:list_files).and_return(files)
|
|
44
|
+
expect(distribution.invalidations).to receive(:create).once.with(paths: files)
|
|
45
|
+
cloudfront.invalidate(options)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
context 'when the amount of files to invalidate is over the limit' do
|
|
50
|
+
it 'creates only one invalidation with all of them' do
|
|
51
|
+
files = (1..(INVALIDATION_LIMIT * 3)).map { |i| "/file_#{i}" }
|
|
52
|
+
allow(cloudfront).to receive(:list_files).and_return(files)
|
|
53
|
+
expect(distribution.invalidations).to receive(:create).once.with(paths: files[0, INVALIDATION_LIMIT])
|
|
54
|
+
expect(distribution.invalidations).to receive(:create).once.with(paths: files[INVALIDATION_LIMIT, INVALIDATION_LIMIT])
|
|
55
|
+
expect(distribution.invalidations).to receive(:create).once.with(paths: files[INVALIDATION_LIMIT * 2, INVALIDATION_LIMIT])
|
|
56
|
+
cloudfront.invalidate(options)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
context 'when files to invalidate are explicitly specified' do
|
|
61
|
+
it 'uses them instead of the files in the build directory' do
|
|
62
|
+
files = (1..3).map { |i| "/file_#{i}" }
|
|
63
|
+
expect(distribution.invalidations).to receive(:create).once.with(paths: files)
|
|
64
|
+
cloudfront.invalidate(options, files)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "doesn't filter them" do
|
|
68
|
+
files = (1..3).map { |i| "/file_#{i}" }
|
|
69
|
+
options.filter = /filter that matches no files/
|
|
70
|
+
expect(distribution.invalidations).to receive(:create).once.with(paths: files)
|
|
71
|
+
cloudfront.invalidate(options, files)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it 'normalizes them' do
|
|
75
|
+
files = %w(file directory/index.html)
|
|
76
|
+
normalized_files = %w(/file /directory/index.html /directory/)
|
|
77
|
+
expect(distribution.invalidations).to receive(:create).once.with(paths: normalized_files)
|
|
78
|
+
cloudfront.invalidate(options, files)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
context 'when the distribution_id is invalid' do
|
|
83
|
+
it 'it raise_error' do
|
|
84
|
+
#fog will return nil if the distribution_id is invalid
|
|
85
|
+
allow_any_instance_of(Fog::CDN::AWS::Distributions).to receive(:get).and_return(nil)
|
|
86
|
+
expect { cloudfront.invalidate(options) }.to raise_error(StandardError, "Cannot access Distribution with id distribution_id_123.")
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: middleman-cloudfront
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrey Korzhuev
|
|
@@ -9,118 +9,76 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date:
|
|
12
|
+
date: 2017-12-29 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
|
-
name:
|
|
16
|
-
requirement: !ruby/object:Gem::Requirement
|
|
17
|
-
requirements:
|
|
18
|
-
- - "~>"
|
|
19
|
-
- !ruby/object:Gem::Version
|
|
20
|
-
version: '1.9'
|
|
21
|
-
type: :runtime
|
|
22
|
-
prerelease: false
|
|
23
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
24
|
-
requirements:
|
|
25
|
-
- - "~>"
|
|
26
|
-
- !ruby/object:Gem::Version
|
|
27
|
-
version: '1.9'
|
|
28
|
-
- !ruby/object:Gem::Dependency
|
|
29
|
-
name: cucumber
|
|
30
|
-
requirement: !ruby/object:Gem::Requirement
|
|
31
|
-
requirements:
|
|
32
|
-
- - "~>"
|
|
33
|
-
- !ruby/object:Gem::Version
|
|
34
|
-
version: '1.3'
|
|
35
|
-
type: :development
|
|
36
|
-
prerelease: false
|
|
37
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
38
|
-
requirements:
|
|
39
|
-
- - "~>"
|
|
40
|
-
- !ruby/object:Gem::Version
|
|
41
|
-
version: '1.3'
|
|
42
|
-
- !ruby/object:Gem::Dependency
|
|
43
|
-
name: aruba
|
|
44
|
-
requirement: !ruby/object:Gem::Requirement
|
|
45
|
-
requirements:
|
|
46
|
-
- - "~>"
|
|
47
|
-
- !ruby/object:Gem::Version
|
|
48
|
-
version: '0.5'
|
|
49
|
-
type: :development
|
|
50
|
-
prerelease: false
|
|
51
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
52
|
-
requirements:
|
|
53
|
-
- - "~>"
|
|
54
|
-
- !ruby/object:Gem::Version
|
|
55
|
-
version: '0.5'
|
|
56
|
-
- !ruby/object:Gem::Dependency
|
|
57
|
-
name: fivemat
|
|
15
|
+
name: rake
|
|
58
16
|
requirement: !ruby/object:Gem::Requirement
|
|
59
17
|
requirements:
|
|
60
|
-
- - "
|
|
18
|
+
- - ">="
|
|
61
19
|
- !ruby/object:Gem::Version
|
|
62
|
-
version:
|
|
20
|
+
version: 0.9.0
|
|
63
21
|
type: :development
|
|
64
22
|
prerelease: false
|
|
65
23
|
version_requirements: !ruby/object:Gem::Requirement
|
|
66
24
|
requirements:
|
|
67
|
-
- - "
|
|
25
|
+
- - ">="
|
|
68
26
|
- !ruby/object:Gem::Version
|
|
69
|
-
version:
|
|
27
|
+
version: 0.9.0
|
|
70
28
|
- !ruby/object:Gem::Dependency
|
|
71
|
-
name:
|
|
29
|
+
name: rspec
|
|
72
30
|
requirement: !ruby/object:Gem::Requirement
|
|
73
31
|
requirements:
|
|
74
32
|
- - "~>"
|
|
75
33
|
- !ruby/object:Gem::Version
|
|
76
|
-
version: '0
|
|
34
|
+
version: '3.0'
|
|
77
35
|
type: :development
|
|
78
36
|
prerelease: false
|
|
79
37
|
version_requirements: !ruby/object:Gem::Requirement
|
|
80
38
|
requirements:
|
|
81
39
|
- - "~>"
|
|
82
40
|
- !ruby/object:Gem::Version
|
|
83
|
-
version: '0
|
|
41
|
+
version: '3.0'
|
|
84
42
|
- !ruby/object:Gem::Dependency
|
|
85
|
-
name:
|
|
43
|
+
name: fog-aws
|
|
86
44
|
requirement: !ruby/object:Gem::Requirement
|
|
87
45
|
requirements:
|
|
88
46
|
- - ">="
|
|
89
47
|
- !ruby/object:Gem::Version
|
|
90
|
-
version: 0.
|
|
91
|
-
type: :
|
|
48
|
+
version: 0.1.1
|
|
49
|
+
type: :runtime
|
|
92
50
|
prerelease: false
|
|
93
51
|
version_requirements: !ruby/object:Gem::Requirement
|
|
94
52
|
requirements:
|
|
95
53
|
- - ">="
|
|
96
54
|
- !ruby/object:Gem::Version
|
|
97
|
-
version: 0.
|
|
55
|
+
version: 0.1.1
|
|
98
56
|
- !ruby/object:Gem::Dependency
|
|
99
|
-
name:
|
|
57
|
+
name: middleman-core
|
|
100
58
|
requirement: !ruby/object:Gem::Requirement
|
|
101
59
|
requirements:
|
|
102
|
-
- - "
|
|
60
|
+
- - ">="
|
|
103
61
|
- !ruby/object:Gem::Version
|
|
104
62
|
version: '3.0'
|
|
105
|
-
type: :
|
|
63
|
+
type: :runtime
|
|
106
64
|
prerelease: false
|
|
107
65
|
version_requirements: !ruby/object:Gem::Requirement
|
|
108
66
|
requirements:
|
|
109
|
-
- - "
|
|
67
|
+
- - ">="
|
|
110
68
|
- !ruby/object:Gem::Version
|
|
111
69
|
version: '3.0'
|
|
112
70
|
- !ruby/object:Gem::Dependency
|
|
113
|
-
name: middleman-
|
|
71
|
+
name: middleman-cli
|
|
114
72
|
requirement: !ruby/object:Gem::Requirement
|
|
115
73
|
requirements:
|
|
116
|
-
- - "
|
|
74
|
+
- - ">="
|
|
117
75
|
- !ruby/object:Gem::Version
|
|
118
76
|
version: '3.0'
|
|
119
77
|
type: :runtime
|
|
120
78
|
prerelease: false
|
|
121
79
|
version_requirements: !ruby/object:Gem::Requirement
|
|
122
80
|
requirements:
|
|
123
|
-
- - "
|
|
81
|
+
- - ">="
|
|
124
82
|
- !ruby/object:Gem::Version
|
|
125
83
|
version: '3.0'
|
|
126
84
|
description: Adds ability to invalidate a specific set of files in your CloudFront
|
|
@@ -138,14 +96,13 @@ files:
|
|
|
138
96
|
- LICENSE
|
|
139
97
|
- README.md
|
|
140
98
|
- Rakefile
|
|
141
|
-
- features/support/env.rb
|
|
142
99
|
- lib/middleman-cloudfront.rb
|
|
143
|
-
- lib/middleman-cloudfront/commands.rb
|
|
100
|
+
- lib/middleman-cloudfront/commands/invalidate.rb
|
|
144
101
|
- lib/middleman-cloudfront/extension.rb
|
|
145
102
|
- lib/middleman-cloudfront/version.rb
|
|
146
103
|
- lib/middleman_extension.rb
|
|
147
104
|
- middleman-cloudfront.gemspec
|
|
148
|
-
- spec/lib/middleman-cloudfront/
|
|
105
|
+
- spec/lib/middleman-cloudfront/invalidate_spec.rb
|
|
149
106
|
- spec/spec_helper.rb
|
|
150
107
|
homepage: https://github.com/andrusha/middleman-cloudfront
|
|
151
108
|
licenses: []
|
|
@@ -158,18 +115,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
158
115
|
requirements:
|
|
159
116
|
- - ">="
|
|
160
117
|
- !ruby/object:Gem::Version
|
|
161
|
-
version:
|
|
118
|
+
version: 2.0.0
|
|
162
119
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
163
120
|
requirements:
|
|
164
121
|
- - ">="
|
|
165
122
|
- !ruby/object:Gem::Version
|
|
166
123
|
version: '0'
|
|
167
124
|
requirements: []
|
|
168
|
-
rubyforge_project:
|
|
169
|
-
rubygems_version: 2.4
|
|
125
|
+
rubyforge_project:
|
|
126
|
+
rubygems_version: 2.7.4
|
|
170
127
|
signing_key:
|
|
171
128
|
specification_version: 4
|
|
172
129
|
summary: Invalidate CloudFront cache after deployment to S3
|
|
173
|
-
test_files:
|
|
174
|
-
- features/support/env.rb
|
|
175
|
-
has_rdoc:
|
|
130
|
+
test_files: []
|
data/features/support/env.rb
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
require "middleman-core/cli"
|
|
2
|
-
require "middleman-cloudfront/extension"
|
|
3
|
-
require "fog"
|
|
4
|
-
|
|
5
|
-
module Middleman
|
|
6
|
-
module Cli
|
|
7
|
-
|
|
8
|
-
class CloudFront < Thor
|
|
9
|
-
include Thor::Actions
|
|
10
|
-
|
|
11
|
-
INVALIDATION_LIMIT = 1000
|
|
12
|
-
INDEX_REGEX = /
|
|
13
|
-
\A
|
|
14
|
-
(.*\/)?
|
|
15
|
-
index\.html
|
|
16
|
-
\z
|
|
17
|
-
/ix
|
|
18
|
-
|
|
19
|
-
check_unknown_options!
|
|
20
|
-
|
|
21
|
-
namespace :invalidate
|
|
22
|
-
|
|
23
|
-
def self.exit_on_failure?
|
|
24
|
-
true
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
desc "invalidate", "A way to deal with your CloudFront distributions"
|
|
28
|
-
def invalidate(options = nil, files = nil)
|
|
29
|
-
if options.nil?
|
|
30
|
-
app_instance = ::Middleman::Application.server.inst
|
|
31
|
-
unless app_instance.respond_to?(:cloudfront_options)
|
|
32
|
-
raise Error, <<-TEXT
|
|
33
|
-
ERROR: You need to activate the cloudfront extension in config.rb.
|
|
34
|
-
|
|
35
|
-
The example configuration is:
|
|
36
|
-
activate :cloudfront do |cf|
|
|
37
|
-
cf.access_key_id = 'I'
|
|
38
|
-
cf.secret_access_key = 'love'
|
|
39
|
-
cf.distribution_id = 'cats'
|
|
40
|
-
cf.filter = /\.html/i # default /.*/
|
|
41
|
-
cf.after_build = true # default is false
|
|
42
|
-
end
|
|
43
|
-
TEXT
|
|
44
|
-
end
|
|
45
|
-
options = app_instance.cloudfront_options
|
|
46
|
-
end
|
|
47
|
-
options.filter ||= /.*/
|
|
48
|
-
[:distribution_id, :filter].each do |key|
|
|
49
|
-
raise StandardError, "Configuration key #{key} is missing." if options.public_send(key).nil?
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
puts "## Invalidating files on CloudFront"
|
|
53
|
-
|
|
54
|
-
fog_options = {
|
|
55
|
-
:provider => 'AWS'
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if options.access_key_id && options.secret_access_key
|
|
59
|
-
fog_options.merge!({
|
|
60
|
-
:aws_access_key_id => options.access_key_id,
|
|
61
|
-
:aws_secret_access_key => options.secret_access_key
|
|
62
|
-
})
|
|
63
|
-
else
|
|
64
|
-
fog_options.merge!({:use_iam_profile => true})
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
cdn = Fog::CDN.new(fog_options)
|
|
68
|
-
|
|
69
|
-
distribution = cdn.distributions.get(options.distribution_id)
|
|
70
|
-
|
|
71
|
-
# CloudFront limits the amount of files which can be invalidated by one request to 1000.
|
|
72
|
-
# If there are more than 1000 files to invalidate, do so sequentially and wait until each validation is ready.
|
|
73
|
-
# If there are max 1000 files, create the invalidation and return immediately.
|
|
74
|
-
files = normalize_files(files || list_files(options.filter))
|
|
75
|
-
return if files.empty?
|
|
76
|
-
|
|
77
|
-
if files.count <= INVALIDATION_LIMIT
|
|
78
|
-
puts "Invalidating #{files.count} files. It might take 10 to 15 minutes until all files are invalidated."
|
|
79
|
-
puts 'Please check the AWS Management Console to see the status of the invalidation.'
|
|
80
|
-
invalidation = distribution.invalidations.create(:paths => files)
|
|
81
|
-
raise StandardError, %(Invalidation status is #{invalidation.status}. Expected "InProgress") unless invalidation.status == 'InProgress'
|
|
82
|
-
else
|
|
83
|
-
slices = files.each_slice(INVALIDATION_LIMIT)
|
|
84
|
-
puts "Invalidating #{files.count} files in #{slices.count} batch(es). It might take 10 to 15 minutes per batch until all files are invalidated."
|
|
85
|
-
slices.each_with_index do |slice, i|
|
|
86
|
-
puts "Invalidating batch #{i + 1}..."
|
|
87
|
-
invalidation = distribution.invalidations.create(:paths => slice)
|
|
88
|
-
invalidation.wait_for { ready? } unless i == slices.count - 1
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
protected
|
|
94
|
-
|
|
95
|
-
def list_files(filter)
|
|
96
|
-
Dir.chdir('build/') do
|
|
97
|
-
Dir.glob('**/*', File::FNM_DOTMATCH).tap do |files|
|
|
98
|
-
# Remove directories
|
|
99
|
-
files.reject! { |f| File.directory?(f) }
|
|
100
|
-
|
|
101
|
-
# Remove files that do not match filter
|
|
102
|
-
files.reject! { |f| f !~ filter }
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def normalize_files(files)
|
|
108
|
-
# Add directories since they have to be invalidated
|
|
109
|
-
# as well if :directory_indexes is active
|
|
110
|
-
files += files.grep(INDEX_REGEX).map do |file|
|
|
111
|
-
file == 'index.html' ? '/' : File.dirname(file) << '/'
|
|
112
|
-
end.uniq
|
|
113
|
-
|
|
114
|
-
# URI encode and add leading slash
|
|
115
|
-
files.map { |f| URI::encode(f.start_with?('/') ? f : "/#{f}") }
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
Base.map({"inv" => "invalidate"})
|
|
121
|
-
end
|
|
122
|
-
end
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
require 'spec_helper'
|
|
2
|
-
|
|
3
|
-
require 'fog/aws/models/cdn/distributions'
|
|
4
|
-
|
|
5
|
-
describe Middleman::Cli::CloudFront do
|
|
6
|
-
let(:cloudfront) { described_class.new }
|
|
7
|
-
let(:options) do
|
|
8
|
-
Middleman::CloudFront::Options.new(
|
|
9
|
-
'access_key_id_123',
|
|
10
|
-
'secret_access_key_123',
|
|
11
|
-
'distribution_id_123',
|
|
12
|
-
'filter_123',
|
|
13
|
-
'after_build_123'
|
|
14
|
-
)
|
|
15
|
-
end
|
|
16
|
-
let(:distribution) { double('distribution', invalidations: double('invalidations')) }
|
|
17
|
-
|
|
18
|
-
describe '#invalidate' do
|
|
19
|
-
before do
|
|
20
|
-
allow_any_instance_of(Fog::CDN::AWS::Distributions).to receive(:get).and_return(distribution)
|
|
21
|
-
allow(distribution.invalidations).to receive(:create) do
|
|
22
|
-
double('invalidation', status: 'InProgress', wait_for: -> {})
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
it 'gets the correct distribution' do
|
|
27
|
-
allow(cloudfront).to receive(:list_files).and_return([])
|
|
28
|
-
expect_any_instance_of(Fog::CDN::AWS::Distributions).to receive(:get).with('distribution_id_123')
|
|
29
|
-
cloudfront.invalidate(options)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
it 'normalizes paths' do
|
|
33
|
-
files = %w(file directory/index.html)
|
|
34
|
-
normalized_files = %w(/file /directory/index.html /directory/)
|
|
35
|
-
allow(cloudfront).to receive(:list_files).and_return(files)
|
|
36
|
-
expect(distribution.invalidations).to receive(:create).once.with(paths: normalized_files)
|
|
37
|
-
cloudfront.invalidate(options)
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
context 'when the amount of files to invalidate is under the limit' do
|
|
41
|
-
it 'divides them up in packages and creates one invalidation per package' do
|
|
42
|
-
files = (1..Middleman::Cli::CloudFront::INVALIDATION_LIMIT).map { |i| "/file_#{i}" }
|
|
43
|
-
allow(cloudfront).to receive(:list_files).and_return(files)
|
|
44
|
-
expect(distribution.invalidations).to receive(:create).once.with(paths: files)
|
|
45
|
-
cloudfront.invalidate(options)
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
context 'when the amount of files to invalidate is over the limit' do
|
|
50
|
-
it 'creates only one invalidation with all of them' do
|
|
51
|
-
files = (1..(Middleman::Cli::CloudFront::INVALIDATION_LIMIT * 3)).map { |i| "/file_#{i}" }
|
|
52
|
-
allow(cloudfront).to receive(:list_files).and_return(files)
|
|
53
|
-
expect(distribution.invalidations).to receive(:create).once.with(paths: files[0, Middleman::Cli::CloudFront::INVALIDATION_LIMIT])
|
|
54
|
-
expect(distribution.invalidations).to receive(:create).once.with(paths: files[Middleman::Cli::CloudFront::INVALIDATION_LIMIT, Middleman::Cli::CloudFront::INVALIDATION_LIMIT])
|
|
55
|
-
expect(distribution.invalidations).to receive(:create).once.with(paths: files[Middleman::Cli::CloudFront::INVALIDATION_LIMIT * 2, Middleman::Cli::CloudFront::INVALIDATION_LIMIT])
|
|
56
|
-
cloudfront.invalidate(options)
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
context 'when files to invalidate are explicitly specified' do
|
|
61
|
-
it 'uses them instead of the files in the build directory' do
|
|
62
|
-
files = (1..3).map { |i| "/file_#{i}" }
|
|
63
|
-
expect(distribution.invalidations).to receive(:create).once.with(paths: files)
|
|
64
|
-
cloudfront.invalidate(options, files)
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
it "doesn't filter them" do
|
|
68
|
-
files = (1..3).map { |i| "/file_#{i}" }
|
|
69
|
-
options.filter = /filter that matches no files/
|
|
70
|
-
expect(distribution.invalidations).to receive(:create).once.with(paths: files)
|
|
71
|
-
cloudfront.invalidate(options, files)
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
it 'normalizes them' do
|
|
75
|
-
files = %w(file directory/index.html)
|
|
76
|
-
normalized_files = %w(/file /directory/index.html /directory/)
|
|
77
|
-
expect(distribution.invalidations).to receive(:create).once.with(paths: normalized_files)
|
|
78
|
-
cloudfront.invalidate(options, files)
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
end
|