class Jamf::PatchTitle

An active Patch Software Title in the JSS.

This class provides access to titles that have been added to Jamf Pro via a PatchInternalSource or a PatchExternalSource, and the versions contained therein.

Patch versions for the title are available in the versions read-only attribute, a Hash of versions keyed by the version string. The values are Jamf::PatchTitle::Version objects.

When creating/activating new Patch Titles, with .make, a unique name:, a source: and a name_id: must be provided - the source must be the name or id of an existing PatchSource, and the name_id must be offered by that source. Once created, the source_id and name_id cannot be changed.

When fetching titles, they can be fetched by id:, source_name_id:, or both source: and name_id:

WARNING: While they can be fetched by name, beware: the JSS does not enforce unique names of titles even thought ruby-jss does. If there are duplicates of the name you fetch, which one you get is undefined.

Use the patch_report class or instance method, or PatchTitle::Version.patch_report, to retrieve a report of computers with a specific version of the title installed, or :all, :latest, or :unknown versions. Reports called on the class or an instance default to :all versions, and are slower to retrieve than a specific version,

@see Jamf::APIObject

Constants

CATEGORY_DATA_TYPE

How is the category stored in the API data?

CATEGORY_SUBSET

Where is the Category in the API JSON?

LATEST_VERSION_ID

when fetching a specific version, this is a valid version

NON_UNIQUE_NAMES
OBJECT_HISTORY_OBJECT_TYPE

