fig 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -9,6 +9,8 @@ require 'fig/command/action/list_configs'
9
9
  require 'fig/command/action/list_dependencies'
10
10
  require 'fig/command/action/list_dependencies/all_configs'
11
11
  require 'fig/command/action/list_dependencies/default'
12
+ require 'fig/command/action/list_dependencies/graphviz'
13
+ require 'fig/command/action/list_dependencies/graphviz_all_configs'
12
14
  require 'fig/command/action/list_dependencies/tree'
13
15
  require 'fig/command/action/list_dependencies/tree_all_configs'
14
16
  require 'fig/command/action/list_local'
@@ -16,6 +18,8 @@ require 'fig/command/action/list_remote'
16
18
  require 'fig/command/action/list_variables'
17
19
  require 'fig/command/action/list_variables/all_configs'
18
20
  require 'fig/command/action/list_variables/default'
21
+ require 'fig/command/action/list_variables/graphviz'
22
+ require 'fig/command/action/list_variables/graphviz_all_configs'
19
23
  require 'fig/command/action/list_variables/tree'
20
24
  require 'fig/command/action/list_variables/tree_all_configs'
21
25
  require 'fig/command/action/options'
@@ -163,6 +167,10 @@ class Fig::Command::Options
163
167
  return @list_tree
164
168
  end
165
169
 
170
+ def graphviz?()
171
+ return @graphviz
172
+ end
173
+
166
174
  def strip_shell_command(argv)
167
175
  argv.each_with_index do |arg, i|
168
176
  case arg
@@ -308,6 +316,13 @@ class Fig::Command::Options
308
316
  @list_tree = true
309
317
  end
310
318
 
319
+ @parser.on(
320
+ '--graphviz',
321
+ 'for listings, output DOT (http://graphviz.org/content/dot-language)'
322
+ ) do
323
+ @graphviz = true
324
+ end
325
+
311
326
  @parser.on(
312
327
  '--list-all-configs',
313
328
  'for listings, follow all configurations of the base package'
@@ -523,7 +538,7 @@ class Fig::Command::Options
523
538
  end
524
539
 
525
540
  @parser.on(
526
- '-l', '--login', 'login to remote repo as a non-anonymous user'
541
+ '-l', '--login', 'login to FTP repo as a non-anonymous user'
527
542
  ) do
528
543
  @login = true
529
544
  end
@@ -678,6 +693,12 @@ class Fig::Command::Options
678
693
  raise Fig::Command::OptionError.new(
679
694
  'Cannot use --suppress-all-includes/--suppress-cross-package-includes with --list-tree.'
680
695
  )
696
+ elsif graphviz?
697
+ # Not conceptually incompatible, just not implemented (would need to
698
+ # handle in command/action/role/list_*)
699
+ raise Fig::Command::OptionError.new(
700
+ 'Cannot use --suppress-all-includes/--suppress-cross-package-includes with --graphviz.'
701
+ )
681
702
  elsif list_all_configs?
682
703
  # Not conceptually incompatible, just not implemented (would need to
683
704
  # handle in command/action/role/list_*)
@@ -696,17 +717,27 @@ class Fig::Command::Options
696
717
  )
697
718
  end
698
719
  elsif list_tree?
699
- if ! @base_action.list_dependencies? && ! @base_action.list_variables?
700
- raise Fig::Command::OptionError.new(
701
- %q<The --list-tree option isn't useful without --list-dependencies/--list-variables.>
702
- )
703
- end
720
+ validate_list_option '--list-tree'
721
+ elsif graphviz?
722
+ validate_list_option '--graphviz'
704
723
  elsif list_all_configs?
705
- if ! @base_action.list_dependencies? && ! @base_action.list_variables?
706
- raise Fig::Command::OptionError.new(
707
- %q<The --list-all-configs option isn't useful without --list-dependencies/--list-variables.>
708
- )
709
- end
724
+ validate_list_option '--list-all-configs'
725
+ end
726
+
727
+ if list_tree? && graphviz?
728
+ raise Fig::Command::OptionError.new(
729
+ 'Cannot use --list-tree and --graphviz at the same time.'
730
+ )
731
+ end
732
+
733
+ return
734
+ end
735
+
736
+ def validate_list_option(option)
737
+ if ! @base_action.list_dependencies? && ! @base_action.list_variables?
738
+ raise Fig::Command::OptionError.new(
739
+ %Q<The #{option} option isn't useful without --list-dependencies/--list-variables.>
740
+ )
710
741
  end
