branchable_cdn_assets 0.5.3 → 0.6.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: ab78d952715ccd097239e67bccdd4e0e1a6c7635
4
- data.tar.gz: f7a994bd5aee7df2ada620b1641e769946abfb8e
3
+ metadata.gz: 5d4b5ae8228cc5c14fb3ed1ed06abf2efd1056b4
4
+ data.tar.gz: 1a3676d5d335a8a329d2196a98ea518bbeb79348
5
5
  SHA512:
6
- metadata.gz: 1f80a5a192ced43ab482fa31e870e7baf429e6e5f33a6e71be4419c0dc4b5de7bcb26105d6a5b47c04056b1324b4ca08ad7ddef6204868ef6ebef39283d30c68
7
- data.tar.gz: 8765796e5a26a58cd1519e0a707889d26d9ea26dea1448cfc53f46e1cc7e394f089a6e106487fd9210da50a3af9d0447f7e698dcfabcb83e7bf5eac9b5ff1a3d
6
+ metadata.gz: 9dca1c4d503f5f9f3c442118cdf17685f02e74d1eedfb7000fff59d107624240edd3fed79545b300a865c4f64a687bbfbf9d93e46e8ae78450ee5d361bdfd050
7
+ data.tar.gz: 7c94bfdadfc58cc6d8b3c9648c2dce11dec0aaa1eb28d0a68afc19d219b3af5aade65dc9a2ace355c504fe03cb38a4b9a9e0faf8cfe9d361ec06249a3c5a4a99
@@ -2,12 +2,13 @@ require_relative 'config/environment_attribute_reader'
2
2
 
3
3
  module BranchableCDNAssets
4
4
  class Config
5
- extend EnvironmentAttributeReader
5
+ include EnvironmentAttributeReader
6
+ extend EnvironmentAttributeReader::ClassMethods
6
7
 
7
8
  attr_reader :raw_data, :branch,
8
9
  :production_branch, :cloudfront, :cdn_dir,
9
- :env, :environments
10
- env_attr_reader :host, :root, :url
10
+ :dir_permissions, :file_permissions, :rsync_flags
11
+ env_attr_reader :host, :root, :url, :manage_cloudfront
11
12
 
12
13
  def initialize data, branch=Asgit.current_branch
13
14
  @raw_data = normalize_data data
@@ -16,13 +17,48 @@ module BranchableCDNAssets
16
17
  @production_branch = raw_data.fetch :production_branch, 'master'
17
18
  @cloudfront = raw_data.fetch :cloudfront, {}
18
19
  @cdn_dir = raw_data.fetch :dir, 'cdn'
20
+ @dir_permissions = env_attr(:dir_permissions) || 755
21
+ @file_permissions = env_attr(:file_permissions) || 644
22
+ @rsync_flags = env_attr(:rsync_flags) || '-aviz'
23
+ end
24
+
25
+ def env
26
+ @env ||= env_for_branch
27
+ end
28
+
29
+ def environments
30
+ @environments ||= add_env_path_modifications_to_env_data raw_data.fetch(:environments, {})
31
+ end
32
+
33
+ def remotes
34
+ return @_remotes if @_remotes
19
35
 
20
- @env = env_for_branch
21
- @environments = add_env_path_modifications_to_env_data raw_data.fetch(:environments, {})
36
+ @_remotes = environments[env.to_sym].fetch(:remotes, [default_remote]).map do |r|
37
+ remote = Remote.new
38
+ default_remote.merge( normalize_data(r) ).each do |k, v|
39
+ remote.public_send "#{k}=", v
40
+ end
41
+ remote
42
+ end
22
43
  end
23
44
 
24
45
  private
25
46
 
47
+ class Remote < Struct.new(:host, :root, :url,
48
+ :dir_permissions, :file_permissions, :rsync_flags)
49
+ end
50
+
51
+ def default_remote
52
+ {
53
+ host: host,
54
+ root: root,
55
+ url: url,
56
+ dir_permissions: dir_permissions,
57
+ file_permissions: file_permissions,
58
+ rsync_flags: rsync_flags
59
+ }
60
+ end
61
+
26
62
  def add_env_path_modifications_to_env_data environments
