Class: Jamf::PatchTitle

Inherits:
APIObject show all
Includes:
Categorizable, Creatable, Updatable
Defined in:
lib/jamf/api/classic/api_objects/patch_title.rb,
lib/jamf/api/classic/api_objects/patch_title/version.rb
more...

Overview

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 Also:

Defined Under Namespace

Classes: Version

Constant Summary collapse

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 Also:

  • for details
{
  patch_software_title: {
    id: -1,
    name: Jamf::BLANK,
    name_id: Jamf::BLANK,
    source_id: -1,
    notifications: {
      email_notification: nil,
      web_notification: nil
    },
    category: {
      id: -1,
      name: Jamf::BLANK
    },
    site: {
      id: -1,
      name: Jamf::BLANK
    },
    versions: [
      {
        software_version: Jamf::BLANK,
        package: {
          id: -1,
          name: Jamf::BLANK
        }
      }
    ]
  }
}.freeze
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 Also:

  • for details
{
  patch_report: {
    name: Jamf::BLANK,
    patch_software_title_id: -1,
    total_computers: 0,
    total_versions: 0,
    versions: [
      {
        software_version: Jamf::BLANK,
        computers: [
          {
            id: -1,
            name: Jamf::BLANK,
            mac_address: Jamf::BLANK,
            alt_mac_address: Jamf::BLANK,
            serial_number: Jamf::BLANK
          }
        ]
      }
    ]
  }
}.freeze
RSRC_BASE =

The base for REST resources of this class

'patchsoftwaretitles'.freeze
RSRC_LIST_KEY =

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

:patch_software_titles
RSRC_OBJECT_KEY =

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

:patch_software_title
NON_UNIQUE_NAMES =
true
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

604
SITE_SUBSET =
:top
CATEGORY_SUBSET =

Where is the Category in the API JSON?

:top
CATEGORY_DATA_TYPE =

How is the category stored in the API data?

Hash
LATEST_VERSION_ID =

when fetching a specific version, this is a valid version

'Latest'.freeze
UNKNOWN_VERSION_ID =

when fetching a specific version, this is a valid version

'Unknown'.freeze
REPORTS_RSRC_BASE =
'patchreports/patchsoftwaretitleid'.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(**args) ⇒ PatchTitle

Returns a new instance of PatchTitle.

[View source]

396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
# File 'lib/jamf/api/classic/api_objects/patch_title.rb', line 396

def initialize(**args)
  super

  if in_jss
    @name_id = @init_data[:name_id]
    @source_id = @init_data[:source_id]
  else
    # source: and source_id: are considered the same, source_id: wins
    @init_data[:source_id] ||= @init_data[:source]

    raise Jamf::MissingDataError, 'source: and name_id: must be provided' unless @init_data[:name_id] && @init_data[:source_id]

    @source_id = Jamf::PatchSource.valid_id(@init_data[:source_id], cnx: @cnx)
    raise Jamf::NoSuchItemError, "No Patch Sources match '#{@init_data[:source]}'" unless source_id

    @name_id = @init_data[:name_id]
    valid_name_id = Jamf::PatchSource.available_name_ids(@source_id, cnx: @cnx).include? @name_id
    raise Jamf::NoSuchItemError, "source #{@init_data[:source]} doesn't offer name_id '#{@init_data[:name_id]}'" unless valid_name_id
  end

  @source_name_id = "#{@source_id}-#{@name_id}"

  @init_data[:notifications] ||= {}
  notifs = @init_data[:notifications]
  @web_notification = notifs[:web_notification].nil? ? false : notifs[:web_notification]
  @email_notification = notifs[:email_notification].nil? ? false : notifs[:email_notification]

  @versions = {}
  @init_data[:versions] ||= []
  @init_data[:versions].each do |vers|
    @versions[vers[:software_version]] = Jamf::PatchTitle::Version.new(self, vers)
  end # each do vers

  @changed_pkgs = []
end

Instance Attribute Details

#email_notificationBoolean Also known as: email_notification?

Returns Are new patches announced by email?.

Returns:

  • (Boolean)

    Are new patches announced by email?


393
394
395
# File 'lib/jamf/api/classic/api_objects/patch_title.rb', line 393

def email_notification
  @email_notification
end

#name_idString (readonly)

Returns the ‘name_id’ for this patch title. name_id is a unique identfier provided by the patch source.