711
742
 
712
743
  return
@@ -720,6 +751,8 @@ class Fig::Command::Options
720
751
  sub_action_name = :Default
721
752
  if list_tree?
722
753
  sub_action_name = list_all_configs? ? :TreeAllConfigs : :Tree
754
+ elsif graphviz?
755
+ sub_action_name = list_all_configs? ? :GraphvizAllConfigs : :Graphviz
723
756
  elsif list_all_configs?
724
757
  sub_action_name = :AllConfigs
725
758
  end
@@ -63,13 +63,18 @@ Querying:
63
63
 
64
64
  fig {--list-local | --list-remote} [...]
65
65
  fig {-g | --get} VARIABLE [DESCRIPTOR] [...]
66
- fig --list-dependencies [--list-tree] [--list-all-configs] [DESCRIPTOR] [...]
67
- fig --list-variables [--list-tree] [--list-all-configs] [DESCRIPTOR] [...]
66
+ fig --list-dependencies [...list options...] [DESCRIPTOR] [...]
67
+ fig --list-variables [...list options...] [DESCRIPTOR] [...]
68
68
  fig --list-configs [DESCRIPTOR] [...]
69
69
  fig --dump-package-definition-text [DESCRIPTOR] [...]
70
70
  fig --dump-package-definition-parsed [DESCRIPTOR] [...]
71
71
  fig --dump-package-definition-for-command-line [DESCRIPTOR] [...]
72
72
 
73
+ List options (represented as "[...list options...]" above):
74
+
75
+ [--list-tree | --graphviz]
76
+ [--list-all-configs]
77
+
73
78
  Standard options (represented as "[...]" above):
74
79
 
75
80
  [-u | --update | -m | --update-if-missing]
data/lib/fig/figrc.rb CHANGED
@@ -17,7 +17,7 @@ class Fig::FigRC
17
17
  def self.find(
18
18
  override_path,
19
19
  specified_repository_url,
20
- login,
20
+ operating_system,
21
21
  fig_home,
22
22
  disable_figrc = false
23
23
  )
@@ -33,7 +33,7 @@ class Fig::FigRC
33
33
  configuration.remote_repository_url = repository_url
34
34
 
35
35
  handle_repository_configuration(
36
- configuration, repository_url, login, fig_home
36
+ configuration, repository_url, operating_system, fig_home
37
37
  )
38
38
 
39
39
  return configuration
@@ -77,7 +77,7 @@ class Fig::FigRC
77
77
  end
78
78
 
79
79
  def self.handle_repository_configuration(
80
- configuration, repository_url, login, fig_home
80
+ configuration, repository_url, operating_system, fig_home
81
81
  )
82
82
  return if repository_url.nil?
83
83
 
@@ -85,11 +85,9 @@ class Fig::FigRC
85
85
  repo_figrc_path =
86
86
  File.expand_path(File.join(fig_home, REPOSITORY_CONFIGURATION))
87
87
 
88
- os = Fig::OperatingSystem.new(login)
89
-
90
88
  repo_config_exists = nil
91
89
  begin
92
- os.download( figrc_url, repo_figrc_path )
90
+ operating_system.download figrc_url, repo_figrc_path
93
91
  repo_config_exists = true
94
92
  rescue Fig::FileNotFoundError
95
93
  repo_config_exists = false
@@ -1,24 +1,19 @@
1
1
  require 'cgi'
2
2
  require 'fileutils'
3
- require 'find'
4
3
  # Must specify absolute path of ::Archive when using
5
4
  # this module to avoid conflicts with Fig::Statement::Archive
6
5
  require 'libarchive_ruby'
7
- require 'net/http'
8
- require 'net/ssh'
9
- require 'net/sftp'
10
- require 'net/netrc'
11
6
  require 'rbconfig'
12
- require 'tempfile'
13
-
14
- require 'highline/import'
15
7
 
16
8
  require 'fig/at_exit'
17
9
  require 'fig/environment_variables/case_insensitive'
18
10
  require 'fig/environment_variables/case_sensitive'
