branchable_cdn_assets 0.6.1 → 0.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 644f8e575d2fbc7650f897c402e2614fe61a0d51
4
- data.tar.gz: 23e3608e0f7aff7e39f22716b8332bc960e65bba
3
+ metadata.gz: 49f567d337137eb1f553b71d39026a2e0db533c4
4
+ data.tar.gz: a83f8ddb7c0346a5a0f2df8c63b863af7ee8f470
5
5
  SHA512:
6
- metadata.gz: 8721d9341ceecc5c3067d32cb6944a1b4fc710acb6026281ecd1b630786755a0382638da9ec753723ec591966db119cd7e7ddd2ae9748d5f1fae7a333c61ad20
7
- data.tar.gz: 04dcf0e047124ef84689627cbf46b0e4b493278ec5fb412f18c1aaee6b26bf9da2447bcd70f4464bf79e43b1e4d039ec0e0244d806912c4abcdb4f6dcdcf38ff
6
+ metadata.gz: 037301f07920418a121d73155478f9e5a3418647e49d7cdd3610103f8553f5cbf7f8be9c4e39b20c12dffde15f51983319bf7a44fc2a4bca45db9b39e80d3d10
7
+ data.tar.gz: 1b96408f5465c10d4bada8e114f2f3d955ebebb96b21a0b80cf2a30254209c83043241bdb621afa4c52b1f28fbc94c5fa6a2c95b1895ba058c7ca2ae3392eab8
@@ -14,7 +14,7 @@ require_relative 'branchable_cdn_assets/shell'
14
14
  require_relative 'branchable_cdn_assets/file_manager'
15
15
  require_relative 'branchable_cdn_assets/manifest'
16
16
  require_relative 'branchable_cdn_assets/config'
17
- require_relative 'branchable_cdn_assets/cloudfront'
17
+ require_relative 'branchable_cdn_assets/invalidator'
18
18
 
19
19
  module BranchableCDNAssets
20
20
  end
@@ -6,20 +6,25 @@ module BranchableCDNAssets
6
6
  extend EnvironmentAttributeReader::ClassMethods
7
7
 
8
8
  attr_reader :raw_data, :branch,
9
- :production_branch, :cloudfront, :cdn_dir,
9
+ :production_branch, :cdn_dir, :file_filter,
10
10
  :dir_permissions, :file_permissions, :rsync_flags
11
- env_attr_reader :host, :root, :url, :manage_cloudfront
11
+ env_attr_reader :host, :root, :url
12
12
 
13
13
  def initialize data, branch=Asgit.current_branch
14
14
  @raw_data = normalize_data data
15
15
  @branch = branch
16
16
 
17
17
  @production_branch = raw_data.fetch :production_branch, 'master'
18
- @cloudfront = raw_data.fetch :cloudfront, {}
19
18
  @cdn_dir = raw_data.fetch :dir, 'cdn'
19
+ @file_filter = raw_data.fetch :file_filter, nil
20
20
  @dir_permissions = env_attr(:dir_permissions) || 755
21
21
  @file_permissions = env_attr(:file_permissions) || 644
22
22
  @rsync_flags = env_attr(:rsync_flags) || '-aviz'
23
+ @allow_local = env_attr(:allow_local) || false
24
+
25
+ if @file_filter && ( !@file_filter.respond_to?(:arity) || @file_filter.arity != 1 )
26
+ raise ArgumentError, "file_filter must be a lambda accepting one argument"
27
+ end
23
28
  end
24
29
 
25
30
  def env
@@ -42,6 +47,21 @@ module BranchableCDNAssets
42
47
  end
43
48
  end
44
49
 
50
+ def invalidator
51
+ return @_invalidator if @_invalidator
52
+
53
+ invalidator_data = env_attr(:invalidator)
54
+ return false unless invalidator_data
55
+
56
+ invalidator_class = Invalidator.find(invalidator_data.keys.first)
57
+ if invalidator_class
58
+ @_invalidator = invalidator_class.new invalidator_data.values.first
59
+ return @_invalidator
60
+ else
61
+ raise ArgumentError, "no invalidators registered for key :#{invalidator_data.keys.first}"
62
+ end
63
+ end
64
+
45
65
  private
46
66
 
