#!/usr/bin/ruby ############################################################################## # tpkg package management system # License: MIT (http://www.opensource.org/licenses/mit-license.php) ############################################################################## # When run from the source repository or from an unpacked copy of the # distribution we want to find the local library, even if there's a copy # installed on the system. The installed copy is likely older, and the API # may be out of sync with this executable. $:.unshift(File.expand_path('../lib', File.dirname(__FILE__))) require 'optparse' require 'tpkg' # # Parse the command line options # @action = nil @action_value = nil @debug = false @prompt = true @quiet = false @sudo = nil @sudo_option = nil @force = false @deploy = false @deploy_params = ARGV # hold parameters for how to invoke tpkg on the machines we're deploying to @deploy_options = {} # options for how to run the deployer @servers = [] @groups = nil @worker_count = 10 @rerun_with_sudo = false @sources = [] @tpkg_options = {} # options for instantiating Tpkg object @init_options = {} # options for how to run init scripts @other_options = {} @compress = "gzip" # # Subroutines # def rerun_with_sudo_if_necessary if Process.euid != 0 && @sudo warn "Executing with sudo" if !@quiet # Depending on how sudo is configured it might remove TPKG_HOME from the # environment. As such we set the base as a command line option to ensure # it survives the sudo process. if ENV['TPKG_HOME'] exec('sudo', '-H', $0, '--base', ENV['TPKG_HOME'], *ARGV) else exec('sudo', '-H', $0, *ARGV) end end end # This method can only be safely called after command line option parsing is # complete @config_file_settings = nil def parse_config_files if @config_file_settings return @config_file_settings end # FIXME: Move config file parsing to tpkg.rb # https://github.com/tpkg/client/issues/22 fsroot = @tpkg_options[:file_system_root] || '' settings = {:sources => []} [File.join(fsroot, Tpkg::DEFAULT_CONFIGDIR, 'tpkg.conf'), File.join(fsroot, ENV['HOME'], ".tpkg.conf")].each do |configfile| if File.exist?(configfile) IO.foreach(configfile) do |line| line.chomp! next if (line =~ /^\s*$/); # Skip blank lines next if (line =~ /^\s*#/); # Skip comments line.strip! # Remove leading/trailing whitespace key, value = line.split(/\s*=\s*/, 2) if key == 'base' settings[:base] = value puts "Loaded base #{value} from #{configfile}" if @debug elsif key == 'source' settings[:sources] << value puts "Loaded source #{value} from #{configfile}" if @debug elsif key == 'report_server' settings[:report_server] = value puts "Loaded report server #{value} from #{configfile}" if @debug elsif key == 'host_group_script' settings[:host_group_script] = value puts "Loaded host group script #{value} from #{configfile}" if @debug elsif key == 'sudo' sudoval = nil # My kingdom for a String#to_boolean if value == 'true' sudoval = true elsif value == 'false' sudoval = false else puts "Unrecognized value #{value} for sudo setting in #{configfile}, ignoring" end if !sudoval.nil? settings[:sudo] = sudoval puts "Loaded sudo #{value} from #{configfile}" if @debug end end end end end @config_file_settings = settings end passphrase_callback = lambda do | package | # ask("Passphrase for #{package}: ", true) begin system 'stty -echo;' print "Passphrase for #{package}: " input = STDIN.gets.chomp ensure system 'stty echo; echo ""' end input end # # Begin main program logic # opts = OptionParser.new(nil, 24, ' ') opts.banner = 'Usage: tpkg [options]' opts.on('--make', '-m', '=DIRECTORY', 'Make a package out of the contents of the directory') do |opt| @action = :make @action_value = opt end installexample = " --install pkgname=version=package_version\n (Example: tpkg --install hive=2.1) Will install hive version 2.1" opts.on('--install', '-i', '=PACKAGES', "Install one or more packages\n#{installexample}", Array) do |opt| @rerun_with_sudo = true @action = :install @action_value = opt end opts.on('--upgrade', '-u', '=PACKAGES', 'Upgrade one or more packages', Array) do |opt| @rerun_with_sudo = true @action = :upgrade @action_value = opt end opts.on('--downgrade', '=PACKAGES', 'Downgrade one or more packages', Array) do |opt| @other_options[:downgrade] = true @rerun_with_sudo = true @action = :upgrade @action_value = opt end opts.on('--servers', '-s', '=SERVERS', Array, 'Servers on which to apply actions, defaults to local') do |opt| @servers.concat(opt) @deploy = true # FIXME: this won't remove options that the user specified as an # abbreviation. I.e. if the option is --servers and the user specified # --serv (which OptionParser will accept as long as it is unambiguous) this # won't detect and remove it. @deploy_params = @deploy_params - ['--servers', '-s', @servers.join(","), "--servers=#{@servers.join(',')}"] end opts.on('--groups', '-g', '=GROUP', Array, 'Group of server on which to apply actions') do |opt| # We'll finish processing this later. To expand the groups we need the name # of the host_group_script from the config file, but we can't safely call # parse_config_files until we're done processing command line options. @groups = opt @deploy = true # FIXME: this won't remove options that the user specified as an # abbreviation. I.e. if the option is --servers and the user specified # --serv (which OptionParser will accept as long as it is unambiguous) this # won't detect and remove it. @deploy_params = @deploy_params - ['--groups', '-g', @groups.join(","), "--groups=#{@groups.join(',')}"] end opts.on('--ua', 'Upgrade all packages') do |opt| @rerun_with_sudo = true @action = :upgrade end opts.on('--remove', '-r', '=PACKAGES', 'Remove one or more packages', Array) do |opt| @rerun_with_sudo = true @action = :remove @action_value = opt end opts.on('--rd', '=PACKAGES', 'Similar to -r but also remove depending packages', Array) do |opt| @rerun_with_sudo = true @other_options[:remove_all_dep] = true @action = :remove @action_value = opt end opts.on('--rp', '=PACKAGES', 'Similar to -r but also remove prerequisites', Array) do |opt| @rerun_with_sudo = true @other_options[:remove_all_prereq] = true @action = :remove @action_value = opt end opts.on('--ra', 'Remove all packages') do |opt| @rerun_with_sudo = true @action = :remove end opts.on('--verify', '-V', '=NAME', 'Verify packages') do |opt| @rerun_with_sudo = true @action = :verify @action_value = opt end opts.on('--start', '=NAME', 'Start the init script for specified package', Array) do |opt| @rerun_with_sudo = true @action = :execute_init @init_options[:packages] = opt @init_options[:cmd] = 'start' end opts.on('--stop', '=NAME', 'Stop the init script for specified package', Array) do |opt| @rerun_with_sudo = true @action = :execute_init @init_options[:packages] = opt @init_options[:cmd] = 'stop' end opts.on('--restart', '=NAME', 'Restart the init script for specified package', Array) do |opt| @rerun_with_sudo = true @action = :execute_init @init_options[:packages] = opt @init_options[:cmd] = 'restart' end opts.on('--reload', '=NAME', 'Reload the init script for specified package', Array) do |opt| @rerun_with_sudo = true @action = :execute_init @init_options[:packages] = opt @init_options[:cmd] = 'reload' end opts.on('--status', '=NAME', 'Get status from init script for specified package', Array) do |opt| @rerun_with_sudo = true @action = :execute_init @init_options[:packages] = opt @init_options[:cmd] = 'status' end opts.on('--start-all', 'Start the init scripts for all packages') do |opt| @rerun_with_sudo = true @action = :execute_init @init_options[:cmd] = 'start' end opts.on('--stop-all', 'Stop the init script for all packages') do |opt| @rerun_with_sudo = true @action = :execute_init @init_options[:cmd] = 'stop' end opts.on('--restart-all', 'Restart the init script for all packages') do |opt| @rerun_with_sudo = true @action = :execute_init @init_options[:cmd] = 'restart' end opts.on('--reload-all', 'Reload the init script for all packages') do |opt| @rerun_with_sudo = true @action = :execute_init @init_options[:cmd] = 'reload' end opts.on('--exec-init', '=NAME', 'Execute init scripts for specified packages', Array) do |opt| @rerun_with_sudo = true @init_options[:packages] = opt @action = :execute_init end opts.on('--init-script', '=NAME', 'What init scripts to execute', Array) do |opt| @rerun_with_sudo = true @init_options[:scripts] = opt end opts.on('--init-cmd', '=CMD', 'Invoke specified init script command') do |opt| @rerun_with_sudo = true @init_options[:cmd] = opt end opts.on('--query', '-q', '=NAMES', 'Check if a package is installed', Array) do |opt| # People mistype -qa instead of --qa frequently if opt == ['a'] warn "NOTE: tpkg -qa queries for a pkg named 'a', you probably want --qa for all pkgs" end @action = :query_installed @action_value = opt end # --qv is deprecated opts.on('--qs', '--qv', '=NAME', 'Check if a package is available on server', Array) do |opt| @action = :query_available @action_value = opt end opts.on('--qa', 'List all installed packages') do |opt| @action = :query_installed end # --qva is deprecated opts.on('--qas', '--qva', 'List all packages on server') do |opt| @action = :query_available end opts.on('--qi', '=NAME', 'Display the info for a package') do |opt| @action = :query_info @action_value = opt end opts.on('--qis', '=NAME', 'Display the info for a package on the server') do |opt| @action = :query_info_available @action_value = opt end opts.on('--ql', '=NAME', 'List the files in a package') do |opt| @action = :query_list_files @action_value = opt end opts.on('--qls', '=NAME', 'List the files in a package on the server') do |opt| @action = :query_list_files_available @action_value = opt end opts.on('--qf', '=FILE', 'List the package that owns a file') do |opt| @action = :query_who_owns_file @action_value = opt end opts.on('--qr', '=NAME', 'List installed packages that require package') do |opt| @action = :query_requires @action_value = opt end opts.on('--qd', '=NAME', 'List the packages on which the given package depends') do |opt| @action = :query_depends @action_value = opt end opts.on('--qds', '=NAME', 'List pkgs on which given package on server depends') do |opt| @action = :query_depends_available @action_value = opt end opts.on('--dw', '=INTEGER', 'Number of workers for deploying') do |opt| @worker_count = opt.to_i # FIXME: this won't remove options that the user specified as an # abbreviation. I.e. if the option is --servers and the user specified # --serv (which OptionParser will accept as long as it is unambiguous) this # won't detect and remove it. @deploy_params = @deploy_params - ['--dw', @worker_count, "--dw=#{opt}"] end opts.on('--qX', '=FILENAME', 'Display raw metadata (tpkg.yml) of the given package') do |opt| @action = :query_tpkg_metadata @action_value = opt end opts.on('--qXs', '=FILENAME', 'Display raw metadata of given package on the server') do |opt| @action = :query_tpkg_metadata_available @action_value = opt end opts.on('--history', 'Display package installation history') do |opt| @action = :query_history end opts.on('--qenv', "Display machine's information") do |opt| @action = :query_env end opts.on('--qconf', "Display tpkg's configuration settings") do |opt| @action = :query_conf end opts.on('--base', '=BASE', 'Base directory for tpkg operations') do |opt| @tpkg_options[:base] = opt end opts.on('--extract', '-x', '=DIRECTORY', 'Extract the metadata for a directory of packages') do |opt| @action = :extract @action_value = opt end opts.on('--source', '=NAME', 'Sources where packages are located', Array) do |opt| @sources = opt end opts.on('--download', '=PACKAGES', 'Download one or more packages', Array) do |opt| @action = :download @action_value = opt end opts.on('-n', '--no-prompt', 'No confirmation prompts') do |opt| @prompt = opt Tpkg::set_prompt(@prompt) end opts.on('--quiet', 'Reduce informative but non-essential output') do |opt| @quiet = opt end opts.on('--no-sudo', 'No calls to sudo for operations that might need root') do |opt| @sudo_option = opt end opts.on('--lock-force', 'Force the removal of an existing lockfile') do |opt| @tpkg_options[:lockforce] = opt end opts.on('--force-replace', 'Replace conflicting pkgs with the new one(s)') do |opt| @other_options[:force_replace] = opt end opts.on('--force', 'Force the execution of a given task') do |opt| @force = opt end opts.on('-o', '--out', '=DIR', 'Output directory for the --make option') do |opt| @other_options[:out] = opt end opts.on('--skip-remove-stop', 'Do not run init script stop on package removal') do |opt| @other_options[:skip_remove_stop] = opt end opts.on('--use-ssh-key [FILE]', 'Use ssh key for deploying instead of password') do |opt| @deploy_options["use-ssh-key"] = true @deploy_options["ssh-key"] = opt # FIXME: this won't remove options that the user specified as an # abbreviation. I.e. if the option is --servers and the user specified # --serv (which OptionParser will accept as long as it is unambiguous) this # won't detect and remove it. @deploy_params = @deploy_params - ['--use-ssh-key', opt, "--use-ssh-key=#{opt}"] end opts.on('--deploy-as', '=USERNAME', 'What username to use for deploying to remote server') do |opt| @deploy_options["deploy-as"] = opt # FIXME: this won't remove options that the user specified as an # abbreviation. I.e. if the option is --servers and the user specified # --serv (which OptionParser will accept as long as it is unambiguous) this # won't detect and remove it. @deploy_params = @deploy_params - ['--deploy-as'] end acceptable_compress_arguments = ['gzip', 'bz2', 'no'] opts.on('--compress [TYPE]', acceptable_compress_arguments, "Compress files when making packages " + "(#{acceptable_compress_arguments.join(',')})") do |opt| # Acceptable if opt == nil # No argument specified by user @compress = true elsif opt == "no" @compress= false else @compress = opt end end opts.on('--test-root TESTDIR', 'For use by the test suite only.') do |opt| @tpkg_options[:file_system_root] = opt end opts.on('--debug', 'Print lots of messages about what tpkg is doing') do |opt| @debug = opt Tpkg::set_debug(@debug) end opts.on('--version', 'Show tpkg version') do |opt| @action = :query_version end opts.on_tail("-h", "--help", "Show this message") do puts opts exit end leftovers = nil begin leftovers = opts.parse(ARGV) rescue OptionParser::ParseError => e $stderr.puts "Error parsing arguments, try --help" $stderr.puts e.message exit 1 end # Display a usage message if the user did not specify a valid action to perform. if !@action puts opts exit end if @groups settings = parse_config_files if settings[:host_group_script] if !File.executable?(settings[:host_group_script].split(' ').first) warn "Warning: host group script #{settings[:host_group_script]} is not executable, execution will likely fail" end servers = [] @groups.each do |group| IO.popen(settings[:host_group_script]<<" "< true) if packages.values.all? {|pkg| pkg.empty?} # If the user requested specific packages and we found no matches # then exit with a non-zero value to indicate failure. This allows # command-line syntax like "tpkg -q foo || tpkg -i foo" to ensure # that a package is installed. ret_val = 1 $stderr.puts "No packages matching '#{@action_value.join(',')}' installed" if !@quiet else packages.each do | name, pkgs | matches.concat(pkgs) end end else # --qa is implemented by setting @action to :query_installed and # @action_value to nil matches = tpkg.installed_packages_that_meet_requirement if matches.empty? ret_val = 1 $stderr.puts "No packages installed" if !@quiet end end if !@quiet matches.sort(&Tpkg::SORT_PACKAGES).each do |pkg| puts pkg[:metadata][:filename] end end when :query_info tpkg = Tpkg.new(@tpkg_options) requirements = [] packages = {} tpkg.parse_requests([@action_value], requirements, packages, :installed_only => true) metadatas = [] packages.each do | name, pkgs | pkgs.each do | pkg | metadatas << pkg[:metadata] end end output_strings = [] already_displayed = {} metadatas.each do |metadata| next if already_displayed[metadata[:filename]] already_displayed[metadata[:filename]] = true output_string = '' [:name, :version, :package_version, :operatingsystem, :architecture, :maintainer, :description, :bugreporting].each do |field| metadata[field] = 'any' if field == :operatingsystem && metadata[field].nil? metadata[field] = 'any' if field == :architecture && metadata[field].nil? if metadata[field] if metadata[field].kind_of?(Array) output_string << "#{field}: #{metadata[field].join(',')}\n" else output_string << "#{field}: #{metadata[field]}\n" end end end if metadata[:dependencies] output_string << "(This package depends on other packages, use --qd to view the dependencies)\n" end # Older versions of tpkg did not insert a tpkg_version field into the # package metadata when building packages tpkg_version = metadata[:tpkg_version] || "< 1.26.1" output_string << "(This package was built with tpkg version #{tpkg_version})\n" output_strings << output_string end print output_strings.join("================================================================================\n") if already_displayed.empty? ret_val = 1 # --qi --quiet doesn't seem like a meaningful combination, so I'm not # suppressing this for @quiet $stderr.puts "No packages matching '#{@action_value}' installed" end when :query_info_available tpkg = Tpkg.new(@tpkg_options) requirements = [] packages = {} tpkg.parse_requests([@action_value], requirements, packages) availpkgs = [] packages.each do | name, pkgs | availpkgs.concat(pkgs) end available = availpkgs.select do |pkg| pkg[:source] != :native_installed && pkg[:source] != :native_available && pkg[:source] != :currently_installed end if available.empty? ret_val = 1 # --qis --quiet doesn't seem like a meaningful combination, so I'm not # suppressing this for @quiet $stderr.puts "No packages matching '#{@action_value}' available" else metadatas = available.collect {|avail| avail[:metadata]} output_strings = [] already_displayed = {} metadatas.each do |metadata| next if already_displayed[metadata[:filename]] already_displayed[metadata[:filename]] = true output_string = '' [:name, :version, :package_version, :operatingsystem, :architecture, :maintainer, :description, :bugreporting].each do |field| metadata[field] = 'any' if field == :operatingsystem && metadata[field].nil? metadata[field] = 'any' if field == :architecture && metadata[field].nil? if metadata[field] if metadata[field].kind_of?(Array) output_string << "#{field}: #{metadata[field].join(',')}\n" else output_string << "#{field}: #{metadata[field]}\n" end end end if metadata[:dependencies] output_string << "(This package depends on other packages, use --qd to view the dependencies)\n" end # Older versions of tpkg did not insert a tpkg_version field into the # package metadata when building packages tpkg_version = metadata[:tpkg_version] || "< 1.26.1" output_string << "(This package was built with tpkg version #{tpkg_version})\n" output_strings << output_string end print output_strings.join("================================================================================\n") end when :query_list_files tpkg = Tpkg.new(@tpkg_options) requirements = [] packages = {} tpkg.parse_requests([@action_value], requirements, packages, :installed_only => true) if packages.values.all? {|pkg| pkg.empty?} ret_val = 1 # --ql --quiet doesn't seem like a meaningful combination, so I'm not # suppressing this for @quiet $stderr.puts "No packages matching '#{@action_value}' installed" else # For this switch we need separate handling for installed and uninstalled # packages. For installed packages we know where their relocatable files # ended up and can give the user regular paths. For uninstalled packaages # we don't know what base will be used when the package is installed, so # we need to indicate to the user that their are relocatable files and # just display their path relative to the eventual base directory. ci_pkgfiles = [] packages.each do | name, pkgs | pkgs.each do | pkg | if pkg[:source] == :currently_installed ci_pkgfiles << pkg[:metadata][:filename] else puts "#{pkg[:source]}:" fip = Tpkg.files_in_package(pkg[:source]) fip[:root].each { |file| puts file } fip[:reloc].each { |file| puts '/' + file } end end end files = tpkg.files_for_installed_packages(ci_pkgfiles) files.each do |pkgfile, fip| puts "#{pkgfile}:" fip[:normalized].each { |file| puts file } end end when :query_list_files_available tpkg = Tpkg.new(@tpkg_options) requirements = [] packages = {} tpkg.parse_requests([@action_value], requirements, packages) availpkgs = [] packages.each do | name, pkgs | availpkgs.concat(pkgs) end available = availpkgs.select do |pkg| pkg[:source] != :native_installed && pkg[:source] != :native_available && pkg[:source] != :currently_installed end if available.empty? ret_val = 1 # --qls --quiet doesn't seem like a meaningful combination, so I'm not # suppressing this for @quiet $stderr.puts "No packages matching '#{@action_value}' available" else downloaddir = Tpkg::tempdir('download') available.each do |pkg| # FIXME: I've duplicated from the install and upgrade methods this logic # to calculate pkgfile, it should be encapsulated in a method pkgfile = nil if File.file?(pkg[:source]) pkgfile = pkg[:source] elsif File.directory?(pkg[:source]) pkgfile = File.join(pkg[:source], pkg[:metadata][:filename]) else pkgfile = tpkg.download(pkg[:source], pkg[:metadata][:filename], downloaddir) end puts "#{pkg[:metadata][:filename]}:" fip = Tpkg.files_in_package(pkgfile) fip[:root].each { |file| puts file } fip[:reloc].each { |file| puts '/' + file } end FileUtils.rm_rf(downloaddir) end when :query_who_owns_file tpkg = Tpkg.new(@tpkg_options) owned = false expanded_file = File.expand_path(@action_value) tpkg.files_for_installed_packages.each do |pkgfile, fip| fip[:normalized].each do |file| if file == expanded_file puts "#{file}: #{pkgfile}" owned = true end end end if !owned ret_val = 1 # --qf --quiet doesn't seem like a meaningful combination, so I'm not # suppressing this for @quiet $stderr.puts "No package owns file '#{@action_value}'" end when :query_available tpkg = Tpkg.new(@tpkg_options) availpkgs = [] if @action_value # The --qs switch is set to accept multiple values (the "Array" # parameter in the opts.on line for --qs) so users can do things like # "tpkg --qs foo,bar" to check multiple packages at once. As such # @action_value is an Array for this switch. requirements = [] packages = {} tpkg.parse_requests(@action_value, requirements, packages) packages.each do | name, pkgs | availpkgs.concat(pkgs) end else # --qas is implemented by setting @action to :query_available and # @action_value to nil availpkgs.concat(tpkg.available_packages_that_meet_requirement) end available = availpkgs.select do |pkg| pkg[:source] != :native_installed && pkg[:source] != :native_available && # The tpkg library treats currently installed packages as "available" # because they are available to meet a user's requirement. I.e. if the # user asks to install ruby and a ruby package is already installed that # satisfies the user's requirement even if there's no ruby package in any # of the sources. But for these query options I think the reasonable # interpretation is that the user would like to know if there's a package # in a source that could be installed. For example, if the user queries # for the availability of ruby and we show it as available because it is # installed, but then they go to another machine with the same sources # defined and try to install ruby and it fails because there is no ruby in # any of the sources I think the user is likely to find that unexpected # and annoying. pkg[:source] != :currently_installed end if available.empty? ret_val = 1 if !@quiet if @action_value $stderr.puts "No packages matching '#{@action_value.join(',')}' available" else $stderr.puts "No packages available" end end else if !@quiet available.sort(&Tpkg::SORT_PACKAGES).each do |pkg| puts "#{pkg[:metadata][:filename]} (#{pkg[:source]})" end end end when :query_requires tpkg = Tpkg.new(@tpkg_options) # Parse the request requirements = [] packages = {} tpkg.parse_requests([@action_value], requirements, packages, :installed_only => true) # Note that we don't stop here in the case of this switch, but continue to # check if anything depends on the package the user asked about. There # shouldn't be a situation where there's another package installed that # depends on the package the user is asking about, but the user's package is # not installed. I.e. if foo depends on bar but only foo is installed and # the user asks what depends on bar, the answer is still foo. That # information might be useful to the user, even though that situation should # have been avoided in the first place. Broken dependency trees due to # manually messing with the repo, using --force, etc. can happen and the # user may be using the --qr option just because they're trying to sort out # a mess. if packages.values.all? {|pkg| pkg.empty?} ret_val = 1 # --qr --quiet doesn't seem like a meaningful combination, so I'm not # suppressing this for @quiet $stderr.puts "No packages matching '#{@action_value}' installed" end # Get dependencies of all installed packages dependencies = {} tpkg.metadata_for_installed_packages.each do |metadata| dependencies[metadata[:filename]] = metadata[:dependencies] end # Check to see if any dependencies match with what the user specified in the # request requirees = {} packages.each do |name, pkgs| pkgs.each do |pkg| next if pkg[:source] != :currently_installed dependencies.each do | requiree, deps | next if deps.nil? deps.each do | dep | if tpkg.package_meets_requirement?(pkg, dep) pkgfilename = pkg[:metadata][:filename] if !requirees[pkgfilename] requirees[pkgfilename] = [] end requirees[pkgfilename] << requiree end end end end end if !requirees.empty? requirees.keys.sort.each do |pkgfilename| puts "The following package(s) require #{pkgfilename}:" # uniq probably isn't necessary, but it can't hurt requirees[pkgfilename].sort.uniq.each do |requiree| puts " #{requiree}" end end else puts "No other package depends on '#{@action_value}'" end when :query_depends tpkg = Tpkg.new(@tpkg_options) requirements = [] packages = {} tpkg.parse_requests([@action_value], requirements, packages, :installed_only => true) if packages.values.all? {|pkg| pkg.empty?} ret_val = 1 # --qd --quiet doesn't seem like a meaningful combination, so I'm not # suppressing this for @quiet $stderr.puts "No packages matching '#{@action_value}' installed" else depends = {} packages.each do |name, pkgs| pkgs.each do |pkg| if pkg[:metadata][:dependencies] pkgfilename = pkg[:metadata][:filename] if !depends[pkgfilename] depends[pkgfilename] = [] end pkg[:metadata][:dependencies].each do |req| depends[pkgfilename] << req end end end end if !depends.empty? outputs = [] depends.keys.sort.each do |pkgfilename| output = "Package #{pkgfilename} depends on:\n" # uniq probably isn't necessary, but it can't hurt outs = [] depends[pkgfilename].sort{|a,b| a[:name]<=>b[:name]}.uniq.each do |req| out = " name: #{req[:name]}\n" req.each do |field, value| next if field == 'name' out << " #{field}: #{value}\n" end outs << out end outputs << output + outs.join("\n") end print outputs.join("\n") else puts "Package '#{@action_value}' does not depend on other packages" end end when :query_depends_available tpkg = Tpkg.new(@tpkg_options) requirements = [] packages = {} tpkg.parse_requests([@action_value], requirements, packages) availpkgs = [] packages.each do | name, pkgs | availpkgs.concat(pkgs) end available = availpkgs.select do |pkg| pkg[:source] != :native_installed && pkg[:source] != :native_available && pkg[:source] != :currently_installed end if available.empty? ret_val = 1 # --qds --quiet doesn't seem like a meaningful combination, so I'm not # suppressing this for @quiet $stderr.puts "No packages matching '#{@action_value}' available" else depends = {} available.each do |pkg| if pkg[:metadata][:dependencies] pkgfilename = pkg[:metadata][:filename] if !depends[pkgfilename] depends[pkgfilename] = [] end pkg[:metadata][:dependencies].each do |req| depends[pkgfilename] << req end end end if !depends.empty? outputs = [] depends.keys.sort.each do |pkgfilename| output = "Package #{pkgfilename} depends on:\n" # uniq probably isn't necessary, but it can't hurt outs = [] depends[pkgfilename].sort{|a,b| a[:name]<=>b[:name]}.uniq.each do |req| out = " name: #{req[:name]}\n" req.each do |field, value| next if field == 'name' out << " #{field}: #{value}\n" end outs << out end outputs << output + outs.join("\n") end print outputs.join("\n") else puts "Package '#{@action_value}' does not depend on other packages" end end when :query_tpkg_metadata tpkg = Tpkg.new(@tpkg_options) requirements = [] packages = {} tpkg.parse_requests([@action_value], requirements, packages, :installed_only => true) if packages.values.all? {|pkg| pkg.empty?} ret_val = 1 # --qX --quiet doesn't seem like a meaningful combination, so I'm not # suppressing this for @quiet $stderr.puts "No packages matching '#{@action_value}' installed" else packages.each do | name, pkgs | pkgs.each do | pkg | pkgfile = nil if pkg[:source] == :currently_installed pkgfile = File.join(tpkg.installed_directory, pkg[:metadata][:filename]) else pkgfile = pkg[:source] end puts Tpkg::extract_tpkg_metadata_file(pkgfile) end end end when :query_tpkg_metadata_available tpkg = Tpkg.new(@tpkg_options) requirements = [] packages = {} tpkg.parse_requests([@action_value], requirements, packages) availpkgs = [] packages.each do | name, pkgs | availpkgs.concat(pkgs) end available = availpkgs.select do |pkg| pkg[:source] != :native_installed && pkg[:source] != :native_available && pkg[:source] != :currently_installed end if available.empty? ret_val = 1 # --qXs --quiet doesn't seem like a meaningful combination, so I'm not # suppressing this for @quiet $stderr.puts "No packages matching '#{@action_value}' available" else downloaddir = Tpkg::tempdir('download') available.each do |pkg| # FIXME: I've duplicated from the install and upgrade methods this logic # to calculate pkgfile, it should be encapsulated in a method pkgfile = nil if File.file?(pkg[:source]) pkgfile = pkg[:source] elsif File.directory?(pkg[:source]) pkgfile = File.join(pkg[:source], pkg[:metadata][:filename]) else pkgfile = tpkg.download(pkg[:source], pkg[:metadata][:filename], downloaddir) end puts Tpkg::extract_tpkg_metadata_file(pkgfile) end FileUtils.rm_rf(downloaddir) end when :query_env tpkg = Tpkg.new(@tpkg_options) puts "Operating System: #{tpkg.os.os}" puts "Architecture: #{tpkg.os.arch}" puts "Tar: #{Tpkg::find_tar}" when :query_conf # This is somewhat arbitrarily limited to the options read from the # tpkg.conf config files. The reason it exists at all is that it is # difficult for users to programatically find out what these will be set to # without recreating all of our logic about deciding which config files to # read, which order to read them in, what environment variables override the # config files, etc. tpkg = Tpkg.new(@tpkg_options) puts "Base: #{tpkg.base}" puts "Sources: #{tpkg.sources.inspect}" puts "Report server: #{tpkg.report_server}" when :query_history tpkg = Tpkg.new(@tpkg_options) tpkg.installation_history when :query_version puts Tpkg::VERSION end exit ret_val