19
- require 'fig/file_not_found_error'
20
11
  require 'fig/logging'
21
12
  require 'fig/network_error'
13
+ require 'fig/protocol/file'
14
+ require 'fig/protocol/ftp'
15
+ require 'fig/protocol/http'
16
+ require 'fig/protocol/sftp'
22
17
  require 'fig/repository_error'
23
18
  require 'fig/url'
24
19
  require 'fig/user_input_error'
@@ -64,33 +59,11 @@ class Fig::OperatingSystem
64
59
  end
65
60
 
66
61
  def initialize(login)
67
- @login = login
68
- @username = ENV['FIG_USERNAME']
69
- @password = ENV['FIG_PASSWORD']
70
- end
71
-
72
- def get_username()
73
- # #ask() comes from highline
74
- @username ||= ask('Username: ') { |q| q.echo = true }
75
- end
76
-
77
- def get_password()
78
- # #ask() comes from highline
79
- @password ||= ask('Password: ') { |q| q.echo = false }
80
- end
81
-
82
- def ftp_login(ftp, host)
83
- if @login
84
- rc = Net::Netrc.locate(host)
85
- if rc
86
- @username = rc.login
87
- @password = rc.password
88
- end
89
- ftp.login(get_username, get_password)
90
- else
91
- ftp.login()
92
- end
93
- ftp.passive = true
62
+ @protocols = {}
63
+ @protocols['file'] = Fig::Protocol::File.new
64
+ @protocols['ftp'] = Fig::Protocol::FTP.new login
65
+ @protocols['http'] = Fig::Protocol::HTTP.new
66
+ @protocols['sftp'] = Fig::Protocol::SFTP.new
94
67
  end
95
68
 
96
69
  def list(dir)
@@ -105,55 +78,11 @@ class Fig::OperatingSystem
105
78
  File.open(path, 'wb') { |f| f.binmode; f << content }
106
79
  end
107
80
 