47
67
  class Remote < Struct.new(:host, :root, :url,
@@ -113,4 +133,4 @@ module BranchableCDNAssets
113
133
  end
114
134
 
115
135
  end
116
- end
136
+ end
@@ -10,10 +10,8 @@ module BranchableCDNAssets
10
10
  check_before :local_file_conflict, methods: [ :push!, :pull! ]
11
11
  check_before :ready_for_production, methods: [ :move_to_production ]
12
12
 
13
- attr_reader :config
14
- attr_reader :root
15
- attr_reader :branch
16
- attr_reader :manifest
13
+ attr_reader :config, :manifest,
14
+ :root, :branch
17
15
 
18
16
  # handles moving files to/from our various cdn locations
19
17
  def initialize config, branch=nil
@@ -62,7 +60,7 @@ module BranchableCDNAssets
62
60
  setup_remotes
63
61
 
64
62
  push_list = push
65
- if config.env.to_sym == :production && config.manage_cloudfront
63
+ if config.env.to_sym == :production
66
64
  invalidate( push_list )
67
65
  end
68
66
 
@@ -108,11 +106,14 @@ module BranchableCDNAssets
108
106
  end
109
107
 
110
108
 
111
- def find file
112
- return :local if list(:local).include?(file)
113
- return File.join(config.url, file) if list(:remote).include?(file)
109
+ def find file, location=:all
110
+ if [:all, :local].include?(location) && list(:local).include?(file)
111
+ return :local
112
+ elsif [:all, :remote].include?(location) && list(:remote).include?(file)
113
+ return File.join(config.url, file)
114
+ end
114
115
 
115
- unless config.env == :production
116
+ if config.env != :production && location != :local
116
117
  production_config = Config.new( config.raw_data, config.production_branch )
117
118
  production_manifest = Manifest.new( File.join( root, "#{config.production_branch}.manifest" ) )
118
119
 
@@ -222,12 +223,17 @@ module BranchableCDNAssets
222
223
  end
223
224
 
224
225
  def invalidate files
225
- to_invalidate = ( files & list(:local) ).map do |f|
226
- File.join( config.cloudfront[:path_prefix], f )
227
- end
226
+ if config.invalidator
227
+ to_invalidate = ( files & list(:local) ).map do |f|
228
+ if config.invalidator.config.respond_to? :path_prefix
229
+ File.join( config.invalidator.config.path_prefix, f )
230
+ else
231
+ f
232
+ end
233
+ end
228
234
 
229
- Cloudfront.new( config.cloudfront )
230
- .invalidate_files( to_invalidate )
235
+ config.invalidator.invalidate_files( to_invalidate )
236
+ end
231
237
  end
232
238
 
233
239
  # create the root dir on remote
@@ -353,6 +359,12 @@ module BranchableCDNAssets
353
359
  !f.match(/\.manifest$/) && !File.directory?(f)
354
360
  end.map do |f|
355
361
  f.sub( "#{File.join( Dir.pwd, root )}/", "" )
362
+ end.select do |f|
363
+ if config.file_filter
364
+ config.file_filter.call(f)
365
+ else
366
+ true
367
+ end
356
368
  end
357
369
  end
358
370
 
@@ -361,4 +373,4 @@ module BranchableCDNAssets
361
373
  end
362
374
 
363
375
  end
364
- end
376
+ end
@@ -36,7 +36,12 @@ module BranchableCDNAssets
36
36
  abort
37
37
  end
38
38
 
39
- if !list(:local).empty?
39
+ if config.allow_local && !(list(:both) - list(:local)).empty?
40
+ puts "local assets detected that aren't on the cdn\n" +
41
+ "you should push or remove them to continue\n" +
42
+ " " + (list(:both) - list(:local)).join("\n ")
43
+ abort
44
+ elsif !list(:local).empty?
40
45
  puts "assets detected in the local cdn directory\n" +
41
46
  "remove them by running the `prune` task"
42
47
  abort
@@ -45,4 +50,4 @@ module BranchableCDNAssets
45
50
 
46
51
  end
47
52
  end
48
- end
53
+ end
@@ -0,0 +1,24 @@
1
+ module BranchableCDNAssets
2
+ module Invalidator
3
+
4
+ class << self
5
+ def invalidators
6
+ @_invalidators ||= {}
7
+ return @_invalidators
8
+ end
9
+
10
+ def register invalidator_key, invalidator_class
11
+ invalidators[invalidator_key] = invalidator_class
12
+ end
13
+
14
+ def find invalidator_key
15
+ invalidators.fetch invalidator_key, false
16
+ end
17
+ end
18
+
19
+ end
20
+ end
21
+
22
+ require_relative 'invalidator/base'
23
+ require_relative 'invalidator/cloudfront'
24
+ require_relative 'invalidator/akamai_ccu'
@@ -0,0 +1,63 @@
1
+ module BranchableCDNAssets
2
+ module Invalidator
3
+ class AkamaiCCU < Base
4
+
5
+ PURGE_PATH = '/ccu/v2/queues'.freeze
6
+ STATUS_PATH = '/ccu/v2/queues'.freeze
7
+ PURGE_STATUS_PATH = '/ccu/v2/purges'.freeze
8
+
9
+ register_as :akamai
10
+ require_keys [:base_url, :access_token, :client_token, :secret_token]
11
+
12
+ set_defaults domain: 'production',
13
+ queue_name: 'default'
14
+
15
+ def invalidate_files files
16
+ url = File.join( config.base_url, PURGE_PATH, config.queue_name )
17
+ resp = HTTP.post( { access_token: config.access_token,
18
+ client_token: config.client_token,
19
+ secret_token: config.secret_token
20
+ }.merge( url: url,
21
+ body: {
22
+ action: 'invalidate',
23
+ domain: config.domain,
24
+ objects: Array(files)
25
+ }))
26
+
27
+
28
+ if resp.code.to_i == 201
29
+ puts "Posted invalidation for #{files.count} files:"
30
+ else
31
+ puts "Invalidation failed:"
32
+ end
33
+
34
+ JSON.parse(resp.body).each do |k,v|
35
+ puts "-> #{k}: #{v}"
36
+ end
37
+ end
38
+
39
+ # queue status or purge status
40
+ def status id=nil
41
+ if id
42
+ resp = HTTP.get({ access_token: config.access_token,
43
+ client_token: config.client_token,
44
+ secret_token: config.secret_token
45
+ }.merge( url: File.join( config.base_url, PURGE_STATUS_PATH, id ) ))
46
+ else
47
+ resp = HTTP.get({ access_token: config.access_token,
48
+ client_token: config.client_token,
49
+ secret_token: config.secret_token
50
+ }.merge( url: File.join( config.base_url, STATUS_PATH, config.queue_name ) ))
51
+ end
52
+
53
+ puts "Status:"
54
+ JSON.parse(resp.body).each do |k,v|
55
+ puts "-> #{k}: #{v}"
56
+ end
57
+ end
58
+
59
+ end
60
+ end
61
+ end
62
+
63
+ require_relative 'akamai_ccu/http'
@@ -0,0 +1,116 @@
1
+ require 'openssl'
2
+ require 'securerandom'
3
+ require 'base64'
4
+ require 'json'
5
+ require 'net/http'
6
+
7
+ # examples for setting auth headers taken from akamai's edgrid wrapper
8
+ # https://github.com/akamai-open/AkamaiOPEN-edgegrid-ruby/blob/master/lib/akamai/edgegrid.rb
9
+ #
10
+ module BranchableCDNAssets
11
+ module Invalidator
12
+ class AkamaiCCU
13
+
14
+ class HTTP
15
+
16
+ attr_reader :ident, :time, :nonce,
17
+ :client_token, :access_token, :secret_token
18
+
19
+ def self.get opts={}
20
+ uri = URI.parse opts.fetch(:url)
21
+ http = Net::HTTP.new uri.host, uri.port
22
+ http.use_ssl = true
23
+
24
+ request = Net::HTTP::Get.new(opts.fetch(:url))
25
+ signed = self.new(opts).sign(request)
26
+
27
+ return http.request signed
28
+ end
29
+
30
+ # opts requires:
31
+ # action
32
+ # url
33
+ # and required keys for HTTP
34
+ #
35
+ def self.post opts={}
36
+ uri = URI.parse opts.fetch(:url)
37
+ http = Net::HTTP.new uri.host, uri.port
38
+ http.use_ssl = true
39
+
40
+ request = Net::HTTP::Post.new( opts.fetch(:url),
41
+ 'Content-Type' => 'application/json' )
42
+
43
+ request.body = opts.fetch(:body).to_json
44
+ signed = self.new(opts).sign(request)
45
+
46
+ return http.request signed
47
+ end
48
+
49
+ def initialize opts={}
50
+ @ident = 'EG1-HMAC-SHA256'
51
+ @time = Time.now
52
+ @nonce = SecureRandom.uuid
53
+
54
+ @client_token = opts[:client_token]
55
+ @access_token = opts[:access_token]
56
+ @secret_token = opts[:secret_token]
57
+ end
58
+
59
+ def formatted_time
60
+ time.utc.strftime('%Y%m%dT%H:%M:%S+0000')
61
+ end
62
+
63
+ # return a signed http_request
64
+ def sign http_request
65
+ unsigned_auth_header = "#{ident} client_token=#{client_token};" +
66
+ "access_token=#{access_token};" +
67
+ "timestamp=#{formatted_time};" +
68
+ "nonce=#{nonce};"
69
+
70
+ uri = URI(http_request.path)
71
+ auth_header = base64_hmac_sha256 [ http_request.method,
72
+ uri.scheme,
73
+ http_request.key?('host') ? http_request['host'] : uri.host,
74
+ uri.request_uri,
75
+ '',
76
+ content_hash(http_request),
77
+ unsigned_auth_header ].join("\t"),
78
+ base64_hmac_sha256( formatted_time, secret_token )
79
+
80
+
81
+
82
+ http_request['Authorization'] = "#{unsigned_auth_header}signature=#{auth_header}"
83
+
84
+ return http_request
85
+ end
86
+
87
+ private
88
+
89
+ def content_hash http_request
90
+ if http_request.method == 'POST' && http_request.body && http_request.body.length > 0
91
+ if http_request.body.length > 2048
92
+ body = http_request.body[0..2047]
93
+ else
94
+ body = http_request.body
95
+ end
96
+
97
+ return base64_sha256(body)
98
+ else
99
+ return ""
100
+ end
101
+ end
102
+
103
+ def base64_sha256 data
104
+ Base64.encode64( OpenSSL::Digest::SHA256.new.digest(data) ).strip
105
+ end
106
+
107
+ def base64_hmac_sha256 data, key
108
+ Base64.encode64( OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, key, data) ).strip
109
+ end
110
+
111
+ end
112
+ end
113
+ end
114
+ end
115
+
116
+
@@ -0,0 +1,45 @@
1
+ module BranchableCDNAssets
2
+ module Invalidator
3
+ class Base
4
+
5
+ class << self
6
+ def register_as key
7
+ BranchableCDNAssets::Invalidator.register key, self
8
+ end
9
+
10
+ def require_keys keys
11
+ @required_keys = Array(keys)
12
+ end
13
+
14
+ def required_keys
15
+ @required_keys ||= []
16
+ end
17
+
18
+ def set_defaults opts={}
19
+ @defaults = opts
20
+ end
21
+
22
+ def defaults
23
+ @defaults ||= {}
24
+ end
25
+ end
26
+
27
+ attr_reader :config
28
+
29
+ def initialize config_opts={}
30
+ config_opts = self.class.defaults.merge config_opts
31
+ unless (self.class.required_keys - config_opts.keys).empty?
32
+ raise ArgumentError, "Missing required config keys: [#{(self.class.required_keys - config_opts.keys).join(', ')}]"
33
+ end
34
+
35
+ @config = Struct.new("Config_#{object_id}", *config_opts.keys).new(*config_opts.values)
36
+ end
37
+
38
+ # @param files [Array]
39
+ def invalidate_files files
40
+ raise NoMethodError, "invalidate_files hasn't been implemented for #{self.class}"
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,25 @@
1
+ module BranchableCDNAssets
2
+ module Invalidator
3
+ class Cloudfront < Base
4
+
5
+ register_as :cloudfront
6
+ require_keys [:distribution_id, :access_key, :secret_key]
7
+
8
+ def cdn
9
+ @cdn ||= ::Fog::CDN.new provider: 'AWS',
10
+ aws_access_key_id: config.access_key,
11
+ aws_secret_access_key: config.secret_key
12
+ end
13
+
14
+ # invalidate a batch of files on fog
15
+ # @param files [Array]
16
+ def invalidate_files files
17
+ resp = cdn.post_invalidation( config.distribution_id, Array(files) )
18
+ resp.body["InvalidationBatch"]["Path"].each do |file|
19
+ puts "Posted an invalidation for #{file}".colorize( :green )
20
+ end
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -55,9 +55,9 @@ module BranchableCDNAssets
55
55
 
