tpkg 2.2.4 → 2.3.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.
data/Rakefile CHANGED
@@ -1,19 +1,19 @@
1
1
  require 'rake/gempackagetask'
2
2
  spec = Gem::Specification.new do |s|
3
- s.name = 'tpkg'
4
- s.summary = 'tpkg Application Packaging & Deployment'
5
- s.add_dependency('facter')
6
- s.add_dependency('net-ssh')
7
- s.add_dependency('kwalify')
8
- s.version = '2.2.4'
3
+ s.name = 'tpkg'
4
+ s.summary = 'tpkg Application Packaging & Deployment'
5
+ s.version = '2.3.0'
9
6
  s.authors = ['Darren Dao', 'Jason Heiss']
10
7
  s.email = 'tpkg-users@lists.sourceforge.net'
11
8
  s.homepage = 'http://tpkg.sourceforge.net'
12
9
  s.rubyforge_project = 'tpkg'
13
10
  s.platform = Gem::Platform::RUBY
14
11
  s.required_ruby_version = '>=1.8'
15
- s.files = Dir['**/**']
12
+ s.files = Dir['**/**']
16
13
  s.executables = [ 'tpkg', 'cpan2tpkg', 'gem2tpkg', 'tpkg_xml_to_yml' ]
14
+ s.add_dependency('facter')
15
+ s.add_dependency('net-ssh')
16
+ s.add_dependency('kwalify')
17
17
  end
18
18
  Rake::GemPackageTask.new(spec).define
19
19
 
@@ -1,7 +1,6 @@
1
1
  #!/opt/tpkg/bin/perl
2
2
  ##############################################################################
3
3
  # tpkg package management system
4
- # Copyright 2009, 2010, 2011 AT&T Interactive
5
4
  # License: MIT (http://www.opensource.org/licenses/mit-license.php)
6
5
  ##############################################################################
7
6
 
@@ -78,7 +77,7 @@ while (scalar @extradepsopts)
78
77
  {
79
78
  my $dep = shift @extradepsopts;
80
79
  # These are optional, shift will return undef if they aren't present
81
- # and we'll handle that properly when creating tpkg.xml
80
+ # and we'll handle that properly when creating tpkg.yml
82
81
  my $depminver = shift @extradepsopts;
83
82
  my $depmaxver = shift @extradepsopts;
84
83
  $extradeps{$dep} = {};
@@ -90,7 +89,7 @@ while (scalar @nativedepsopts)
90
89
  {
91
90
  my $dep = shift @nativedepsopts;
92
91
  # These are optional, shift will return undef if they aren't present
93
- # and we'll handle that properly when creating tpkg.xml
92
+ # and we'll handle that properly when creating tpkg.yml
94
93
  my $depminver = shift @nativedepsopts;
95
94
  my $depmaxver = shift @nativedepsopts;
96
95
  $nativedeps{$dep} = {};
@@ -315,23 +314,29 @@ MODULE: foreach my $name ($extinst->modules)
315
314
  $packlistfile =~ s/^$workdir//;
316
315
  # Make directory tree in $tpkgdir
317
316
  mkpath("$tpkgdir/root/" . dirname($packlistfile));
317
+
318
318
  # Copy file
319
- copy("$workdir/$packlistfile", "$tpkgdir/root/$packlistfile");
319
+ my $src = "$workdir/$packlistfile";
320
+ my $dst = "$tpkgdir/root/$packlistfile";
321
+ copy($src, $dst);
322
+
323
+ # preserve permissions
324
+ # (avoiding dependency on File::Copy::Recursive::fcopy)
325
+ my $mode = (stat($src))[2];
326
+ chmod($mode & 07777, $dst);
327
+
320
328
  if ($packlistfile =~ quotemeta($Config{archname}))
321
329
  {
322
330
  $nativefile = 1;
323
331
  }
324
332
  }
325
- # Create tpkg.xml
326
- open(my $xmlfh, '>', "$tpkgdir/tpkg.xml") or die;
327
- print $xmlfh '<?xml version="1.0" encoding="UTF-8"?>', "\n";
328
- print $xmlfh '<!DOCTYPE tpkg SYSTEM "http://tpkg.sourceforge.net/tpkg-1.0.dtd">', "\n";
329
- print $xmlfh '<tpkg>', "\n";
330
- print $xmlfh " <name>cpan-perl$majorminor-$pkgname</name>", "\n";
331
- print $xmlfh " <version>$ver</version>", "\n";
332
- print $xmlfh " <package_version>$pkgver</package_version>", "\n";
333
- print $xmlfh " <description>cpan package for $pkgname</description>", "\n";
334
- print $xmlfh ' <maintainer>cpan2tpkg</maintainer>', "\n";
333
+ # Create tpkg.yml
334
+ open(my $ymlfh, '>', "$tpkgdir/tpkg.yml") or die;
335
+ print $ymlfh "name: cpan-perl$majorminor-$pkgname", "\n";
336
+ print $ymlfh "version: $ver", "\n";
337
+ print $ymlfh "package_version: $pkgver", "\n";
338
+ print $ymlfh "description: cpan package for $pkgname", "\n";
339
+ print $ymlfh 'maintainer: cpan2tpkg', "\n";
335
340
  # If the package has native code then it needs to be flagged as
336
341
  # specific to the OS and architecture
337
342
  if ($nativefile)
@@ -360,68 +365,58 @@ MODULE: foreach my $name ($extinst->modules)
360
365
  # vice-versa
361
366
  if ($os =~ /RedHat-(.*)/)
362
367
  {
363
- $os = $os . ",CentOS-$1";
368
+ $os = $os . ", CentOS-$1";
364
369
  }
365
370
  elsif ($os =~ /CentOS-(.*)/)
366
371
  {
367
- $os = $os . ",RedHat-$1";
372
+ $os = $os . ", RedHat-$1";
368
373
  }
369
- print $xmlfh " <operatingsystem>$os</operatingsystem>", "\n";
370
- print $xmlfh " <architecture>$arch</architecture>", "\n";
374
+ print $ymlfh "operatingsystem: [$os]", "\n";
375
+ print $ymlfh "architecture: [$arch]", "\n";
371
376
  }
372
377
  # Insert appropriate dependencies into the package
373
- print $xmlfh ' <dependencies>', "\n";
378
+ print $ymlfh 'dependencies:', "\n";
374
379
  foreach my $dep (keys %deps)
375
380
  {
376
- print $xmlfh ' <dependency>', "\n";
377
- print $xmlfh " <name>cpan-perl$majorminor-$dep</name>", "\n";
381
+ print $ymlfh " - name: cpan-perl$majorminor-$dep", "\n";
378
382
  if ($deps{$dep}{minimum_version})
379
383
  {
380
- print $xmlfh " <minimum_version>$deps{$dep}{minimum_version}</minimum_version>", "\n";
384
+ print $ymlfh " minimum_version: $deps{$dep}{minimum_version}", "\n";
381
385
  }
382
386
  if ($deps{$dep}{maximum_version})
383
387
  {
384
- print $xmlfh " <maximum_version>$deps{$dep}{maximum_version}</maximum_version>", "\n";
388
+ print $ymlfh " maximum_version: $deps{$dep}{maximum_version}", "\n";
385
389
  }
386
- print $xmlfh ' </dependency>', "\n";
387
390
  }
388
391
  foreach my $extradep (keys %extradeps)
389
392
  {
390
- print $xmlfh ' <dependency>', "\n";
391
- print $xmlfh " <name>$extradep</name>", "\n";
393
+ print $ymlfh " - name: $extradep", "\n";
392
394
  if ($extradeps{$extradep}{minimum_version})
393
395
  {
394
- print $xmlfh " <minimum_version>$extradeps{$extradep}{minimum_version}</minimum_version>", "\n";
396
+ print $ymlfh " minimum_version: $extradeps{$extradep}{minimum_version}", "\n";
395
397
  }
396
398
  if ($extradeps{$extradep}{maximum_version})
397
399
  {
398
- print $xmlfh " <maximum_version>$extradeps{$extradep}{maximum_version}</maximum_version>", "\n";
400
+ print $ymlfh " maximum_version: $extradeps{$extradep}{maximum_version}", "\n";
399
401
  }
400
- print $xmlfh ' </dependency>', "\n";
401
402
  }
402
403
  foreach my $nativedep (keys %nativedeps)
403
404
  {
404
- print $xmlfh ' <dependency>', "\n";
405
- print $xmlfh " <name>$nativedep</name>", "\n";
405
+ print $ymlfh " - name: $nativedep", "\n";
406
406
  if ($nativedeps{$nativedep}{minimum_version})
407
407
  {
408
- print $xmlfh " <minimum_version>$nativedeps{$nativedep}{minimum_version}</minimum_version>", "\n";
408
+ print $ymlfh " minimum_version: $nativedeps{$nativedep}{minimum_version}", "\n";
409
409
  }
410
410
  if ($nativedeps{$nativedep}{maximum_version})
411
411
  {
412
- print $xmlfh " <maximum_version>$nativedeps{$nativedep}{maximum_version}</maximum_version>", "\n";
412
+ print $ymlfh " maximum_version: $nativedeps{$nativedep}{maximum_version}", "\n";
413
413
  }
414
- print $xmlfh ' <native/>', "\n";
415
- print $xmlfh ' </dependency>', "\n";
414
+ print $ymlfh ' native: true', "\n";
416
415
  }