108
- def strip_paths_for_list(ls_output, packages, path)
109
- if not ls_output.nil?
110
- ls_output = ls_output.gsub(path + '/', '').gsub(path, '').split("\n")
111
- ls_output.each do |line|
112
- parts = line.gsub(/\\/, '/').sub(/^\.\//, '').sub(/:$/, '').chomp().split('/')
113
- packages << parts.join('/') if parts.size == 2
114
- end
115
- end
116
- end
117
-
118
81
  def download_list(url)
119
82
  begin
120
- uri = Fig::URL.parse(url)
121
- rescue
122
- Fig::Logging.fatal %Q<Unable to parse url: "#{url}">
123
- raise Fig::NetworkError.new
124
- end
83
+ protocol, uri = decode_protocol url
125
84
 
126
- begin
127
- case uri.scheme
128
- when 'ftp'
129
- ftp = Net::FTP.new(uri.host)
130
- ftp_login(ftp, uri.host)
131
- ftp.chdir(uri.path)
132
- dirs = ftp.nlst
133
- ftp.close
134
-
135
- download_ftp_list(uri, dirs)
136
- when 'ssh'
137
- packages = []
138
- Net::SSH.start(uri.host, uri.user) do |ssh|
139
- ls = ssh.exec!("[ -d #{uri.path} ] && find #{uri.path}")
140
- strip_paths_for_list(ls, packages, uri.path)
141
- end
142
- packages
143
- when 'file'
144
- packages = []
145
- unescaped_path = CGI.unescape uri.path
146
- return packages if ! File.exist?(unescaped_path)
147
-
148
- ls = ''
149
- Find.find(unescaped_path) { |file| ls << file.to_s; ls << "\n" }
150
-
151
- strip_paths_for_list(ls, packages, unescaped_path)
152
- return packages
153
- else
154
- Fig::Logging.fatal "Protocol not supported: #{url}"
155
- raise Fig::NetworkError.new "Protocol not supported: #{url}"
156
- end
85
+ return protocol.download_list uri
157
86
  rescue SocketError => error
158
87
  Fig::Logging.debug error.message
159
88
  raise Fig::NetworkError.new "#{url}: #{error.message}"
@@ -163,150 +92,23 @@ class Fig::OperatingSystem
163
92
  end
164
93
  end
165
94
 
166
- def download_ftp_list(uri, dirs)
167
- # Run a bunch of these in parallel since they're slow as hell
168
- num_threads = (ENV['FIG_FTP_THREADS'] || '16').to_i
169
- threads = []
170
- all_packages = []
171
- (0..num_threads-1).each { |num| all_packages[num] = [] }
172
- (0..num_threads-1).each do |num|
173
- threads << Thread.new do
174
- packages = all_packages[num]
175
- ftp = Net::FTP.new(uri.host)
176
- ftp_login(ftp, uri.host)
177
- ftp.chdir(uri.path)
178
- pos = num
179
- while pos < dirs.length
180
- pkg = dirs[pos]
181
- begin
182
- ftp.nlst(dirs[pos]).each do |ver|
183
- packages << pkg + '/' + ver
184
- end
185
- rescue Net::FTPPermError
186
- # Ignore this error because it's indicative of the FTP library
187
- # encountering a file or directory that it does not have
188
- # permission to open. Fig needs to be able to have secure
189
- # repos/packages and there is no way easy way to deal with the
190
- # permissions issues other than consuming these errors.
191
- #
192
- # Actually, with FTP, you can't tell the difference between a
193
- # file not existing and not having permission to access it (which
194
- # is probably a good thing).
195
- end
196
- pos += num_threads
197
- end
198
- ftp.close
199
- end
200
- end
201
- threads.each { |thread| thread.join }
202
- all_packages.flatten.sort
203
- end
204
-
205
95
  # Determine whether we need to update something. Returns nil to indicate
206
96
  # "don't know".
207
97
  def path_up_to_date?(url, path)
208
98
  return false if ! File.exist? path
209
99
 
210
- uri = Fig::URL.parse(url)
211
- case uri.scheme
212
- when 'ftp'
213
- begin
214
- ftp = Net::FTP.new(uri.host)
215
- ftp_login(ftp, uri.host)
216
-
217
- if ftp.mtime(uri.path) <= File.mtime(path)
218
- return true
219
- end
220
-
221
- return false
222
- rescue Net::FTPPermError => error
223
- Fig::Logging.debug error.message
224
- raise Fig::FileNotFoundError.new error.message, url
225
- rescue SocketError => error
226
- Fig::Logging.debug error.message
227
- raise Fig::FileNotFoundError.new error.message, url
228
- end
229
- when 'http'
230
- return nil # Not implemented
231
- when 'ssh'
232
- when 'file'
233
- begin
234
- unescaped_path = CGI.unescape uri.path
235
- if File.mtime(unescaped_path) <= File.mtime(path)
236
- return true
237
- end
238
-
239
- return false
240
- rescue Errno::ENOENT => error
241
- raise Fig::FileNotFoundError.new error.message, url
242
- end
243
- else
244
- raise_unknown_protocol(url)
245
- end
100
+ protocol, uri = decode_protocol url
101
+ return protocol.path_up_to_date? uri, path
246
102
  end
247
103
 
248
104
  # Returns whether the file was not downloaded because the file already
249
105
  # exists and is already up-to-date.
250
106
  def download(url, path)
251
- FileUtils.mkdir_p(File.dirname(path))
252
- uri = Fig::URL.parse(url)
253
- case uri.scheme
254
- when 'ftp'
255
- begin
256
- ftp = Net::FTP.new(uri.host)
257
- ftp_login(ftp, uri.host)
258
-
259
- if File.exist?(path) && ftp.mtime(uri.path) <= File.mtime(path)
260
- Fig::Logging.debug "#{path} is up to date."
261
- return false
262
- else
263
- log_download(url, path)
264
- ftp.getbinaryfile(uri.path, path, 256*1024)
265
- return true
266
- end
267
- rescue Net::FTPPermError => error
268
- Fig::Logging.debug error.message
269
- raise Fig::FileNotFoundError.new error.message, url
270
- rescue SocketError => error
271
- Fig::Logging.debug error.message
272
- raise Fig::FileNotFoundError.new error.message, url
273
- rescue Errno::ETIMEDOUT => error
274
- Fig::Logging.debug error.message
275
- raise Fig::FileNotFoundError.new error.message, url
276
- end
277
- when 'http'
278
- log_download(url, path)
279
- File.open(path, 'wb') do |file|
280
- file.binmode
281
-
282
- begin
283
- download_via_http_get(url, file)
284
- rescue SystemCallError => error
285
- Fig::Logging.debug error.message
286
- raise Fig::FileNotFoundError.new error.message, url
287
- rescue SocketError => error
288
- Fig::Logging.debug error.message
289
- raise Fig::FileNotFoundError.new error.message, url
290
- end
291
- end
292
- when 'ssh'
293
- # TODO need better way to do conditional download
294
- timestamp = File.exist?(path) ? File.mtime(path).to_i : 0
295
- # Requires that remote installation of fig be at the same location as the local machine.
296
- command = `which fig-download`.strip + " #{timestamp} #{uri.path}"
297
- log_download(url, path)
298
- ssh_download(uri.user, uri.host, path, command)
299
- when 'file'
300
- begin
301
- unescaped_path = CGI.unescape uri.path
302
- FileUtils.cp(unescaped_path, path)
303
- return true
304
- rescue Errno::ENOENT => error
305
- raise Fig::FileNotFoundError.new error.message, url
306
- end
307
- else
308
- raise_unknown_protocol(url)
309
- end
107
+ protocol, uri = decode_protocol url
108
+
109
+ FileUtils.mkdir_p(File.dirname path)
110
+
111
+ return protocol.download uri, path
310
112
  end
311
113
 
312
114
  # Returns the basename and full path to the download.
@@ -343,43 +145,12 @@ class Fig::OperatingSystem
343
145
 
344
146
  def upload(local_file, remote_file)
345
147
  Fig::Logging.debug "Uploading #{local_file} to #{remote_file}."
346
- uri = Fig::URL.parse(remote_file)
347
- case uri.scheme
348
- when 'ssh'
349
- ssh_upload(uri.user, uri.host, local_file, remote_file)
350
- when 'ftp'
351
- # fail unless system "curl -T #{local_file} --create-dirs --ftp-create-dirs #{remote_file}"
352
- require 'net/ftp'
353
- ftp_uri = Fig::URL.parse(ENV['FIG_REMOTE_URL'])
354
- ftp_root_path = ftp_uri.path
355
- ftp_root_dirs = ftp_uri.path.split('/')
356
- remote_publish_path = uri.path[0, uri.path.rindex('/')]
357
- remote_publish_dirs = remote_publish_path.split('/')
358
- # Use array subtraction to deduce which project/version folder to upload
359
- # to, i.e. [1,2,3] - [2,3,4] = [1]
360
- remote_project_dirs = remote_publish_dirs - ftp_root_dirs
361
- Net::FTP.open(uri.host) do |ftp|
362
- ftp_login(ftp, uri.host)
363
- # Assume that the FIG_REMOTE_URL path exists.
364
- ftp.chdir(ftp_root_path)
365
- remote_project_dirs.each do |dir|
366
- # Can't automatically create parent directories, so do it manually.
367
- if ftp.nlst().index(dir).nil?
368
- ftp.mkdir(dir)
369
- ftp.chdir(dir)
370
- else
371
- ftp.chdir(dir)
372
- end
373
- end
374
- ftp.putbinaryfile(local_file)
375
- end
376
- when 'file'
377
- unescaped_path = CGI.unescape uri.path
378
- FileUtils.mkdir_p(File.dirname(unescaped_path))
379
- FileUtils.cp(local_file, unescaped_path)
380
- else
381
- raise_unknown_protocol(uri)
382
- end
148
+
149
+
150
+ protocol, uri = decode_protocol remote_file
151
+ protocol.upload local_file, uri
152
+
153
+ return
383
154
  end
384
155
 
385
156
  def delete_and_recreate_directory(dir)
@@ -397,7 +168,7 @@ class Fig::OperatingSystem
397
168
  end
398
169
  else
399
170
  if ! File.exist?(target) || File.mtime(source) != File.mtime(target)
400
- log_info "#{msg} #{target}" if msg
171
+ Fig::Logging.info "#{msg} #{target}" if msg
401
172
  FileUtils.mkdir_p(File.dirname(target))
402
173
  FileUtils.cp(source, target)
403
174
  File.utime(File.atime(source), File.mtime(source), target)
@@ -409,10 +180,6 @@ class Fig::OperatingSystem
409
180
  Dir.chdir(directory) { FileUtils.mv(from, to, :force => true) }
410
181
  end
411
182
 
412
- def log_info(msg)
413
- Fig::Logging.info msg
414
- end
415
-
416
183
  # Expects files_to_archive as an Array of filenames.
417
184
  def create_archive(archive_name, files_to_archive)
418
185
  # TODO: Need to verify files_to_archive exists.
@@ -509,9 +276,13 @@ class Fig::OperatingSystem
509
276
 
510
277
  private
511
278
 
512
- SUCCESS = 0
513
- NOT_MODIFIED = 3
514
- NOT_FOUND = 4
279
+ def decode_protocol(url)
280
+ uri = Fig::URL.parse(url)
281
+ protocol = @protocols[uri.scheme]
282
+ raise_unknown_protocol(url) if protocol.nil?
283
+
284
+ return protocol, uri
285
+ end
515
286
 
516
287
  def check_archive_entry_for_windows(entry, archive_path)
517
288
  bad_type = nil
@@ -536,77 +307,6 @@ class Fig::OperatingSystem
536
307
  return
537
308
  end
538
309
 
539
- # path = The local path the file should be downloaded to.
540
- # command = The command to be run on the remote host.
541
- def ssh_download(user, host, path, command)
542
- return_code = nil
543
- tempfile = Tempfile.new('tmp')
544
- Net::SSH.start(host, user) do |ssh|
545
- ssh.open_channel do |channel|
546
- channel.exec(command)
547
- channel.on_data() { |ch, data| tempfile << data }
548
- channel.on_extended_data() { |ch, type, data| Fig::Logging.error "SSH Download ERROR: #{data}" }
549
- channel.on_request('exit-status') { |ch, request|
550
- return_code = request.read_long
551
- }
552
- end
553
- end
554
-
555
- tempfile.close()
556
-
557
- case return_code
558
- when NOT_MODIFIED
559
- tempfile.delete
560
- return false
561
- when NOT_FOUND
562
- tempfile.delete
563
- raise Fig::FileNotFoundError.new 'Remote path not found', path
564
- when SUCCESS
565
- FileUtils.mv(tempfile.path, path)
566
- return true
567
- else
568
- tempfile.delete
569
- Fig::Logging.fatal "Unable to download file #{path}: #{return_code}"
570
- raise Fig::NetworkError.new("Unable to download file #{path}: #{return_code}")
571
- end
572
- end
573
-
574
- def ssh_upload(user, host, local_file, remote_file)
575
- uri = Fig::URL.parse(remote_file)
576
- dir = uri.path[0, uri.path.rindex('/')]
577
- Net::SSH.start(host, user) do |ssh|
578
- ssh.exec!("mkdir -p #{dir}")
579
- end
580
- Net::SFTP.start(host, user) do |sftp|
581
- sftp.upload!(local_file, uri.path)
582
- end
583
- end
584
-
585
- def download_via_http_get(uri_string, file, redirection_limit = 10)
586
- if redirection_limit < 1
587
- Fig::Logging.debug 'Too many HTTP redirects.'
588
- raise Fig::FileNotFoundError.new 'Too many HTTP redirects.', uri_string
589
- end
590
-
591
- response = Net::HTTP.get_response(URI(uri_string))
592
-
593
- case response
594
- when Net::HTTPSuccess then
595
- file.write(response.body)
596
- when Net::HTTPRedirection then
597
- location = response['location']
598
- Fig::Logging.debug "Redirecting to #{location}."
599
- download_via_http_get(location, file, redirection_limit - 1)
600
- else
601
- Fig::Logging.debug "Download failed: #{response.code} #{response.message}."
602
- raise Fig::FileNotFoundError.new(
603
- "Download failed: #{response.code} #{response.message}.", uri_string
604
- )
605
- end
606
-
607
- return
608
- end
609
-
610
310
  def raise_unknown_protocol(url)
611
311
  Fig::Logging.fatal %Q<Don't know how to handle the protocol in "#{url}".>
612
312
  raise Fig::NetworkError.new(
@@ -615,8 +315,4 @@ class Fig::OperatingSystem
615
315
 
616
316
  return
617
317
  end
618
-
619
- def log_download(url, path)
620
- Fig::Logging.debug "Downloading #{url} to #{path}."
621
- end
622
318
  end