27
63
  Hash[*environments.map do |env,data|
28
64
  data = data.dup
@@ -31,14 +67,14 @@ module BranchableCDNAssets
31
67
  data[:url] = File.join( data[:url], branch )
32
68
  end
33
69
 
34
- [env,data]
70
+ [env, data]
35
71
  end.flatten]
36
72
  end
37
73
 
38
74
  def env_for_branch
39
75
  if branch == production_branch
40
76
  return :production
41
- elsif raw_data.fetch(:environments).has_key?( branch.to_sym )
77
+ elsif raw_data.fetch(:environments, {}).has_key?( branch.to_sym )
42
78
  return branch.to_sym
43
79
  else
44
80
  return raw_data.fetch(:default_env, 'staging').to_sym
@@ -2,12 +2,18 @@ module BranchableCDNAssets
2
2
  class Config
3
3
 
4
4
  module EnvironmentAttributeReader
5
- def env_attr_reader *keys
6
- keys.each do |key|
7
- define_method key do
8
- environments[env.to_sym].fetch(key) {
9
- raise ArgumentError, "No key '#{key.to_s}' exists for '#{env}'"
10
- }
5
+
6
+ def env_attr key
7
+ environments.fetch(env.to_sym, {})
8
+ .fetch(key, nil)
9
+ end
10
+
11
+ module ClassMethods
12
+ def env_attr_reader *keys
13
+ keys.each do |key|
14
+ define_method key do
15
+ env_attr(key) || raise( ArgumentError, "No key '#{key.to_s}' exists for '#{env}'" )
16
+ end
11
17
  end
12
18
  end
13
19
  end
@@ -1,4 +1,5 @@
1
1
  require_relative 'file_manager/checks'
2
+ require_relative 'file_manager/permissions'
2
3
 
3
4
  module BranchableCDNAssets
4
5
  class FileManager
@@ -26,35 +27,44 @@ module BranchableCDNAssets
26
27
  # @return [Array]
27
28
  def list where=:all
28
29
  case where
29
- when :local then
30
- return list_local_files
31
- when :remote then
32
- return list_remote_files
33
- when :both then
34
- return list_local_files & list_remote_files
35
- else
36
- return list_local_files + list_remote_files
30
+ when :local then return list_local_files
31
+ when :remote then return list_remote_files
32
+ when :both then return list_local_files & list_remote_files
33
+ else return list_local_files + list_remote_files
37
34
  end
38
35
  end
39
36
 
37
+
38
+ # make cdn dir on the remotes with given permissions
39
+ def setup
40
+ setup_remotes
41
+
42
+ return nil
43
+ end
44
+
45
+
40
46
  # destructive pull, removes references to remote
41
47
  # files once they're pulled
42
48
  # @return [Array] the pulled files
43
49
  def pull!
44
- pull_list = pull
50
+ pull_list = pull_all
45
51
  @manifest.remove_files( pull_list )
46
52
  @manifest.update_source_file!
53
+
47
54
  return pull_list
48
55
  end
49
56
 
57
+
50
58
  # destructive push, removes the local versions of
51
59
  # the pushed files and adds them to the manifest
52
60
  # @return [Array] the pushed files
53
61
  def push!
54
- setup_remote
62
+ setup_remotes
55
63
 
56
64
  push_list = push
57
- invalidate( push_list ) if config.env.to_sym == :production
65
+ if config.env.to_sym == :production && config.manage_cloudfront
66
+ invalidate( push_list )
67
+ end
58
68
 
59
69
  @manifest.merge_files( push_list )
60
70
  @manifest.update_source_file!
@@ -62,6 +72,7 @@ module BranchableCDNAssets
62
72
  return push_list
63
73
  end
64
74
 
75
+
65
76
  # remove local files that exist on the remote
66
77
  # @return [Array]
67
78
  def prune!
@@ -71,9 +82,12 @@ module BranchableCDNAssets
71
82
  list(:both).each do |f|
72
83
  Shell.run_local "rm #{File.join( root, f )}"
73
84
  end
85
+
74
86
  remove_empty_directories
87
+ return nil
75
88
  end
76
89
 
90
+
77
91
  # move the current cdn files to the production cdn
78
92
  # guarded to only run from prod