56
56
  # remove the manifest source_file
57
57
  def destroy!
58
- File.delete(source_file)
58
+ File.delete(source_file) if File.exist?(source_file)
59
59
  true
60
60
  end
61
61
 
62
62
  end
63
- end
63
+ end
@@ -1,3 +1,3 @@
1
1
  module BranchableCDNAssets
2
- VERSION = "0.6.1"
3
- end
2
+ VERSION = "0.7.0" unless defined?(BranchableCDNAssets::VERSION)
3
+ end
@@ -11,7 +11,15 @@ describe BranchableCDNAssets::Config do
11
11
  production: {
12
12
  host: 'production',
13
13
  url: 'http://production.com',
14
- root: '/var/www/production'
14
+ root: '/var/www/production',
15
+ invalidator: {
16
+ cloudfront: {
17
+ access_key: 'FooBar',
18
+ secret_key: 'SecretBaz',
19
+ distribution_id: 'Distro',
20
+ path_prefix: '/'
21
+ }
22
+ }
15
23
  },
16
24
  test: {
17
25
  host: 'test',
@@ -28,12 +36,6 @@ describe BranchableCDNAssets::Config do
28
36
  url: 'http://default.com',
29
37
  root: '/var/www/default'
30
38
  }
31
- },
32
- cloudfront: {
33
- access_key: 'FooBar',
34
- secret_key: 'SecretBaz',
35
- distribution: 'Distro',
36
- path_prefix: '/'
37
39
  }
