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
@return [Boolean] Are new patches announced by email?
@return [Boolean] Are new patches announced by email?
@return [String] the ‘name_id’ for this patch title. name_id
is a unique
identfier provided by the patch source
@return [Integer] the id of the patch source from which we get patches
for this title
@return [Boolean] Are new patches announced in the JSS
web ui?
@return [Boolean] Are new patches announced in the JSS
web ui?
Public Class Methods
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
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
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
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
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
@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
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
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
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
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
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
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
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
wrapper to fetch versions after creating
Jamf::APIObject::create
# File lib/jamf/api/classic/api_objects/patch_title.rb 485 def create 486 super 487 end
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
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
Remove the various cached data from the instance_variables used to create pretty-print (pp) output.
@return [Array] the desired instance_variables
Jamf::APIObject#pretty_print_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
wrapper to clear @changed_pkgs after updating
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
@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
@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
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 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
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