Class: D3::Client::Receipt
- Inherits:
-
Object
- Object
- D3::Client::Receipt
- Includes:
- Basename
- Defined in:
- lib/d3/client/receipt.rb
Overview
Receipt - a d3 package that is currently installed on this machine.
D3 receipts are stored as their native ruby objects in a YAML file located at D3::Client::Receipt::DATASTORE
When the module loads, the file is read if it exists and all receipts are available in
The datastore contains a Hash of D3::Client::Receipt objects, keyed by their basenames (only one installation of a basename can be on a machine at a time)
Constant Summary collapse
- DATASTORE =
This YAML file stores all D3::Client::Receipts on this machine
D3::SUPPORT_DIR + "receipts.yaml"
- DATASTORE_LOCKFILE =
This locks the loading of receipts when there’s a potential to write them back ou. See Receipt.load_receipts.
D3::SUPPORT_DIR + "receipts.lock"
- DATASTORE_LOCK_TIMEOUT =
How many seconds by default to keep trying to get the datastore lockfile.
10
- DATASTORE_STALE_LOCK_AGE =
If a lockfile is this many seconds old, warn that it might be stale and need manual cleanup. 600 secs = 10 min
600
- LAST_APP_USAGE_DIR =
This dir contains a plist for each GUI user, containing the last time any app was brought to the foreground for that user It’s updated by the helper app d3RepoMan.app which should always be running while a GUI user is logged in if expiration is turned on.
D3::SUPPORT_DIR + "Usage"
- APP_USAGE_MONITOR_PROC =
This is the process (as listed in the output of ‘/bin/ps -A -c -o comm’) that updates the LAST_APP_USAGE_FILE. If it isn’t running as root when expiration is attempted, then expiration won’t happen.
"d3RepoMan"
- MAX_APP_USAGE_UPDATE_AGE =
The newest of the plists in the LAST_APP_USAGE_DIR must have been updated within the last X number of seconds, or else we assume either no one’s logged in for a while, or something’s wrong with the usage monitoring, since nothing new has come to the foreground in that long. If so, nothing will be expired. Default is 24 hours
60 * 60 * 24
- REQUIRED_INIT_ARGS =
These args are required when creating a new D3::Client::Receipt
[ :basename, :version, :revision, :admin, :id, :jamf_rcpt_file, :status ]
- CHANGABLE_ATTRIBS =
Only these attributes can be changed after a receipt is created
[ :status, :removable, :pre_remove_script_id, :post_remove_script_id, :expiration, :expiration_paths, :prohibiting_processes ]
- @@installed_rcpts =
The current receipts. See D3::Client::Receipt.load_receipts and D3::Client::Receipt.all
nil
- @@got_lock =
Do we currently have the rw lock?
nil
Instance Attribute Summary collapse
-
#admin ⇒ String
included
from Basename
readonly
Who’s uploading, releasing, installing, or archiving this thing?.
-
#apple_pkg_ids ⇒ Array<String>
readonly
If its an apple pkg, what pkg_id’s does it install?.
-
#basename ⇒ String
included
from Basename
readonly
The basname of the thing installed.
-
#custom_expiration ⇒ Boolean
Is the expiration on this rcpt a custom one? If so, it’ll be carried forward when auto-updates occur.
-
#expiration ⇒ Integer
included
from Basename
readonly
The days of disuse before an expirable edition expires.
-
#expiration_paths ⇒ String
included
from Basename
readonly
The path to the executable that needs come to the foreground to prevent expiration.
-
#frozen ⇒ Boolean
Is this rcpt exempt from auto-updates to its basename? If so, d3 sync will not update it, but a manual d3 install still can, and will re-enable syncs.
-
#id ⇒ Integer
included
from Basename
readonly
The JSS id of this package.
-
#installed_at ⇒ Time
readonly
When was it installed?.
-
#jamf_rcpt_file ⇒ Pathnamee
readonly
The JAMF rcpt file for this installation.
-
#last_usage ⇒ Time?
readonly
The last usage date for this receipt and the number of days ago that was.
-
#last_usage_as_of ⇒ Time?
readonly
When was @last_usage updated? nil if never checked, or no @expiration_paths.
-
#manually_installed ⇒ Boolean
(also: #manual?)
readonly
Was this pkg manually installed?.
-
#package_type ⇒ Symbol
included
from Basename
readonly
Is this package a .dmg or .pkg?.
-
#post_remove_script_id ⇒ Integer?
(also: #post_remove_script?)
The jss id of the post-remove-script.
-
#pre_remove_script_id ⇒ Integer?
(also: #pre_remove_script?)
The jss id of the pre-remove-script.
-
#prohibiting_processes ⇒ Array<String>
included
from Basename
readonly
An array of Strings for matching to the output lines of ‘/bin/ps -A -c -o comm’.
-
#removable ⇒ Boolean
(also: #removable?)
Can it be uninstalled?.
-
#revision ⇒ Integer
included
from Basename
readonly
The d3 release number of the thing installed.
-
#status ⇒ Symbol
included
from Basename
readonly
Whats the d3 status of this package? One of the values of D3::Basename::STATUSES.
-
#version ⇒ String
included
from Basename
readonly
The version of the thing installed.
Class Method Summary collapse
-
.add_receipt(receipt, replace = false) ⇒ void
Add a D3::Client::Receipt to the local rcpt database.
-
.all(refresh = false) ⇒ Hash{String => D3::Client::Receipt}
A hash of all d3 receipts currently installed on this machine.
-
.basenames(refresh = false) ⇒ Object
Return an array of the basenames of all installed d3 pkgs.
-
.delete_receipt(basename) ⇒ Object
An alias of selfself.remove_receipt.
-
.deprecated(refresh = false) ⇒ Hash
Return a hash of D3::Client::Receipt objects for all installed deprecated d3 pkgs, keyed by their basenames.
-
.find_receipt(rcpt_to_find) ⇒ D3::Client::Receipt?
Given a basename, edition, or id return the matching D3::Receipt or nil if no match.
-
.force_clear_datastore_lock ⇒ Object
Force the release of the lock, regardless of who has it Useful for testing, but very dangerous - could cause data loss.
-
.frozen(refresh = false) ⇒ Hash
Return a hash of D3::Client::Receipt objects for all installed frozen d3 receipts, keyed by their basenames.
-
.get_datastore_lock(lock_timeout = DATASTORE_LOCK_TIMEOUT) ⇒ void
Try to get the lock for read-write access to the datastore.
-
.got_lock? ⇒ boolean
Do we currently have the rw lock on the rcpt file?.
-
.live(refresh = false) ⇒ Hash
Return a hash of D3::Client::Receipt objects for all installed live d3 pkgs, keyed by their basenames.
-
.load_receipts(rw = false, lock_timeout = DATASTORE_LOCK_TIMEOUT) ⇒ void
Load in the existing rcpt database if it exists.
-
.manual(refresh = false) ⇒ Hash
Return a hash of D3::Client::Receipt objects for all manually installed pkgs (live or pilot) keyed by their basenames.
-
.os_pkg_rcpts(refresh = false) ⇒ Object
An array of apple bundle id’s for all .[m]pkgs currently known to the OS’s receipt db.
-
.pilots(refresh = false) ⇒ Hash
Return a hash of D3::Client::Receipt objects for all installed pilot d3 pkgs, keyed by their basenames.
-
.rebuild_database ⇒ void
Rebuild the receipt database by reading the jamf receipts and using server data.
-
.release_datastore_lock ⇒ Object
Release the rw lock on the datastore, if we have it.
-
.reload_receipts(rw = false, lock_timeout = DATASTORE_LOCK_TIMEOUT) ⇒ void
Reload the existing rcpt database.
-
.remove_receipt(basename) ⇒ void
Delete a D3::Client::Receipt from the local databse.
-
.save_receipts(release_lock = true) ⇒ void
Write existing rcpt database to disk.
Instance Method Summary collapse
-
#<=>(other) ⇒ Object
included
from Basename
Use comparable to give sortability and equality.
-
#days_since_last_usage ⇒ Integer?
Return the number of days since the last usage for the @expiration_paths for this receipt s Returns nil if last_usage is nil.
-
#delete ⇒ void
Delete this receipt from the local machine.
-
#deleted? ⇒ Boolean
Has this rcpt been deleted? See also #delete.
-
#deprecated? ⇒ Boolean
included
from Basename
Is the status :deprecated?.
-
#edition ⇒ String
included
from Basename
While several packages can have the same basename, the combination of basename, version, and revision (called the ‘edition’) must be unique among the d3 packages.
-
#expiration=(new_val) ⇒ void
Set a new expiration period WARNING: setting this to a lower value might cause the rcpt to be uninstalled at the next sync.
-
#expiration_paths=(new_val) ⇒ void
Set a new expiration path WARNING: changing this to a new value might cause the rcpt to be uninstalled at the next sync.
-
#expiration_paths_match?(other_exp_paths) ⇒ Boolean
included
from Basename
Does a given array of pathnames have the same elements as This is generally used to compare two @expiration_paths arrays for “equality”.
-
#expire(verbose = false, force = D3.forced?) ⇒ String?
Expire this item - uninstall it if no foreground use in the expiration period.
-
#formatted_details ⇒ String
installed pkg.
-
#freeze ⇒ void
Freeze this rcpt.
-
#frozen? ⇒ Boolean
Is this rcpt frozen?.
-
#initialize(args = {}) ⇒ Receipt
constructor
Args are: - :basename, required - :version, required - :revision, required - :admin, required - :id, required - :status, required, :pilot or :live (or rarely :deprecated) - :jamf_rcpt_file, required.
-
#live? ⇒ Boolean
included
from Basename
Is the status :live?.
-
#make_live ⇒ Object
If a currently installed pilot goes live, just change it’s state and mark it so.
-
#missing? ⇒ Boolean
included
from Basename
Is the status :missing?.
-
#pilot? ⇒ Boolean
included
from Basename
Is the status :pilot?.
-
#prohibiting_processes=(new_val) ⇒ Object
Set new prohibiting process(es).
-
#repair ⇒ void
Repair any missing or invalid data in the receipt based on the matching D3::Package data.
-
#run_post_remove(verbose = false) ⇒ Array<Integer, String>
Run the post-remove script, return the exit status and output.
-
#run_pre_remove(verbose = false) ⇒ Array<Integer, String>
Run the pre-remove script, return the exit status and output.
-
#saved? ⇒ Boolean
included
from Basename
Is the status :saved?.
-
#should_expire? ⇒ Boolean
Should this item be expired right now?.
-
#skipped? ⇒ Boolean
included
from Basename
Is this pkg skipped? See Database::PACKAGE_STATUSES for details.
-
#status=(new_status) ⇒ Symbol
set the status - for rcpts, this can’t be a private method.
-
#unfreeze ⇒ void
(also: #thaw)
Unfreeze this rcpt.
-
#uninstall(verbose = false, force = D3::forced?) ⇒ void
UnInstall this pkg, and return the output of ‘jamf uninstall’ or “receipts removed”.
-
#uninstall_via_apple_rcpt(verbose = false) ⇒ Boolean
Uninstall this .pkg by looking up the files it installed via pkgutil and deleting them directly.
-
#update ⇒ Object
Update the current receipt in the receipt store.
Constructor Details
#initialize(args = {}) ⇒ Receipt
Args are:
- :basename, required
- :version, required
- :revision, required
- :admin, required
- :id, required
- :status, required, :pilot or :live (or rarely :deprecated)
- :jamf_rcpt_file, required
- :apple_pkg_ids, optional in general, required for .pkg installers
- :installed_at, optional, defaults to Time.now
- :removable, optional, defaults to false
- :frozen, optional, defaults to false
- :pre_remove_script_id, optional
- :post_remove_script_id, optional
550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 |
# File 'lib/d3/client/receipt.rb', line 550 def initialize(args = {}) missing_args = REQUIRED_INIT_ARGS - args.keys unless missing_args.empty? raise JSS::MissingDataError, "D3::Client::Receipt initialization requires these arguments: :#{REQUIRED_INIT_ARGS.join(', :')}" end args[:installed_at] ||= Time.now @basename = args[:basename] @version = args[:version] @revision = args[:revision] @admin = args[:admin] @id = args[:id] @status = args[:status] # if we were given a string, convert to a Pathname # and if it was just a filename, add the Receipts Folder path @jamf_rcpt_file = Pathname.new args[:jamf_rcpt_file] if @jamf_rcpt_file.parent != JSS::Client::RECEIPTS_FOLDER @jamf_rcpt_file = JSS::Client::RECEIPTS_FOLDER + @jamf_rcpt_file end @apple_pkg_ids = args[:apple_pkg_ids] @installed_at = args[:installed_at] ? args[:installed_at].to_time : Time.now @removable = args[:removable] @prohibiting_processes = args[:prohibiting_processes] @prohibiting_processes ||= [] @frozen = args[:frozen] @pre_remove_script_id = args[:pre_remove_script_id] @post_remove_script_id = args[:post_remove_script_id] @expiration = args[:expiration].to_i @expiration_paths = args[:expiration_paths] @expiration_paths ||= [] @custom_expiration = args[:custom_expiration] @manually_installed = (@admin != D3::AUTO_INSTALL_ADMIN) @package_type = @jamf_rcpt_file.to_s.end_with?(".dmg") ? :dmg : :pkg end |
Instance Attribute Details
#admin ⇒ String (readonly) Originally defined in module Basename
Returns who’s uploading, releasing, installing, or archiving this thing?.
#apple_pkg_ids ⇒ Array<String> (readonly)
Returns if its an apple pkg, what pkg_id’s does it install?.
496 497 498 |
# File 'lib/d3/client/receipt.rb', line 496 def apple_pkg_ids @apple_pkg_ids end |
#basename ⇒ String (readonly) Originally defined in module Basename
Returns the basname of the thing installed.
#custom_expiration ⇒ Boolean
Returns is the expiration on this rcpt a custom one? If so, it’ll be carried forward when auto-updates occur.
516 517 518 |
# File 'lib/d3/client/receipt.rb', line 516 def custom_expiration @custom_expiration end |
#expiration ⇒ Integer (readonly) Originally defined in module Basename
Returns the days of disuse before an expirable edition expires. 0=never.
#expiration_paths ⇒ String (readonly) Originally defined in module Basename
Returns the path to the executable that needs come to the foreground to prevent expiration.
#frozen ⇒ Boolean
Returns is this rcpt exempt from auto-updates to its basename? If so, d3 sync will not update it, but a manual d3 install still can, and will re-enable syncs.
521 522 523 |
# File 'lib/d3/client/receipt.rb', line 521 def frozen @frozen end |
#id ⇒ Integer (readonly) Originally defined in module Basename
Returns the JSS id of this package.
#installed_at ⇒ Time (readonly)
Returns when was it installed?.
493 494 495 |
# File 'lib/d3/client/receipt.rb', line 493 def installed_at @installed_at end |
#jamf_rcpt_file ⇒ Pathnamee (readonly)
Returns the JAMF rcpt file for this installation.
490 491 492 |
# File 'lib/d3/client/receipt.rb', line 490 def jamf_rcpt_file @jamf_rcpt_file end |
#last_usage ⇒ Time? (readonly)
The last usage date for this receipt and the number of days ago that was
If we have access to the usage plists maintained by d3RepoMan, then read them and find the last usage, store it in @last_usage , and return it
If we don’t have access, return @last_usage, which is updated during d3 sync. Its up to the caller to use @last_usage_as_of appropriately.
If @last_usage has never been set, or there is no expiration path, returns nil.
525 526 527 |
# File 'lib/d3/client/receipt.rb', line 525 def last_usage @last_usage end |
#last_usage_as_of ⇒ Time? (readonly)
Returns When was @last_usage updated? nil if never checked, or no @expiration_paths.
529 530 531 |
# File 'lib/d3/client/receipt.rb', line 529 def last_usage_as_of @last_usage_as_of end |
#manually_installed ⇒ Boolean (readonly) Also known as: manual?
Returns was this pkg manually installed?.
499 500 501 |
# File 'lib/d3/client/receipt.rb', line 499 def manually_installed @manually_installed end |
#package_type ⇒ Symbol (readonly) Originally defined in module Basename
Returns Is this package a .dmg or .pkg?.
#post_remove_script_id ⇒ Integer? Also known as: post_remove_script?
Returns the jss id of the post-remove-script.
511 512 513 |
# File 'lib/d3/client/receipt.rb', line 511 def post_remove_script_id @post_remove_script_id end |
#pre_remove_script_id ⇒ Integer? Also known as: pre_remove_script?
Returns the jss id of the pre-remove-script.
507 508 509 |
# File 'lib/d3/client/receipt.rb', line 507 def pre_remove_script_id @pre_remove_script_id end |
#prohibiting_processes ⇒ Array<String> (readonly) Originally defined in module Basename
Returns an array of Strings for matching to the output lines of ‘/bin/ps -A -c -o comm’. If there’s a match, this pkg won’t be installed or uninstalled without a graceful quit.
#removable ⇒ Boolean Also known as: removable?
Returns can it be uninstalled?.
503 504 505 |
# File 'lib/d3/client/receipt.rb', line 503 def removable @removable end |
#revision ⇒ Integer (readonly) Originally defined in module Basename
Returns the d3 release number of the thing installed.
#status ⇒ Symbol Originally defined in module Basename
Returns whats the d3 status of this package? One of the values of D3::Basename::STATUSES.
#version ⇒ String (readonly) Originally defined in module Basename
Returns the version of the thing installed.
Class Method Details
.add_receipt(receipt, replace = false) ⇒ void
This method returns an undefined value.
Add a D3::Client::Receipt to the local rcpt database
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 |
# File 'lib/d3/client/receipt.rb', line 285 def self.add_receipt(receipt, replace = false) raise JSS::InvalidDataError, "Argument must be a D3::Client::Receipt" unless receipt.is_a? D3::Client::Receipt D3.log "Attempting to #{replace ? "replace" : "add"} receipt for #{receipt.edition}.", :debug self.reload_receipts :rw begin unless replace if @@installed_rcpts.member? receipt.basename raise JSS::AlreadyExistsError, "There's already a receipt on this machine for basemame '#{receipt.basename}'" end # if end # unless replace replacing = @@installed_rcpts[receipt.basename] ? true : false @@installed_rcpts[receipt.basename] = receipt self.save_receipts D3.log "#{replacing ? "Replaced" : "Added"} receipt for #{receipt.edition}", :info ensure # always release the rw lock even after an error self.release_datastore_lock end # begin end |
.all(refresh = false) ⇒ Hash{String => D3::Client::Receipt}
A hash of all d3 receipts currently installed on this machine. keyed by their basenames. (Only one edition of a basename can be installed at a time)
365 366 367 368 369 |
# File 'lib/d3/client/receipt.rb', line 365 def self.all (refresh = false) refresh = true if @@installed_rcpts.nil? self.reload_receipts if refresh @@installed_rcpts end |
.basenames(refresh = false) ⇒ Object
Return an array of the basenames of all installed d3 pkgs. This doesn’t include those items installed by other jamf methods
375 376 377 |
# File 'lib/d3/client/receipt.rb', line 375 def self.basenames(refresh = false) self.all(refresh).keys end |
.delete_receipt(basename) ⇒ Object
An alias of selfself.remove_receipt
333 |
# File 'lib/d3/client/receipt.rb', line 333 def self.delete_receipt(basename) ; self.remove_receipt(basename) ; end |
.deprecated(refresh = false) ⇒ Hash
Return a hash of D3::Client::Receipt objects for all installed deprecated d3 pkgs, keyed by their basenames
402 403 404 |
# File 'lib/d3/client/receipt.rb', line 402 def self.deprecated(refresh = false) self.all(refresh).select {|b,r| r.deprecated? } end |
.find_receipt(rcpt_to_find) ⇒ D3::Client::Receipt?
Given a basename, edition, or id return the matching D3::Receipt or nil if no match. If a basename is used, any edition installed will be returned if there is one.
If an edition or id is used, nil will be returned unless that exact pkg is installed.
348 349 350 351 352 353 354 355 356 |
# File 'lib/d3/client/receipt.rb', line 348 def self.find_receipt (rcpt_to_find) if self.all.keys.include? rcpt_to_find return self.all[rcpt_to_find] end self.all.values.each do |rcpt| return rcpt if rcpt.edition == rcpt_to_find or rcpt.id == rcpt_to_find.to_i end return nil end |
.force_clear_datastore_lock ⇒ Object
Force the release of the lock, regardless of who has it Useful for testing, but very dangerous - could cause data loss.
263 264 265 266 267 |
# File 'lib/d3/client/receipt.rb', line 263 def self.force_clear_datastore_lock D3.log "Force-clearing the receipt write lock", :debug DATASTORE_LOCKFILE.delete if DATASTORE_LOCKFILE.exist? @@got_lock = false end |
.frozen(refresh = false) ⇒ Hash
Return a hash of D3::Client::Receipt objects for all installed frozen d3 receipts, keyed by their basenames
411 412 413 |
# File 'lib/d3/client/receipt.rb', line 411 def self.frozen(refresh = false) self.all(refresh).select {|b,r| r.frozen? } end |
.get_datastore_lock(lock_timeout = DATASTORE_LOCK_TIMEOUT) ⇒ void
This method returns an undefined value.
Try to get the lock for read-write access to the datastore. Raise an exception if we fail after the timeout
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
# File 'lib/d3/client/receipt.rb', line 222 def self.get_datastore_lock (lock_timeout = DATASTORE_LOCK_TIMEOUT) D3.log "Attempting to get receipt datastore write lock.", :debug # try to get it 10x per second... if DATASTORE_LOCKFILE.exist? D3.log "Lock in use, retrying for #{lock_timeout} secs", :debug max_tries = lock_timeout * 10 tries = 0 while tries < max_tries do sleep 0.1 tries += 1 if DATASTORE_LOCKFILE.exist? end # while end # if DATASTORE_LOCKFILE.exist? if DATASTORE_LOCKFILE.exist? errmsg = "Couldn't get receipt write lock after #{lock_timeout} seconds." lockfile_age = (Time.now - DATASTORE_LOCKFILE.ctime).to_i # if its stale, warn that it might need manual fixing errmsg += " Potentially stale. Please investigate manually." if lockfile_age > DATASTORE_STALE_LOCK_AGE D3.log errmsg, :error raise JSS::TimeoutError, errmsg else DATASTORE_LOCKFILE.parent.mkpath DATASTORE_LOCKFILE.jss_save $$.to_s D3.log "Acquired write lock on receipt datastore.", :debug @@got_lock = true end end |
.got_lock? ⇒ boolean
Do we currently have the rw lock on the rcpt file?
273 274 275 |
# File 'lib/d3/client/receipt.rb', line 273 def self.got_lock? @@got_lock end |
.live(refresh = false) ⇒ Hash
Return a hash of D3::Client::Receipt objects for all installed live d3 pkgs, keyed by their basenames
393 394 395 |
# File 'lib/d3/client/receipt.rb', line 393 def self.live(refresh = false) self.all(refresh).select {|b,r| r.live? } end |
.load_receipts(rw = false, lock_timeout = DATASTORE_LOCK_TIMEOUT) ⇒ void
This method returns an undefined value.
Load in the existing rcpt database if it exists. This makes them available in @@installed_rcpts and from D3::Client::Receipt.all
When loading read-write, if another process has loaded them read-write, and hasn’t saved them yet, a lock file will be present and this load will retry for lock_timeout seconds before raising an exception
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/d3/client/receipt.rb', line 139 def self.load_receipts(rw = false, lock_timeout = DATASTORE_LOCK_TIMEOUT) # have we already loaded them? # (use self.reload if needed) return if @@installed_rcpts D3.log "Loading receipts, #{rw ? 'read-write' : 'read-only'}", :debug # get the lock if needed self.get_datastore_lock(lock_timeout) if rw @@installed_rcpts = DATASTORE.file? ? YAML.load(DATASTORE.read) : {} @@installed_rcpts ||= {} D3.log "Receipts loaded", :debug end |
.manual(refresh = false) ⇒ Hash
Return a hash of D3::Client::Receipt objects for all manually installed pkgs (live or pilot) keyed by their basenames
420 421 422 |
# File 'lib/d3/client/receipt.rb', line 420 def self.manual(refresh = false) self.all(refresh).select {|b,r| r.manual? } end |
.os_pkg_rcpts(refresh = false) ⇒ Object
An array of apple bundle id’s for all .[m]pkgs currently known to the OS’s receipt db
427 428 429 430 431 |
# File 'lib/d3/client/receipt.rb', line 427 def self.os_pkg_rcpts(refresh = false) @@os_pkg_rcpts = nil if refresh return @@os_pkg_rcpts if @@os_pkg_rcpts @@os_pkg_rcpts = `#{JSS::Composer::PKG_UTIL} --pkgs`.split("\n") end |
.pilots(refresh = false) ⇒ Hash
Return a hash of D3::Client::Receipt objects for all installed pilot d3 pkgs, keyed by their basenames
384 385 386 |
# File 'lib/d3/client/receipt.rb', line 384 def self.pilots(refresh = false) self.all(refresh).select{|b,r| r.pilot? } end |
.rebuild_database ⇒ void
This method returns an undefined value.
Rebuild the receipt database by reading the jamf receipts and using server data.
438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 |
# File 'lib/d3/client/receipt.rb', line 438 def self.rebuild_database orig_rcpts = self.all :refresh new_rcpts = {} jamf_rcpts = JSS::Client::RECEIPTS_FOLDER.children D3::Package.all.values.each do |d3_pkg| next unless jamf_rcpts.include? d3_pkg.receipt # do we already have a rcpt for this edition? if orig_rcpts[d3_pkg.basename] and (orig_rcpts[d3_pkg.basename].edition == d3_pkg.edition) orig_rcpt = orig_rcpts[d3_pkg.basename] else orig_rcpt = nil end # if there's more than one of the same basename (which means # someone installed a d3 pkg via non-d3 means) then # which one wins? I say the last one, but log it. if new_rcpts.keys.include? d3_pkg.basename D3.log "Rebuilding local receipt database: multiple Jamf Pro installs of basename '#{d3_pkg.basename}'", :warn new_rcpts.delete d3_pkg.basename end # new_rcpts.keys.include? d3_pkg.basename new_rcpts[d3_pkg.basename] = D3::Client::Receipt.new(:basename => d3_pkg.basename, :version => d3_pkg.version, :revision => d3_pkg.revision, :admin => (orig_rcpt ? orig_rcpt.admin : "unknown"), :installed_at => (orig_rcpt ? orig_rcpt.installed_at : Time.now), :id => d3_pkg.id, :status => d3_pkg.status, :jamf_rcpt_file => d3_pkg.receipt, :apple_pkg_ids => d3_pkg.apple_receipt_data.map{|r| r[:apple_pkg_id]}, :removable => d3_pkg.removable, :pre_remove_script_id => d3_pkg.pre_remove_script_id, :post_remove_script_id => d3_pkg.post_remove_script_id, :expiration => d3_pkg.expiration, :expiration_paths => d3_pkg.expiration_paths ) end # .each do |d3_pkg| @@installed_rcpts = new_rcpts self.save_receipts end |
.release_datastore_lock ⇒ Object
Release the rw lock on the datastore, if we have it.
253 254 255 256 257 258 |
# File 'lib/d3/client/receipt.rb', line 253 def self.release_datastore_lock return nil unless @@got_lock DATASTORE_LOCKFILE.delete if DATASTORE_LOCKFILE.exist? D3.log "Receipt datastore write lock released", :debug @@got_lock = false end |
.reload_receipts(rw = false, lock_timeout = DATASTORE_LOCK_TIMEOUT) ⇒ void
This method returns an undefined value.
Reload the existing rcpt database
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
# File 'lib/d3/client/receipt.rb', line 166 def self.reload_receipts (rw = false, lock_timeout = DATASTORE_LOCK_TIMEOUT) # if we haven't loaded them at all yet, just do that. unless @@installed_rcpts self.load_receipts rw, lock_timeout return end # unless @@installed_rcpts D3.log "Reloading receipts, #{rw ? 'read-write' : 'read-only'}", :debug # Are we trying to re-load with rw? if rw # if we already have the lock, then we don't need to get it again self.get_datastore_lock(lock_timeout) unless @@got_lock else # not reloading rw, so release the lock if we have it self.release_datastore_lock if @@got_lock end # reload it @@installed_rcpts = DATASTORE.file? ? YAML.load(DATASTORE.read) : {} @@installed_rcpts ||= {} D3.log "Receipts reloaded", :debug end |
.remove_receipt(basename) ⇒ void
This method returns an undefined value.
Delete a D3::Client::Receipt from the local databse
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 |
# File 'lib/d3/client/receipt.rb', line 311 def self.remove_receipt(basename) D3.log "Attempting to remove receipt for basename #{basename}", :info self.reload_receipts :rw begin old_rcpt = self.all[basename] if old_rcpt @@installed_rcpts.delete basename D3.log "Removed receipt for #{old_rcpt.edition}", :debug self.save_receipts else D3.log "No receipt for basename #{basename}", :debug end # if old_rcpt ensure self.release_datastore_lock end # begin end |
.save_receipts(release_lock = true) ⇒ void
This method returns an undefined value.
Write existing rcpt database to disk
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
# File 'lib/d3/client/receipt.rb', line 195 def self.save_receipts(release_lock = true) raise JSS::MissingDataError, "Receipts not loaded, can't save." unless @@installed_rcpts D3.log "Saving receipts", :debug unless @@got_lock D3.log "Receipts were loaded read-only, can't save", :error raise JSS::UnsupportedError,"Receipts were loaded read-only, can't save" end # ensure any deleted rcpts are gone @@installed_rcpts.delete_if{|basename, rcpt| rcpt.deleted? } DATASTORE.parent.mktree unless DATASTORE.parent.directory? DATASTORE.jss_save YAML.dump(@@installed_rcpts) D3.log "Receipts saved", :debug if release_lock self.release_datastore_lock end end |
Instance Method Details
#<=>(other) ⇒ Object Originally defined in module Basename
Use comparable to give sortability and equality.
#days_since_last_usage ⇒ Integer?
Return the number of days since the last usage for the @expiration_paths for this receipt s Returns nil if last_usage is nil
See also #last_usage
1066 1067 1068 1069 1070 |
# File 'lib/d3/client/receipt.rb', line 1066 def days_since_last_usage lu = last_usage return nil unless lu ((Time.now - lu) / 60 / 60 / 24).to_i end |
#delete ⇒ void
This method returns an undefined value.
Delete this receipt from the local machine. This removes both the JAMF receipt file, and the D3::Client::Receipt from the datastore, and sets @deleted to true.
890 891 892 893 894 895 |
# File 'lib/d3/client/receipt.rb', line 890 def delete @jamf_rcpt_file.delete if @jamf_rcpt_file.exist? D3::Client::Receipt.remove_receipt @basename D3.log "Deleted JAMF receipt file #{@jamf_rcpt_file.basename}", :debug @deleted = true end |
#deleted? ⇒ Boolean
Returns has this rcpt been deleted? See also #delete.
900 901 902 |
# File 'lib/d3/client/receipt.rb', line 900 def deleted? @deleted end |
#deprecated? ⇒ Boolean Originally defined in module Basename
Is the status :deprecated?
#edition ⇒ String Originally defined in module Basename
While several packages can have the same basename, the combination of basename, version, and revision (called the ‘edition’) must be unique among the d3 packages.
#expiration=(new_val) ⇒ void
This method returns an undefined value.
Set a new expiration period WARNING: setting this to a lower value might cause the rcpt to be uninstalled at the next sync.
855 856 857 858 |
# File 'lib/d3/client/receipt.rb', line 855 def expiration= (new_val) raise JSS::InvalidDataError, "#{edition} is not removable, no expiration allowed." unless @removable or new_val.to_i == 0 @expiration = new_val.to_i end |
#expiration_paths=(new_val) ⇒ void
This method returns an undefined value.
Set a new expiration path WARNING: changing this to a new value might cause the rcpt to be uninstalled at the next sync.
869 870 871 |
# File 'lib/d3/client/receipt.rb', line 869 def expiration_paths= (new_val) @expiration_paths = new_val end |
#expiration_paths_match?(other_exp_paths) ⇒ Boolean Originally defined in module Basename
Does a given array of pathnames have the same elements as This is generally used to compare two @expiration_paths arrays for “equality”
#expire(verbose = false, force = D3.forced?) ⇒ String?
Expire this item - uninstall it if no foreground use in the expiration period
1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 |
# File 'lib/d3/client/receipt.rb', line 1041 def expire(verbose = false, force = D3.forced?) return nil unless should_expire? begin D3::Client.set_env :expiring, edition D3.log "Expiring #{edition} after #{expiration} days of no use.", :warn uninstall verbose, force D3.log "Done expiring #{edition}", :info rescue D3.log "There was an error expiring #{edition}:\n #{$!}", :error D3.log_backtrace ensure D3::Client.unset_env :expiring end return deleted? ? edition : nil end |
#formatted_details ⇒ String
installed pkg
907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 |
# File 'lib/d3/client/receipt.rb', line 907 def formatted_details deets = <<-END_DEETS Edition: #{@edition} Status: #{@status} Frozen: #{frozen? ? "yes" : "no"} Install date: #{@installed_at.strftime "%Y-%m-%d %H:%M:%S"} Installed by: #{@admin} Manually installed: #{manual?} JAMF receipt file: #{@jamf_rcpt_file.basename} Jamf Pro Pkg ID: #{@id} Un-installable: #{removable? ? "yes" : "no"} END_DEETS if removable? if JSS::API.connected? pre_name = pre_remove_script_id ? JSS::Script.map_all_ids_to(:name)[pre_remove_script_id] : "none" post_name = post_remove_script_id ? JSS::Script.map_all_ids_to(:name)[post_remove_script_id] : "none" else # not connected pre_name = pre_remove_script_id ? "yes" : "none" post_name = post_remove_script_id ? "yes" : "none" end deets += <<-END_DEETS Pre-remove script: #{pre_name} Post-remove script: #{post_name} END_DEETS end # if removable? if @package_type == :pkg and @apple_pkg_ids deets += <<-END_DEETS Apple.pkg ids: #{@apple_pkg_ids.join(', ')} END_DEETS end if @expiration_paths if @expiration.to_i > 0 lu = last_usage if lu.nil? last_usage_display = "Unknonwn" elsif lu == @installed_at last_usage_display = "Not since installation (#{days_since_last_usage} days ago)" else last_usage_display = "#{lu.strftime '%Y-%m-%d %H:%M:%S'} (#{days_since_last_usage} days ago)" end # if my_last_usage == @installed_at deets += <<-END_DEETS Expiration period: #{@expiration} days#{@custom_expiration ? ' (custom)' : ''} Expiration path(s): #{D3::Database::ARRAY_OF_PATHNAMES_TO_COMMA_STRING.call @expiration_paths} Last brought to foreground: #{last_usage_display} END_DEETS end # if exp > 0 end # if exp path return deets end |
#freeze ⇒ void
This method returns an undefined value.
Freeze this rcpt
833 834 835 |
# File 'lib/d3/client/receipt.rb', line 833 def freeze @frozen = true end |
#frozen? ⇒ Boolean
Is this rcpt frozen?
824 825 826 827 |
# File 'lib/d3/client/receipt.rb', line 824 def frozen? return true if @frozen return false end |
#live? ⇒ Boolean Originally defined in module Basename
Is the status :live?
#make_live ⇒ Object
If a currently installed pilot goes live, just change it’s state and mark it so.
962 963 964 965 966 967 |
# File 'lib/d3/client/receipt.rb', line 962 def make_live return true if live? D3.log "Marking pilot receipt #{edition} live", :debug @status = :live update end |
#missing? ⇒ Boolean Originally defined in module Basename
Is the status :missing?
#pilot? ⇒ Boolean Originally defined in module Basename
Is the status :pilot?
#prohibiting_processes=(new_val) ⇒ Object
Set new prohibiting process(es)
874 875 876 |
# File 'lib/d3/client/receipt.rb', line 874 def prohibiting_processes=(new_val) @prohibiting_processes = new_val end |
#repair ⇒ void
This method returns an undefined value.
Repair any missing or invalid data in the receipt based on the matching D3::Package data
800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 |
# File 'lib/d3/client/receipt.rb', line 800 def repair raise JSS::UnsupportedError, "This receipt has been deleted" if @deleted d3_pkg = D3::Package.fetch :id => @id @basename = d3_pkg.basename @version = d3_pkg.version @revision = d3_pkg.revision @admin ||= "Repaired" @status = d3_pkg.status @jamf_rcpt_file = d3_pkg.receipt @apple_pkg_ids = d3_pkg.apple_receipt_data.map{|r| r[:apple_pkg_id]} @removable = d3_pkg.removable @manually_installed = (@admin != D3::AUTO_INSTALL_ADMIN) @package_type = @jamf_rcpt_file.end_with?(".dmg") ? :dmg : :pkg @expiration = d3_pkg.expiration @expiration_paths = d3_pkg.expiration_paths end |
#run_post_remove(verbose = false) ⇒ Array<Integer, String>
Run the post-remove script, return the exit status and output
734 735 736 737 738 739 740 741 742 743 744 745 746 |
# File 'lib/d3/client/receipt.rb', line 734 def run_post_remove (verbose = false) D3::Client.set_env :post_remove, edition D3.log "Running post_remove script", :debug begin result = JSS::Script.fetch(:id => @post_remove_script_id).run :verbose => verbose, :show_output => verbose rescue D3::ScriptError raise PostRemoveError, $! ensure D3::Client.unset_env :post_remove end D3.log "Finished post_remove script", :debug return result end |
#run_pre_remove(verbose = false) ⇒ Array<Integer, String>
Run the pre-remove script, return the exit status and output
714 715 716 717 718 719 720 721 722 723 724 725 726 |
# File 'lib/d3/client/receipt.rb', line 714 def run_pre_remove (verbose = false) D3::Client.set_env :pre_remove, edition D3.log "Running pre_remove script", :debug begin result = JSS::Script.fetch(:id => @pre_remove_script_id).run :verbose => verbose, :show_output => verbose rescue D3::ScriptError raise PreRemoveError, $! ensure D3::Client.unset_env :pre_remove end D3.log "Finished pre_remove script", :debug return result end |
#saved? ⇒ Boolean Originally defined in module Basename
Is the status :saved?
#should_expire? ⇒ Boolean
Should this item be expired right now?
973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 |
# File 'lib/d3/client/receipt.rb', line 973 def should_expire? # gotta be expirable return false if @expiration.nil? or @expiration == 0 # gotta have an expiration path if @expiration_paths.empty? D3.log "Not expiring #{edition} because: No Expiration Path(s) for #{edition}", :debug return false end # must have up-to-date last usage data # this also checks for usage dir existence and plist age my_last_usage = last_usage unlaunched_days = days_since_last_usage # gotta have expirations turned on system-wide unless D3::CONFIG.client_expiration_allowed D3.log "Not expiring #{edition} because: expirations not allowed on this client", :debug return false end # gotta be removable unless @removable D3.log "Not expiring #{edition} because: not removable", :debug return false end # gotta have an expiration set for this rcpt. if (not @expiration.is_a? Fixnum) or @expiration <= 0 D3.log "Not expiring #{edition} because: expiration value is invalid", :debug return false end # the app usage monitor must be running all_procs = `/bin/ps -A -c -o user -o comm`.split("\n") if all_procs.select{|p| p =~ /\s#{APP_USAGE_MONITOR_PROC}$/}.empty? D3.log "Not expiring #{edition} because: '#{APP_USAGE_MONITOR_PROC}' isn't running", :debug return false end # did we get any usage dates above? unless my_last_usage and unlaunched_days D3.log "Not expiring #{edition} because: could not retrieve last usage data", :debug return false end # must be unlaunched for at least the expiration period if unlaunched_days <= @expiration D3.log "Not expiring #{edition} because: path has launched within #{expiration} days", :debug return false end # gotta be connected to d3 unless D3.connected? D3.log "Not expiring #{edition} because: not connected to the servers", :debug return false end # if we're here, expire this thing return true end |
#skipped? ⇒ Boolean Originally defined in module Basename
Returns Is this pkg skipped? See Database::PACKAGE_STATUSES for details.
#status=(new_status) ⇒ Symbol
set the status - for rcpts, this can’t be a private method
1165 1166 1167 1168 1169 |
# File 'lib/d3/client/receipt.rb', line 1165 def status= (new_status) raise JSS::InvalidDataError, "status must be one of :#{D3::Basename::STATUSES.join(', :')}" unless D3::Basename::STATUSES.include? new_status @status = new_status update end |
#unfreeze ⇒ void Also known as: thaw
This method returns an undefined value.
Unfreeze this rcpt
841 842 843 |
# File 'lib/d3/client/receipt.rb', line 841 def unfreeze @frozen = false end |
#uninstall(verbose = false, force = D3::forced?) ⇒ void
This method returns an undefined value.
UnInstall this pkg, and return the output of ‘jamf uninstall’ or “receipts removed”
If there’s a pre-remove script, and it exits with a status of 111, the d3 & jamf receipts are removed, but the actual uninstall doesn’t happen. This would be usefull if the uninstall process is too complex for ‘jamf uninstall’ and is totally performed by the script.
For receipts from .pkg installers, the force option will force deletion even if the JSS isn’t available. It does this by using the No pre- or post- remove scripts will be run. Use with caution.
620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 |
# File 'lib/d3/client/receipt.rb', line 620 def uninstall (verbose = false, force = D3::forced?) raise D3::UninstallError, "#{edition} is not uninstallable" unless self.removable? depiloting = pilot? && skipped? begin # ...ensure... if uninstall_prohibited_by_process? and (not force) raise D3::InstallError, "#{edition} cannot be uninstalled now because one or more of the following processes is running: #{D3::Admin::OPTIONS[:prohibiting_processes][:display_conversion].call @prohibiting_processes}" end D3::Client.set_env :removing, edition D3.log "Uninstalling #{edition}", :warn # run a preflight if needed. if pre_remove_script? (exit_status, output) = run_pre_remove verbose if exit_status == 111 delete D3.log "pre_remove script exited 111, deleted receipt for #{edition} but not doing any more.", :info return true elsif exit_status != 0 raise D3::UninstallError, "Error running pre_remove script (exited #{exit_status}), not uninstalling #{edition}" end # flight_status[0] == 111 end # if preflight? # if it is still on the server... if JSS::Package.all_ids.include? @id # uninstall the pkg D3.log "Running 'jamf uninstall' of #{edition}", :debug uninstall_worked = JSS::Package.fetch(:id => @id).uninstall(:verbose => verbose).exitstatus == 0 # if it isn't on the server any more.... else D3.log "Package is gone from server, no index available", :info # if forced, deleting the rcpt is 'uninstalling' if force D3.log "Force-deleting receipt for #{edition}.", :info uninstall_worked = true # no force else # we can't do anything with dmgs if @package_type == :dmg D3.log "Package was a .dmg, can't uninstall.\n Use --force to remove the receipt", :error uninstall_worked = false else uninstall_worked = uninstall_via_apple_rcpt end # if @package_type == :dmg end # if force end # JSS::Package.all_ids.include? @id ## Uninstall worked, so do more things and stuffs if uninstall_worked # remove this rcpt delete D3.log "Done, uninstalled #{edition}", :warn # run a postflight if needed if post_remove_script? (exit_status, output) = run_post_remove verbose if exit_status != 0 raise D3::UninstallError, "Error running post_remove script (exited #{exit_status}) for #{edition}" end end # if post_install_script? # uninstall failed, but force deletes rececipt else if force D3.log "Uninstall failed, but force-deleting receipt for #{edition}.", :warn delete else raise D3::UninstallError, "There was a problem uninstalling #{edition}" end # if force end #if uninstall_worked # do any sync-type auto installs if we just removed a pilot # then the machine will get any live edition if it should. D3::Client.do_auto_installs(OpenStruct.new) if depiloting ensure D3::Client.unset_env :removing end # begin...ensure end |
#uninstall_via_apple_rcpt(verbose = false) ⇒ Boolean
Uninstall this .pkg by looking up the files it installed via pkgutil and deleting them directly. Doesn’t talk to the JSS and only works for .pkg installers (.dmg installers don’t write their file lists to the local package db.) This means that it won’t run pre/post remove scripts either!
758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 |
# File 'lib/d3/client/receipt.rb', line 758 def uninstall_via_apple_rcpt (verbose = false) D3.log "Uninstalling #{edition} via Apple pkg receipts", :debug raise D3::UninstallError, "#{edition} is not a .pkg installer. Can't use Apple receipts." if @package_type == :dmg to_delete = {} begin installed_apple_rcpts = `#{JSS::Composer::PKG_UTIL} --pkgs`.split("\n") @apple_pkg_ids.each do |pkgid| unless installed_apple_rcpts.include? pkgid D3.log "No local Apple receipt for '#{pkgid}', ignoring", :warn next end # this gets them in reverse order, so we can # delete files and then test for and delete empty dirs on the way to_delete[pkgid] = `#{JSS::Composer::PKG_UTIL} --files '#{pkgid}' 2>/dev/null`.split("\n").reverse raise D3::UninstallError, "Error querying pkg file list for '#{pkgid}'" if $CHILD_STATUS.exitstatus > 0 end # each pkgid to_delete.each do |pkgid, paths| D3.log "Deleting items installed by apple pkg-id #{pkgid}", :debug paths.each do |path| target = Pathname.new "/#{path}" target.delete if target.file? target.rmdir if target.directory? and target.children.empty? D3.log "Deleted #{path}", :debug end # each path system "#{JSS::Composer::PKG_UTIL} --forget '#{pkgid}' &>/dev/null" end # each |pkgid, paths| rescue D3.log $!, :warn D3.log_backtrace return false end # begin return true end |
#update ⇒ Object
Update the current receipt in the receipt store
879 880 881 |
# File 'lib/d3/client/receipt.rb', line 879 def update D3::Client::Receipt.add_receipt(self, :replace) end |