38
40
  }
39
41
  end
@@ -98,9 +100,26 @@ describe BranchableCDNAssets::Config do
98
100
  end
99
101
  end
100
102
 
101
- describe "#cloudfront" do
102
- it "is set to the passed value" do
103
- expect( described_class.new( data ).cloudfront ).to eq data[:cloudfront]
103
+ describe "#invalidator" do
104
+
105
+ it "returns instance of given invalidator class" do
106
+ expect( described_class.new( data, 'production' ).invalidator ).to be_a BranchableCDNAssets::Invalidator::Cloudfront
107
+ end
108
+
109
+ it "returns false if no invalidator given" do
110
+ subject_data = data.dup
111
+ subject_data[:environments][:production].delete(:invalidator)
112
+
113
+ expect( described_class.new( subject_data, 'production' ).invalidator ).to be false
114
+ end
115
+
116
+ it "raises error if given an unregistered invalidator" do
117
+ subject_data = data.dup
118
+ subject_data[:environments][:production][:invalidator] = { foo: 'bar' }
119
+
120
+ expect{
121
+ described_class.new(subject_data, 'production').invalidator
122
+ }.to raise_error
104
123
  end
105
124
  end
106
125
 
@@ -114,6 +133,27 @@ describe BranchableCDNAssets::Config do
114
133
  end
115
134
  end
116
135
 