79
93
  def move_to_production branch
@@ -86,25 +100,127 @@ module BranchableCDNAssets
86
100
  push!
87
101
  end
88
102
 
103
+
89
104
  def remove_empty_directories
90
105
  empty_directories.each do |dir|
91
106
  Dir.rmdir( dir )
92
107
  end
93
108
  end
94
109
 
110
+
95
111
  def find file
96
- return :local if list(:local).include?(file)
97
- return File.join( config.url, file ) if list(:remote).include?(file)
112
+ return :local if list(:local).include?(file)
113
+ return File.join(config.url, file) if list(:remote).include?(file)
98
114
 
99
115
  unless config.env == :production
100
116
  production_config = Config.new( config.raw_data, config.production_branch )
101
117
  production_manifest = Manifest.new( File.join( root, "#{config.production_branch}.manifest" ) )
118
+
102
119
  return File.join( production_config.url, file ) if production_manifest.files.include?(file)
103
120
  end
104
121
  end
105
122
 
123
+
124
+ # x files were not on every remote
125
+ # remote-x (x files):
126
+ # - file/path
127
+ # - file/path
128
+ #
129
+ # remote-y (x files):
130
+ # - path/to/file
131
+ # - path
132
+ def doctor
133
+ db = uniq_files_per_remote
134
+ count = config.remotes.flat_map { |r| db[r.host].length }.inject(0, :+)
135
+
136
+ if count > 0
137
+ puts "#{count} files were not on every remote"
138
+ config.remotes.each do |remote|
139
+ puts "#{remote.host} (#{db[remote.host].length} files)"
140
+ if db[remote.host].length > 0
141
+ db[remote.host].each { |f| puts " + #{f}" }
142
+ end
143
+ end
144
+ else
145
+ puts "all good, content is the same on all remotes"
146
+ end
147
+
148
+ return nil
149
+ end
150
+
151
+ def heal
152
+ db = uniq_files_per_remote
153
+ all_uniq = []
154
+ config.remotes.each do |remote|
155
+ all_uniq.push *db[remote.host]
156
+ list_file = create_list_file db[remote.host].join("\n")
157
+ pull_list = map_rsync_output_to_list rsync_files_from_remote(list_file, remote)
158
+ list_file.unlink
159
+
160
+ puts "#{pull_list.length} files pulled from #{remote.host}"
161
+ end
162
+
163
+ config.remotes.each do |remote|
164
+ list_file = create_list_file ( all_uniq - db[remote.host] ).join("\n")
165
+ push_list = map_rsync_output_to_list rsync_files_to_remote( list_file, remote )
166
+ list_file.unlink
167
+
168
+ puts "#{push_list.length} files pushed to #{remote.host}"
169
+ end
170
+
171
+ nil
172
+ end
173
+
106
174
  private
107
175
 