Returns:

  • (String)

    the ‘name_id’ for this patch title. name_id is a unique identfier provided by the patch source


379
380
381
# File 'lib/jamf/api/classic/api_objects/patch_title.rb', line 379

def name_id
  @name_id
end

#need_to_updateBoolean (readonly) Originally defined in module Updatable

Returns do we have unsaved changes?.

Returns:

  • (Boolean)

    do we have unsaved changes?

#source_idInteger (readonly)

Returns the id of the patch source from which we get patches for this title.

Returns:

  • (Integer)

    the id of the patch source from which we get patches for this title


383
384
385
# File 'lib/jamf/api/classic/api_objects/patch_title.rb', line 383

def source_id
  @source_id
end

#source_name_idString (readonly)

Returns the source_id and name_id joined by ‘-’, a unique identifier.

Returns:

  • (String)

    the source_id and name_id joined by ‘-’, a unique identifier


386
387
388
# File 'lib/jamf/api/classic/api_objects/patch_title.rb', line 386

def source_name_id
  @source_name_id
end

#web_notificationBoolean Also known as: web_notification?

Returns Are new patches announced in the JSS web ui?.

Returns:

  • (Boolean)

    Are new patches announced in the JSS web ui?


389
390
391
# File 'lib/jamf/api/classic/api_objects/patch_title.rb', line 389

def web_notification
  @web_notification
end

Class Method Details

.all(refresh = false, source_id: nil, api: nil, cnx: Jamf.cnx) ⇒ Object

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

[View source]

172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/jamf/api/classic/api_objects/patch_title.rb', line 172

def self.all(refresh = false, source_id: nil, api: nil, cnx: Jamf.cnx)
  cnx = api if api

  data = super refresh, cnx: cnx
  data.each do |info|
    info[:id] = info[:id].to_i
    info[:source_name_id] = "#{info[:source_id]}-#{info[:name_id]}"
    info[:source_id] = info[:source_id].to_i
  end
  return data unless source_id

  data.select { |p| p[:source_id] == source_id }
end

.all_ids(refresh = false, source_id: nil, api: nil, cnx: Jamf.cnx) ⇒ Object

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.

[View source]

200
201
202
203
204
# File 'lib/jamf/api/classic/api_objects/patch_title.rb', line 200

def self.all_ids(refresh = false, source_id: nil, api: nil, cnx: Jamf.cnx)
  cnx = api if api

  all(refresh, source_id: source_id, cnx: cnx).map { |i| i[:id] }
end

.all_names(refresh = false, source_id: nil, api: nil, cnx: Jamf.cnx) ⇒ Object

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.

[View source]

190
191
192
193
194
# File 'lib/jamf/api/classic/api_objects/patch_title.rb', line 190

def self.all_names(refresh = false, source_id: nil, api: nil, cnx: Jamf.cnx)
  cnx = api if api

  all(refresh, source_id: source_id, cnx: cnx).map { |i| i[:name] }
end

.all_source_ids(refresh = false, api: nil, cnx: Jamf.cnx) ⇒ Array<Integer>

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.

Parameters:

  • refresh (Boolean) (defaults to: false)

    should the data be re-queried from the API?

  • cnx (Jamf::Connection) (defaults to: Jamf.cnx)

    an API connection to use for the query. Defaults to the corrently active API. See Connection

Returns:

  • (Array<Integer>)

    the ids of the patch sources used in the JSS

[View source]

221
222
223
224
225
# File 'lib/jamf/api/classic/api_objects/patch_title.rb', line 221

def self.all_source_ids(refresh = false, api: nil, cnx: Jamf.cnx)
  cnx = api if api

  all(refresh, cnx: cnx).map { |i| i[:source_id] }.sort.uniq
end

.all_source_name_ids(refresh = false, api: nil, cnx: Jamf.cnx) ⇒ Array<String>

Returns all ‘source_name_id’ values for active patches.

Returns:

  • (Array<String>)

    all ‘source_name_id’ values for active patches

[View source]

229
230
231
232
233
# File 'lib/jamf/api/classic/api_objects/patch_title.rb', line 229

def self.all_source_name_ids(refresh = false, api: nil, cnx: Jamf.cnx)
  cnx = api if api

  all(refresh, cnx: cnx).map { |i| i[:source_name_id] }
end

.fetch(identifier = nil, **params) ⇒ Object

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

[View source]

