branchable_cdn_assets 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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