#!/usr/bin/ruby ### Copyright 2016 Pixar ### ### Licensed under the Apache License, Version 2.0 (the "Apache License") ### with the following modification; you may not use this file except in ### compliance with the Apache License and the following modification to it: ### Section 6. Trademarks. is deleted and replaced with: ### ### 6. Trademarks. This License does not grant permission to use the trade ### names, trademarks, service marks, or product names of the Licensor ### and its affiliates, except as required to comply with Section 4(c) of ### the License and to reproduce the content of the NOTICE file. ### ### You may obtain a copy of the Apache License at ### ### http://www.apache.org/licenses/LICENSE-2.0 ### ### Unless required by applicable law or agreed to in writing, software ### distributed under the Apache License with the above modification is ### distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY ### KIND, either express or implied. See the Apache License for the specific ### language governing permissions and limitations under the Apache License. ### ### # == Synopsis # d3admin - a commandline tool for creating and maintaining d3 pkgs in Casper # # == Usage # d3admin [options] action target.... # # For help use: d3admin help # # == Author # Chris Lasell # # == Copyright # Copyright (c) 2016 Pixar Animation Studios. ############ # Modules, Libraries, etc ############ # Load libraries require 'd3' ###################### # The Script Object ###################### class App ### Setup def initialize ### parse the commandline parse_commandline # can't be run as root other than help, search or report - no keychain, among other things raise "d3admin can't make server changes as root" if JSS.superuser? and (not @action =~ /^[hsr]/) end #initialize ### Run def run if @action == "help" show_help @options.helptype return end # run config if it hasn't ever run unless D3::Admin::Prefs.prefs[:last_config] || JSS.superuser? puts puts "******** INITIAL D3ADMIN CONFIGURATION ********" puts config # but dont run it again if that's the action chosen return if @action =~ /^c/ end ### our admin is us @admin = @options.admin || ENV['USER'] # admin can't be a badmin if D3.badmins.include? @admin and D3::Admin::ACTIONS_NEEDING_ADMIN.include? @action raise D3::PermissionError, "d3admin cannot do '#{@action}' as #{@admin}." end # config before connecting if @action =~ /^c/ and !JSS.superuser? config return end ### connect to the server, prompting for info as needed if @admin == 'root' D3::Client.connect else D3::Admin::Auth.connect end case @action when /^a/ then add_pilot_package when /^e/ then edit_package when /^l/ then make_package_live when /^d/ then delete_package when /^i/ then show_package_info when /^s/ then search when /^r/ then show_report else raise ArgumentError, "#{D3::Admin::Help::USAGE}\nUnknown action, must be one of #{D3::Admin::ACTIONS.join(', ')}" end # case end #run ### Parse the command line ### ### @return [void] ### def parse_commandline # Debugging file? if so, always set debug. ARGV << "--debug" if D3::DEBUG_FILE.exist? # this holds everything that comes from the commandline # or from prompting the user @options = OpenStruct.new # cli option defaults @options.helptype = :help # --status can be given multiple times, or can be a comma-separated list. # the results end up in this array @options.status = [] ### see Admin::CLIOpts opt_arry = D3::Admin::OPTIONS.values.map{|o| o[:cli] } opts = GetoptLong.new *opt_arry opts.each do |opt, arg| case opt #general when '--help' @action = "help" @options.helptype = :help return when '--extended-help' @action = "help" @options.helptype = :extended_help return when '--debug' D3::Admin.debug = true when '--d3-version' @action = "help" @options.helptype = :show_d3_version return when '--walkthru' @options.walkthru = true when '--auto-confirm' @options.auto_confirm = true when '--admin' @options.admin = arg # search and report when '--status' @options.status += arg.split(/,\s*/) when '--queue' @options.report_q = true when '--frozen' @options.report_frozen = true when '--computers' @options.report_computers = true when '--groups' @options.search_groups = true # add/edit when '--import' @options.import = true @options.import_from = arg.empty? ? nil : arg when '--no-inherit' @options.no_inherit = true when '--basename' @options.basename = arg when '--version' @options.version = arg when '--revision' @options.revision = arg when '--description' @options.description = arg when '--package-name' @options.package_name = arg when '--filename' @options.filename = arg when '--edition' @options.edition = arg when '--source-path' @options.source_path = arg when '--dmg' @options.package_build_type = 'd' when '--preserve-owners' @options.pkg_preserve_owners = 'y' when '--pkg-id' @options.pkg_identifier = arg when '--workspace' @options.workspace = arg when '--pre-install' @options.pre_install = arg when '--post-install' @options.post_install = arg when '--pre-remove' @options.pre_remove = arg when '--post-remove' @options.post_remove = arg when '--auto-groups' @options.auto_groups = arg when '--excluded-groups' @options.excluded_groups = arg when '--prohibiting-processes' @options.prohibiting_processes = arg when '--cpu_type' @options.cpu_type = arg when '--category' @options.category = arg when '--reboot' # dft is no, so if arg is empty or /^n(o)$/i, it should be nil, # otherwise must be /^y(es)$/i if arg.empty? or arg =~ /^no?$/i @options.reboot = "n" elsif arg =~ /^y(es)?$/i @options.reboot = "y" else raise ArgumentError, "--reboot must be 'y' or 'n'" end when '--remove-first' # dft is no, so if arg is empty or /^n(o)$/i, it should be nil, # otherwise must be /^y(es)$/i if arg.empty? or arg =~ /^no?$/i @options.remove_first = "n" elsif arg =~ /^y(es)?$/i @options.remove_first = "y" else raise ArgumentError, "--remove-first must be 'y' or 'n'" end when '--removable' # dft is yes, so if arg is empty or /^y(es)$/i, it should be nil, # otherwise must be /^n(o)$/i if arg.empty? or arg =~ /^y(es)?$/i @options.removable = "y" elsif arg =~ /^no?$/i @options.removable = "n" else raise ArgumentError, "--removable must be 'y' or 'n'" end when '--oses' @options.oses = arg when '--expiration' @options.expiration = arg when '--expiration-path', '--expiration_paths' @options.expiration_paths = arg # delete when '--keep-scripts' @options.keep_scripts = 'y' when '--keep-in-jss' @options.keep_in_jss = 'y' end # case end # opts.each # the action is always the first thing in ARGV # after the options have been removed action_arg = ARGV.shift unless action_arg raise ArgumentError, "#{D3::Admin::Help::USAGE}\nAction, must be one of #{D3::Admin::ACTIONS.join(', ')}" end # one or more letters is all we need to specify the action... @action = D3::Admin::ACTIONS.select{|a| a.start_with? action_arg}.first unless @action and D3::Admin::ACTIONS.include? @action raise ArgumentError, "#{D3::Admin::Help::USAGE}\nAction, must be one of #{D3::Admin::ACTIONS.join(', ')}" end # anything remaining in ARGV is one or more targets # to work on. @targets = ARGV end # parse_commandline ### Show the help message ### ### @return [void] ### def show_help (type) text = case type when :help D3::Admin::Help.help_text when :extended_help D3::Admin::Help.extended_help_text when :show_d3_version d3_version_text end D3.less_text text return true end #show help ### the d3 version text to spew ### ### @return [String] the text def d3_version_text <<-ENDVERS D3 module version: #{D3::VERSION} JSS module version: #{JSS::VERSION} ENDVERS end ### Add a new pilot package from an existing ### JSS package def import_pilot_pkg_from_jss if @options.walkthru @options.import_from = D3::Admin::Interactive.get_value :import unless @options.import_from @options.version = D3::Admin::Interactive.get_value :version @options.revision = D3::Admin::Interactive.get_value :revision else raise JSS::MissingDataError, "A version must be provided with --version" unless @options.version raise JSS::MissingDataError, "A revision must be provided with --revision" unless @options.revision end # if walk thru imported_pkg = D3::Package.import( @options.import_from, basename: @options.basename, version: @options.version, revision: @options.revision, dist_pw: D3::Admin::Auth.rw_credentials(:dist)[:password] ) imported_pkg.admin = @admin edit_package imported_pkg end ### Add a new package to d3 for piloting ### In this method, the following are OpenStructs: ### - @options holds the value from the commandline ### - default_options holds the default values, either as ### defined in D3::Admin::OPTIONS or inherited ### from an older package ### - new_package_options holds the validated values for ### making the new package ### ### @return [void] ### def add_pilot_package ## we must have a basename before going farther @options.basename = @targets.first if @options.walkthru @options.basename ||= D3::Admin::Interactive.get_basename else raise ArgumentError, "A basename must be provided as a target. Use -H for help" unless @options.basename end if @options.import import_pilot_pkg_from_jss return end puts "Adding a new pilot package to d3..." # this holds the validated data used for making the new pkg new_package_options = OpenStruct.new # now figure out our default values.. default_options = D3::Admin::Add.get_default_options(@options.basename, @options.no_inherit) # get all new package options into new_package_options # via walkthru ... if @options.walkthru new_package_options = D3::Admin::Add.loop_thru_add_walkthru default_options # via the commandline else # if we were given a new version but not a new revision, # set the new revision to 1 if @options[:version] and @options[:version] != default_options[:version] @options[:revision] ||= 1 @options[:edition] ||= "#{@options.basename}-#{@options[:version]}-#{@options[:revision]}" @options.package_name ||= @options[:edition] @options.filename ||= "#{@options[:edition]}.#{default_options.package_build_type}" end # for any options that weren't given on the commandline, use the defaults default_options.each_pair { |opt, val| @options[opt] ||= val } new_package_options = D3::Admin::Add.add_pilot_cli(@options) ########################## end # if @options.walkthru new_package_options.edition = "#{new_package_options.basename}-#{new_package_options.version}-#{new_package_options.revision}" # make a summary of the new values to display for confirmation, if no walkthru unless @options.walkthru lines = [] opts_to_confirm = D3::Admin::Add::NEW_PKG_OPTIONS opts_to_confirm += D3::Admin::Add::BUILD_OPTIONS if new_package_options.package_build_type opts_to_confirm += D3::Admin::Add::PKG_OPTIONS if new_package_options.package_build_type == :pkg #D3::Admin::Add::NEW_PKG_OPTIONS.each do |opt| opts_to_confirm.each do |opt| lbl = D3::Admin::OPTIONS[opt][:label] if D3::Admin::OPTIONS[opt][:display_conversion] disp = D3::Admin::OPTIONS[opt][:display_conversion].call(new_package_options[opt]) else disp = new_package_options[opt] end lines << "#{lbl}: #{disp}" end settings_conf_disp = "\n******* New d3 Package Settings *******\n" settings_conf_disp += "Edition: #{ new_package_options.edition}\n" settings_conf_disp += "Basename: #{ new_package_options.basename}\n" settings_conf_disp += lines.join "\n" settings_conf_disp += "\n" puts settings_conf_disp end # Get confirmation confirm "Create a new package '#{new_package_options.edition}' in d3\nwith settings shown above." D3::Admin::Add.add_new_package new_package_options puts "Done!" puts "To pilot it, run 'sudo d3 install #{new_package_options.edition}' on a test machine." puts "To make it live, run 'd3admin live #{new_package_options.edition}' on your machine." end # add pilot ### Make a package live. ### ### @return [void] ### def make_package_live pkg_id = get_pkg_from_cli_or_prompt if pkg_id.nil? puts "No edition given to make live or no matching package found" return end pkg = D3::Package.new id: pkg_id if pkg.status == :live puts "Doh, '#{pkg.edition}' is already live" return end # is this a rollback? rollback_warning = "" if pkg.skipped? or pkg.deprecated? rollback_warning = "\n\nWARNING: You're rolling back to an older edition!\n" rollback_warning += "ALL non-frozen installs of '#{pkg.basename}' will be downgraded to this edition! \n" end # is the prev. live pkg in use in any policies? policy_warning = "" pkg_id_being_deprecated = D3::Package.basenames_to_live_ids[pkg.basename] if pkg_id_being_deprecated outgoing_pkg = D3::Package.new(id: pkg_id_being_deprecated) pols_used_by_old_pkg = outgoing_pkg.policy_ids unless pols_used_by_old_pkg.empty? names = pols_used_by_old_pkg.map{|pid| JSS::Policy.map_all_ids_to(:name)[pid]}.join(', ') policy_warning = "\n\nWARNING: the current live package is in use by these Casper Policies:\n" policy_warning += " #{names}\n" policy_warning += "You might want to update them to use the new live package." end # unless empty end # if pkg_id_being_deprecated confirm "Make #{pkg.edition} live for basename '#{pkg.basename}'#{rollback_warning}#{policy_warning}" , "" pkg.make_live @admin puts "Done!" puts "New installs of basename '#{pkg.basename}' will get #{pkg.version}-#{pkg.revision}" puts "Existing installs will be updated at the next d3 sync." end ### Edit an existing or importing package ### ### @param pkg[D3::Package, nil] A possibly unsaved D3::Package to edit. ### This is usually used for importing JSS pkgs into d3. ### ### @return [void] ### def edit_package (pkg=nil) unless pkg pkg_id = get_pkg_from_cli_or_prompt pkg = pkg_id ? D3::Package.new(id: pkg_id) : nil end if pkg.nil? puts "No targets given to edit or no matching package found" return end if @options.walkthru changes_to_make = D3::Admin::Edit.loop_thru_editing_walkthru pkg else changes_to_make = D3::Admin::Edit.validate_cli_edits (@options) end # if @options.walkthru # Confirm the edition is still good if vers or rev changed changes_to_make = D3::Admin::Edit.check_new_edition pkg, changes_to_make # Confirm the auto and excl groups don't conflict with each other D3::Admin::Edit.check_for_new_group_overlaps pkg, changes_to_make # exit if no changes on edit if changes_to_make.empty? and not @options.import puts "No changes to make!" return end # confirm if @options.import confirm_heading = "Import #{pkg.edition}, JSS id #{pkg.id}, into d3" conf_deets = "with these non-default settings..." else confirm_heading = "Make changes to #{pkg.edition}, JSS id #{pkg.id}" conf_deets = "Here are the changes..." end D3::Admin::Edit::EDITING_OPTIONS.each do |opt| next unless changes_to_make.keys.include? opt new_val = changes_to_make[opt] opt_def = D3::Admin::OPTIONS[opt] label = opt_def[:label] if opt_def[:display_conversion] new_val_display = opt_def[:display_conversion].call(new_val) else new_val_display = new_val end new_val_display = new_val_display.to_s.empty? ? 'none' : new_val_display conf_deets += "\n#{label}: #{new_val_display}" end confirm confirm_heading, conf_deets # Save if importing pkg.save if @options.import # make the changes D3::Admin::Edit.process_edits pkg, changes_to_make puts @options.import ? "Done: the package has been imported to d3." : "Done! Your changes have been saved." end # edit_pkg ### delete a pkg, optionallay archiving it ### ### @return [void] ### def delete_package pkg_id = get_pkg_from_cli_or_prompt if pkg_id.nil? puts "No targets given to delete or no matching package found" return end # check to see if the pkg is missing if D3::Package.package_data[pkg_id][:status] == :missing || (not JSS::Package.all_ids.include? pkg_id) delete_missing_package pkg_id return else pkg = D3::Package.new id: pkg_id end got_scripts = (not pkg.script_ids.values.empty?) if got_scripts if @options.walkthru @options.keep_scripts = D3::Admin::Interactive.get_value(:get_keep_scripts, default = 'n', check_method = :validate_yes_no) @options.keep_in_jss = D3::Admin::Interactive.get_value(:get_keep_in_jss, default = 'n', check_method = :validate_yes_no) end # if @options.walkthru if @options.keep_scripts deets = " - Keeping pre- or post- scripts in the JSS" else deets = " - Deleting pre- or post- scripts not used elsewhere" end # if @options.keep_scripts else deets = " - No pre- or post- scripts to delete" end # if got_scripts if @options.keep_in_jss deets += "\n - Leaving the Casper package in the JSS" else deets += "\n - Deleting the Casper package as well as the d3 data" end confirm "DELETE #{pkg.edition}, JSS id #{pkg.id}\nDist.Point Filename: #{pkg.filename}", deets if @options.keep_scripts && got_scripts puts "Scanning for other packages or policies using pre- or post- scripts before deleting..." end script_actions = pkg.delete( admin: @admin, keep_scripts: @options.keep_scripts, keep_in_jss: @options.keep_in_jss, rwpw: D3::Admin::Auth.rw_credentials(:dist)[:password] ) unless script_actions.empty? puts "Here' what happened to the scripts:" puts script_actions.join "\n" end puts "Done! #{pkg.edition} has been deleted" end # delete package ### Delete a package that's missing from the JSS ### ### def delete_missing_package (pkgid) pkg_data = D3::Package.package_data[pkgid] script_ids = [pkg_data[:pre_install_script_id], pkg_data[:post_install_script_id], pkg_data[:pre_remove_script_id], pkg_data[:post_remove_script_id] ].compact if script_ids.empty? deets = " - No pre- or post- scripts to delete" else if @options.keep_scripts deets = " - Keeping pre- or post- scripts in the JSS" else deets = " - Deleting pre- or post- scripts not in use elsewhere" end # if @options.keep_scripts end # if script_ids.empty? confirm "DELETE #{pkg_data[:edition]}\n** Already missing from the JSS **", deets JSS::DB_CNX.db.query "DELETE FROM #{D3::Database::PACKAGE_TABLE[:table_name]} WHERE package_id = #{pkgid}" puts "Package #{pkg_data[:edition]} deleted." if (not @options.keep_scripts) && (not script_ids.empty?) puts "Scanning for other packages or policies using pre- or post- scripts before deleting..." policy_scripts = D3.policy_scripts script_ids.each do |victim_script_id| next unless JSS::Script.all_ids.include? victim_script_id victim_script_name = JSS::Script.map_all_ids_to(:name)[victim_script_id] pol_users = [] policy_scripts.each do |pol, pol_scripts| if pol_scripts.include? victim_script_id puts "Script '#{victim_script_name}' in use by policy '#{pol}'" pol_users << pol end end # policy scripts.each d3_users = (D3::Package.packages_for_script(victim_script_id) - [pkgid]) d3_users.each{|pkgid| puts "Script '#{victim_script_name}' in use by d3 edition '#{D3::Package.ids_to_editions[pkgid]}'" } if pol_users.empty? and d3_users.empty? JSS::Script.new(id: victim_script_id).delete puts "Deleted script '#{victim_script_name}'" end end # do script id end # if @options.keep_scripts && (not script_ids.empty?) end # delete_missing_package (pkgid) ### Get an existing pkg id from the user via prompt (if walkthru) or the ### first thing listed in @targets. We don't instantiate the pkg here ### because it might be :missing in the JSS. ### ### @return [Ingeter, nil] an existing d3 pkg id if one was chosen ### def get_pkg_from_cli_or_prompt if @options.walkthru and @targets.first.nil? D3::Admin::Interactive.get_value :get_existing_package, nil, :validate_existing_package else pkg_data = D3::Package.find_package(@targets.first, :hash) return pkg_data ? pkg_data[:id] : nil end end ### Get confirmation before making any changes to a pkg. ### ### This method just taks a string of text to show the user ### and shows it below a standard, attention-getting header line ### It then waits for the user to type y or n and exits if n was typed. ### ### @param action[String] a textual representation of what we're confirming ### ### @param details[String] The details requireing confirmation ### ### def confirm (action, details = nil) puts puts "*****************************************" puts "Ahoy there! You are about to:\n#{action}" puts "*****************************************" puts details if details if @options.auto_confirm puts "auto-confirmed! Here we go....." else puts reply = Readline.readline("Are you SURE? (y/n): ", false) return true if reply =~ /^y/i puts "Cancelled - wise choice!" exit 0 end # if autoconfirm end # confirm ### show details about an existing package ### ### @return [void] ### def show_package_info pkg_id = get_pkg_from_cli_or_prompt if pkg_id.nil? puts "No targets given or no matching package found" return end pkg = D3::Package.new id: pkg_id pkg_deets = <<-ENDDEETS ***************************************** Details about #{pkg.edition} JSS id: #{pkg.id}, Status: #{pkg.status} **************************************** ENDDEETS pkg_deets += "Basename: #{pkg.basename}\n" pkg_deets += "Version: #{pkg.version}\n" pkg_deets += "Revision: #{pkg.revision}\n" pkg_deets += "Added to on: #{pkg.added_date.strftime "%Y-%m-%d"}\n" pkg_deets += "Added by: #{pkg.added_by}\n" if pkg.release_date pkg_deets += "Released on: #{pkg.release_date.strftime "%Y-%m-%d"}\n" pkg_deets += "Released by: #{pkg.released_by}\n" end done = [:basename, :version, :revision, :added_by, :added_date, :released_by, :release_date] D3::Admin::Edit::EDITING_OPTIONS.each do |opt| next if done.include? opt label = D3::Admin::OPTIONS[opt][:label] if D3::Admin::OPTIONS[opt][:display_conversion] val_display = D3::Admin::OPTIONS[opt][:display_conversion].call(pkg.send opt) else val_display = pkg.send opt end val_display = val_display.to_s.empty? ? "none" : val_display pkg_deets += "#{label}: #{val_display}\n" end puts pkg_deets puts end # show_package_info ### search for and print a list of packages in d3 ### ### @return [void] ### def search D3::Admin::Report.connect_for_reports if @options.walkthru # prompt for search type? search_for = D3::Admin::Interactive.prompt_for_data( desc: "SEARCH PACKAGES BY?\nAre you searching for packages by basename, or by scoped groups?\nEnter 'b' or 'g'", prompt: "Basenames or Groups", default: 'b', required: false ) @options.search_groups = true if search_for =~ /^g/i @targets = [D3::Admin::Interactive.get_search_target] if @targets.empty? @options.status = D3::Admin::Interactive.get_status_for_filter.split(/,\s*/) if @options.status.empty? end #if @options.walkthru @options.status = [] if @options.status.include? "all" # show all pkgs or group scope if @targets.empty? or @targets.include? "all" if @options.search_groups D3::Admin::Report.list_all_pkgs_with_scope @options.status else D3::Admin::Report.list_packages nil, @options.status end return end # show specific basenames or group scopes @targets.each do |search_target| # groups? if @options.search_groups found_groups = JSS::ComputerGroup.all_names.select{|gn| gn =~ /#{search_target}/} if found_groups.empty? puts "No computer groups in Casper match '#{search_target}'" else found_groups.each {|fgn| D3::Admin::Report.list_scoped_installs fgn, @options.status, :auto D3::Admin::Report.list_scoped_installs fgn, @options.status, :excluded } # found_groups.each end # if found_groups.empty? # basenames else found_basenames = D3::Package.all_basenames.select{|bn| bn =~ /#{search_target}/} if found_basenames.empty? puts "No basenames in d3 match '#{search_target}'" else found_basenames.each {|fbn| D3::Admin::Report.list_packages fbn, @options.status } end # if found_basenames.empty? end # if @options.search_groups end # @targets.each do |search_target| end # def search ### show a report ### ### @return [void] ### def show_report D3::Admin::Report.connect_for_reports # targets are basenames or computer names if @options.walkthru # prompt for target? if @targets.empty? one_or_all = D3::Admin::Interactive.prompt_for_data( desc: "ALL COMPUTERS?\nAre you reporting for a single computer or all computers?\nEnter 'all' for all computers, '1' for one", prompt: "All or 1", default: 'all', required: false ) # basename report across computers if one_or_all == "all" @targets = [(D3::Admin::Interactive.get_value :get_basename, nil, :validate_basename)] # reporting single computers else @options.report_computers = true computer_id = D3::Admin::Interactive.get_value :get_computer, nil, :validate_computer @targets = [JSS::Computer.map_all_ids_to(:name)[computer_id]] end end # if targets empty # prompt for rcpts or puppies rcpts_or_pups = D3::Admin::Interactive.prompt_for_data( desc: "RECEIPTS OR PUPPIES?\nShould the report list receipts or pending puppytime installs?\nEnter 'r' for receipts, 'p' for puppies", prompt: "Receipts or puppies", required: false, default: 'r' ) @options.report_q = rcpts_or_pups =~ /^p/i ? true : false # prompt for status or frozen @options.status = D3::Admin::Interactive.get_status_for_filter(:with_frozen).split(/,\s*/) if @options.status.empty? end #if @options.walkthru @options.status = [] if @options.status.include? "all" # pass the --frozen option with the --status options, since # its treated like a pseudo-status, and thats where walkthru puts it too @options.status << "frozen" if @options.report_frozen reporting = @options.report_q ? :puppies : :receipts if @targets.empty? puts "No basename or computer name given for report" return end @targets.each do |target| # report puppies if reporting == :puppies if @options.report_computers D3::Admin::Report.report_single_puppy_queue target, @options.status else D3::Admin::Report.report_puppy_queues target, @options.status end # report receipts else if @options.report_computers D3::Admin::Report.report_single_computer_receipts target, @options.status else D3::Admin::Report.report_basename_receipts target, @options.status end end # if reporting puppies # blank line between reports, for clarity puts end # targets each end # show_report ### Run the config, saving hostnames, usernames and pws ### as needed ### ### @param display[Boolean] show the current admin config rather than ### set it. ### ### @return [void] ### def config if @targets.include? 'display' D3::Admin::Prefs.display_config else D3::Admin::Prefs.config @targets, @options end end # config end # class App ### save terminal state incase user interrupts during readline or less if $stdin.tty? stty_save = `stty -g`.chomp trap("SIGINT") do puts "\nCancelled! Woot!" system('stty', stty_save) exit 0 end end begin app = App.new app.run rescue puts "An error occurred: #{$!.class}: #{$!}" puts $@ if D3::Admin.debug exit 1 ensure if D3::Admin::Auth.connected? # JSS::DistributionPoint.master_distribution_point.unmount if JSS::DistributionPoint.master_distribution_point.mounted? D3::Admin::Auth.disconnect end end # begin