136
+ describe "#file_filter" do
137
+ it "returns nil if not set" do
138
+ expect( described_class.new( data ).file_filter ).to eq nil
139
+ end
140
+
141
+ it "raises an argument error if not a proc with one argument" do
142
+ expect{
143
+ described_class.new( data.merge(file_filter: "foo") )
144
+ }.to raise_error ArgumentError
145
+
146
+ expect{
147
+ described_class.new( data.merge(file_filter: lambda { |x,y| } ) )
148
+ }.to raise_error ArgumentError
149
+ end
150
+
151
+ it "returns the given proc" do
152
+ filter_proc = lambda { |file_path| file_path }
153
+ expect( described_class.new( data.merge(file_filter: filter_proc) ).file_filter ).to eq filter_proc
154
+ end
155
+ end
156
+
117
157
  describe "#env" do
118
158
  context "when the current branch matches production_branch" do
119
159
  before :each do
@@ -159,4 +199,4 @@ describe BranchableCDNAssets::Config do
159
199
  end
160
200
  end
161
201
 
162
- end
202
+ end
@@ -30,6 +30,22 @@ describe BranchableCDNAssets::FileManager do
30
30
  it "returns nil if asset is missing" do
31
31
  expect( @manager.find('missing') ).to be_nil
32
32
  end
33
+
34
+ it "returns :local if told to look local and file exists" do
35
+ expect( @manager.find('image_one', :local) ).to eq :local
36
+ end
37
+
38
+ it "returns nil if told to look local and file only on remote" do
39
+ expect( @manager.find('image_remote_one', :local) ).to be_nil
40
+ end
41
+
42
+ it "returns remote file if told to look remote and file exists on both" do
43
+ expect( @manager.find('image_one', :remote) ).to eq 'http://production.com/image_one'
44
+ end
45
+
46
+ it "returns nil if told to look remote and file only local" do
47
+ expect( @manager.find('image_two', :remote) ).to be_nil
48
+ end
33
49
  end
34
50
 
35
51
  context "when on staging branch" do
@@ -54,8 +70,28 @@ describe BranchableCDNAssets::FileManager do
54
70
  it "returns nil if asset is missing" do
55
71
  expect( @manager.find('missing') ).to be_nil
56
72
  end
73
+
74
+ it "returns :local if told to look local and file exists" do
75
+ expect( @manager.find('image_one', :local) ).to eq :local
76
+ end
77
+
78
+ it "returns nil if told to look local and file only on remote" do
79
+ expect( @manager.find('image_remote_one', :local) ).to be_nil
80
+ end
81
+
82
+ it "returns remote file if told to look remote and file exists on both" do
83
+ expect( @manager.find('image_one', :remote) ).to eq 'http://staging.com/staging/image_one'
84
+ end
85
+
86
+ it "returns production file if told to look remote and file exists only on prod" do
87
+ expect( @manager.find('image_remote_one', :remote) ).to eq 'http://production.com/image_remote_one'
88
+ end
89
+
90
+ it "returns nil if told to look remote and file only local" do
91
+ expect( @manager.find('image_two', :remote) ).to be_nil
92
+ end
57
93
  end
58
94
 
59
95
  end
60
96
 
61
- end
97
+ end
@@ -5,22 +5,35 @@ describe BranchableCDNAssets::FileManager do
5
5
  before :each do