417
416
  # Insert an appropriate dependency on Perl itself
418
- print $xmlfh ' <dependency>', "\n";
419
- print $xmlfh " <name>perl</name>", "\n";
420
- print $xmlfh " <minimum_version>", sprintf("%vd", $^V), "</minimum_version>", "\n";
421
- print $xmlfh " <maximum_version>$majordotminor.9999</maximum_version>", "\n";
422
- print $xmlfh ' </dependency>', "\n";
423
- print $xmlfh ' </dependencies>', "\n";
424
- print $xmlfh '</tpkg>', "\n";
417
+ print $ymlfh ' - name: perl', "\n";
418
+ print $ymlfh ' minimum_version: ', sprintf("%vd", $^V), "\n";
419
+ print $ymlfh " maximum_version: $majordotminor.9999", "\n";
425
420
  # Build package
426
421
  system("tpkg --make $tpkgdir");
427
422
  }
@@ -1,12 +1,14 @@
1
1
  #!/usr/bin/ruby -w
2
2
  ##############################################################################
3
3
  # tpkg package management system
4
- # Copyright 2009, 2010, 2011 AT&T Interactive
5
4
  # License: MIT (http://www.opensource.org/licenses/mit-license.php)
6
5
  ##############################################################################
7
6
 
8
- # Ensure we can find tpkg.rb
9
- $:.unshift File.join(File.dirname(__FILE__), "..", "lib")
7
+ # When run from the source repository or from an unpacked copy of the
8
+ # distribution we want to find the local library, even if there's a copy
9
+ # installed on the system. The installed copy is likely older, and the API
10
+ # may be out of sync with this executable.
11
+ $:.unshift(File.expand_path('../lib', File.dirname(__FILE__)))
10
12
 
11
13
  require 'fileutils' # FileUtils.cp, rm, etc.
12
14
  require 'tempfile' # Tempfile
