middleman-cloudfront 0.2.1 → 0.4.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/.github/workflows/ci_cd.yml +20 -0
- data/.tool-versions +1 -0
- data/README.md +3 -1
- data/Rakefile +0 -6
- data/lib/middleman-cloudfront/commands/invalidate.rb +147 -0
- data/lib/middleman-cloudfront/extension.rb +19 -24
- data/lib/middleman-cloudfront/version.rb +1 -1
- data/lib/middleman-cloudfront.rb +3 -6
- data/middleman-cloudfront.gemspec +5 -13
- data/spec/lib/middleman-cloudfront/invalidate_spec.rb +89 -0
- metadata +37 -68
- data/.travis.yml +0 -7
- 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: d747f88048b94a728aae2e9f7a5be6edf7555a30d931da06e64292a7ec9647c3
|
4
|
+
data.tar.gz: fae578477731498e1b3d520052ea39b87d9d32b47837492867d90e48d0d1ec72
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aa5e3a1ae12a5f96752154a2a51e3e56e4e3715b53f193b54fcfe4ff4474912e9b07d26524b7793aaa0307a0854c407e86e5fd649773121d6a5dd01740f7098c
|
7
|
+
data.tar.gz: 0c3e070652bfa6a7d797abdb842fb8fc80bad9a96ec3783d0bc12a752723c44de73225ecab6cd534d15826ec85079a060502e77f595dcfe9643638d955c7ad59
|
@@ -0,0 +1,20 @@
|
|
1
|
+
name: CI / CD
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
|
6
|
+
jobs:
|
7
|
+
test:
|
8
|
+
strategy:
|
9
|
+
fail-fast: false
|
10
|
+
matrix:
|
11
|
+
os: [ubuntu-latest, macos-latest]
|
12
|
+
ruby: ['2.2', '2.7', '3.0', '3.1', head]
|
13
|
+
runs-on: ${{ matrix.os }}
|
14
|
+
steps:
|
15
|
+
- uses: actions/checkout@v3
|
16
|
+
- uses: ruby/setup-ruby@v1
|
17
|
+
with:
|
18
|
+
ruby-version: ${{ matrix.ruby }}
|
19
|
+
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
20
|
+
- run: bundle exec rake
|
data/.tool-versions
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby 3.2.1
|
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
|
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'middleman-cli'
|
2
|
+
require 'middleman-cloudfront/extension'
|
3
|
+
require 'fog/aws'
|
4
|
+
require 'addressable/uri'
|
5
|
+
|
6
|
+
module Middleman
|
7
|
+
module Cli
|
8
|
+
module CloudFront
|
9
|
+
# This class provides an "invalidate" command for the middleman CLI.
|
10
|
+
class Invalidate < ::Thor::Group
|
11
|
+
include Thor::Actions
|
12
|
+
|
13
|
+
INVALIDATION_LIMIT = 1000
|
14
|
+
INDEX_REGEX = /
|
15
|
+
\A
|
16
|
+
(.*\/)?
|
17
|
+
index\.html
|
18
|
+
\z
|
19
|
+
/ix
|
20
|
+
|
21
|
+
check_unknown_options!
|
22
|
+
|
23
|
+
def self.exit_on_failure?
|
24
|
+
true
|
25
|
+
end
|
26
|
+
|
27
|
+
def invalidate(options = nil, files = nil)
|
28
|
+
|
29
|
+
# If called via commandline, discover config (from bin/middleman)
|
30
|
+
if options.nil?
|
31
|
+
app = Middleman::Application.new do
|
32
|
+
config[:mode] = :config
|
33
|
+
config[:exit_before_ready] = true
|
34
|
+
config[:watcher_disable] = true
|
35
|
+
config[:disable_sitemap] = true
|
36
|
+
end
|
37
|
+
|
38
|
+
# Get the options from the cloudfront extension
|
39
|
+
extension = app.extensions[:cloudfront]
|
40
|
+
unless extension.nil?
|
41
|
+
options = extension.options
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
if options.nil?
|
46
|
+
configuration_usage
|
47
|
+
end
|
48
|
+
|
49
|
+
[:distribution_id, :filter].each do |key|
|
50
|
+
raise StandardError, "Configuration key #{key} is missing." if options.public_send(key).nil?
|
51
|
+
end
|
52
|
+
|
53
|
+
puts '## Invalidating files on CloudFront'
|
54
|
+
|
55
|
+
fog_options = {
|
56
|
+
:provider => 'AWS'
|
57
|
+
}
|
58
|
+
|
59
|
+
fog_options.merge!(
|
60
|
+
if options.access_key_id && options.secret_access_key
|
61
|
+
{
|
62
|
+
:aws_access_key_id => options.access_key_id,
|
63
|
+
:aws_secret_access_key => options.secret_access_key
|
64
|
+
}
|
65
|
+
else
|
66
|
+
{ :use_iam_profile => true }
|
67
|
+
end
|
68
|
+
)
|
69
|
+
|
70
|
+
cdn = Fog::CDN.new(fog_options)
|
71
|
+
|
72
|
+
distribution = cdn.distributions.get(options.distribution_id)
|
73
|
+
|
74
|
+
raise StandardError, "Cannot access Distribution with id #{options.distribution_id}." if distribution.nil?
|
75
|
+
|
76
|
+
|
77
|
+
# CloudFront limits the amount of files which can be invalidated by one request to 1000.
|
78
|
+
# If there are more than 1000 files to invalidate, do so sequentially and wait until each validation is ready.
|
79
|
+
# If there are max 1000 files, create the invalidation and return immediately.
|
80
|
+
files = normalize_files(files || list_files(options.filter))
|
81
|
+
return if files.empty?
|
82
|
+
|
83
|
+
if files.count <= INVALIDATION_LIMIT
|
84
|
+
puts "Invalidating #{files.count} files. It might take 10 to 15 minutes until all files are invalidated."
|
85
|
+
puts 'Please check the AWS Management Console to see the status of the invalidation.'
|
86
|
+
invalidation = distribution.invalidations.create(:paths => files)
|
87
|
+
raise StandardError, %(Invalidation status is #{invalidation.status}. Expected "InProgress") unless invalidation.status == 'InProgress'
|
88
|
+
else
|
89
|
+
slices = files.each_slice(INVALIDATION_LIMIT)
|
90
|
+
puts "Invalidating #{files.count} files in #{slices.count} batch(es). It might take 10 to 15 minutes per batch until all files are invalidated."
|
91
|
+
slices.each_with_index do |slice, i|
|
92
|
+
puts "Invalidating batch #{i + 1}..."
|
93
|
+
invalidation = distribution.invalidations.create(:paths => slice)
|
94
|
+
invalidation.wait_for { ready? } unless i == slices.count - 1
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
protected
|
100
|
+
|
101
|
+
def configuration_usage
|
102
|
+
raise Error, <<-TEXT
|
103
|
+
ERROR: You need to activate the cloudfront extension in config.rb.
|
104
|
+
|
105
|
+
The example configuration is:
|
106
|
+
activate :cloudfront do |cf|
|
107
|
+
cf.access_key_id = 'I'
|
108
|
+
cf.secret_access_key = 'love'
|
109
|
+
cf.distribution_id = 'cats'
|
110
|
+
# cf.filter = /\.html/i # default /.*/
|
111
|
+
# cf.after_build = true # default is false
|
112
|
+
end
|
113
|
+
TEXT
|
114
|
+
end
|
115
|
+
|
116
|
+
def list_files(filter)
|
117
|
+
Dir.chdir('build/') do
|
118
|
+
Dir.glob('**/*', File::FNM_DOTMATCH).tap do |files|
|
119
|
+
# Remove directories
|
120
|
+
files.reject! { |f| File.directory?(f) }
|
121
|
+
|
122
|
+
# Remove files that do not match filter
|
123
|
+
files.reject! { |f| f !~ filter }
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def normalize_files(files)
|
129
|
+
# Add directories since they have to be invalidated
|
130
|
+
# as well if :directory_indexes is active
|
131
|
+
files += files.grep(INDEX_REGEX).map do |file|
|
132
|
+
file == 'index.html' ? '/' : File.dirname(file) << '/'
|
133
|
+
end.uniq
|
134
|
+
|
135
|
+
# URI encode and add leading slash
|
136
|
+
files.map { |f| Addressable::URI::encode(f.start_with?('/') ? f : "/#{f}") }
|
137
|
+
end
|
138
|
+
|
139
|
+
# Add to CLI
|
140
|
+
Base.register(self, 'invalidate', 'invalidate', 'Invalidate a cloudfront distribution.')
|
141
|
+
|
142
|
+
# Map "inv" to "invalidate"
|
143
|
+
Base.map('inv' => 'invalidate')
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
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
|
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
|
@@ -15,25 +15,17 @@ 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
|
-
|
37
|
-
s.add_dependency 'middleman-core', '~> 3.0'
|
38
|
-
end
|
27
|
+
s.add_dependency 'addressable', '>= 2.8.1'
|
28
|
+
s.add_dependency 'fog-aws', '>= 0.1.1'
|
29
|
+
s.add_dependency 'middleman-core', '>= 3.0'
|
30
|
+
s.add_dependency 'middleman-cli', '>= 3.0'
|
39
31
|
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,126 +1,98 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: middleman-cloudfront
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrey Korzhuev
|
8
8
|
- Manuel Meurer
|
9
|
-
autorequire:
|
9
|
+
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2023-03-30 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
|
15
|
+
name: rake
|
44
16
|
requirement: !ruby/object:Gem::Requirement
|
45
17
|
requirements:
|
46
|
-
- - "
|
18
|
+
- - ">="
|
47
19
|
- !ruby/object:Gem::Version
|
48
|
-
version:
|
20
|
+
version: 0.9.0
|
49
21
|
type: :development
|
50
22
|
prerelease: false
|
51
23
|
version_requirements: !ruby/object:Gem::Requirement
|
52
24
|
requirements:
|
53
|
-
- - "
|
25
|
+
- - ">="
|
54
26
|
- !ruby/object:Gem::Version
|
55
|
-
version:
|
27
|
+
version: 0.9.0
|
56
28
|
- !ruby/object:Gem::Dependency
|
57
|
-
name:
|
29
|
+
name: rspec
|
58
30
|
requirement: !ruby/object:Gem::Requirement
|
59
31
|
requirements:
|
60
32
|
- - "~>"
|
61
33
|
- !ruby/object:Gem::Version
|
62
|
-
version: '
|
34
|
+
version: '3.0'
|
63
35
|
type: :development
|
64
36
|
prerelease: false
|
65
37
|
version_requirements: !ruby/object:Gem::Requirement
|
66
38
|
requirements:
|
67
39
|
- - "~>"
|
68
40
|
- !ruby/object:Gem::Version
|
69
|
-
version: '
|
41
|
+
version: '3.0'
|
70
42
|
- !ruby/object:Gem::Dependency
|
71
|
-
name:
|
43
|
+
name: addressable
|
72
44
|
requirement: !ruby/object:Gem::Requirement
|
73
45
|
requirements:
|
74
|
-
- - "
|
46
|
+
- - ">="
|
75
47
|
- !ruby/object:Gem::Version
|
76
|
-
version:
|
77
|
-
type: :
|
48
|
+
version: 2.8.1
|
49
|
+
type: :runtime
|
78
50
|
prerelease: false
|
79
51
|
version_requirements: !ruby/object:Gem::Requirement
|
80
52
|
requirements:
|
81
|
-
- - "
|
53
|
+
- - ">="
|
82
54
|
- !ruby/object:Gem::Version
|
83
|
-
version:
|
55
|
+
version: 2.8.1
|
84
56
|
- !ruby/object:Gem::Dependency
|
85
|
-
name:
|
57
|
+
name: fog-aws
|
86
58
|
requirement: !ruby/object:Gem::Requirement
|
87
59
|
requirements:
|
88
60
|
- - ">="
|
89
61
|
- !ruby/object:Gem::Version
|
90
|
-
version: 0.
|
91
|
-
type: :
|
62
|
+
version: 0.1.1
|
63
|
+
type: :runtime
|
92
64
|
prerelease: false
|
93
65
|
version_requirements: !ruby/object:Gem::Requirement
|
94
66
|
requirements:
|
95
67
|
- - ">="
|
96
68
|
- !ruby/object:Gem::Version
|
97
|
-
version: 0.
|
69
|
+
version: 0.1.1
|
98
70
|
- !ruby/object:Gem::Dependency
|
99
|
-
name:
|
71
|
+
name: middleman-core
|
100
72
|
requirement: !ruby/object:Gem::Requirement
|
101
73
|
requirements:
|
102
|
-
- - "
|
74
|
+
- - ">="
|
103
75
|
- !ruby/object:Gem::Version
|
104
76
|
version: '3.0'
|
105
|
-
type: :
|
77
|
+
type: :runtime
|
106
78
|
prerelease: false
|
107
79
|
version_requirements: !ruby/object:Gem::Requirement
|
108
80
|
requirements:
|
109
|
-
- - "
|
81
|
+
- - ">="
|
110
82
|
- !ruby/object:Gem::Version
|
111
83
|
version: '3.0'
|
112
84
|
- !ruby/object:Gem::Dependency
|
113
|
-
name: middleman-
|
85
|
+
name: middleman-cli
|
114
86
|
requirement: !ruby/object:Gem::Requirement
|
115
87
|
requirements:
|
116
|
-
- - "
|
88
|
+
- - ">="
|
117
89
|
- !ruby/object:Gem::Version
|
118
90
|
version: '3.0'
|
119
91
|
type: :runtime
|
120
92
|
prerelease: false
|
121
93
|
version_requirements: !ruby/object:Gem::Requirement
|
122
94
|
requirements:
|
123
|
-
- - "
|
95
|
+
- - ">="
|
124
96
|
- !ruby/object:Gem::Version
|
125
97
|
version: '3.0'
|
126
98
|
description: Adds ability to invalidate a specific set of files in your CloudFront
|
@@ -131,26 +103,26 @@ executables: []
|
|
131
103
|
extensions: []
|
132
104
|
extra_rdoc_files: []
|
133
105
|
files:
|
106
|
+
- ".github/workflows/ci_cd.yml"
|
134
107
|
- ".gitignore"
|
135
|
-
- ".
|
108
|
+
- ".tool-versions"
|
136
109
|
- Gemfile
|
137
110
|
- Guardfile
|
138
111
|
- LICENSE
|
139
112
|
- README.md
|
140
113
|
- Rakefile
|
141
|
-
- features/support/env.rb
|
142
114
|
- lib/middleman-cloudfront.rb
|
143
|
-
- lib/middleman-cloudfront/commands.rb
|
115
|
+
- lib/middleman-cloudfront/commands/invalidate.rb
|
144
116
|
- lib/middleman-cloudfront/extension.rb
|
145
117
|
- lib/middleman-cloudfront/version.rb
|
146
118
|
- lib/middleman_extension.rb
|
147
119
|
- middleman-cloudfront.gemspec
|
148
|
-
- spec/lib/middleman-cloudfront/
|
120
|
+
- spec/lib/middleman-cloudfront/invalidate_spec.rb
|
149
121
|
- spec/spec_helper.rb
|
150
122
|
homepage: https://github.com/andrusha/middleman-cloudfront
|
151
123
|
licenses: []
|
152
124
|
metadata: {}
|
153
|
-
post_install_message:
|
125
|
+
post_install_message:
|
154
126
|
rdoc_options: []
|
155
127
|
require_paths:
|
156
128
|
- lib
|
@@ -158,18 +130,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
158
130
|
requirements:
|
159
131
|
- - ">="
|
160
132
|
- !ruby/object:Gem::Version
|
161
|
-
version:
|
133
|
+
version: 2.0.0
|
162
134
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
163
135
|
requirements:
|
164
136
|
- - ">="
|
165
137
|
- !ruby/object:Gem::Version
|
166
138
|
version: '0'
|
167
139
|
requirements: []
|
168
|
-
|
169
|
-
|
170
|
-
signing_key:
|
140
|
+
rubygems_version: 3.4.6
|
141
|
+
signing_key:
|
171
142
|
specification_version: 4
|
172
143
|
summary: Invalidate CloudFront cache after deployment to S3
|
173
|
-
test_files:
|
174
|
-
- features/support/env.rb
|
175
|
-
has_rdoc:
|
144
|
+
test_files: []
|
data/.travis.yml
DELETED
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
|