176
+ def pull_all
177
+ db = uniq_files_per_remote
178
+ pulled = []
179
+ config.remotes.each_with_index do |remote, idx|
180
+ if idx == 0
181
+ file_list = db[remote.host] + db["all"]
182
+ else
183
+ file_list = db[remote.host]
184
+ end
185
+
186
+ list_file = create_list_file file_list.join("\n")
187
+ pull_list = map_rsync_output_to_list rsync_files_from_remote( list_file, remote )
188
+ list_file.unlink
189
+
190
+ pulled.push *pull_list
191
+ puts "#{pull_list.length} files pulled from #{remote.host}"
192
+ end
193
+
194
+ return pulled
195
+ end
196
+
197
+ def files_per_remote
198
+ config.remotes.each_with_object({}) do |remote, collection|
199
+ resp = Shell.run_remote "find #{remote.root} -type f", hostname: remote.host
200
+ if resp.success?
201
+ collection[remote.host] = resp.stdout.split("\n")
202
+ .map { |f| f.sub /^#{Regexp.escape(remote.root)}\/?/, '' }
203
+ else
204
+ warn "problem pulling file list for #{remote.host}\n#{resp.stderr}"
205
+ end
206
+ end
207
+ end
208
+
209
+ def uniq_files_per_remote
210
+ files_by_remote = files_per_remote
211
+ files_on_all = []
212
+ files_by_remote.each_with_index do |(remote, files), idx|
213
+ if idx == 0
214
+ files_on_all = files
215
+ else
216
+ files_on_all = files_on_all & files
217
+ end
218
+ end
219
+
220
+ Hash[ files_by_remote.map { |r, files| [r, (files - files_on_all)] }
221
+ .push ["all", files_on_all] ]
222
+ end
223
+
108
224
  def invalidate files
109
225
  to_invalidate = ( files & list(:local) ).map do |f|
110
226
  File.join( config.cloudfront[:path_prefix], f )
@@ -115,9 +231,15 @@ module BranchableCDNAssets
115
231
  end
116
232
 
117
233
  # create the root dir on remote
118
- def setup_remote
119
- resp = Shell.run_remote "mkdir -p #{config.root}", hostname: config.host
120
- raise resp.stderr unless resp.success?
234
+ def setup_remotes
235
+ config.remotes.each do |remote|
236
+ resp = Shell.run_remote "mkdir -p -m #{remote.dir_permissions} #{remote.root}", hostname: remote.host
237
+ unless resp.success?
238
+ raise "problem setting up #{remote.host}\n#{resp.stderr}"
239
+ else
240
+ puts "set up #{remote.root} on #{remote.host}"
241
+ end
242
+ end
121
243
  end
122
244
 
123
245
  def empty_directories
@@ -140,20 +262,30 @@ module BranchableCDNAssets
140
262
  def push
141
263
  list_file = create_list_file( list(:local).join("\n") )
142
264
  ensure_local_file_permissions
143
- push_list = map_rsync_output_to_list rsync_files_to_remote(list_file)
144
- list_file.unlink
145
265
 
266
+ push_list = config.remotes.flat_map do |remote|
267
+ map_rsync_output_to_list( rsync_files_to_remote(list_file, remote) )
268
+ end.uniq
269
+
270
+ list_file.unlink
146
271
  push_list = resolve_conflicts(push_list)
147
272
  puts "#{push_list.length} files pushed to #{branch}"
148
273
  return push_list
149
274
  end
150
275
 
151
- def rsync_files_to_remote list_file
152
- resp = Shell.run_local "rsync -aviz --files-from=#{list_file.path} " +
153
- "-e ssh #{root}/ #{config.host}:#{config.root}"
276
+ def rsync_files_to_remote list_file, remote
277
+ dir_perms = Permissions.new( remote.dir_permissions ).to_chmod
278
+ file_perms = Permissions.new( remote.file_permissions ).to_chmod
279
+ file_permissions = "--chmod=" +
280
+ "Du=#{dir_perms.user},Dg=#{dir_perms.group},Do=#{dir_perms.others}," +
281
+ "Fu=#{file_perms.user},Fg=#{file_perms.group},Fo=#{file_perms.others}"
154
282
 
155
- raise "rsync failed\n#{resp.stderr}" unless resp.success?
156
- return resp.stdout
283
+
284
+ resp = Shell.run_local "rsync #{remote.rsync_flags} #{file_permissions} --files-from=#{list_file.path} " +
285
+ "-e ssh #{root}/ #{remote.host}:#{remote.root}"
286
+
287
+ raise "rsync failed for #{remote.host}\n#{resp.stderr}" unless resp.success?
288
+ resp.stdout
157
289
  end
158
290
 
159
291
  def ensure_local_file_permissions
@@ -174,9 +306,9 @@ module BranchableCDNAssets
174
306
  return pull_list
175
307
  end
176
308
 
177
- def rsync_files_from_remote list_file
178
- resp = Shell.run_local "rsync -aviz --files-from=#{list_file.path}" +
179
- " -e ssh #{config.host}:#{config.root} #{root}/"
309
+ def rsync_files_from_remote list_file, remote
310
+ resp = Shell.run_local "rsync #{config.rsync_flags} --files-from=#{list_file.path}" +
311
+ " -e ssh #{remote.host}:#{remote.root} #{root}/"
180
312
 
181
313
  raise "rsync failed\n#{resp.stderr}" unless resp.success?
182
314
  return resp.stdout
@@ -5,17 +5,21 @@ module BranchableCDNAssets
5
5
  def manifest_list_clean_for_master
6
6
  manifests = Dir[ File.join( root, "*.manifest" ) ]
7
7
  if Asgit.current_branch == "master" && manifests.length > 1
8
- raise "there should only be a production manifest on the master branch\n" +
9
- "consider cleaning up with rake cdn:move_to_production[branch_name]"
8
+ raise "the master branch should only have a production manifest\n" +
9
+ "move branched assets to production with the `move_to_production` task"
10
10
  end
11
11
  end
12
12
 
13
13
  def local_file_conflict
14
14
  intersection = list(:both)
15
15
  if !intersection.empty?
16
- puts "#{intersection.length} files are duplicated locally and on the remote"
17
- unless Shell.get_input("do you want to continue? (y|n)") == "y"
16
+ puts "#{intersection.length} files detected locally and in the manifest"
17
+
18
+ if Shell.get_input("would you like to list the conflicting files? (y|n) ") == "y"
18
19
  puts "the conflicting files: \n#{intersection.join("\n")}"
20
+ end
21
+
22
+ unless Shell.get_input("do you want to continue? (y|n) ") == "y"
19
23
  abort
20
24
  end
21
25
  end
@@ -23,15 +27,18 @@ module BranchableCDNAssets
23
27
 
24
28
  def ready_for_production
25
29
  if Asgit.current_branch != "master"
26
- puts "you shouldn't move to production except from master"
30
+ puts "you shouldn't move to production except from master branch"
27
31
  abort
28
32
  end
33
+
29
34
  if !Asgit.remote_up_to_date?
30
- puts "make sure you've pulled all remote changes"
35
+ puts "make sure you're repo is in sync with the remote"
31
36
  abort
32
37
  end
38
+
33
39
  if !list(:local).empty?
34
- puts "push all your local files and commit first"
40
+ puts "assets detected in the local cdn directory\n" +
41
+ "remove them by running the `prune` task"
35
42
  abort
36
43
  end
37
44
  end
@@ -0,0 +1,34 @@
1
+ module BranchableCDNAssets
2
+ class FileManager
3
+
4
+ class Permissions
5
+
6
+ attr_reader :user, :group, :others
7
+
8
+ def initialize int
9
+ @user, @group, @others = int.to_s.chars.to_a
10
+ end
11
+
12
+ def to_chmod
13
+ Struct.new(:user, :group, :others)
14
+ .new( chmod_map(user), chmod_map(group), chmod_map(others) )
15
+ end
16
+
17
+ private
18
+
19
+ def chmod_map num
20
+ {
21
+ "7" => "rwx",
22
+ "6" => "rw",
23
+ "5" => "rx",
24
+ "4" => "r",
25
+ "3" => "wx",
26
+ "2" => "w",
27
+ "1" => "x",
28
+ "0" => "-"
29
+ }.fetch num
30
+ end
31
+ end
32
+
33
+ end
34
+ end
@@ -7,7 +7,7 @@ module BranchableCDNAssets
7
7
 
8
8
  def initialize source_file
9
9
  @source_file = source_file
10
- @file_set = File.exists?(source_file) ? read_source_file : Set.new
10
+ @file_set = File.exists?(source_file) ? read_source_file : Set.new
11
11
  end
12
12
 
13
13
  # list files set as an Array
@@ -15,17 +15,20 @@ module BranchableCDNAssets
15
15
  attr_reader :file_manager, :rake_namespace
16
16
 
17
17
  def initialize namespace, config
18
- @file_manager = FileManager.new config
18
+ @file_manager = FileManager.new config
19
19
  @rake_namespace = namespace
20
20
  end
21
21
 
22
22
  def tasks
23
23
  {
24
- list: 'list of local files',
24
+ list: 'list of local files',
25
25
  pull!: 'move the current branch\'s remote files to local',
26
26
  push!: 'move local files to the current branch\'s remote',
27
27
  prune!: 'remove local files with the same name as remote files',
28
- move_to_production: 'move named branch files to production cdn'
28
+ move_to_production: 'move named branch files to production cdn',
29
+ doctor: 'list file differences between remotes',
30
+ heal: 'pulls then pushes necessary files to make all remotes lists match',
31
+ setup: 'setup dir on remotes'
29
32
  }
30
33
  end
31
34
 
@@ -59,7 +62,7 @@ module BranchableCDNAssets
59
62
  def register_task task_name, task_desc
60
63
  in_namespace do
61
64
  desc task_desc
62
- task task_name.to_s.sub('!','') do
65
+ task task_name.to_s.sub('!', '') do
63
66
  puts file_manager.public_send( :with_check, task_name )
64
67
  end
65
68
  end
@@ -1,3 +1,3 @@
1
1
  module BranchableCDNAssets
2
- VERSION = "0.5.3"
2
+ VERSION = "0.6.0"
3
3
  end
@@ -9,6 +9,13 @@ describe BranchableCDNAssets::FileManager do
9
9
  cdn_dir: 'cdn',
10
10
  host: 'host',
11
11
  root: 'root',
12
+ manage_cloudfront: true,
13
+ dir_permissions: 755,
14
+ file_permissions: 644,
15
+ rsync_flags: '-aviz',
16
+ remotes: [
17
+ BranchableCDNAssets::Config::Remote.new( 'host', 'root', 'url', 755, 644, '-aviz' )
18
+ ],
12
19
  cloudfront: {
13
20
  path_prefix: '/prefix'
14
21
  }
@@ -140,10 +147,16 @@ describe BranchableCDNAssets::FileManager do
140
147
  end
141
148
 
142
149
  it "returns list of pulled files" do
150
+ allow( BranchableCDNAssets::Shell ).to receive(:run_remote)
151
+ .with("find root -type f", hostname: "host")
152
+ .and_return(@success_response)
143
153
  expect( described_class.new(@config).pull! ).to match_array @file_list
144
154
  end
145
155
 
146
156
  it "removes files & updates manifest" do
157
+ allow( BranchableCDNAssets::Shell ).to receive(:run_remote)
158
+ .with("find root -type f", hostname: "host")
159
+ .and_return(@success_response)
147
160
  expect( @manifest ).to receive(:remove_files).with(@file_list)
148
161
  expect( @manifest ).to receive(:update_source_file!)
149
162
  described_class.new(@config).pull!
@@ -172,11 +185,15 @@ describe BranchableCDNAssets::FileManager do
172
185
 
173
186
  context "when setup_remote is successful" do
174
187
  before :each do
175
- allow( BranchableCDNAssets::Shell ).to receive(:run_remote).with('mkdir -p root', hostname: 'host').and_return(@success_response)
188
+ allow( BranchableCDNAssets::Shell ).to receive(:run_remote)
189
+ .with('mkdir -p -m 755 root', hostname: 'host')
190
+ .and_return(@success_response)
176
191
  end
177
192
 
178
193
  it "sets up remote" do
179
- expect( BranchableCDNAssets::Shell ).to receive(:run_remote).with('mkdir -p root', hostname: 'host').and_return(@success_response)
194
+ expect( BranchableCDNAssets::Shell ).to receive(:run_remote)
195
+ .with('mkdir -p -m 755 root', hostname: 'host')
196
+ .and_return(@success_response)
180
197
  described_class.new(@config).push!
181
198
  end
182
199
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: branchable_cdn_assets
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.3
4
+ version: 0.6.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: 2014-05-27 00:00:00.000000000 Z
11
+ date: 2015-02-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -93,6 +93,7 @@ files:
93
93
  - lib/branchable_cdn_assets/config/environment_attribute_reader.rb
94
94
  - lib/branchable_cdn_assets/file_manager.rb
95
95
  - lib/branchable_cdn_assets/file_manager/checks.rb
96
+ - lib/branchable_cdn_assets/file_manager/permissions.rb
96
97
  - lib/branchable_cdn_assets/manifest.rb
97
98
  - lib/branchable_cdn_assets/rake_tasks.rb
98
99
  - lib/branchable_cdn_assets/shell.rb
@@ -128,7 +129,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
128
129
  version: '0'
129
130
  requirements: []
130
131
  rubyforge_project:
131
- rubygems_version: 2.2.2
132
+ rubygems_version: 2.4.5
132
133
  signing_key:
133
134
  specification_version: 4
134
135
  summary: Helpers for syncing and finding assets accross multiple remotes