@@ -56,7 +58,7 @@ opts.on('--extra-deps', '=EXTRADEPS', Array, 'Extra dependencies to add to the p
56
58
  while !opt.empty?
57
59
  dep = opt.shift
58
60
  # These are optional, shift will return nil if they aren't present
59
- # and we'll handle that properly when creating tpkg.xml
61
+ # and we'll handle that properly when creating tpkg.yml
60
62
  depminver = opt.shift
61
63
  depmaxver = opt.shift
62
64
  @extradeps[dep] = {}
@@ -69,7 +71,7 @@ opts.on('--native-deps', '=NATIVEDEPS', Array, 'Native dependencies to add to th
69
71
  while !opt.empty?
70
72
  dep = opt.shift
71
73
  # These are optional, shift will return nil if they aren't present
72
- # and we'll handle that properly when creating tpkg.xml
74
+ # and we'll handle that properly when creating tpkg.yml
73
75
  depminver = opt.shift
74
76
  depmaxver = opt.shift
75
77
  @nativedeps[dep] = {}
@@ -273,7 +275,7 @@ def package(gem)
273
275
  operator.sub!(/^\(/, '')
274
276
  depver.sub!(/\)$/, '')
275
277
  depver.sub!(/,$/, '')
276
- # Save the dependency info for tpkg.xml
278
+ # Save the dependency info for tpkg.yml
277
279
  # http://rubygems.org/read/chapter/16
278
280
  deps[depgem] = {}
279
281
  case operator
@@ -393,77 +395,83 @@ def package(gem)
393
395
  pkgnamesuffix = '-' + @gemdep.first.sub(/\W/, '')
394
396
  end
395
397
 
396
- # Add tpkg.xml
398
+ # Determine if we're packaging a gem the user requested or a dependency gem.
399
+ # If we're packaging a dependency gem we don't want to add @extradeps or
400
+ # @nativedeps. Say you run gem2tpkg for gem foo. You specify some extra
401
+ # dependencies with --extra-deps and/or --native-deps. Gem foo depends on
402
+ # gems bar and baz. The generated tpkgs for bar and baz will include those
403
+ # extra dependencies. I don't think they should. Bar and baz may be very
404
+ # general gems with no close relationship to foo. It seems to me if the user
405
+ # needs bar and baz to have the same extra dependencies they should gem2tpkg
406
+ # those separately. We shouldn't assume everything in the dependency chain
407
+ # needs those same dependencies.
408
+ packaging_requested_gem = false
409
+ if @gems.include?(gem)
410
+ packaging_requested_gem = true
411
+ end
412
+
413
+ # Add tpkg.yml
397
414
  os = nil
398
415
  arch = nil
399
- File.open(File.join(pkgdir, 'tpkg.xml'), 'w') do |file|
400
- file.puts '<?xml version="1.0" encoding="UTF-8"?>'
401
- file.puts '<!DOCTYPE tpkg SYSTEM "http://tpkg.sourceforge.net/tpkg-1.0.dtd">'
402
- file.puts '<tpkg>'
403
- file.puts " <name>gem-#{gem}#{pkgnamesuffix}</name>"
404
- file.puts " <version>#{gemspec.version.to_s}</version>"
405
- file.puts " <package_version>#{@pkgver}</package_version>"
406
- file.puts " <description>Ruby gem for #{gem}</description>"
407
- file.puts ' <maintainer>gem2tpkg</maintainer>'
416
+ File.open(File.join(pkgdir, 'tpkg.yml'), 'w') do |file|
417
+
418
+ file.puts "name: gem-#{gem}#{pkgnamesuffix}"
419
+ file.puts "version: #{gemspec.version.to_s}"
420
+ file.puts "package_version: #{@pkgver}"
421
+ file.puts "description: Ruby gem for #{gem}"
422
+ file.puts 'maintainer: gem2tpkg'
408
423
  # If the gemspec lists any extensions then the package has native
409
424
  # code and needs to be flagged as specific to the OS and architecture
410
425
  if gemspec.extensions && !gemspec.extensions.empty?
411
426
  os = Tpkg::get_os
412
427
  if os =~ /RedHat-(.*)/
413
- os = os + ",CentOS-#{$1}"
428
+ os = os + ", CentOS-#{$1}"
414
429
  elsif os =~ /CentOS-(.*)/
415
- os = os + ",RedHat-#{$1}"
430
+ os = os + ", RedHat-#{$1}"
416
431
  end
417
432
  arch = Facter['hardwaremodel'].value
418
- file.puts " <operatingsystem>#{os}</operatingsystem>"
419
- file.puts " <architecture>#{arch}</architecture>"
433
+ file.puts "operatingsystem: [#{os}]"
434
+ file.puts "architecture: [#{arch}]"
420
435
  end
421
436
  if !deps.empty? ||
422
- !@extradeps.empty? || !@nativedeps.empty? ||
437
+ (!@extradeps.empty? && packaging_requested_gem) ||
438
+ (!@nativedeps.empty? && packaging_requested_gem) ||
423
439
  !@gemdep.empty?
424
- file.puts ' <dependencies>'
440
+ file.puts 'dependencies:'
425
441
  deps.each do |depgem, depvers|
426
- file.puts ' <dependency>'
427
- file.puts " <name>gem-#{depgem}#{pkgnamesuffix}</name>"
428
- if depvers[:minimum_version]
429
- file.puts " <minimum_version>#{depvers[:minimum_version]}</minimum_version>"
430
- end
431
- if depvers[:maximum_version]
432
- file.puts " <maximum_version>#{depvers[:maximum_version]}</maximum_version>"
433
- end
434
- file.puts ' </dependency>'
435
- end
436
- @extradeps.each do |extradep, depvers|
437
- file.puts ' <dependency>'
438
- file.puts " <name>#{extradep}</name>"
442
+ file.puts " - name: gem-#{depgem}#{pkgnamesuffix}"
439
443
  if depvers[:minimum_version]
440
- file.puts " <minimum_version>#{depvers[:minimum_version]}</minimum_version>"
444
+ file.puts " minimum_version: #{depvers[:minimum_version]}"
441
445
  end
442
446
  if depvers[:maximum_version]
443
- file.puts " <maximum_version>#{depvers[:maximum_version]}</maximum_version>"
447
+ file.puts " maximum_version: #{depvers[:maximum_version]}"
444
448
  end
445
- file.puts ' </dependency>'
446
449
  end
447
- @nativedeps.each do |nativedep, depvers|
448
- file.puts ' <dependency>'
449
- file.puts " <name>#{nativedep}</name>"
450
- if depvers[:minimum_version]
451
- file.puts " <minimum_version>#{depvers[:minimum_version]}</minimum_version>"
450
+ if packaging_requested_gem
451
+ @extradeps.each do |extradep, depvers|
452
+ file.puts " - name: #{extradep}"
453
+ if depvers[:minimum_version]
454
+ file.puts " minimum_version: #{depvers[:minimum_version]}"
455
+ end
456
+ if depvers[:maximum_version]
457
+ file.puts " maximum_version: #{depvers[:maximum_version]}"
458
+ end
452
459
  end
453
- if depvers[:maximum_version]
454
- file.puts " <maximum_version>#{depvers[:maximum_version]}</maximum_version>"
460
+ @nativedeps.each do |nativedep, depvers|
461
+ file.puts " - name: #{nativedep}"
462
+ if depvers[:minimum_version]
463
+ file.puts " minimum_version: #{depvers[:minimum_version]}"
464
+ end
465
+ if depvers[:maximum_version]
466
+ file.puts " maximum_version: #{depvers[:maximum_version]}"
467
+ end
468
+ file.puts ' native: true'
455
469
  end
456
- file.puts ' <native/>'
457
- file.puts ' </dependency>'
458
470
  end
459
471
  @gemdep.each do |gemdep|
460
- file.puts ' <dependency>'
461
- file.puts " <name>#{gemdep}</name>"
462
- file.puts ' </dependency>'
472
+ file.puts " - name: #{gemdep}"
463
473
  end
464
- file.puts ' </dependencies>'
465
474
  end
466
- file.puts '</tpkg>'
467
475
  end
468
476
 
469
477
  # Make package
data/bin/tpkg CHANGED
@@ -1,12 +1,14 @@
1
1
  #!/usr/bin/ruby -w
2
2
  ##############################################################################
3
3
  # tpkg package management system
4
- # Copyright 2009, 2010, 2011 AT&T Interactive
5
4
  # License: MIT (http://www.opensource.org/licenses/mit-license.php)
6
5
  ##############################################################################
7
6
 
8
- # Ensure we can find tpkg.rb
9
- $:.unshift File.join(File.dirname(__FILE__), "..", "lib")
7
+ # When run from the source repository or from an unpacked copy of the
8
+ # distribution we want to find the local library, even if there's a copy
9
+ # installed on the system. The installed copy is likely older, and the API
10
+ # may be out of sync with this executable.
11
+ $:.unshift(File.expand_path('../lib', File.dirname(__FILE__)))
10
12
 
11
13
  require 'optparse'
12
14
  require 'tpkg'
@@ -20,15 +22,23 @@ require 'tpkg'
20
22
  @debug = false
21
23
  @prompt = true
22
24
  @quiet = false
23
- @sudo = true
24
- @lockforce = false
25
+ @sudo = nil
26
+ # Neither of the common Windows environments for running Ruby have
27
+ # sudo, so turn it off by default on those platforms
28
+ if RUBY_PLATFORM == 'i386-mingw32' || RUBY_PLATFORM == 'i386-cygwin'
29
+ @sudo = false
30
+ else
31
+ @sudo = true
32
+ end
25
33
  @force = false
26
34
  @deploy = false
27
35
  @deploy_params = ARGV # hold parameters for how to invoke tpkg on the machines we're deploying to
28
36
  @deploy_options = {} # options for how to run the deployer
29
- @servers = nil
37
+ @servers = []
38
+ @groups = nil
30
39
  @worker_count = 10
31
40
  @rerun_with_sudo = false
41
+ @sources = []
32
42
  @tpkg_options = {} # options for instantiating Tpkg object
33
43
  @init_options = {} # options for how to run init scripts
34
44
  @other_options = {}
@@ -37,7 +47,7 @@ require 'tpkg'
37
47
 
38
48
  def rerun_with_sudo_if_necessary
39
49
  if Process.euid != 0 && @sudo
40
- warn "Executing with sudo"
50
+ warn "Executing with sudo" if !@quiet
41
51
  # Depending on how sudo is configured it might remove TPKG_HOME from the
42
52
  # environment. As such we set the base as a command line option to ensure
43
53
  # it survives the sudo process.
@@ -49,21 +59,53 @@ def rerun_with_sudo_if_necessary
49
59
  end
50
60
  end
51
61
 
62
+ # This method can only be safely called after command line option parsing is
63
+ # complete
64
+ @config_file_settings = nil
65
+ def parse_config_files
66
+ if @config_file_settings
67
+ return @config_file_settings
68
+ end
69
+
70
+ # FIXME: Move config file parsing to tpkg.rb
71
+ # http://sourceforge.net/apps/trac/tpkg/ticket/28
72
+ fsroot = @tpkg_options[:file_system_root] ? @tpkg_options[:file_system_root] : ''
73
+ settings = {:sources => []}
74
+ [File.join(fsroot, Tpkg::DEFAULT_CONFIGDIR, 'tpkg.conf'),
75
+ File.join(fsroot, ENV['HOME'], ".tpkg.conf")].each do |configfile|
76
+ if File.exist?(configfile)
77
+ IO.foreach(configfile) do |line|
78
+ line.chomp!
79
+ next if (line =~ /^\s*$/); # Skip blank lines
80
+ next if (line =~ /^\s*#/); # Skip comments
81
+ line.strip! # Remove leading/trailing whitespace
82
+ key, value = line.split(/\s*=\s*/, 2)
83
+ if key == 'base'
84
+ settings[:base] = value
85
+ puts "Loaded base #{value} from #{configfile}" if @debug
86
+ elsif key == 'source'
87
+ settings[:sources] << value
88
+ puts "Loaded source #{value} from #{configfile}" if @debug
89
+ elsif key == 'report_server'
90
+ settings[:report_server] = value
91
+ puts "Loaded report server #{value} from #{configfile}" if @debug
92
+ elsif key == 'host_group_script'
93
+ settings[:host_group_script] = value
94
+ puts "Loaded host group script #{value} from #{configfile}" if @debug
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ @config_file_settings = settings
101
+ end
102
+
52
103
  opts = OptionParser.new(nil, 24, ' ')
53
104
  opts.banner = 'Usage: tpkg [options]'
54
- opts.on('--servers', '-s', '=SERVERS', Array, 'Servers to apply the actions to') do |opt|
55
- @servers = opt
56
- @deploy = true
57
- @deploy_params = @deploy_params - ['--servers', '-s', @servers.join(","), "--servers=#{@servers.join(',')}"]
58
- end
59
105
  opts.on('--make', '-m', '=DIRECTORY', 'Make a package out of the contents of the directory') do |opt|
60
106
  @action = :make
61
107
  @action_value = opt
62
108
  end
63
- opts.on('--extract', '-x', '=DIRECTORY', 'Extract the metadata for a directory of packages') do |opt|
64
- @action = :extract
65
- @action_value = opt
66
- end
67
109
  installexample = " --install pkgname=version=package_version\n (Example: tpkg --install hive=2.1) Will install hive version 2.1"
68
110
  opts.on('--install', '-i', '=PACKAGES', "Install one or more packages\n#{installexample}", Array) do |opt|
69
111
  @rerun_with_sudo = true
@@ -81,6 +123,27 @@ opts.on('--downgrade', '=PACKAGES', 'Downgrade one or more packages', Array) do
81
123
  @action = :upgrade
82
124
  @action_value = opt
83
125
  end
126
+ opts.on('--servers', '-s', '=SERVERS', Array, 'Servers on which to apply actions, defaults to local') do |opt|
127
+ @servers.concat(opt)
128
+ @deploy = true
129
+ # FIXME: this won't remove options that the user specified as an
130
+ # abbreviation. I.e. if the option is --servers and the user specified
131
+ # --serv (which OptionParser will accept as long as it is unambiguous) this
132
+ # won't detect and remove it.
133
+ @deploy_params = @deploy_params - ['--servers', '-s', @servers.join(","), "--servers=#{@servers.join(',')}"]
134
+ end
135
+ opts.on('--groups', '-g', '=GROUP', Array, 'Group of server on which to apply actions') do |opt|
136
+ # We'll finish processing this later. To expand the groups we need the name
137
+ # of the host_group_script from the config file, but we can't safely call
138
+ # parse_config_files until we're done processing command line options.
139
+ @groups = opt
140
+ @deploy = true
141
+ # FIXME: this won't remove options that the user specified as an
142
+ # abbreviation. I.e. if the option is --servers and the user specified
143
+ # --serv (which OptionParser will accept as long as it is unambiguous) this
144
+ # won't detect and remove it.
145
+ @deploy_params = @deploy_params - ['--groups', '-g', @groups.join(","), "--servers=#{@groups.join(',')}"]
146
+ end
84
147
  opts.on('--ua', 'Upgrade all packages') do |opt|
85
148
  @rerun_with_sudo = true
86
149
  @action = :upgrade
@@ -174,7 +237,7 @@ opts.on('--init-cmd', '=CMD', 'Invoke specified init script command') do |opt|
174
237
  @rerun_with_sudo = true
175
238
  @init_options[:cmd] = opt
176
239
  end
177
- opts.on('--query', '-q', '=NAMES', 'List installed packages', Array) do |opt|
240
+ opts.on('--query', '-q', '=NAMES', 'Check if a package is installed', Array) do |opt|
178
241
  # People mistype -qa instead of --qa frequently
179
242
  if opt == ['a']
180
243
  warn "NOTE: tpkg -qa queries for a pkg named 'a', you probably want --qa for all pkgs"
@@ -182,49 +245,67 @@ opts.on('--query', '-q', '=NAMES', 'List installed packages', Array) do |opt|
182
245
  @action = :query_installed
183
246
  @action_value = opt
184
247
  end
248
+ # --qv is deprecated
249
+ opts.on('--qs', '--qv', '=NAME', 'Check if a package is available on server', Array) do |opt|
250
+ @action = :query_available
251
+ @action_value = opt
252
+ end
185
253
  opts.on('--qa', 'List all installed packages') do |opt|
186
254
  @action = :query_installed
187
255
  end
188
- opts.on('--qi', '=NAME', 'Info for packages') do |opt|
256
+ # --qva is deprecated
257
+ opts.on('--qas', '--qva', 'List all packages on server') do |opt|
258
+ @action = :query_available
259
+ end
260
+ opts.on('--qi', '=NAME', 'Display the info for a package') do |opt|
189
261
  @action = :query_info
190
262
  @action_value = opt
191
263
  end
192
- opts.on('--ql', '=NAME', 'List files in installed packages') do |opt|
193
- @action = :query_list_files
264
+ opts.on('--qis', '=NAME', 'Display the info for a package on the server') do |opt|
265
+ @action = :query_info_available
194
266
  @action_value = opt
195
267
  end
196
- opts.on('--qf', '=FILE', 'List the package that owns a file') do |opt|
197
- @action = :query_who_owns_file
268
+ opts.on('--ql', '=NAME', 'List the files in a package') do |opt|
269
+ @action = :query_list_files
198
270
  @action_value = opt
199
271
  end
200
- opts.on('--qv', '=NAME', 'List available packages') do |opt|
201
- @action = :query_available
272
+ opts.on('--qls', '=NAME', 'List the files in a package on the server') do |opt|
273
+ @action = :query_list_files_available
202
274
  @action_value = opt
203
275
  end
204
- opts.on('--qva', 'List all available packages') do |opt|
205
- @action = :query_available
276
+ opts.on('--qf', '=FILE', 'List the package that owns a file') do |opt|
277
+ @action = :query_who_owns_file
278
+ @action_value = opt
206
279
  end
207
280
  opts.on('--qr', '=NAME', 'List installed packages that require package') do |opt|
208
281
  @action = :query_requires
209
282
  @action_value = opt
210
283
  end
211
- opts.on('--qd', '=NAME', 'List the packages that package depends on') do |opt|
284
+ opts.on('--qd', '=NAME', 'List the packages on which the given package depends') do |opt|
212
285
  @action = :query_depends
213
286
  @action_value = opt
214
287
  end
215
- opts.on('--qld', '=NAME', 'Similar to --qd, but only look at local packages') do |opt|
216
- @action = :query_local_depends
288
+ opts.on('--qds', '=NAME', 'List pkgs on which given package on server depends') do |opt|
289
+ @action = :query_depends_available
217
290
  @action_value = opt
218
291
  end
219
292
 
220
293
  opts.on('--dw', '=INTEGER', 'Number of workers for deploying') do |opt|
221
294
  @worker_count = opt.to_i
295
+ # FIXME: this won't remove options that the user specified as an
296
+ # abbreviation. I.e. if the option is --servers and the user specified
297
+ # --serv (which OptionParser will accept as long as it is unambiguous) this
298
+ # won't detect and remove it.
222
299
  @deploy_params = @deploy_params - ['--dw', @worker_count, "--dw=#{opt}"]
223
300
  end
224
- opts.on('--qX', '=FILENAME', 'Display tpkg.xml or tpkg.yml of the given package') do |opt|
301
+ opts.on('--qX', '=FILENAME', 'Display raw metadata (tpkg.yml) of the given package') do |opt|
225
302
  @action = :query_tpkg_metadata
226
303
  @action_value = opt
227
304
  end
305
+ opts.on('--qXs', '=FILENAME', 'Display raw metadata of given package on the server') do |opt|
306
+ @action = :query_tpkg_metadata_available
307
+ @action_value = opt
308
+ end
228
309
  opts.on('--history', 'Display package installation history') do |opt|
229
310
  @action = :query_history
230
311
  end
@@ -237,8 +318,12 @@ end
237
318
  opts.on('--base', '=BASE', 'Base directory for tpkg operations') do |opt|
238
319
  @tpkg_options[:base] = opt
239
320
  end
321
+ opts.on('--extract', '-x', '=DIRECTORY', 'Extract the metadata for a directory of packages') do |opt|
322
+ @action = :extract
323
+ @action_value = opt
324
+ end
240
325
  opts.on('--source', '=NAME', 'Sources where packages are located', Array) do |opt|
241
- @tpkg_options[:sources] = opt
326
+ @sources = opt
242
327
  end
243
328
  opts.on('--download', '=PACKAGES', 'Download one or more packages', Array) do |opt|
244
329
  @action = :download
@@ -255,7 +340,7 @@ opts.on('--no-sudo', 'No calls to sudo for operations that might need root') do
255
340
  @sudo = opt
256
341
  end
257
342
  opts.on('--lock-force', 'Force the removal of an existing lockfile') do |opt|
258
- @lockforce = opt
343
+ @tpkg_options[:lockforce] = opt
259
344
  end
260
345
  opts.on('--force-replace', 'Replace conflicting pkgs with the new one(s)') do |opt|
261
346
  @other_options[:force_replace] = opt
@@ -263,20 +348,38 @@ end
263
348
  opts.on('--force', 'Force the execution of a given task') do |opt|
264
349
  @force = opt
265
350
  end
266
- opts.on('-o', '--out', '=DIR', 'Output directory for the -m option') do |opt|
351
+ opts.on('-o', '--out', '=DIR', 'Output directory for the --make option') do |opt|
267
352
  @other_options[:out] = opt
268
353
  end
354
+ opts.on('--skip-remove-stop', 'Do not run init script stop on package removal') do |opt|
355
+ @other_options[:skip_remove_stop] = opt
356
+ end
269
357
  opts.on('--use-ssh-key [FILE]', 'Use ssh key for deploying instead of password') do |opt|
270
358
  @deploy_options["use-ssh-key"] = true
271
359
  @deploy_options["ssh-key"] = opt
360
+ # FIXME: this won't remove options that the user specified as an
361
+ # abbreviation. I.e. if the option is --servers and the user specified
362
+ # --serv (which OptionParser will accept as long as it is unambiguous) this
363
+ # won't detect and remove it.
272
364
  @deploy_params = @deploy_params - ['--use-ssh-key', opt, "--use-ssh-key=#{opt}"]
273
365
  end
274
366
  opts.on('--deploy-as', '=USERNAME', 'What username to use for deploying to remote server') do |opt|
275
367
  @deploy_options["deploy-as"] = opt
368
+ # FIXME: this won't remove options that the user specified as an
369
+ # abbreviation. I.e. if the option is --servers and the user specified
370
+ # --serv (which OptionParser will accept as long as it is unambiguous) this
371
+ # won't detect and remove it.
276
372
  @deploy_params = @deploy_params - ['--deploy-as']
277
373
  end
278
- opts.on('--compress', '=[TYPE]', 'Compress files when making packages') do |opt|
279
- if opt == "no"
374
+ acceptable_compress_arguments = ['gzip', 'bz2', 'no']
375
+ opts.on('--compress [TYPE]',
376
+ acceptable_compress_arguments,
377
+ "Compress files when making packages " +
378
+ "(#{acceptable_compress_arguments.join(',')})") do |opt|
379
+ # Acceptable
380
+ if opt == nil # No argument specified by user
381
+ @compress = true
382
+ elsif opt == "no"
280
383
  @compress= false
281
384
  else
282
385
  @compress = opt
@@ -297,7 +400,14 @@ opts.on_tail("-h", "--help", "Show this message") do
297
400
  exit
298
401
  end
299
402
 
300
- leftovers = opts.parse(ARGV)
403
+ leftovers = nil
404
+ begin
405
+ leftovers = opts.parse(ARGV)
406
+ rescue OptionParser::ParseError => e
407
+ $stderr.puts "Error parsing arguments, try --help"
408
+ $stderr.puts e.message
409
+ exit 1
410
+ end
301
411
 
302
412
  # Rerun with sudo if necessary, unless it's a deploy, then
303
413
  # we don't need to run with sudo on this machine. It will run with sudo
@@ -312,69 +422,75 @@ if !@action
312
422
  exit
313
423
  end
314
424
 
425
+ if @groups
426
+ settings = parse_config_files
427
+ if settings[:host_group_script]
428
+ if !File.executable?(settings[:host_group_script])
429
+ warn "Warning: host group script #{settings[:host_group_script]} is not executable, execution will likely fail"
430
+ end
431
+ servers = []
432
+ @groups.each do |group|
433
+ IO.popen(settings[:host_group_script]) do |pipe|
434
+ pipe.each_line do |line|
435
+ servers << line.chomp
436
+ end
437
+ end
438
+ end
439
+ servers.uniq!
440
+ puts "Expanded groups into #{servers.length} servers" if @debug
441
+ @servers.concat(servers)
442
+ else
443
+ abort "No host_group_script defined in config files, can't expand groups"
444
+ end
445
+ end
446
+
315
447
  #
316
448
  # Figure out base directory, sources and other configuration
317
449
  #
318
450
 
319
- def instantiate_tpkg(options = {})
320
- base = options[:base]
321
- sources = options[:sources] || []
322
- report_server = nil
451
+ def instantiate_tpkg
452
+ settings = parse_config_files
323
453
 
324
454
  # base can come from four possible places. They take precedence in this
325
455
  # order:
326
456
  # - command line option
327
457
  # - TPKG_HOME environment variable
328
458
  # - config file
329
- # - Tpkg::DEFAULT_BASE
330
-
459
+ # - Tpkg::DEFAULT_BASE constant defined in tpkg.rb
331
460
  if ENV['TPKG_HOME']
332
- if !base
333
- base = ENV['TPKG_HOME']
461
+ if !@tpkg_options[:base]
462
+ @tpkg_options[:base] = ENV['TPKG_HOME']
334
463
  # Warn the user, as this could potentially be confusing
335
464
  # if they don't realize there's an environment variable set.
336
- warn "Using base '#{base}' base from $TPKG_HOME"
465
+ warn "Using base '#{@tpkg_options[:base]}' base from $TPKG_HOME" if !@quiet
337
466
  else
338
- warn "Ignoring TPKG_HOME" if @debug
467
+ warn "Ignoring $TPKG_HOME" if @debug
339
468
  end
340
469
  end
341
-
342
- # FIXME: Move config file parsing to tpkg.rb
343
- # http://sourceforge.net/apps/trac/tpkg/ticket/28
344
- fsroot = options[:file_system_root] ? options[:file_system_root] : ''
345
- [File.join(fsroot, Tpkg::DEFAULT_CONFIGDIR, 'tpkg.conf'), File.join(fsroot, ENV['HOME'], ".tpkg.conf")].each do |configfile|
346
- if File.exist?(configfile)
347
- IO.foreach(configfile) do |line|
348
- line.chomp!
349
- next if (line =~ /^\s*$/); # Skip blank lines
350
- next if (line =~ /^\s*#/); # Skip comments
351
- line.strip! # Remove leading/trailing whitespace
352
- key, value = line.split(/\s*=\s*/, 2)
353
- if key == 'base'
354
- if !base
355
- # Warn the user, as this could potentially be confusing
356
- # if they don't realize there's a config file lying
357
- # around
358
- base = value
359
- warn "Using base #{base} from #{configfile}"
360
- else
361
- warn "Ignoring 'base' option in #{@configfile}" if @debug
362
- end
363
- elsif key == 'source'
364
- sources << value
365
- puts "Loaded source #{value} from #{configfile}" if (@debug)
366
- elsif key == 'report_server'
367
- report_server = value
368
- puts "Loaded report server #{report_server} from #{configfile}" if (@debug)
369
- end
370
- end
470
+ if settings[:base]
471
+ if !@tpkg_options[:base]
472
+ # Warn the user, as this could potentially be confusing
473
+ # if they don't realize there's a config file lying
474
+ # around
475
+ @tpkg_options[:base] = settings[:base]
476
+ warn "Using base #{@tpkg_options[:base]} from config file" if !@quiet
477
+ else
478
+ warn "Ignoring 'base' option in config file" if @debug
371
479
  end
372
480
  end
373
-
374
- if !base
375
- base = Tpkg::DEFAULT_BASE
481
+ if !@tpkg_options[:base]
482
+ @tpkg_options[:base] = Tpkg::DEFAULT_BASE
376
483
  end
377
484
 
485
+ # Sources can come from the command line and config files. We use the
486
+ # combined set of sources.
487
+ @sources.concat(settings[:sources])
488
+ @tpkg_options[:sources] = @sources
489
+
490
+ @tpkg_options[:report_server] = settings[:report_server]
491
+ @tpkg_options[:sudo] = @sudo
492
+ @tpkg_options[:force] = @force
493
+
378
494
  if !@sudo
379
495
  curruid = Process.euid
380
496
  if curruid == 0
@@ -383,23 +499,19 @@ def instantiate_tpkg(options = {})
383
499
  # modified by other users who properly run --no-sudo as a regular user.
384
500
  raise "--no-sudo cannot be used as 'root' user or via sudo"
385
501
  end
386
- baseuid = File.stat(base).uid
387
- # We want to ensure that all --no-sudo usage within a given base directory
388
- # is done under the same account.
389
- if baseuid != curruid
390
- raise "Base dir #{base} owned by UID #{baseuid}, not your UID #{curruid}"
502
+ fsroot = @tpkg_options[:file_system_root] ? @tpkg_options[:file_system_root] : ''
503
+ base = File.join(fsroot, @tpkg_options[:base])
504
+ if File.exist?(base)
505
+ baseuid = File.stat(base).uid
506
+ # We want to ensure that all --no-sudo usage within a given base directory
507
+ # is done under the same account.
508
+ if baseuid != curruid
509
+ raise "Base dir #{@tpkg_options[:base]} owned by UID #{baseuid}, not your UID #{curruid}"
510
+ end
391
511
  end
392
512
  end
393
513
 
394
- # FIXME: This is ugly. We would set the appropriate things in options and
395
- # call Tpkg.new(options)
396
- tpkg = Tpkg.new(:file_system_root => options[:file_system_root],
397
- :base => base,
398
- :sources => sources,
399
- :report_server => report_server,
400
- :lockforce => @lockforce,
401
- :force => @force,
402
- :sudo => @sudo)
514
+ tpkg = Tpkg.new(@tpkg_options)
403
515
  end
404
516
 
405
517
  passphrase_callback = lambda do | package |
@@ -466,25 +578,25 @@ when :make
466
578
  when :extract
467
579
  Tpkg::extract_metadata(@action_value)
468
580
  when :install
469
- tpkg = instantiate_tpkg(@tpkg_options)
581
+ tpkg = instantiate_tpkg
470
582
  ret_val = tpkg.install(@action_value, passphrase_callback, @other_options)
471
583
  when :upgrade
472
- tpkg = instantiate_tpkg(@tpkg_options)
584
+ tpkg = instantiate_tpkg
473
585
  ret_val = tpkg.upgrade(@action_value, passphrase_callback, @other_options)
474
586
  when :remove
475
- tpkg = instantiate_tpkg(@tpkg_options)
587
+ tpkg = instantiate_tpkg
476
588
  ret_val = tpkg.remove(@action_value, @other_options)
477
589
  when :download
478
- tpkg = instantiate_tpkg(@tpkg_options)
590
+ tpkg = instantiate_tpkg
479
591
  ret_val = tpkg.download_pkgs(@action_value, @other_options)
480
592
  when :verify
481
593
  result = nil
482
594
  # Verify a given .tpkg file
483
- if File.exist?(@action_value)
595
+ if File.file?(@action_value)
484
596
  Tpkg::verify_package_checksum(@action_value)
485
597
  # Verify an installed pkg
486
598
  else
487
- tpkg = instantiate_tpkg(@tpkg_options)
599
+ tpkg = instantiate_tpkg
488
600
  results = tpkg.verify_file_metadata([@action_value])
489
601
  if results.length == 0
490
602
  puts "No package found"
@@ -501,206 +613,525 @@ when :verify
501
613
  puts "Package verification failed" unless success
502
614
  end
503
615
  when :execute_init
504
- tpkg = instantiate_tpkg(@tpkg_options)
616
+ tpkg = instantiate_tpkg
505
617
  if @init_options[:cmd].nil?
506
618
  raise "You didn't specify what init command to run"
507
619
  end
508
620
  ret_val = tpkg.execute_init(@init_options)
509
621
  when :query_installed
510
- tpkg = instantiate_tpkg(@tpkg_options)
511
- queryreq = nil
622
+ tpkg = instantiate_tpkg
512
623
  matches = []
513
624
  if @action_value
514
- @action_value.each do | value |
515
- queryreq = Tpkg::parse_request(value, tpkg.installed_directory)
516
- match = tpkg.installed_packages_that_meet_requirement(queryreq)
517
-
625
+ # The --query switch is set to accept multiple values (the "Array"
626
+ # parameter in the opts.on line for --query) so users can do things like
627
+ # "tpkg -q foo,bar" to check multiple packages at once. As such
628
+ # @action_value is an Array for this switch.
629
+ requirements = []
630
+ packages = {}
631
+ tpkg.parse_requests(@action_value, requirements, packages,
632
+ :installed_only => true)
633
+ if packages.values.all? {|pkg| pkg.empty?}
518
634
  # If the user requested specific packages and we found no matches
519
635
  # then exit with a non-zero value to indicate failure. This allows
520
636
  # command-line syntax like "tpkg -q foo || tpkg -i foo" to ensure
521
637
  # that a package is installed.
522
- ret_val = 1 if match.empty?
523
- matches |= match
638
+ ret_val = 1
639
+ $stderr.puts "No packages matching '#{@action_value}' installed" if !@quiet
640
+ else
641
+ packages.each do | name, pkgs |
642
+ matches.concat(pkgs)
643
+ end
524
644
  end
525
645
  else
526
- matches = tpkg.installed_packages_that_meet_requirement(queryreq)
646
+ # --qa is implemented by setting @action to :query_installed and
647
+ # @action_value to nil
648
+ matches = tpkg.installed_packages_that_meet_requirement
649
+ if matches.empty?
650
+ ret_val = 1
651
+ $stderr.puts "No packages installed" if !@quiet
652
+ end
527
653
  end
528
-
529
654
  if !@quiet
530
655
  matches.sort(&Tpkg::SORT_PACKAGES).each do |pkg|
531
656
  puts pkg[:metadata][:filename]
532
657
  end
533
658
  end
534
659
  when :query_info
535
- metadatas = nil
536
- if File.exist?(@action_value)
537
- metadatas = [Tpkg::metadata_from_package(@action_value)]
538
- else
539
- tpkg = instantiate_tpkg(@tpkg_options)
540
- metadatas = []
541
- requirements = []
542
- packages = {}
543
- tpkg.parse_requests([@action_value], requirements, packages)
544
- packages.each do | name, pkgs |
545
- pkgs.each do | pkg |
546
- metadatas << pkg[:metadata]
547
- end
660
+ tpkg = instantiate_tpkg
661
+ requirements = []
662
+ packages = {}
663
+ tpkg.parse_requests([@action_value], requirements, packages,
664
+ :installed_only => true)
665
+ metadatas = []
666
+ packages.each do | name, pkgs |
667
+ pkgs.each do | pkg |
668
+ metadatas << pkg[:metadata]
548
669
  end
549
670
  end
671
+ output_strings = []
550
672
  already_displayed = {}
551
673
  metadatas.each do |metadata|
552
674
  next if already_displayed[metadata[:filename]]
553
675
  already_displayed[metadata[:filename]] = true
676
+ output_string = ''
554
677
  [:name, :version, :package_version, :operatingsystem, :architecture, :maintainer, :description, :bugreporting].each do |field|
555
678
  metadata[field] = 'any' if field == :operatingsystem && metadata[field].nil?
556
679
  metadata[field] = 'any' if field == :architecture && metadata[field].nil?
557
680
  if metadata[field]
558
681
  if metadata[field].kind_of?(Array)
559
- puts "#{field}: #{metadata[field].join(',')}"
682
+ output_string << "#{field}: #{metadata[field].join(',')}\n"
560
683
  else
561
- puts "#{field}: #{metadata[field]}"
684
+ output_string << "#{field}: #{metadata[field]}\n"
562
685
  end
563
686
  end
564
687
  end
688
+ if metadata[:dependencies]
689
+ output_string << "(This package depends on other packages, use --qd to view the dependencies)\n"
690
+ end
691
+ # Older versions of tpkg did not insert a tpkg_version field into the
692
+ # package metadata when building packages
565
693
  tpkg_version = metadata[:tpkg_version] || "< 1.26.1"
566
- puts "This package was built with tpkg version #{tpkg_version}."
567
- if metadata[:dependencies]
568
- puts "This package depends on other packages, use --qd/--qld to view the dependencies."
694
+ output_string << "(This package was built with tpkg version #{tpkg_version})\n"
695
+ output_strings << output_string
696
+ end
697
+ print output_strings.join("================================================================================\n")
698
+ if already_displayed.empty?
699
+ ret_val = 1
700
+ # --qi --quiet doesn't seem like a meaningful combination, so I'm not
701
+ # suppressing this for @quiet
702
+ $stderr.puts "No packages matching '#{@action_value}' installed"
703
+ end
704
+ when :query_info_available
705
+ tpkg = instantiate_tpkg
706
+ requirements = []
707
+ packages = {}
708
+ tpkg.parse_requests([@action_value], requirements, packages)
709
+ availpkgs = []
710
+ packages.each do | name, pkgs |
711
+ availpkgs.concat(pkgs)
712
+ end
713
+ available = availpkgs.select do |pkg|
714
+ pkg[:source] != :native_installed &&
715
+ pkg[:source] != :native_available &&
716
+ pkg[:source] != :currently_installed
717
+ end
718
+ if available.empty?
719
+ ret_val = 1
720
+ # --qis --quiet doesn't seem like a meaningful combination, so I'm not
721
+ # suppressing this for @quiet
722
+ $stderr.puts "No packages matching '#{@action_value}' available"
723
+ else
724
+ metadatas = available.collect {|avail| avail[:metadata]}
725
+ output_strings = []
726
+ already_displayed = {}
727
+ metadatas.each do |metadata|
728
+ next if already_displayed[metadata[:filename]]
729
+ already_displayed[metadata[:filename]] = true
730
+ output_string = ''
731
+ [:name, :version, :package_version, :operatingsystem, :architecture, :maintainer, :description, :bugreporting].each do |field|
732
+ metadata[field] = 'any' if field == :operatingsystem && metadata[field].nil?
733
+ metadata[field] = 'any' if field == :architecture && metadata[field].nil?
734
+ if metadata[field]
735
+ if metadata[field].kind_of?(Array)
736
+ output_string << "#{field}: #{metadata[field].join(',')}\n"
737
+ else
738
+ output_string << "#{field}: #{metadata[field]}\n"
739
+ end
740
+ end
741
+ end
742
+ if metadata[:dependencies]
743
+ output_string << "(This package depends on other packages, use --qd to view the dependencies)\n"
744
+ end
745
+ # Older versions of tpkg did not insert a tpkg_version field into the
746
+ # package metadata when building packages
747
+ tpkg_version = metadata[:tpkg_version] || "< 1.26.1"
748
+ output_string << "(This package was built with tpkg version #{tpkg_version})\n"
749
+ output_strings << output_string
569
750
  end
570
- puts "================================================================================"
751
+ print output_strings.join("================================================================================\n")
571
752
  end
572
753
  when :query_list_files
573
- tpkg = instantiate_tpkg(@tpkg_options)
574
- pkgfiles = nil
575
- if File.exist?(@action_value)
576
- fipfile = Tpkg::files_in_package(@action_value)
577
- tpkg.normalize_paths(fipfile)
578
- puts "#{@action_value}:"
579
- fipfile[:normalized].each { |file| puts file }
754
+ tpkg = instantiate_tpkg
755
+ requirements = []
756
+ packages = {}
757
+ tpkg.parse_requests([@action_value], requirements, packages,
758
+ :installed_only => true)
759
+ if packages.values.all? {|pkg| pkg.empty?}
760
+ ret_val = 1
761
+ # --ql --quiet doesn't seem like a meaningful combination, so I'm not
762
+ # suppressing this for @quiet
763
+ $stderr.puts "No packages matching '#{@action_value}' installed"
580
764
  else
581
- pkgfiles = []
582
- metadatas = []
583
- requirements = []
584
- packages = {}
585
-
586
- queryreq = Tpkg::parse_request(@action_value)
587
- instpkgs = tpkg.installed_packages_that_meet_requirement(queryreq)
588
- if instpkgs.nil? or instpkgs.empty?
589
- ret_val = 1
590
- puts "Could not find any installed packages that meet the request \"#{@action_value}\""
591
- end
592
- instpkgs.each do | pkg |
593
- pkgfiles << pkg[:metadata][:filename]
765
+ # For this switch we need separate handling for installed and uninstalled
766
+ # packages. For installed packages we know where their relocatable files
767
+ # ended up and can give the user regular paths. For uninstalled packaages
768
+ # we don't know what base will be used when the package is installed, so
769
+ # we need to indicate to the user that their are relocatable files and
770
+ # just display their path relative to the eventual base directory.
771
+ ci_pkgfiles = []
772
+ packages.each do | name, pkgs |
773
+ pkgs.each do | pkg |
774
+ if pkg[:source] == :currently_installed
775
+ ci_pkgfiles << pkg[:metadata][:filename]
776
+ else
777
+ puts "#{pkg[:source]}:"
778
+ fip = Tpkg.files_in_package(pkg[:source])
779
+ fip[:root].each { |file| puts file }
780
+ fip[:reloc].each { |file| puts '<relocatable>/' + file }
781
+ end
782
+ end
594
783
  end
595
-
596
- files = tpkg.files_for_installed_packages(pkgfiles)
784
+ files = tpkg.files_for_installed_packages(ci_pkgfiles)
597
785
  files.each do |pkgfile, fip|
598
786
  puts "#{pkgfile}:"
599
787
  fip[:normalized].each { |file| puts file }
600
788
  end
601
789
  end
790
+ when :query_list_files_available
791
+ tpkg = instantiate_tpkg
792
+ requirements = []
793
+ packages = {}
794
+ tpkg.parse_requests([@action_value], requirements, packages)
795
+ availpkgs = []
796
+ packages.each do | name, pkgs |
797
+ availpkgs.concat(pkgs)
798
+ end
799
+ available = availpkgs.select do |pkg|
800
+ pkg[:source] != :native_installed &&
801
+ pkg[:source] != :native_available &&
802
+ pkg[:source] != :currently_installed
803
+ end
804
+ if available.empty?
805
+ ret_val = 1
806
+ # --qls --quiet doesn't seem like a meaningful combination, so I'm not
807
+ # suppressing this for @quiet
808
+ $stderr.puts "No packages matching '#{@action_value}' available"
809
+ else
810
+ downloaddir = Tpkg::tempdir('download')
811
+ available.each do |pkg|
812
+ # FIXME: I've duplicated from the install and upgrade methods this logic
813
+ # to calculate pkgfile, it should be encapsulated in a method
814
+ pkgfile = nil
815
+ if File.file?(pkg[:source])
816
+ pkgfile = pkg[:source]
817
+ elsif File.directory?(pkg[:source])
818
+ pkgfile = File.join(pkg[:source], pkg[:metadata][:filename])
819
+ else
820
+ pkgfile = download(pkg[:source], pkg[:metadata][:filename], downloaddir)
821
+ end
822
+ puts "#{pkg[:metadata][:filename]}:"
823
+ fip = Tpkg.files_in_package(pkgfile)
824
+ fip[:root].each { |file| puts file }
825
+ fip[:reloc].each { |file| puts '<relocatable>/' + file }
826
+ end
827
+ FileUtils.rm_rf(downloaddir)
828
+ end
602
829
  when :query_who_owns_file
603
- tpkg = instantiate_tpkg(@tpkg_options)
830
+ tpkg = instantiate_tpkg
831
+ owned = false
832
+ expanded_file = File.expand_path(@action_value)
604
833
  tpkg.files_for_installed_packages.each do |pkgfile, fip|
605
834
  fip[:normalized].each do |file|
606
- if file == File.expand_path(@action_value)
835
+ if file == expanded_file
607
836
  puts "#{file}: #{pkgfile}"
837
+ owned = true
608
838
  end
609
839
  end
610
840
  end
841
+ if !owned
842
+ ret_val = 1
843
+ # --qf --quiet doesn't seem like a meaningful combination, so I'm not
844
+ # suppressing this for @quiet
845
+ $stderr.puts "No package owns file '#{@action_value}'"
846
+ end
611
847
  when :query_available
612
- tpkg = instantiate_tpkg(@tpkg_options)
613
- queryreq = nil
848
+ tpkg = instantiate_tpkg
849
+ availpkgs = []
614
850
  if @action_value
615
- queryreq = Tpkg::parse_request(@action_value)
851
+ # The --qs switch is set to accept multiple values (the "Array"
852
+ # parameter in the opts.on line for --qs) so users can do things like
853
+ # "tpkg --qs foo,bar" to check multiple packages at once. As such
854
+ # @action_value is an Array for this switch.
855
+ requirements = []
856
+ packages = {}
857
+ tpkg.parse_requests(@action_value, requirements, packages)
858
+ packages.each do | name, pkgs |
859
+ availpkgs.concat(pkgs)
860
+ end
861
+ else
862
+ # --qas is implemented by setting @action to :query_available and
863
+ # @action_value to nil
864
+ availpkgs.concat(tpkg.available_packages_that_meet_requirement)
616
865
  end
617
- tpkg.available_packages_that_meet_requirement(queryreq).each do |pkg|
618
- next if pkg[:source] == :native_installed
619
- next if pkg[:source] == :native_available
620
- puts "#{pkg[:metadata][:filename]} (#{pkg[:source]})"
866
+ available = availpkgs.select do |pkg|
867
+ pkg[:source] != :native_installed &&
868
+ pkg[:source] != :native_available &&
869
+ # The tpkg library treats currently installed packages as "available"
870
+ # because they are available to meet a user's requirement. I.e. if the
871
+ # user asks to install ruby and a ruby package is already installed that
872
+ # satisfies the user's requirement even if there's no ruby package in any
873
+ # of the sources. But for these query options I think the reasonable
874
+ # interpretation is that the user would like to know if there's a package
875
+ # in a source that could be installed. For example, if the user queries
876
+ # for the availability of ruby and we show it as available because it is
877
+ # installed, but then they go to another machine with the same sources
878
+ # defined and try to install ruby and it fails because there is no ruby in
879
+ # any of the sources I think the user is likely to find that unexpected
880
+ # and annoying.
881
+ pkg[:source] != :currently_installed
882
+ end
883
+ if available.empty?
884
+ ret_val = 1
885
+ if !@quiet
886
+ if @action_value
887
+ $stderr.puts "No packages matching '#{@action_value}' available"
888
+ else
889
+ $stderr.puts "No packages available"
890
+ end
891
+ end
892
+ else
893
+ if !@quiet
894
+ available.sort(&Tpkg::SORT_PACKAGES).each do |pkg|
895
+ puts "#{pkg[:metadata][:filename]} (#{pkg[:source]})"
896
+ end
897
+ end
621
898
  end
622
899
  when :query_requires
623
- tpkg = instantiate_tpkg(@tpkg_options)
624
-
625
- # parse the request
900
+ tpkg = instantiate_tpkg
901
+
902
+ # Parse the request
626
903
  requirements = []
627
904
  packages = {}
628
- tpkg.parse_requests([@action_value], requirements, packages)
629
-
630
- # get dependencies of all installed packages
905
+ tpkg.parse_requests([@action_value], requirements, packages,
906
+ :installed_only => true)
907
+
908
+ # Note that we don't stop here in the case of this switch, but continue to
909
+ # check if anything depends on the package the user asked about. There
910
+ # shouldn't be a situation where there's another package installed that
911
+ # depends on the package the user is asking about, but the user's package is
912
+ # not installed. I.e. if foo depends on bar but only foo is installed and
913
+ # the user asks what depends on bar, the answer is still foo. That
914
+ # information might be useful to the user, even though that situation should
915
+ # have been avoided in the first place. Broken dependency trees due to
916
+ # manually messing with the repo, using --force, etc. can happen and the
917
+ # user may be using the --qr option just because they're trying to sort out
918
+ # a mess.
919
+ if packages.values.all? {|pkg| pkg.empty?}
920
+ ret_val = 1
921
+ # --qr --quiet doesn't seem like a meaningful combination, so I'm not
922
+ # suppressing this for @quiet
923
+ $stderr.puts "No packages matching '#{@action_value}' installed"
924
+ end
925
+
926
+ # Get dependencies of all installed packages
631
927
  dependencies = {}
632
928
  tpkg.metadata_for_installed_packages.each do |metadata|
633
929
  dependencies[metadata[:filename]] = metadata[:dependencies]
634
930
  end
635
-
636
- # check to see if the any required dependencies match with what the
637
- # user specified in the request
931
+
932
+ # Check to see if any dependencies match with what the user specified in the
933
+ # request
934
+ requirees = {}
638
935
  packages.each do |name, pkgs|
639
936
  pkgs.each do |pkg|
640
937
  next if pkg[:source] != :currently_installed
641
- puts "The following package(s) require #{pkg[:metadata][:filename]}:"
642
938
  dependencies.each do | requiree, deps |
643
939
  next if deps.nil?
644
940
  deps.each do | dep |
645
941
  if Tpkg::package_meets_requirement?(pkg, dep)
646
- puts " #{requiree}"
942
+ pkgfilename = pkg[:metadata][:filename]
943
+ if !requirees[pkgfilename]
944
+ requirees[pkgfilename] = []
945
+ end
946
+ requirees[pkgfilename] << requiree
647
947
  end
648
948
  end
649
949
  end
650
950
  end
651
951
  end
652
- when :query_local_depends
653
- tpkg = instantiate_tpkg(@tpkg_options)
654
- queryreq = Tpkg::parse_request(@action_value)
655
- instpkgs = tpkg.installed_packages_that_meet_requirement(queryreq)
656
- instpkgs.each do | pkg |
657
- puts pkg[:metadata][:filename] + ':'
658
- if pkg[:metadata][:dependencies]
659
- pkg[:metadata][:dependencies].each do |req|
660
- puts " Requires #{req[:name]}"
661
- req.each do |field, value|
662
- next if field == :name
663
- puts " #{field}: #{value}"
664
- end
665
- end
952
+
953
+ if !requirees.empty?
954
+ requirees.keys.sort.each do |pkgfilename|
955
+ puts "The following package(s) require #{pkgfilename}:"
956
+ # uniq probably isn't necessary, but it can't hurt
957
+ requirees[pkgfilename].sort.uniq.each do |requiree|
958
+ puts " #{requiree}"
959
+ end
666
960
  end
961
+ else
962
+ puts "No other package depends on '#{@action_value}'"
667
963
  end
668
964
  when :query_depends
669
- tpkg = instantiate_tpkg(@tpkg_options)
965
+ tpkg = instantiate_tpkg
966
+
967
+ requirements = []
968
+ packages = {}
969
+ tpkg.parse_requests([@action_value], requirements, packages,
970
+ :installed_only => true)
971
+
972
+ if packages.values.all? {|pkg| pkg.empty?}
973
+ ret_val = 1
974
+ # --qd --quiet doesn't seem like a meaningful combination, so I'm not
975
+ # suppressing this for @quiet
976
+ $stderr.puts "No packages matching '#{@action_value}' installed"
977
+ else
978
+ depends = {}
979
+ packages.each do |name, pkgs|
980
+ pkgs.each do |pkg|
981
+ if pkg[:metadata][:dependencies]
982
+ pkgfilename = pkg[:metadata][:filename]
983
+ if !depends[pkgfilename]
984
+ depends[pkgfilename] = []
985
+ end
986
+ pkg[:metadata][:dependencies].each do |req|
987
+ depends[pkgfilename] << req
988
+ end
989
+ end
990
+ end
991
+ end
992
+
993
+ if !depends.empty?
994
+ outputs = []
995
+ depends.keys.sort.each do |pkgfilename|
996
+ output = "Package #{pkgfilename} depends on:\n"
997
+ # uniq probably isn't necessary, but it can't hurt
998
+ outs = []
999
+ depends[pkgfilename].sort{|a,b| a[:name]<=>b[:name]}.uniq.each do |req|
1000
+ out = " name: #{req[:name]}\n"
1001
+ req.each do |field, value|
1002
+ next if field == 'name'
1003
+ out << " #{field}: #{value}\n"
1004
+ end
1005
+ outs << out
1006
+ end
1007
+ outputs << output + outs.join("\n")
1008
+ end
1009
+ print outputs.join("\n")
1010
+ else
1011
+ puts "Package '#{@action_value}' does not depend on other packages"
1012
+ end
1013
+ end
1014
+ when :query_depends_available
1015
+ tpkg = instantiate_tpkg
670
1016
  requirements = []
671
1017
  packages = {}
672
1018
  tpkg.parse_requests([@action_value], requirements, packages)
673
- packages.each do |name, pkgs|
674
- already_displayed = {}
675
- pkgs.each do |pkg|
676
- # parse_requests returns both installed and available packages.
677
- # The same package may show up in both, skip any duplicates.
678
- next if already_displayed[pkg[:filename]]
679
- already_displayed[pkg[:metadata][:filename]] = true
680
- puts pkg[:metadata][:filename] + ':'
1019
+ availpkgs = []
1020
+ packages.each do | name, pkgs |
1021
+ availpkgs.concat(pkgs)
1022
+ end
1023
+ available = availpkgs.select do |pkg|
1024
+ pkg[:source] != :native_installed &&
1025
+ pkg[:source] != :native_available &&
1026
+ pkg[:source] != :currently_installed
1027
+ end
1028
+
1029
+ if available.empty?
1030
+ ret_val = 1
1031
+ # --qds --quiet doesn't seem like a meaningful combination, so I'm not
1032
+ # suppressing this for @quiet
1033
+ $stderr.puts "No packages matching '#{@action_value}' available"
1034
+ else
1035
+ depends = {}
1036
+ available.each do |pkg|
681
1037
  if pkg[:metadata][:dependencies]
1038
+ pkgfilename = pkg[:metadata][:filename]
1039
+ if !depends[pkgfilename]
1040
+ depends[pkgfilename] = []
1041
+ end
682
1042
  pkg[:metadata][:dependencies].each do |req|
683
- puts " Requires #{req[:name]}"
1043
+ depends[pkgfilename] << req
1044
+ end
1045
+ end
1046
+ end
1047
+
1048
+ if !depends.empty?
1049
+ outputs = []
1050
+ depends.keys.sort.each do |pkgfilename|
1051
+ output = "Package #{pkgfilename} depends on:\n"
1052
+ # uniq probably isn't necessary, but it can't hurt
1053
+ outs = []
1054
+ depends[pkgfilename].sort{|a,b| a[:name]<=>b[:name]}.uniq.each do |req|
1055
+ out = " name: #{req[:name]}\n"
684
1056
  req.each do |field, value|
685
- next if field == :name
686
- puts " #{field}: #{value}"
1057
+ next if field == 'name'
1058
+ out << " #{field}: #{value}\n"
687
1059
  end
1060
+ outs << out
688
1061
  end
1062
+ outputs << output + outs.join("\n")
689
1063
  end
1064
+ print outputs.join("\n")
1065
+ else
1066
+ puts "Package '#{@action_value}' does not depend on other packages"
690
1067
  end
691
1068
  end
692
1069
  when :query_tpkg_metadata
693
- tpkg = instantiate_tpkg(@tpkg_options)
694
- if File.exist?(@action_value)
695
- puts Tpkg::extract_tpkg_metadata_file(@action_value)
696
- elsif File.exists?(File.join(tpkg.installed_directory, @action_value))
697
- puts Tpkg::extract_tpkg_metadata_file(File.join(tpkg.installed_directory, @action_value))
1070
+ tpkg = instantiate_tpkg
1071
+ requirements = []
1072
+ packages = {}
1073
+ tpkg.parse_requests([@action_value], requirements, packages,
1074
+ :installed_only => true)
1075
+
1076
+ if packages.values.all? {|pkg| pkg.empty?}
1077
+ ret_val = 1
1078
+ # --qX --quiet doesn't seem like a meaningful combination, so I'm not
1079
+ # suppressing this for @quiet
1080
+ $stderr.puts "No packages matching '#{@action_value}' installed"
698
1081
  else
699
- puts "File #{@action_value} doesn't exist."
1082
+ packages.each do | name, pkgs |
1083
+ pkgs.each do | pkg |
1084
+ pkgfile = nil
1085
+ if pkg[:source] == :currently_installed
1086
+ pkgfile = File.join(tpkg.installed_directory, pkg[:metadata][:filename])
1087
+ else
1088
+ pkgfile = pkg[:source]
1089
+ end
1090
+ puts Tpkg::extract_tpkg_metadata_file(pkgfile)
1091
+ end
1092
+ end
1093
+ end
1094
+ when :query_tpkg_metadata_available
1095
+ tpkg = instantiate_tpkg
1096
+ requirements = []
1097
+ packages = {}
1098
+ tpkg.parse_requests([@action_value], requirements, packages)
1099
+ availpkgs = []
1100
+ packages.each do | name, pkgs |
1101
+ availpkgs.concat(pkgs)
1102
+ end
1103
+ available = availpkgs.select do |pkg|
1104
+ pkg[:source] != :native_installed &&
1105
+ pkg[:source] != :native_available &&
1106
+ pkg[:source] != :currently_installed
1107
+ end
1108
+
1109
+ if available.empty?
1110
+ ret_val = 1
1111
+ # --qXs --quiet doesn't seem like a meaningful combination, so I'm not
1112
+ # suppressing this for @quiet
1113
+ $stderr.puts "No packages matching '#{@action_value}' available"
1114
+ else
1115
+ downloaddir = Tpkg::tempdir('download')
1116
+ available.each do |pkg|
1117
+ # FIXME: I've duplicated from the install and upgrade methods this logic
1118
+ # to calculate pkgfile, it should be encapsulated in a method
1119
+ pkgfile = nil
1120
+ if File.file?(pkg[:source])
1121
+ pkgfile = pkg[:source]
1122
+ elsif File.directory?(pkg[:source])
1123
+ pkgfile = File.join(pkg[:source], pkg[:metadata][:filename])
1124
+ else
1125
+ pkgfile = download(pkg[:source], pkg[:metadata][:filename], downloaddir)
1126
+ end
1127
+ puts Tpkg::extract_tpkg_metadata_file(pkgfile)
1128
+ end
1129
+ FileUtils.rm_rf(downloaddir)
700
1130
  end
701
1131
  when :query_env
702
1132
  puts "Operating System: #{Tpkg::get_os}"
703
1133
  puts "Architecture: #{Tpkg::get_arch}"
1134
+ puts "Tar: #{Tpkg::find_tar}"
704
1135
  when :query_conf
705
1136
  # This is somewhat arbitrarily limited to the options read from the
706
1137
  # tpkg.conf config files. The reason it exists at all is that it is
@@ -708,12 +1139,12 @@ when :query_conf
708
1139
  # without recreating all of our logic about deciding which config files to
709
1140
  # read, which order to read them in, what environment variables override the
710
1141
  # config files, etc.
711
- tpkg = instantiate_tpkg(@tpkg_options)
1142
+ tpkg = instantiate_tpkg
712
1143
  puts "Base: #{tpkg.base}"
713
1144
  puts "Sources: #{tpkg.sources.inspect}"
714
1145
  puts "Report server: #{tpkg.report_server}"
715
1146
  when :query_history
716
- tpkg = instantiate_tpkg(@tpkg_options)
1147
+ tpkg = instantiate_tpkg
717
1148
  tpkg.installation_history
718
1149
  when :query_version
719
1150
  puts Tpkg::VERSION