6
6
  @config_data = {
7
7
  env: 'default',
8
- branch: 'branch',
9
8
  cdn_dir: 'cdn',
10
- host: 'host',
11
- root: 'root',
12
- manage_cloudfront: true,
13
9
  dir_permissions: 755,
14
10
  file_permissions: 644,
15
11
  rsync_flags: '-aviz',
16
12
  remotes: [
17
13
  BranchableCDNAssets::Config::Remote.new( 'host', 'root', 'url', 755, 644, '-aviz' )
18
14
  ],
19
- cloudfront: {
20
- path_prefix: '/prefix'
15
+ environments: {
16
+ staging: {
17
+ host: 'host',
18
+ root: 'root',
19
+ url: 'http://foo.com'
20
+ },
21
+ production: {
22
+ host: 'host',
23
+ root: 'root',
24
+ url: 'http://foo.com',
25
+ invalidator: {
26
+ cloudfront: {
27
+ distribution_id: '',
28
+ access_key: '',
29
+ secret_key: '',
30
+ path_prefix: '/prefix'
31
+ }
32
+ }
33
+ }
21
34
  }
22
35
  }
23
- @config = instance_double( "BranchableCDNAssets::Config", @config_data )
36
+ @config = BranchableCDNAssets::Config.new @config_data, 'master'
24
37
  allow( BranchableCDNAssets::Config ).to receive(:new).and_return( @config )
25
38
 
26
39
  @file_list = [
@@ -35,8 +48,9 @@ describe BranchableCDNAssets::FileManager do
35
48
  @manifest = instance_double("BranchableCDNAssets::Manifest", files: @file_list )
36
49
  allow( BranchableCDNAssets::Manifest ).to receive(:new).and_return( @manifest )
37
50
 
38
- @cloudfront = instance_double("BranchableCDNAssets::Cloudfront")
39
- allow( BranchableCDNAssets::Cloudfront ).to receive(:new).and_return( @cloudfront )
51
+ @cloudfront = BranchableCDNAssets::Invalidator::Cloudfront.new(@config_data[:environments][:production][:invalidator][:cloudfront])
52
+ allow( BranchableCDNAssets::Invalidator::Cloudfront ).to receive(:new).and_return( @cloudfront )
53
+ allow( @cloudfront ).to receive(:invalidate_files)
40
54
 
41
55
  @tempfile = instance_double( "Tempfile",
42
56
  unlink: nil,
@@ -89,8 +103,10 @@ describe BranchableCDNAssets::FileManager do
89
103
  cdn_dir: 'cdn',
90
104
  host: 'host',
91
105
  root: 'root',
92
- cloudfront: {
93
- path_prefix: '/prefix'
106
+ invalidator: {
107
+ cloudfront: {
108
+ path_prefix: '/prefix'
109
+ }
94
110
  }
95
111
  }
96
112
  @config = instance_double( "BranchableCDNAssets::Config", @config_data )
@@ -127,6 +143,16 @@ describe BranchableCDNAssets::FileManager do
127
143
  expect( described_class.new(@config).list(:local) ).to match_array local_files
128
144
  end
129
145
 
146
+ it "uses the given file_filter to filter files when :local" do
147
+ file_filter = lambda { |file_path| return !file_path.match('directory') }
148
+ @config = instance_double( "BranchableCDNAssets::Config",
149
+ @config_data.merge( file_filter: file_filter,
150
+ branch: 'master' ) )
151
+ allow( BranchableCDNAssets::Config ).to receive(:new).and_return( @config )
152
+
153
+ expect( described_class.new( @config ).list(:local) ).to match_array local_files - ['directory/dir_file']
154
+ end
155
+
130
156
  it "returns both local and remote files when :all" do
131
157
  expect( described_class.new(@config).list() ).to match_array remote_files + local_files
132
158
  end
@@ -0,0 +1,91 @@
1
+ require 'spec_helper'
2
+
3
+ describe BranchableCDNAssets::Invalidator::Base do
4
+
5
+ after :each do
6
+ Object.send(:remove_const, :Foo) if defined? Foo
7
+ end
8
+
9
+ describe "::register_as" do
10
+ it "calls register with given key on BranchableCDNAssets::Invalidator" do
11
+ class Foo < BranchableCDNAssets::Invalidator::Base; end
12
+ expect( BranchableCDNAssets::Invalidator ).to receive(:register)
13
+ .with(:foo, Foo)
14
+
15
+ class Foo
16
+ register_as :foo
17
+ end
18
+ end
19
+ end
20
+
21
+ describe "::require_keys" do
22
+ it "sets class.required_keys" do
23
+ class Foo < BranchableCDNAssets::Invalidator::Base
24
+ require_keys [:foo, :bar]
25
+ end
26
+
27
+ expect( Foo.required_keys ).to match_array [:foo, :bar]
28
+ end
29
+ end
30
+
31
+ describe "::set_defaults" do
32
+ it "sets class.defaults" do
33
+ class Foo < BranchableCDNAssets::Invalidator::Base
34
+ set_defaults foo: 'bar'
35
+ end
36
+
37
+ expect( Foo.defaults ).to eq foo: 'bar'
38
+ end
39
+ end
40
+
41
+ describe "#initialize" do
42
+ it "takes a hash and converts to config struct" do
43
+ config = {
44
+ foo: 'bar',
45
+ wu: 'tang'
46
+ }
47
+
48
+ subject = described_class.new(config)
49
+
50
+ expect( subject.config.foo ).to eq 'bar'
51
+ expect( subject.config.wu ).to eq 'tang'
52
+ end
53
+
54
+ it "uses defaults for key if key not given" do
55
+ class Foo < BranchableCDNAssets::Invalidator::Base
56
+ set_defaults foo: 'bar'
57
+ end
58
+
59
+ subject = Foo.new baz: 'bang'
60
+ expect( subject.config.foo ).to eq 'bar'
61
+ end
62
+
63
+ it "given value even if default given" do
64
+ class Foo < BranchableCDNAssets::Invalidator::Base
65
+ set_defaults foo: 'bar'
66
+ end
67
+
68
+ subject = Foo.new foo: 'bang'
69
+ expect( subject.config.foo ).to eq 'bang'
70
+ end
71
+
72
+ it "raises ArgumentError if missing required keys" do
73
+ class Foo < BranchableCDNAssets::Invalidator::Base
74
+ require_keys [:boom]
75
+ end
76
+
77
+ expect{
78
+ Foo.new foo: 'bar'
79
+ }.to raise_error ArgumentError
80
+ end
81
+ end
82
+
83
+ describe "#invalidate_files" do
84
+ it "raises a NoMethodError, it's not implemented" do
85
+ expect{
86
+ described_class.new.invalidate_files('foo')
87
+ }.to raise_error NoMethodError
88
+ end
89
+ end
90
+
91
+ end
@@ -1,8 +1,8 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe BranchableCDNAssets::Cloudfront do
3
+ describe BranchableCDNAssets::Invalidator::Cloudfront do
4
4
 
5
- let(:keys) do
5
+ let(:config) do
6
6
  {
7
7
  distribution_id: 'dist_id',
8
8
  access_key: 'access_key',
@@ -13,19 +13,22 @@ describe BranchableCDNAssets::Cloudfront do
13
13
  describe "#initialize" do
14
14
 
15
15
  before :each do
16
- @subject = described_class.new(keys)
16
+ @subject = described_class.new(config)
17
17
  end
18
18
 
19
19
  it "sets #distribution_id" do
20
- expect( @subject.distribution_id ).to eq 'dist_id'
20
+ expect( @subject.config.distribution_id ).to eq 'dist_id'
21
21
  end
22
22
  it "sets #access_key" do
23
- expect( @subject.access_key ).to eq 'access_key'
23
+ expect( @subject.config.access_key ).to eq 'access_key'
24
24
  end
25
25
  it "sets #secret_key" do
26
- expect( @subject.secret_key ).to eq 'secret_key'
26
+ expect( @subject.config.secret_key ).to eq 'secret_key'
27
27
  end
28
28
 
29
+ end
30
+
31
+ describe "#cdn" do
29
32
  it "sets #cdn with a Fog instance" do
30
33
  fog = double("Fog::CDN")
31
34
  expect( Fog::CDN ).to receive(:new).with(
@@ -33,9 +36,8 @@ describe BranchableCDNAssets::Cloudfront do
33
36
  aws_access_key_id: 'access_key',
34
37
  aws_secret_access_key: 'secret_key'
35
38
  ).and_return(fog)
36
- expect( described_class.new(keys).cdn ).to eq fog
39
+ expect( described_class.new(config).cdn ).to eq fog
37
40
  end
38
-
39
41
  end
40
42
 
41
43
  describe "#invalidate_files" do
@@ -51,7 +53,7 @@ describe BranchableCDNAssets::Cloudfront do
51
53
  aws_access_key_id: 'access_key',
52
54
  aws_secret_access_key: 'secret_key'
53
55
  ).and_return(@fog)
54
- @subject = described_class.new(keys)
56
+ @subject = described_class.new(config)
55
57
  end
56
58
 
57
59
  it "calls cloudfront to invalidate given files" do
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+
3
+ describe BranchableCDNAssets::Invalidator do
4
+
5
+ def clean_invalidators
6
+ described_class.instance_variable_set :@_invalidators, nil
7
+ end
8
+
9
+ def clean_foo_class
10
+ Object.send(:remove_const, :Foo) if defined? Foo
11
+ end
12
+
13
+ before :each do
14
+ clean_invalidators
15
+ end
16
+
17
+ after :each do
18
+ clean_foo_class
19
+ end
20
+
21
+ describe "::invalidators" do
22
+ it "returns empty hash if no invalidators are registered" do
23
+ expect( described_class.invalidators ).to eq({})
24
+ end
25
+ it "returns hash of registered invalidators" do
26
+ class Foo < BranchableCDNAssets::Invalidator::Base
27
+ register_as :foo
28
+ end
29
+ expect( described_class.invalidators ).to eq({foo: Foo})
30
+ end
31
+ end
32
+
33
+ describe "::register" do
34
+ it "adds invalidator to invalidators hash" do
35
+ class Foo; end
36
+ described_class.register :foo, Foo
37
+ expect( described_class.invalidators ).to eq({foo: Foo})
38
+ end
39
+ end
40
+
41
+ describe "::find" do
42
+ it "returns invalidator class if one is registered" do
43
+ class Foo; end
44
+ described_class.register :foo, Foo
45
+
46
+ expect( described_class.find(:foo) ).to eq Foo
47
+ end
48
+ it "returns false if invalidator isn't registered" do
49
+ expect( described_class.find(:bar) ).to eq false
50
+ end
51
+ end
52
+
53
+ end
@@ -108,6 +108,7 @@ describe BranchableCDNAssets::Manifest do
108
108
  manifest = BranchableCDNAssets::Manifest.new('foo')
109
109
 
110
110
  expect( File ).not_to receive(:open)
111
+ allow( File ).to receive(:exist?).with('foo').and_return(true)
111
112
  expect( File ).to receive(:delete).with('foo')
112
113
 
113
114
  manifest.update_source_file!
@@ -135,4 +136,4 @@ describe BranchableCDNAssets::Manifest do
135
136
  end
136
137
 
137
138
 
138
- end
139
+ end
metadata CHANGED
@@ -1,86 +1,86 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: branchable_cdn_assets
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steven Sloan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-05-27 00:00:00.000000000 Z
11
+ date: 2015-11-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: '10.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ~>
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '10.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: colorize
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ~>
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0.6'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ~>
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0.6'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: asgit
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ~>
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0.1'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ~>
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0.1'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: here_or_there
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ~>
59
+ - - "~>"
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0.1'
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ~>
66
+ - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0.1'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: fog
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - ~>
73
+ - - "~>"
74
74
  - !ruby/object:Gem::Version
75
75
  version: '1.18'
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - ~>
80
+ - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '1.18'
83
- description: ' Helpers for syncing and finding assets accross multiple remotes '
83
+ description: " Helpers for syncing and finding assets accross multiple remotes "
84
84
  email: marketing-dev@mailchimp.com
85
85
  executables: []
86
86
  extensions: []
@@ -88,12 +88,16 @@ extra_rdoc_files: []
88
88
  files:
89
89
  - lib/branchable_cdn_assets.rb
90
90
  - lib/branchable_cdn_assets/check_before.rb
91
- - lib/branchable_cdn_assets/cloudfront.rb
92
91
  - lib/branchable_cdn_assets/config.rb
93
92
  - lib/branchable_cdn_assets/config/environment_attribute_reader.rb
94
93
  - lib/branchable_cdn_assets/file_manager.rb
95
94
  - lib/branchable_cdn_assets/file_manager/checks.rb
96
95
  - lib/branchable_cdn_assets/file_manager/permissions.rb
96
+ - lib/branchable_cdn_assets/invalidator.rb
97
+ - lib/branchable_cdn_assets/invalidator/akamai_ccu.rb
98
+ - lib/branchable_cdn_assets/invalidator/akamai_ccu/http.rb
99
+ - lib/branchable_cdn_assets/invalidator/base.rb
100
+ - lib/branchable_cdn_assets/invalidator/cloudfront.rb
97
101
  - lib/branchable_cdn_assets/manifest.rb
98
102
  - lib/branchable_cdn_assets/rake_tasks.rb
99
103
  - lib/branchable_cdn_assets/shell.rb
@@ -103,10 +107,12 @@ files:
103
107
  - spec/lib/branchable_cdn_assets/config_spec.rb
104
108
  - spec/lib/branchable_cdn_assets/file_manager/find_spec.rb
105
109
  - spec/lib/branchable_cdn_assets/file_manager_spec.rb
110
+ - spec/lib/branchable_cdn_assets/invalidator/base_spec.rb
111
+ - spec/lib/branchable_cdn_assets/invalidator/cloudfront_spec.rb
112
+ - spec/lib/branchable_cdn_assets/invalidator_spec.rb
106
113
  - spec/lib/branchable_cdn_assets/manifest_spec.rb
107
114
  - spec/lib/branchable_cdn_assets/rake_tasks_spec.rb
108
115
  - spec/lib/branchable_cdn_assets_spec.rb
109
- - spec/lib/cloudfront_spec.rb
110
116
  - spec/spec_helper.rb
111
117
  - spec/support/given.rb
112
118
  - spec/support/hash.rb
@@ -120,17 +126,17 @@ require_paths:
120
126
  - lib
121
127
  required_ruby_version: !ruby/object:Gem::Requirement
122
128
  requirements:
123
- - - '>='
129
+ - - ">="
124
130
  - !ruby/object:Gem::Version
125
131
  version: 1.9.3
126
132
  required_rubygems_version: !ruby/object:Gem::Requirement
127
133
  requirements:
128
- - - '>='
134
+ - - ">="
129
135
  - !ruby/object:Gem::Version
130
136
  version: '0'
131
137
  requirements: []
132
138
  rubyforge_project:
133
- rubygems_version: 2.4.5
139
+ rubygems_version: 2.4.5.1
134
140
  signing_key:
135
141
  specification_version: 4
136
142
  summary: Helpers for syncing and finding assets accross multiple remotes
@@ -140,10 +146,12 @@ test_files:
140
146
  - spec/lib/branchable_cdn_assets/config_spec.rb
141
147
  - spec/lib/branchable_cdn_assets/file_manager/find_spec.rb
142
148
  - spec/lib/branchable_cdn_assets/file_manager_spec.rb
149
+ - spec/lib/branchable_cdn_assets/invalidator/base_spec.rb
150
+ - spec/lib/branchable_cdn_assets/invalidator/cloudfront_spec.rb
151
+ - spec/lib/branchable_cdn_assets/invalidator_spec.rb
143
152
  - spec/lib/branchable_cdn_assets/manifest_spec.rb
144
153
  - spec/lib/branchable_cdn_assets/rake_tasks_spec.rb
145
154
  - spec/lib/branchable_cdn_assets_spec.rb
146
- - spec/lib/cloudfront_spec.rb
147
155
  - spec/spec_helper.rb
148
156
  - spec/support/given.rb
149
157
  - spec/support/hash.rb
@@ -1,28 +0,0 @@
1
- module BranchableCDNAssets
2
- class Cloudfront
3
-
4
- attr_reader :access_key, :secret_key, :distribution_id,
5
- :cdn
6
-
7
- # handles communication with cloudfront
8
- def initialize keys
9
- @distribution_id = keys.fetch :distribution_id
10
- @access_key = keys.fetch :access_key
11
- @secret_key = keys.fetch :secret_key
12
-
13
- @cdn = ::Fog::CDN.new provider: 'AWS',
14
- aws_access_key_id: access_key,
15
- aws_secret_access_key: secret_key
16
- end
17
-
18
- # invalidate a batch of files on fog
19
- # @param files [Array]
20
- def invalidate_files files
21
- resp = cdn.post_invalidation( distribution_id, Array(files) )
22
- resp.body["InvalidationBatch"]["Path"].each do |file|
23
- puts "Posted an invalidation for #{file}".colorize( :green )
24
- end
25
- end
26
-
27
- end
28
- end