317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
# File 'lib/jamf/api/classic/api_objects/patch_title.rb', line 317

def self.fetch(identifier = nil, **params)
  # default connection if unspecified
  cnx = params.delete :cnx
  cnx ||= params.delete :api # backward compatibility, deprecated
  cnx ||= Jamf.cnx

  # source: and source_id: are considered the same, source_id: wins
  params[:source_id] ||= params[:source]

  # if given a source name in params[:source_id] this converts it to an id
  if params[:source_id]
    params[:source_id] = Jamf::PatchInternalSource.valid_id(params[:source_id], cnx: cnx)
    params[:source_id] ||= Jamf::PatchExternalSource.valid_id(params[:source_id], cnx: cnx)
  end

  id =
    if identifier
      valid_id identifier, cnx: cnx

    elsif params[:id]
      all_ids(cnx: cnx).include?(params[:id]) ? params[:id] : nil

    elsif params[:source_name_id]
      # TODO: make 'map_all' work with :source_name_id
      # map_all(:source_name_id, to: :id, cnx: cnx)[params[:source_name_id]]
      map_all(:id, to: :source_name_id, cnx: cnx).invert[params[:source_name_id]]

      # WARNING: name_id may not be unique
    elsif params[:name_id]
      # TODO: make 'map_all' work with :source_name_id
      # map_all(:source_name_id, to: :id, cnx: cnx)[params[:source_name_id]]
      map_all(:id, to: :name_id, cnx: cnx).invert[params[:name_id]]

    # WARNING: name_id may not be unique
    elsif params[:name]
      # map_all_ids_to(:name, cnx: cnx).invert[params[:name]]
      map_all(:name, to: :id, cnx: cnx)[params[:name]]
    end

  raise Jamf::NoSuchItemError, "No matching #{name} found" unless id

  super id: id, cnx: cnx
end

.patch_report(title, version: :all, api: nil, cnx: Jamf.cnx) ⇒ Hash

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.

Parameters:

  • title (Integer, String)

    The name or id of the software title to report.

  • version (String, Symbol) (defaults to: :all)

    Limit the report to this version. Can be a string version number like ‘8.13.2’ or :latest, :unknown, or :all. Defaults to :all

  • cnx (Jamf::Connection) (defaults to: Jamf.cnx)

    an API connection to use for the query. Defaults to the corrently active API. See Connection

Returns:

  • (Hash)

    the patch report for the version(s) specified.

Raises:

[View source]

261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/jamf/api/classic/api_objects/patch_title.rb', line 261

def self.patch_report(title, version: :all, api: nil, cnx: Jamf.cnx)
  cnx = api if api

  title_id = valid_id title, cnx: cnx
  raise Jamf::NoSuchItemError, "No PatchTitle matches '#{title}'" unless title_id

  rsrc = patch_report_rsrc title_id, version

  # TODO: remove this and adjust parsing when jamf fixes the JSON
  raw_report = XMLWorkaround.data_via_xml(rsrc, PATCH_REPORT_DATA_MAP, cnx)[:patch_report]

  report = {}
  report[:total_computers] = raw_report[:total_computers]
  report[:total_versions] = raw_report[:total_versions]

  if raw_report[:versions].is_a? Hash
    vs = raw_report[:versions][:version][:software_version].to_s
    comps = raw_report[:versions][:version][:computers]
    comps = [] if comps.empty?
    report[:versions] = { vs => comps }
    return report
  end

  report[:versions] = {}
  raw_report[:versions].each do |v|
    report[:versions][v[:software_version].to_s] = v[:computers].empty? ? [] : v[:computers]
  end
  report
end

.valid_id(ident, refresh = false, api: nil, cnx: Jamf.cnx) ⇒ Object

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

[View source]

365
366
367
368
369
370
371
372
# File 'lib/jamf/api/classic/api_objects/patch_title.rb', line 365

def self.valid_id(ident, refresh = false, api: nil, cnx: Jamf.cnx)
  cnx = api if api

  id = all_ids(refresh, cnx: cnx).include?(ident) ? ident : nil
  id ||= map_all(:id, to: :source_name_id).invert[ident]
  id ||= map_all(:id, to: :name).invert[ident]
  id
end

Instance Method Details

#category=(new_cat) ⇒ void Originally defined in module Categorizable

This method returns an undefined value.