the object type for this object in the object history table. See {APIObject#add_object_history_entry} TODO: comfirm this in 10.4

PATCH_REPORT_DATA_MAP

TODO: remove this and adjust parsing when jamf fixes the JSON Data map for PatchReport XML data parsing cuz Borked JSON @see {Jamf::XMLWorkaround} for details

REPORTS_RSRC_BASE
RSRC_BASE

The base for REST resources of this class

RSRC_LIST_KEY

the hash key used for the JSON list output of all objects in the JSS

RSRC_OBJECT_KEY

The hash key used for the JSON object output. It’s also used in various error messages

SITE_SUBSET
UNKNOWN_VERSION_ID

when fetching a specific version, this is a valid version

USE_XML_WORKAROUND

TODO: remove this and adjust parsing when jamf fixes the JSON Data map for PatchTitle XML data parsing cuz Borked JSON @see {Jamf::XMLWorkaround} for details

Attributes

email_notification[R]

@return [Boolean] Are new patches announced by email?

email_notification?[R]

@return [Boolean] Are new patches announced by email?

name_id[R]

@return [String] the ‘name_id’ for this patch title. name_id is a unique

identfier provided by the patch source
source_id[R]

@return [Integer] the id of the patch source from which we get patches

for this title
source_name_id[R]

@return [String] the source_id and name_id joined by ‘-’, a unique identifier

web_notification[R]

@return [Boolean] Are new patches announced in the JSS web ui?

web_notification?[R]

@return [Boolean] Are new patches announced in the JSS web ui?

Public Class Methods

all(refresh = false, source_id: nil, api: nil, cnx: Jamf.cnx) click to toggle source

The same as @see APIObject.all but also takes an optional source_id: parameter, which limites the results to patch titles with the specified source_id.

Also - since the combined source_id and name_id are unique, create an identifier key ‘:source_name_id’ by joining them with ‘-’

JAMF BUG: More broken json - the id is coming as a string. so here we turn it into an integer manually :-( Ditto for source_id

Calls superclass method Jamf::APIObject::all
    # File lib/jamf/api/classic/api_objects/patch_title.rb
173 def self.all(refresh = false, source_id: nil, api: nil, cnx: Jamf.cnx)
174   cnx = api if api
175 
176   data = super refresh, cnx: cnx
177   data.each do |info|
178     info[:id] = info[:id].to_i
179     info[:source_name_id] = "#{info[:source_id]}-#{info[:name_id]}"
180     info[:source_id] = info[:source_id].to_i
181   end
182   return data unless source_id
183 
184   data.select { |p| p[:source_id] == source_id }
185 end
all_ids(refresh = false, source_id: nil, api: nil, cnx: Jamf.cnx) click to toggle source

The same as @see APIObject.all_ids but also takes an optional source_id: parameter, which limites the results to patch titles with the specified source_id.

    # File lib/jamf/api/classic/api_objects/patch_title.rb
201 def self.all_ids(refresh = false, source_id: nil, api: nil, cnx: Jamf.cnx)
202   cnx = api if api
203 
204   all(refresh, source_id: source_id, cnx: cnx).map { |i| i[:id] }
205 end
all_names(refresh = false, source_id: nil, api: nil, cnx: Jamf.cnx) click to toggle source

The same as @see APIObject.all_names but also takes an optional source_id: parameter, which limites the results to patch titles with the specified source_id.

    # File lib/jamf/api/classic/api_objects/patch_title.rb
191 def self.all_names(refresh = false, source_id: nil, api: nil, cnx: Jamf.cnx)
192   cnx = api if api
193 
194   all(refresh, source_id: source_id, cnx: cnx).map { |i| i[:name] }
195 end
all_source_ids(refresh = false, api: nil, cnx: Jamf.cnx) click to toggle source

Returns an Array of unique source_ids used by active Patches

e.g. if there are patches that come from one internal source and two external sources this might return [1,3,4].

Regardless of how many patches come from each source, the source id appears only once in this array.

@param refresh should the data be re-queried from the API?

@param cnx [Jamf::Connection] an API connection to use for the query.

Defaults to the corrently active API. See {Jamf::Connection}

@return [Array<Integer>] the ids of the patch sources used in the JSS

    # File lib/jamf/api/classic/api_objects/patch_title.rb
222 def self.all_source_ids(refresh = false, api: nil, cnx: Jamf.cnx)
223   cnx = api if api
224 
225   all(refresh, cnx: cnx).map { |i| i[:source_id] }.sort.uniq
226 end
all_source_name_ids(refresh = false, api: nil, cnx: Jamf.cnx) click to toggle source

@return [Array<String>] all ‘source_name_id’ values for active patches

    # File lib/jamf/api/classic/api_objects/patch_title.rb
230 def self.all_source_name_ids(refresh = false, api: nil, cnx: Jamf.cnx)
231   cnx = api if api
232 
233   all(refresh, cnx: cnx).map { |i| i[:source_name_id] }
234 end
fetch(identifier = nil, **params) click to toggle source

Patch titles only have an id-based GET resource in the API. so all other lookup values have to be converted to ID before the call to super

NOTE: The only truely unique identifiers are id and source_name_id

Calls superclass method Jamf::APIObject::fetch
    # File lib/jamf/api/classic/api_objects/patch_title.rb
318 def self.fetch(identifier = nil, **params)
319   # default connection if unspecified
320   cnx = params.delete :cnx
321   cnx ||= params.delete :api # backward compatibility, deprecated
322   cnx ||= Jamf.cnx
323 
324   # source: and source_id: are considered the same, source_id: wins
325   params[:source_id] ||= params[:source]
326 
327   # if given a source name in params[:source_id] this converts it to an id
328   if params[:source_id]
329     params[:source_id] = Jamf::PatchInternalSource.valid_id(params[:source_id], cnx: cnx)
330     params[:source_id] ||= Jamf::PatchExternalSource.valid_id(params[:source_id], cnx: cnx)
331   end
332 
333   id =
334     if identifier
335       valid_id identifier, cnx: cnx
336 
337     elsif params[:id]
338       all_ids(cnx: cnx).include?(params[:id]) ? params[:id] : nil
339 
340     elsif params[:source_name_id]
341       # TODO: make 'map_all' work with :source_name_id
342       # map_all(:source_name_id, to: :id, cnx: cnx)[params[:source_name_id]]
343       map_all(:id, to: :source_name_id, cnx: cnx).invert[params[:source_name_id]]
344 
345       # WARNING: name_id may not be unique
346     elsif params[:name_id]
347       # TODO: make 'map_all' work with :source_name_id
348       # map_all(:source_name_id, to: :id, cnx: cnx)[params[:source_name_id]]
349       map_all(:id, to: :name_id, cnx: cnx).invert[params[:name_id]]
350 
351     # WARNING: name_id may not be unique
352     elsif params[:name]
353       # map_all_ids_to(:name, cnx: cnx).invert[params[:name]]
354       map_all(:name, to: :id, cnx: cnx)[params[:name]]
355     end
356 
357   raise Jamf::NoSuchItemError, "No matching #{name} found" unless id
358 
359   super id: id, cnx: cnx
360 end
new(**args) click to toggle source
Calls superclass method Jamf::APIObject::new
    # File lib/jamf/api/classic/api_objects/patch_title.rb
397 def initialize(**args)
398   super
399 
400   if in_jss
401     @name_id = @init_data[:name_id]
402     @source_id = @init_data[:source_id]
403   else
404     # source: and source_id: are considered the same, source_id: wins
405     @init_data[:source_id] ||= @init_data[:source]
406 
407     raise Jamf::MissingDataError, 'source: and name_id: must be provided' unless @init_data[:name_id] && @init_data[:source_id]
408 
409     @source_id = Jamf::PatchSource.valid_id(@init_data[:source_id], cnx: @cnx)
410     raise Jamf::NoSuchItemError, "No Patch Sources match '#{@init_data[:source]}'" unless source_id
411 
412     @name_id = @init_data[:name_id]
413     valid_name_id = Jamf::PatchSource.available_name_ids(@source_id, cnx: @cnx).include? @name_id
414     raise Jamf::NoSuchItemError, "source #{@init_data[:source]} doesn't offer name_id '#{@init_data[:name_id]}'" unless valid_name_id
415   end
416 
417   @source_name_id = "#{@source_id}-#{@name_id}"
418 
419   @init_data[:notifications] ||= {}
420   notifs = @init_data[:notifications]
421   @web_notification = notifs[:web_notification].nil? ? false : notifs[:web_notification]
422   @email_notification = notifs[:email_notification].nil? ? false : notifs[:email_notification]
423 
424   @versions = {}
425   @init_data[:versions] ||= []
426   @init_data[:versions].each do |vers|
427     @versions[vers[:software_version]] = Jamf::PatchTitle::Version.new(self, vers)
428   end # each do vers
429 
430   @changed_pkgs = []
431 end
patch_report(title, version: :all, api: nil, cnx: Jamf.cnx) click to toggle source

Get a patch report for a softwaretitle, without fetching an instance. Defaults to reporting all versions. Specifiying a version will be faster.

The Hash returned has 3 keys:

- :total_comptuters [Integer] total computers found for the requested version(s)
- :total versions [Integer] How many versions does this title have?
    Always 1 if you report a specific version
- :versions [Hash {String => Array<Hash>}] Keys are the version(s) requested
  values are Arrays of Hashes, one per computer with the keyed version
  installed. Computer Hashes have identifiers as keys.

PatchTitle#patch_report calls this method, as does PatchTitle::Version.patch_report.

@param title[Integer, String] The name or id of the software title to

report.

@param version Limit the report to this version.

Can be a string version number like '8.13.2' or :latest, :unknown,
or :all. Defaults to :all

@param cnx [Jamf::Connection] an API connection to use for the query.

Defaults to the corrently active API. See {Jamf::Connection}

@return [Hash] the patch report for the version(s) specified.

    # File lib/jamf/api/classic/api_objects/patch_title.rb
262 def self.patch_report(title, version: :all, api: nil, cnx: Jamf.cnx)
263   cnx = api if api
264 
265   title_id = valid_id title, cnx: cnx
266   raise Jamf::NoSuchItemError, "No PatchTitle matches '#{title}'" unless title_id
267 
268   rsrc = patch_report_rsrc title_id, version
269 
270   # TODO: remove this and adjust parsing when jamf fixes the JSON
271   raw_report = XMLWorkaround.data_via_xml(rsrc, PATCH_REPORT_DATA_MAP, cnx)[:patch_report]
272 
273   report = {}
274   report[:total_computers] = raw_report[:total_computers]
275   report[:total_versions] = raw_report[:total_versions]
276 
277   if raw_report[:versions].is_a? Hash
278     vs = raw_report[:versions][:version][:software_version].to_s
279     comps = raw_report[:versions][:version][:computers]
280     comps = [] if comps.empty?
281     report[:versions] = { vs => comps }
282     return report
283   end
284 
285   report[:versions] = {}
286   raw_report[:versions].each do |v|
287     report[:versions][v[:software_version].to_s] = v[:computers].empty? ? [] : v[:computers]
288   end
289   report
290 end
valid_id(ident, refresh = false, api: nil, cnx: Jamf.cnx) click to toggle source

Override the {APIObject.valid_id}, since patch sources are so non-standard Accept id, source_name_id, or name. Note name may not be unique, and if not, ymmv

    # File lib/jamf/api/classic/api_objects/patch_title.rb
366 def self.valid_id(ident, refresh = false, api: nil, cnx: Jamf.cnx)
367   cnx = api if api
368 
369   id = all_ids(refresh, cnx: cnx).include?(ident) ? ident : nil
370   id ||= map_all(:id, to: :source_name_id).invert[ident]
371   id ||= map_all(:id, to: :name).invert[ident]
372   id
373 end

Private Class Methods

patch_report_rsrc(id, vers) click to toggle source

given a requested version, return the rest rsrc for getting a patch report for it.

    # File lib/jamf/api/classic/api_objects/patch_title.rb
298 def self.patch_report_rsrc(id, vers)
299   case vers
300   when :all
301     "#{REPORTS_RSRC_BASE}/#{id}"
302   when :latest
303     "#{REPORTS_RSRC_BASE}/#{id}/version/#{LATEST_VERSION_ID}"
304   when :unknown
305     "#{REPORTS_RSRC_BASE}/#{id}/version/#{UNKNOWN_VERSION_ID}"
306   else
307     "#{REPORTS_RSRC_BASE}/#{id}/version/#{vers}"
308   end
309 end

Public Instance Methods

changed_pkg_for_version(version) click to toggle source

this is called by Jamf::PatchTitle::Version#package= to update @changed_pkgs which is used by rest_xml to change the package assigned to a patch version in this title.

    # File lib/jamf/api/classic/api_objects/patch_title.rb
479 def changed_pkg_for_version(version)
480   @changed_pkgs << version
481   @need_to_update = true
482 end
create() click to toggle source

wrapper to fetch versions after creating

Calls superclass method Jamf::APIObject::create
    # File lib/jamf/api/classic/api_objects/patch_title.rb
485 def create
486   super
487 end
email_notification=(new_setting) click to toggle source

Set email notifications on or off

@param new_setting Should email notifications be on or off?

@return [void]

    # File lib/jamf/api/classic/api_objects/patch_title.rb
456 def email_notification=(new_setting)
457   return if email_notification == new_setting
458   raise Jamf::InvalidDataError, 'New Setting must be boolean true or false' unless Jamf::TRUE_FALSE.include? @email_notification = new_setting
459 
460   @need_to_update = true
461 end
patch_report(vers = :all) click to toggle source

Get a patch report for this title.

See the class method Jamf::PatchTitle.patch_report

    # File lib/jamf/api/classic/api_objects/patch_title.rb
500 def patch_report(vers = :all)
501   Jamf::PatchTitle.patch_report id, version: vers, cnx: @cnx
502 end
Also aliased as: version_report, report
pretty_print_instance_variables() click to toggle source

Remove the various cached data from the instance_variables used to create pretty-print (pp) output.

@return [Array] the desired instance_variables

    # File lib/jamf/api/classic/api_objects/patch_title.rb
512 def pretty_print_instance_variables
513   vars = super
514   vars.delete :@versions
515   vars
516 end
report(vers = :all)
Alias for: patch_report
update() click to toggle source

wrapper to clear @changed_pkgs after updating

Calls superclass method Jamf::APIObject#update
    # File lib/jamf/api/classic/api_objects/patch_title.rb
490 def update
491   response = super
492   @changed_pkgs.clear
493   response
494 end
version_report(vers = :all)
Alias for: patch_report
versions() click to toggle source

@return [Hash{String => Jamf::PatchTitle::Version}] The Jamf::PatchVersions fetched for

this title, keyed by version string
    # File lib/jamf/api/classic/api_objects/patch_title.rb
435 def versions
436   return @versions unless in_jss
437   return @versions unless @versions.empty?
438 
439   # if we are in jss, and versions is empty, re-fetch them
440   @versions = self.class.fetch(id: id).versions
441 end
versions_with_packages() click to toggle source

@return [Hash] Subset of @versions, containing those which have packages

assigned
    # File lib/jamf/api/classic/api_objects/patch_title.rb
446 def versions_with_packages
447   versions.select { |_ver_string, vers| vers.package_assigned? }
448 end
web_notification=(new_setting) click to toggle source

Set web notifications on or off

@param new_setting Should email notifications be on or off?

@return [void]

    # File lib/jamf/api/classic/api_objects/patch_title.rb
469 def web_notification=(new_setting)
470   return if web_notification == new_setting
471   raise Jamf::InvalidDataError, 'New Setting must be boolean true or false' unless Jamf::TRUE_FALSE.include? @web_notification = new_setting
472 
473   @need_to_update = true
474 end

Private Instance Methods

add_changed_pkg_xml(obj) click to toggle source

add xml for any package changes to patch versions

    # File lib/jamf/api/classic/api_objects/patch_title.rb
545 def add_changed_pkg_xml(obj)
546   versions_elem = obj.add_element 'versions'
547   @changed_pkgs.each do |vers|
548     velem = versions_elem.add_element 'version'
549     velem.add_element('software_version').text = vers.to_s
550     pkg = velem.add_element 'package'
551     # leave am empty package element to remove the pkg assignement
552     next if versions[vers].package_id == :none
553 
554     pkg.add_element('id').text = versions[vers].package_id.to_s
555   end # do vers
556 end
rest_xml() click to toggle source

Return the REST XML for this title, with the current values, for saving or updating.

    # File lib/jamf/api/classic/api_objects/patch_title.rb
524 def rest_xml
525   doc = REXML::Document.new # Jamf::Connection::XML_HEADER
526   obj = doc.add_element RSRC_OBJECT_KEY.to_s
527 
528   obj.add_element('name').text = name
529   obj.add_element('name_id').text = name_id
530   obj.add_element('source_id').text = source_id
531 
532   notifs = obj.add_element 'notifications'
533   notifs.add_element('web_notification').text = web_notification?.to_s
534   notifs.add_element('email_notification').text = email_notification?.to_s
535 
536   add_changed_pkg_xml obj unless @changed_pkgs.empty?
537 
538   add_category_to_xml doc
539   add_site_to_xml doc
540 
541   doc.to_s
542 end