Module: D3
- Defined in:
- lib/d3.rb,
lib/d3.rb,
lib/d3/log.rb,
lib/d3/admin.rb,
lib/d3/state.rb,
lib/d3/client.rb,
lib/d3/package.rb,
lib/d3/utility.rb,
lib/d3/version.rb,
lib/d3/basename.rb,
lib/d3/database.rb,
lib/d3/admin/add.rb,
lib/d3/constants.rb,
lib/d3/puppytime.rb,
lib/d3/admin/auth.rb,
lib/d3/admin/edit.rb,
lib/d3/admin/help.rb,
lib/d3/client/cli.rb,
lib/d3/exceptions.rb,
lib/d3/admin/prefs.rb,
lib/d3/admin/state.rb,
lib/d3/client/auth.rb,
lib/d3/client/help.rb,
lib/d3/admin/report.rb,
lib/d3/client/lists.rb,
lib/d3/admin/options.rb,
lib/d3/configuration.rb,
lib/d3/admin/validate.rb,
lib/d3/client/receipt.rb,
lib/d3/package/mixins.rb,
lib/d3/package/aliases.rb,
lib/d3/package/getters.rb,
lib/d3/package/setters.rb,
lib/d3/package/validate.rb,
lib/d3/admin/interactive.rb,
lib/d3/package/constants.rb,
lib/d3/package/questions.rb,
lib/d3/client/environment.rb,
lib/d3/package/attributes.rb,
lib/d3/package/constructor.rb,
lib/d3/client/class_methods.rb,
lib/d3/package/class_methods.rb,
lib/d3/puppytime/puppy_queue.rb,
lib/d3/client/class_variables.rb,
lib/d3/package/client_actions.rb,
lib/d3/package/server_actions.rb,
lib/d3/package/class_variables.rb,
lib/d3/package/private_methods.rb,
lib/d3/puppytime/pending_puppy.rb
Overview
The D3 module provides the foundation, and the guts, of d3. Most of the work of the executables (d3, d3admin, d3helper, puppytime) is performed here. It in turn, is built upon the JSS module, provided by the ruby-jss gem, for API and MySQL access to the JSS.
Defined Under Namespace
Modules: Admin, Basename, Database, PuppyTime Classes: Client, Configuration, InstallError, Log, Package, PermissionError, PostInstallError, PostRemoveError, PreInstallError, PreRemoveError, ScriptError, UninstallError
Constant Summary collapse
- LOG =
the singleton instance of our logger
D3::Log.instance
- VERSION =
'3.0.26'.freeze
- SUPPORT_DIR =
where do we keep our stuff?
Pathname.new "/Library/Application Support/d3"
- DFT_CLI_ADMIN =
the default name to use as the @@script_admin
"unknown"
- DFT_PUPPY_ADMIN =
Shouldn’t see this ever, but if there’s no admin name for a pkg in the puppy queue, use this
"unknown-puppyq"
- AUTO_INSTALL_ADMIN =
the ‘admin’ name when a pkg is auto-installed during a d3 sync
"auto-installed"
- STANDARD_AUTO_GROUP =
When this word is used as an auto_group name it means that all clients should get the package automatically.
"standard"
- DISALLOWED_ADMINS =
When a real admin name is needed, it cant be one of these, or those listed in D3::CONFIG.client_prohibited_admin_names or we’ll raise an exception
[nil, "", "root", DFT_CLI_ADMIN, AUTO_INSTALL_ADMIN]
- REPORT_CONNECTION_TIMEOUT =
reports can take a long time to generate, lets set the timeout to a long time.
3600
- DEBUG_FILE =
when this file exists, d3, d3admin are set to debug mode. This is useful when you have a d3 command embedded in some other tool, but need to get debug logging.
Pathname.new "/tmp/d3debug-on"
- CONFIG =
The single instance of Configuration must be created before the LOG, since the log looks here for file names
D3::Configuration.instance
- PUPPY_Q =
here’s our one queue instance
D3::PuppyTime::PuppyQueue.instance
- @@loaded =
Set to true after all files are required
true
- @@verbosity =
This stores the current level of log messages sent to stdout. See D3::Log.level to set the level for messages sent to the log.
D3::Log::DFT_VERBOSITY
- @@force =
Have we been asked to be forceful, and perform unnatural acts? Force is used in many different was in many places so we’ll store it here and anything can access it using D3::force, D3::unforce, and D3::forced?
false
Class Method Summary collapse
-
.admin ⇒ String
Try to figure out the login name of the admin running this code.
-
.badmins ⇒ Array
The list of names not allowed as the –admin option in d3.
-
.col_widths(data, header_row = []) ⇒ Array<Integer>
Given an Array of Arrays representing rows and columns of data figure out the widest width of each column and return an array of integers representing those widths.
-
.connect_for_reports(api_user, api_pw, db_user, db_pw) ⇒ Hash<String>
Reconnect to both the API and DB with a much larger timeout, and using an alternate DB server if one is defined.
-
.connected? ⇒ false, Hash
Are we connected to the API and DB servers?.
-
.force ⇒ void
Turn on force, module-wide.
-
.forced? ⇒ Boolean
Is force turned on, module-wide?.
-
.generate_report(lines, type: :fixed, header_row: [], **args) ⇒ String
Generate a report of columned data, either fixed-width or tab-delimited.
-
.less_text(text, show_help = true) ⇒ Object
Send a string to the terminal, possibly piping it through ‘less’ if the number of lines is greater than the number of terminal lines minus 3.
-
.loaded? ⇒ Boolean
is the module fully loaded?.
-
.log(msg, severity = D3::Log::DFT_LOG_LEVEL) ⇒ void
Log a message to the d3 log, possibly sending it to stderr as well.
-
.log_backtrace(e = $@) ⇒ Object
Log the lines of backtrace from the most recent exception but only if the current severity is :debug.
-
.parse_plist(plist) ⇒ Object
Parse a plist into a Ruby data structure.
-
.policy_scripts ⇒ Hash{String => Array<Integer>}
Get the ids of all scripts used by all policies This is a hash of PolicyName => Array of Script id’s.
-
.prohibited_by_process_running?(xprocs) ⇒ Boolean
Is there a process running that would prevent un/installation?.
-
.run_policy(policy, type, verbose = false) ⇒ boolean
Run a Jamf Pro policy on the local machine.
-
.unforce ⇒ void
Turn off force, module-wide.
-
.verbosity ⇒ Integer
The current verbosity level.
-
.verbosity=(new_val) ⇒ void
Set the level of verbosity to stderr.
Class Method Details
.admin ⇒ String
Try to figure out the login name of the admin running this code
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/d3/utility.rb', line 47 def self.admin no_good = self.badmins # use the USER if it's valid admin = ENV['USER'] # otherwise, try SUDO_USER admin = ENV['SUDO_USER'] if no_good.include? admin # otherwise, try SSH_CLIENT_USER admin = ENV['SSH_CLIENT_USER'] if no_good.include? admin # otherwise, use the default, which might still be bad admin = DFT_CLI_ADMIN if no_good.include? admin return admin end |
.badmins ⇒ Array
The list of names not allowed as the –admin option in d3
This just combines DISALLOWED_ADMINS and D3::CONFIG.client_prohibited_admin_names
73 74 75 76 |
# File 'lib/d3/utility.rb', line 73 def self.badmins return D3::DISALLOWED_ADMINS unless D3::CONFIG.client_prohibited_admin_names return D3::DISALLOWED_ADMINS + D3::CONFIG.client_prohibited_admin_names end |
.col_widths(data, header_row = []) ⇒ Array<Integer>
Given an Array of Arrays representing rows and columns of data figure out the widest width of each column and return an array of integers representing those widths
225 226 227 228 229 230 231 232 233 234 |
# File 'lib/d3/utility.rb', line 225 def self.col_widths (data, header_row = []) widths = header_row.map{|c| c.to_s.length} data.each do |row| row.each_index do |col| this_width = row[col].to_s.length widths[col] = this_width if this_width > widths[col].to_i end # do field end # do line widths end |
.connect_for_reports(api_user, api_pw, db_user, db_pw) ⇒ Hash<String>
Reconnect to both the API and DB with a much larger timeout, and using an alternate DB server if one is defined. Should be used by either the D3::Client for lists, or D3::Admin for reports, with appropriate credentials.
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 |
# File 'lib/d3/utility.rb', line 309 def self.connect_for_reports(api_user, api_pw, db_user, db_pw) JSS::API.connect :user => api_user, :pw => api_pw, :timeout => REPORT_CONNECTION_TIMEOUT if D3::CONFIG.report_db_server begin JSS::DB_CNX.connect( :server => D3::CONFIG.report_db_server, :user => db_user, :pw => db_pw, :timeout => REPORT_CONNECTION_TIMEOUT ) return {db: D3::CONFIG.report_db_server, api: JSS::CONFIG.api_server_name} rescue Mysql::ServerError::AccessDeniedError raise JSS::AuthenticationError, "Authentication error on report_db_server: Credentials must match #{JSS::CONFIG.db_username} on #{JSS::CONFIG.db_server_name}" end # begin end # if rpt db server JSS::DB_CNX.connect :user => db_user, :pw => db_pw, :timeout => REPORT_CONNECTION_TIMEOUT return {db: JSS::CONFIG.db_server_name, api: JSS::CONFIG.api_server_name} end |
.connected? ⇒ false, Hash
Are we connected to the API and DB servers?
returns false if the are not both connected returns a hash like this if both are connected:
=> “user@api_server”, :db => “sql_user@db_server”
112 113 114 115 116 117 118 119 |
# File 'lib/d3/state.rb', line 112 def self.connected? return false unless loaded? return false unless JSS::API.connected? and JSS::DB_CNX.connected? return { :api => (JSS::API.cnx.[:user] + "@" + JSS::API.cnx.[:server]), :db => (JSS::DB_CNX.user + "@" + JSS::DB_CNX.server) } end |
.force ⇒ void
This method returns an undefined value.
Turn on force, module-wide
80 81 82 83 |
# File 'lib/d3/state.rb', line 80 def self.force @@force = true D3::Client.set_env :force end |
.forced? ⇒ Boolean
Is force turned on, module-wide?
98 99 100 |
# File 'lib/d3/state.rb', line 98 def self.forced? @@force end |
.generate_report(lines, type: :fixed, header_row: [], **args) ⇒ String
Generate a report of columned data, either fixed-width or tab-delimited. The title line(s) are pre-pended with ‘# ’ for easier exclusion when using the report as input for some other program. If the :type is :fixed, so will the column header line.
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
# File 'lib/d3/utility.rb', line 162 def self.generate_report (lines, type: :fixed, header_row: [], **args) raise JSS::InvalidDataError, "The first argument must be an Array of Arrays" unless lines.is_a? Array raise JSS::InvalidDataError, "The header_row must be an Array" unless header_row.is_a? Array return "" if lines.empty? # tab delim is easy if type== :tab report_tab = header_row.join("\t") lines.each{|line| report_tab += "\n#{line.join("\t")}" } return report_tab.strip end # if :tab # below here, fixed width format = "" line_width = 0 header_row[0] = "# #{header_row[0]}" self.col_widths(lines, header_row).each do |w| # make sure there's a space between columns col_width = w + 1 # add the column to the printf format format += "%-#{col_width}s" line_width += col_width end format += "\n" # limit the total line width for the header the width of the terminal if IO.console height, width = IO.console.winsize line_width = width if line_width > width else line_width = 80 end # title if given report = args[:title] ? "# #{args[:title]}\n" : "" unless header_row.empty? raise JSS::InvalidDataError, "Header row must have #{lines[0].count} items" unless header_row.count == lines[0].count # then the header line if given report += format % header_row # add a separator report += "#" + ("-" * (line_width -1)) + "\n" end # add the rows lines.each { |line| report += format % line } return report end |
.less_text(text, show_help = true) ⇒ Object
Send a string to the terminal, possibly piping it through ‘less’ if the number of lines is greater than the number of terminal lines minus 3
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 |
# File 'lib/d3/utility.rb', line 247 def self.less_text (text, show_help = true) unless IO.console puts text return end height, width = IO.console.winsize if text.lines.count <= (height - 3) puts text return end if show_help help = "#------' ' next, 'b' prev, 'q' exit, 'h' help ------" text = "#{help}\n#{text}" end # point stdout through less, print, then restore stdout less = IO.popen("/usr/bin/less","w") begin less.puts text # this catches the quitting of 'less' before all the output # is displayed rescue Errno::EPIPE => e true ensure less.close end end |
.loaded? ⇒ Boolean
is the module fully loaded?
33 34 35 |
# File 'lib/d3/state.rb', line 33 def self.loaded? @@loaded end |
.log(msg, severity = D3::Log::DFT_LOG_LEVEL) ⇒ void
This method returns an undefined value.
Log a message to the d3 log, possibly sending it to stderr as well.
The message will appear in the log:
- if the log is writable by the current user
- based upon its severity level, and the current D3::Log.level.
Any message more severe than the log level will be logged.
The message will also appear on stderr if the message severity is at or higher than the current @@verbosity.
If the @@verbosity is :debug the messages to stderr will be prefixed with the message severity.
In the d3 command, @@verbosity is controlled with the -v, -q and -d options
See also D3::Log.log and the ruby Logger module. See also D3::verbosity=
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/d3/log.rb', line 55 def self.log (msg, severity = D3::Log::DFT_LOG_LEVEL) = D3::Log.check_level(severity) # send to stderr if needed if >= @@verbosity if @@verbosity == D3::Log::LOG_LEVELS[:debug] STDERR.puts "#{severity}: #{msg}" else STDERR.puts msg end end # # send to the logger D3::Log.instance.log msg, severity end |
.log_backtrace(e = $@) ⇒ Object
Log the lines of backtrace from the most recent exception but only if the current severity is :debug
74 75 76 77 |
# File 'lib/d3/log.rb', line 74 def self.log_backtrace( e = $@ ) return unless D3::LOG.level == :debug e.backtrace.each{|line| D3.log " #{line}", :debug } end |
.parse_plist(plist) ⇒ Object
Parse a plist into a Ruby data structure. This enhances Plist::parse_xml taking file paths, as well as XML Strings and reading the files regardless of binary/XML format.
see JSS::parse_plist TODO - make all calls to this go directly to JSS.parse_plist
290 291 292 |
# File 'lib/d3/utility.rb', line 290 def self.parse_plist (plist) JSS.parse_plist plist end |
.policy_scripts ⇒ Hash{String => Array<Integer>}
Get the ids of all scripts used by all policies This is a hash of PolicyName => Array of Script id’s
128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/d3/utility.rb', line 128 def self.policy_scripts qry = <<-ENDQ SELECT p.name, GROUP_CONCAT(ps.script_id) AS script_ids FROM policies p JOIN policy_scripts ps ON p.policy_id = ps.policy_id GROUP BY p.policy_id ENDQ res = JSS::DB_CNX.db.query qry p_scripts = {} res.each{|r| p_scripts[r[0]] = r[1].split(/,\s*/).map{|id| id.to_i} } p_scripts end |
.prohibited_by_process_running?(xprocs) ⇒ Boolean
Is there a process running that would prevent un/installation?
34 35 36 37 38 39 40 41 |
# File 'lib/d3/utility.rb', line 34 def self.prohibited_by_process_running? (xprocs) # this is needed in case saved rcpts have nil or a string instead # of an array, from pre v3.0.12 xprocs = JSS.to_s_and_a(xprocs)[:arrayform] processes = `/bin/ps -A -c -o comm`.split("\n") current_prohibiting = processes & xprocs return true unless current_prohibiting.empty? end |
.run_policy(policy, type, verbose = false) ⇒ boolean
Run a Jamf Pro policy on the local machine
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/d3/utility.rb', line 88 def self.run_policy (policy, type, verbose = false) D3.log "Running #{type} policy", :info # if numeric, and there's a policy with that id if policy =~ /^\d+$/ and polname = JSS::Policy.map_all_ids_to(:name)[policy] D3.log "Executing #{type} policy '#{polname}', id: #{policy}", :debug pol_to_run = "-id #{policy}" # if there's a policy with that name elsif polid = JSS::Policy.map_all_ids_to(:name).invert[policy] D3.log "Executing #{type} policy '#{policy}', id: #{polid}", :debug pol_to_run = "-id #{polid}" # else assume its a trigger else D3.log "Executing #{type} policy with trigger '#{policy}'", :debug pol_to_run = "-event '#{policy}'" end output = JSS::Client.run_jamf "policy", pol_to_run, verbose if D3::LOG.level == :debug D3.log "Policy execution output:", :debug output.lines.each{|l| D3.log " #{l.chomp}", :debug} end if output.include? "No policies were found for the" D3.log "No policy matching '#{policy}' was found in the JSS", :warn return false else D3.log "Done executing #{type} policy", :debug return true end end |
.unforce ⇒ void
This method returns an undefined value.
Turn off force, module-wide
89 90 91 92 |
# File 'lib/d3/state.rb', line 89 def self.unforce @@force = false D3::Client.unset_env :force end |
.verbosity ⇒ Integer
The current verbosity level
45 46 47 |
# File 'lib/d3/state.rb', line 45 def self.verbosity @@verbosity end |
.verbosity=(new_val) ⇒ void
This method returns an undefined value.
Set the level of verbosity to stderr. Messages logged via D3#log, of this severity and higher, will show up on stderr They may show up in the log depending on the D3::LOG.level
58 59 60 61 62 63 64 65 66 |
# File 'lib/d3/state.rb', line 58 def self.verbosity= (new_val) # range is 0-4 if we're given an integer # so force it to be in the range. if new_val.is_a? Fixnum new_val = 0 if new_val < 0 new_val = 4 if new_val > 4 end @@verbosity = D3::Log.check_level(new_val) end |