Change the category of this object. Any of the NON_CATEGORIES values will unset the category

Parameters:

  • new_cat (Integer, String)

    The new category

Raises:

#category_assigned?Boolean Also known as: categorized? Originally defined in module Categorizable

Does this object have a category assigned?

Returns:

  • (Boolean)

    Does this object have a category assigned?

#category_idInteger Originally defined in module Categorizable

The id of the category for this object.

Returns:

  • (Integer)

    The id of the category for this object.

#category_nameString Also known as: category Originally defined in module Categorizable

The name of the category for this object. For backward compatibility, this is aliased to just ‘category’

Returns:

  • (String)

    The name of the category for this object.

#category_objectJamf::Category Originally defined in module Categorizable

The Jamf::Category instance for this object’s category

Returns:

  • (Jamf::Category)

    The Jamf::Category instance for this object’s category

#changed_pkg_for_version(version) ⇒ Object

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.

[View source]

481
482
483
484
# File 'lib/jamf/api/classic/api_objects/patch_title.rb', line 481

def changed_pkg_for_version(version)
  @changed_pkgs << version
  @need_to_update = true
end

#clone(new_name, api: nil, cnx: nil) ⇒ APIObject Originally defined in module Creatable

make a clone of this API object, with a new name. The class must be creatable

Parameters:

  • name (String)

    the name for the new object

  • cnx (Jamf::Connection) (defaults to: nil)

    the API in which to create the object Defaults to the API used to instantiate this object

Returns:

  • (APIObject)

    An unsaved clone of this APIObject with the given name

Raises:

#createObject

wrapper to fetch versions after creating

[View source]

487
488
489
# File 'lib/jamf/api/classic/api_objects/patch_title.rb', line 487

def create
  super
end

#evaluate_new_category(new_cat) ⇒ Array<String, Integer> Originally defined in module Categorizable

Given a category name or id, return the name and id TODO: use APIObject.exist? and/or APIObject.valid_id

Parameters:

  • new_cat (String, Integer)

    The name or id of a possible category

Returns:

  • (Array<String, Integer>)

    The matching name and id, which may be nil.

#name=(newname) ⇒ void Originally defined in module Updatable

This method returns an undefined value.

Change the name of this item Remember to #update to push changes to the server.

Parameters:

  • newname (String)

    the new name

Raises:

#patch_report(vers = :all) ⇒ Object Also known as: version_report, report

Get a patch report for this title.

See the class method Jamf::PatchTitle.patch_report

[View source]

502
503
504
# File 'lib/jamf/api/classic/api_objects/patch_title.rb', line 502

def patch_report(vers = :all)
  Jamf::PatchTitle.patch_report id, version: vers, cnx: @cnx
end

#pretty_print_instance_variablesArray

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

Returns:

  • (Array)

    the desired instance_variables

[View source]

514
515
516
517
518
# File 'lib/jamf/api/classic/api_objects/patch_title.rb', line 514

def pretty_print_instance_variables
  vars = super
  vars.delete :@versions
  vars
end

#unset_categoryvoid Originally defined in module Categorizable

This method returns an undefined value.

Set the category to nothing

#updateObject

wrapper to clear @changed_pkgs after updating

[View source]

492
493
494
495
496
# File 'lib/jamf/api/classic/api_objects/patch_title.rb', line 492

def update
  response = super
  @changed_pkgs.clear
  response
end

#versionsHash{String => Jamf::PatchTitle::Version}

Returns The Jamf::PatchVersions fetched for this title, keyed by version string.

Returns:

[View source]

434
435
436
437
438
439
440
441
442
443
# File 'lib/jamf/api/classic/api_objects/patch_title.rb', line 434

def versions
  return @versions unless in_jss
  return @versions unless @versions.empty?

  # if we are in jss, and versions is empty, re-fetch them
  # this SHOULDN't cause a loop, and shouldn't even ever happen
  # because for the title to be active and fetchable, there must be
  # at least one version.
  @versions = self.class.fetch(id: id, cnx: cnx).versions
end

#versions_with_packagesHash

Returns Subset of @versions, containing those which have packages assigned.

Returns:

  • (Hash)

    Subset of @versions, containing those which have packages assigned

[View source]

448
449
450
# File 'lib/jamf/api/classic/api_objects/patch_title.rb', line 448

def versions_with_packages
  versions.select { |_ver_string, vers| vers.package_assigned? }
end