class Jamf::APIObject

This class is the parent to all JSS API objects. It provides standard methods and constants that apply to all API resouces.

See the README.md file for general info about using subclasses of Jamf::APIObject

Subclassing

Initilize / Constructor

All subclasses must call ‘super` in their initialize method, which will call the method defined here in APIObject. Not only does this retrieve the data from the API, it parses the raw JSON data into a Hash, & stores it in @init_data.

In general, subclasses should do any class-specific argument checking before calling super, and then afterwards use the contents of @init_data to populate any class-specific attributes. Populating @id, @name, @rest_rsrc, and @in_jss are handled here.

This class also handles parsing @init_data for any mixed-in modules, e.g. Scopable, Categorizable or Extendable. See those modules for any requirements they have when including them.

Object Creation

If a subclass should be able to be created in the JSS be sure to include {Jamf::Creatable}

The constructor should verify any extra required data in the args

See {Jamf::Creatable} for more details.

Object Modification

If a subclass should be modifiable in the JSS, include {Jamf::Updatable}, q.v. for details.

Object Deletion

All subclasses can be deleted in the JSS.

Required Constants

Subclasses must provide certain constants in order to correctly interpret API data and communicate with the API:

RSRC_BASE [String]

The base for REST resources of this class

e.g. ‘computergroups’ in

https://casper.mycompany.com:8443/JSSResource/computergroups/id/12

RSRC_LIST_KEY [Symbol]

When GETting the RSRC_BASE for a subclass, an Array of Hashes is returned with one Hash of basic info for each object of that type in the JSS. All objects have their JSS id and name in that Hash, some have other data as well. This Array is used for a variety of purposes when using ruby-jss, since it gives you basic info about all objects, without having to fetch each one individually.

Here’s the top of the output from the ‘computergroups’ RSRC_BASE:

{:computer_groups=>
  [{:id=>1020, :name=>"config-no-turnstile", :is_smart=>true},
   {:id=>1357, :name=>"10.8 Laptops", :is_smart=>true},
   {:id=>1094, :name=>"wifi_cert-expired", :is_smart=>true},
   {:id=>1144, :name=>"mytestgroup", :is_smart=>false},
   ...

Notice that the Array we want is embedded in a one-item Hash, and the key in that Hash for the desired Array is the Symbol :computer_groups.

That symbol is the value needed in the RSRC_LIST_KEY constant.

The ‘.all_ids’, ‘.all_names’ and other ‘.all_*’ class methods use the list-resource Array to extract other Arrays of the desired values - which can be used to check for existance without retrieving an entire object, among other uses.

RSRC_OBJECT_KEY [Symbol]

The one-item Hash key used for individual JSON object output. It’s also used in various error messages

As with the list-resource output mentioned above, when GETting a specific object resource, there’s an extra layer of encapsulation in a one-item Hash. Here’s the top of the JSON for a single computer group fetched from ‘…computergroups/id/1043’

{:computer_group=>
  {:id=>1043,
   :name=>"tmp-no-d3",
   :is_smart=>false,
   :site=>{:id=>-1, :name=>"None"},
   :criteria=>[],
   :computers=>[
   ...

The data for the group itself is the inner Hash.

The RSRC_OBJECT_KEY in this case is set to :computer_group - the key in the top-level, one-item Hash that we need to get the real Hash about the object.

Optional Constants

OTHER_LOOKUP_KEYS

Fetching individual objects from the API is usuallly done via the object’s unique JSS id, via a resrouce URL like so:

...JSSResource/<RSRC_BASE>/id/<idnumber>

Most objects can also be looked-up by name, because the API also has and endpoint ..JSSResource/<RSRC_BASE>/name/<name> (See {NON_UNIQUE_NAMES} below)

Some objects, like Computers and MobileDevices, have other values that serve as unique identifiers and can also be used as ‘lookup keys’ for fetching individual objects. When this is the case, those values always appear in the objects list-resource data (See {RSRC_LIST_KEY} above).

For example, here’s a summary-hash for a single MobileDevice from the list-resource ‘…JSSResource/mobiledevices’, which you might get in the Array returned by Jamf::MobileDevice.all:

{
  :id=>3964,
  :name=>"Bear",
  :device_name=>"Bear",
  :udid=>"XXX",
  :serial_number=>"YYY2244MM60",
  :phone_number=>"510-555-1212",
  :wifi_mac_address=>"00:00:00:00:00:00",
  :managed=>true,
  :supervised=>false,
  :model=>"iPad Pro (9.7-inch Cellular)",
  :model_identifier=>"iPad6,4",
  :modelDisplay=>"iPad Pro (9.7-inch Cellular)",
  :model_display=>"iPad Pro (9.7-inch Cellular)",
  :username=>"fred"
}

For MobileDevices, serial_number, udid, and wifi_mac_address are also all unique identifiers for an individual device, and can be used to fetch them.

To specify other identifiers for an APIObject subclass, create the constant OTHER_LOOKUP_KEYS containing a Hash of Hashes, like so:

OTHER_LOOKUP_KEYS = {
   serial_number: {
     aliases: [:serialnumber, :sn],
     fetch_rsrc_key: :serialnumber
   },
   udid: {
     fetch_rsrc_key: :udid
   },
   wifi_mac_address: {
     aliases: [:macaddress, :macaddr],
     fetch_rsrc_key: :macaddress
   }
}.freeze

The keys in OTHER_LOOKUP_KEYS are the keys in a summary-hash data from .all that hold a unique identifier. Each value is a Hash with one or two keys:

- aliases: [Array<Symbol>]
   Aliases for that identifier, i.e. abbreviations or spelling variants.
   These aliases can be used in fetching, and they also have
   matching `.all_<aliase>s` methods.

   If no aliases are needed, don't specify anything, as with the udid:
   in the example above

- fetch_rsrc_key: [Symbol]
  Often a unique identifier can be used to build a URL for fetching (or
  updating or deleteing) an object with that value, rather than with id.
  For example, while the MobileDevice in the example data above would
  normally be fetched at the resource 'JSSResource/mobiledevices/id/3964'
  it can also be fetched at
 'JSSResource/mobiledevices/serialnumber/YYY2244MM60'.
  Since the URL is built using 'serialnumber', the symbol :serialnumber
  is used as the fetch_rsrc_key.

  Setting a fetch_rsrc_key: for one of the OTHER_LOOKUP_KEYS tells ruby-jss
  that such a URL is available, and fetching by that lookup key will be
  faster when using that URL.

  If a fetch_rsrc_key is not set, fetching will be slower, since the fetch
  method must first refresh the list of all available objects to find the
  id to use for building the resource URL.
  This is also true when fetching without specifying which lookup key to
  use, e.g. `.fetch 'foo'` vs. `.fetch sn: 'foo'`

The OTHER_LOOKUP_KEYS, if defined, are merged with the DEFAULT_LOOKUP_KEYS defined below via the {APIObject.lookup_keys} class method, They are used for:

NON_UNIQUE_NAMES

Some JSS objects, like Computers and MobileDevices, do not treat names as unique in the JSS, but they can still be used for fetching objects. The API itself will return data for a non-unique name lookup, but there’s no way to guarantee which object you get back.

In those subclasses, set NON_UNIQUE_NAMES to any value, and a Jamf::AmbiguousError exception will be raised when trying to fetch by name and the name isn’t unique.

Because of the extra processing, the check for this state will only happen when NON_UNIQUE_NAMES is set. If not set at all, the check doesn’t happen and if multiple objects have the same name, which one is returned is undefined.

When that’s the case, fetching explicitly by name, or when fetching with a plain search term that matches a non-unique name, will raise a Jamf::AmbiguousError exception,when the name isn’t unique. If that happens, you’ll have to use some other identifier to fetch the desired object.

Note: Fetching, finding valid id, and name collisions are case-insensitive.

Constants

API_SOURCE

which API do APIObjects come from? The JPAPI equivalent is in Jamf::JPAPIResource

DEFAULT_LOOKUP_KEYS

See the discussion of ‘Lookup Keys’ in the comments/docs for {Jamf::APIObject}

OBJECT_HISTORY_TABLE

This table holds the object history for JSS objects. Object history is not available via the API, only MySQL.

OK_INSTANTIATORS

‘.new’ can only be called from these methods:

Attributes

api[R]

@return [Jamf::Connection] the API connection thru which we deal with

this object.
cnx[R]

@return [Jamf::Connection] the API connection thru which we deal with

this object.
id[R]

@return [Integer] the JSS id number

in_jss[R]

@return [Boolean] is it in the JSS?

in_jss?[R]

@return [Boolean] is it in the JSS?

init_data[R]

@return the parsed JSON data retrieved from the API when this object was

fetched
name[R]

@return [String] the name

rest_rsrc[R]

@return [String] the Rest resource for API access (the part after “JSSResource/” )

Public Class Methods

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

Return an Array of Hashes for all objects of this subclass in the JSS.

This method is only valid in subclasses of Jamf::APIObject, and is the parsed JSON output of an API query for the resource defined in the subclass’s RSRC_BASE

e.g. for Jamf::Computer, with the RSRC_BASE of :computers, This method retuens the output of the ‘JSSResource/computers’ resource, which is a list of all computers in the JSS.

Each item in the Array is a Hash with at least two keys, :id and :name. The class methods .all_ids and .all_names provide easier access to those dataas mapped Arrays.

Some API classes provide other keys in each Hash, e.g. :udid (for computers and mobile devices) or :is_smart (for groups).

For those keys that are listed in a subclass’s lookup_keys method, there are matching methods ‘.all_(key)s` which return an array just of those values, from the values of this hash. For example, `.all_udids` will use the .all array to return an array of just udids, if the subclass defines :udid in its OTHER_LOOKUP_KEYS (See ’Lookup Keys’ in the class comments/docs above)

Subclasses should provide appropriate .all_xxx class methods for accessing any other other values as Arrays, e.g. Jamf::Computer.all_managed

– Caching

The results of the first call to .all for each subclass is cached in the .c_object_list_cache of the given {Jamf::Connection} and that cache is used for all future calls, so as to not requery the server every time.

To force requerying to get updated data, provided a truthy argument. I usually use :refresh, so that it’s obvious what I’m doing, but true, 1, or anything besides false or nil will work.

The various methods that use the output of this method also take the refresh parameter which will be passed here as needed.

– Alternate API connections

To query an APIConnection other than the currently active one, provide one via the cnx: named parameter.

@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<Hash{:name=>String, :id=> Integer}>]

    # File lib/jamf/api/classic/base_classes/api_object.rb
510 def self.all(refresh = false, api: nil, cnx: Jamf.cnx)
511   cnx = api if api
512 
513   validate_not_metaclass(self)
514 
515   cache = cnx.c_object_list_cache
516   cache_key = self::RSRC_LIST_KEY
517   cnx.flushcache(cache_key) if refresh
518   return cache[cache_key] if cache[cache_key]
519 
520   cache[cache_key] = cnx.c_get(self::RSRC_BASE)[cache_key]
521 end
all_objects(refresh = false, api: nil, cnx: Jamf.cnx) click to toggle source

Return an Array of Jamf::APIObject subclass instances e.g when called on Jamf::Package, return a hash of Jamf::Package instancesa for every package in the JSS.

WARNING: This may be slow as it has to look up each object individually! use it wisely.

@param refresh should the data 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<APIObject>] the objects requested

    # File lib/jamf/api/classic/base_classes/api_object.rb
650 def self.all_objects(refresh = false, api: nil, cnx: Jamf.cnx)
651   cnx = api if api
652 
653   objects_cache_key ||= "#{self::RSRC_LIST_KEY}_objects".to_sym
654   api_cache = cnx.c_object_list_cache
655   api_cache[objects_cache_key] = nil if refresh
656 
657   return api_cache[objects_cache_key] if api_cache[objects_cache_key]
658 
659   all_result = all(refresh, cnx: cnx)
660   api_cache[objects_cache_key] = all_result.map do |o|
661     fetch id: o[:id], cnx: cnx, refresh: false
662   end
663 end
create(**args) click to toggle source

Make a ruby instance of a not-yet-existing APIObject.

This is how to create new objects in the JSS. A name: must be provided, and different subclasses can take other named parameters.

For retrieving existing objects in the JSS, use {APIObject.fetch}

After calling this you’ll have a local instance, which will be created in the JSS when you call create on it. see {APIObject#create}

@param name The name of this object, generally must be uniqie

@param cnx [Jamf::Connection] the connection thru which to make this

object. Defaults to the deault API connection in Jamf.cnx

@param args [Hash] The data for creating an object, such as name:

See {APIObject#initialize}

@return [APIObject] The un-created ruby-instance of a JSS object

     # File lib/jamf/api/classic/base_classes/api_object.rb
1139 def self.create(**args)
1140   validate_not_metaclass(self)
1141   unless constants.include?(:CREATABLE)
1142     raise Jamf::UnsupportedError, "Creating #{self::RSRC_LIST_KEY} isn't yet supported. Please use other Casper workflows."
1143   end
1144   raise ArgumentError, "Use '#{self.class}.fetch id: xx' to retrieve existing JSS objects" if args[:id]
1145 
1146   args[:cnx] ||= args[:api] # deprecated
1147   args[:cnx] ||= Jamf.cnx
1148   args[:id] = :new
1149   new(**args)
1150 end
define_identifier_list_methods() click to toggle source

Loop through the defined lookup keys and make .all_<key>s methods for each one, with alises as needed.

This is called automatically when subclasses are loaded by zeitwerk

    # File lib/jamf/api/classic/base_classes/api_object.rb
297 def self.define_identifier_list_methods
298   Jamf.load_msg "Defining list-methods for APIObject subclass #{self}"
299 
300   lookup_keys.each do |als, key|
301     meth_name = key.to_s.end_with?('s') ? "all_#{key}es" : "all_#{key}s"
302     if als == key
303       # the all_ method - skip if defined in the class
304       next if singleton_methods.include? meth_name.to_sym
305 
306       define_singleton_method meth_name do |refresh = false, cached_list: nil, api: nil, cnx: Jamf.cnx|
307         cnx = api if api
308         list = cached_list || all(refresh, cnx: cnx)
309         list.map { |i| i[key] }
310       end
311       Jamf.load_msg "Defined method #{self}##{meth_name}"
312 
313     else
314       # an alias - skip if defined in the class
315       als_name = als.to_s.end_with?('s') ? "all_#{als}es" : "all_#{als}s"
316 
317       next if singleton_methods.include? als_name.to_sym
318 
319       define_singleton_method als_name do |refresh = false, api: nil, cnx: Jamf.cnx|
320         cnx = api if api
321         send meth_name, refresh, cnx: cnx
322       end
323       Jamf.load_msg "Defined alias '#{als_name}' of #{self}##{meth_name}"
324 
325     end # if
326   end # lookup_keys.each
327 
328   true
329 end
delete(victims, refresh = true, api: nil, cnx: Jamf.cnx) click to toggle source

Delete one or more API objects by jss_id without instantiating them. Non-existent id’s are skipped and an array of skipped ids is returned.

If an Array is provided, it is passed through uniq! before being processed.

@param victims An object id or an array of them

to be deleted

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

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

@return [Array<Integer>] The id’s that didn’t exist when we tried to

delete them.
     # File lib/jamf/api/classic/base_classes/api_object.rb
1183 def self.delete(victims, refresh = true, api: nil, cnx: Jamf.cnx)
1184   cnx = api if api
1185 
1186   validate_not_metaclass(self)
1187 
1188   raise Jamf::InvalidDataError, 'Parameter must be an Integer ID or an Array of them' unless victims.is_a?(Integer) || victims.is_a?(Array)
1189 
1190   case victims
1191   when Integer
1192     victims = [victims]
1193   when Array
1194     victims.uniq!
1195   end
1196 
1197   skipped = []
1198   current_ids = all_ids refresh, cnx: cnx
1199   victims.each do |vid|
1200     if current_ids.include? vid
1201       cnx.c_delete "#{self::RSRC_BASE}/id/#{vid}"
1202     else
1203       skipped << vid
1204     end # if current_ids include vid
1205   end # each victim
1206 
1207   # clear any cached all-lists or id-maps for this class
1208   # so they'll re-cache as needed
1209   cnx.flushcache self::RSRC_LIST_KEY
1210   # all :refresh, cnx: cnx
1211 
1212   skipped
1213 end
duplicate_names(refresh = false, api: nil, cnx: Jamf.cnx) click to toggle source

@return [Hash {String => Integer}] name => number of occurances

    # File lib/jamf/api/classic/base_classes/api_object.rb
525 def self.duplicate_names(refresh = false, api: nil, cnx: Jamf.cnx)
526   cnx = api if api
527 
528   return {} unless defined? self::NON_UNIQUE_NAMES
529 
530   dups = {}
531   all(refresh, cnx: cnx).each do |obj|
532     if dups[obj[:name]]
533       dups[obj[:name]] += 1
534     else
535       dups[obj[:name]] = 1
536     end # if
537   end # all(refresh, cnx: cnx).each
538   dups.delete_if { |_k, v| v == 1 }
539   dups
540 end
exist?(identifier, refresh = false, api: nil, cnx: Jamf.cnx) click to toggle source

Return true or false if an object of this subclass with the given Identifier exists on the server

@param identfier [String,Integer] An identifier for an object, a value for one of the available lookup_keys

@param refresh [Boolean] Should the data be re-read from the server

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

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

@return [Boolean] does an object with the given identifier exist?

    # File lib/jamf/api/classic/base_classes/api_object.rb
831 def self.exist?(identifier, refresh = false, api: nil, cnx: Jamf.cnx)
832   cnx = api if api
833 
834   !valid_id(identifier, refresh, cnx: cnx).nil?
835 end
fetch(searchterm = nil, **args) click to toggle source

Retrieve an object from the API and return an instance of this APIObject subclass.

@example

# computer where 'xyxyxyxy'  is in any of the lookup key fields
Jamf::Computer.fetch 'xyxyxyxy'

# computer where 'xyxyxyxy' is the serial number
Jamf::Computer.fetch serial_number: 'xyxyxyxy'

Fetching is faster when specifying a lookup key, and that key has a fetch_rsrc_key defined in its OTHER_LOOKUP_KEYS constant, as in the second example above.

When no lookup key is given, as in the first example above, or when that key doesn’t have a defined fetch_rsrc_key, ruby-jss uses the currently cached list resource data to find the id matching the value given, and that id is used to fetch the object. (see ‘List Resources and Lookup Keys’ in the APIObject comments/docs above)

Since that cached list data may be out of date, you can provide the param ‘refrsh: true`, to reload the list from the server. This will cause the fetch to be slower still, so use with caution.

For creating new objects in the JSS, use {APIObject.make}

@param searchterm[String, Integer] An single value to

search for in all the lookup keys for this clsss. This is slower
than specifying a lookup key

@param args the remaining options for fetching an object.

If no searchterm is provided, one of the args must be a valid
lookup key and value to find in that key, e.g. `serial_number: '1234567'`

@option args cnx an API connection to use for the query.

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

@option args refresh should the summary list of all objects be

reloaded from the API before being used to look for this object.

@return [APIObject] The ruby-instance of a JSS object

     # File lib/jamf/api/classic/base_classes/api_object.rb
 961 def self.fetch(searchterm = nil, **args)
 962   validate_not_metaclass(self)
 963 
 964   # which connection?
 965   cnx = args.delete :cnx
 966   cnx ||= args.delete :api # backward compatibility, deprecated
 967   cnx ||= Jamf.cnx
 968 
 969   # refresh the .all list if needed
 970   if args.delete(:refresh) || searchterm == :random
 971     all(:refresh, cnx: cnx)
 972     just_refreshed = true
 973   else
 974     just_refreshed = false
 975   end
 976 
 977   # a random object?
 978   if searchterm == :random || args[:random]
 979     rnd_thing = all(cnx: cnx).sample
 980     raise Jamf::NoSuchItemError, "No #{self::RSRC_LIST_KEY} found" unless rnd_thing
 981 
 982     return new id: rnd_thing[:id], cnx: cnx
 983   end
 984 
 985   # get the lookup key and value, if given
 986   fetch_key, fetch_val = args.to_a.first
 987   fetch_rsrc_key = fetch_rsrc_key(fetch_key)
 988 
 989   # names should raise an error if more than one exists,
 990   # so we always have to do id_for_identifier, which will do so.
 991   if fetch_rsrc_key == :name
 992     id = id_for_identifier fetch_key, fetch_val, !just_refreshed, cnx: cnx
 993     fetch_rsrc = id ? "#{self::RSRC_BASE}/name/#{CGI.escape fetch_val.to_s}" : nil
 994 
 995   # if the fetch rsrc key exists, it can be used directly in an endpoint path
 996   # so, use it directly, rather than looking up the id first.
 997   elsif fetch_rsrc_key
 998     fetch_rsrc = "#{self::RSRC_BASE}/#{fetch_rsrc_key}/#{CGI.escape fetch_val.to_s}"
 999 
1000   # it has an OTHER_LOOKUP_KEY but that key doesn't have a fetch_rsrc
1001   # so we look in the .map_all_ids_to_* hash for it.
1002   elsif fetch_key
1003     id = id_for_identifier fetch_key, fetch_val, !just_refreshed, cnx: cnx
1004     fetch_rsrc = id ? "#{self::RSRC_BASE}/id/#{id}" : nil
1005 
1006   # no fetch key was given in the args, so try a search term
1007   elsif searchterm
1008     id = valid_id searchterm, cnx: cnx
1009     fetch_rsrc = id ? "#{self::RSRC_BASE}/id/#{id}" : nil
1010 
1011   else
1012     raise ArgumentError, 'Missing searchterm or fetch key'
1013   end
1014 
1015   new fetch_rsrc: fetch_rsrc, cnx: cnx
1016 end
fetch_rsrc_key(lookup_key) click to toggle source

Given a lookup key, or an alias of one, return the matching fetch_rsrc_key for building a fetch/GET resource URL, or nil if no fetch_rsrc_key is defined.

See {OTHER_LOOKUP_KEYS} in the APIObject class comments/docs above for details.

@param lookup_key [Symbol] A lookup key, or an aliases of one, for this

subclass.

@return [Symbol, nil] the fetch_rsrc_key for that lookup key.

    # File lib/jamf/api/classic/base_classes/api_object.rb
418 def self.fetch_rsrc_key(lookup_key)
419   parse_lookup_keys unless @fetch_rsrc_keys
420   @fetch_rsrc_keys[lookup_key]
421 end
get_name(a_thing) click to toggle source

Some API objects contain references to other API objects. Usually those references are a Hash containing the :id and :name of the target. Sometimes, however the reference is just the name of the target.

A Script has a property :category, which comes from the API as a String, the name of the category for that script. e.g. “GoodStuff”

A Policy also has a property :category, but it comes from the API as a Hash with both the name and id, e.g. !{:id => 8, :name => “GoodStuff”}

When that reference is to a single thing (like the category to which something belongs) APIObject subclasses usually store only the name, and use the name when returning data to the API.

When an object references a list of related objects (like the computers assigned to a user) that list will be and Array of Hashes as above, with both the :id and :name

This method is just a handy way to extract the name regardless of how it comes from the API. Most APIObject subclasses use it in their initialize method

@param a_thing the api data from which we’re extracting the name

@return [String] the name extracted from a_thing

    # File lib/jamf/api/classic/base_classes/api_object.rb
908 def self.get_name(a_thing)
909   case a_thing
910   when String
911     a_thing
912   when Hash
913     a_thing[:name]
914   when nil
915     nil
916   end
917 end
get_raw(id, format: :json, as_string: false, api: nil, cnx: Jamf.cnx) click to toggle source

Fetch the mostly- or fully-raw JSON or XML data for an object of this subclass.

By default, returns the JSON data parsed into a Hash.

When format: is anything but :json, returns the XML data parsed into a REXML::Document

When as_string: is truthy, returns an unparsed JSON String (or XML String if format: is not :json) as it comes directly from the API.

When fetching raw JSON, the returned Hash will have its keys symbolized.

This can be substantialy faster than instantiating, especially when you don’t need all the ruby goodness of a full instance, but just want a few values for an object that aren’t available in the ‘all` data

This is really just a wrapper around {APIConnection.c_get} that automatically fills in the RSRC::BASE value for you.

@param id [Integer] the id of the object to fetch

@param format :json or :xml, defaults to :json

@param as_string return the raw JSON or XML string as it comes

from the API, do not parse into a Hash or REXML::Document

@param cnx [Jamf::Connection] the connection thru which to fetch this

object. Defaults to the deault API connection in Jamf.cnx

@return [Hash, REXML::Document, String] the raw data for the object

     # File lib/jamf/api/classic/base_classes/api_object.rb
1050 def self.get_raw(id, format: :json, as_string: false, api: nil, cnx: Jamf.cnx)
1051   cnx = api if api
1052 
1053   validate_not_metaclass(self)
1054   rsrc = "#{self::RSRC_BASE}/id/#{id}"
1055   data = cnx.c_get rsrc, format, raw_json: as_string
1056   return data if format == :json || as_string
1057 
1058   REXML::Document.new(**data)
1059 end
id_for_identifier(key, val, refresh = false, api: nil, cnx: Jamf.cnx) click to toggle source

Return the id of the object of this subclass with the given lookup key == a given identifier.

Return nil if no object has that value in that key

@example

# get the id for the computer with serialnum 'xyxyxyxy'
Jamf::Computer.id_for_identifier :serial_number, 'xyxyxyxy'

# => the Integer id, or nil if no such serial number

Raises a Jamf::Ambiguous error if there’s more than one matching value for any key, which might be true of names for Computers and Devices

This is similar to .valid_id, except only one key is searched

@param key [Symbol] they key in which to look for the identifier. Must be

a valid lookup key for this subclass.

@param identfier [String,Integer] An identifier for an object, a value for

one of the available lookup_keys

@param refresh [Boolean] Should the cached summary data be re-read from

the server first?

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

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

@return [Integer, nil] the id of the matching object, or nil if it

doesn't exist
    # File lib/jamf/api/classic/base_classes/api_object.rb
797 def self.id_for_identifier(key, val, refresh = false, api: nil, cnx: Jamf.cnx)
798   cnx = api if api
799 
800   # refresh if needed
801   all(refresh, cnx: cnx) if refresh
802 
803   # get the real key if an alias was used
804   key = real_lookup_key key
805 
806   # do id's expicitly, they are integers
807   return all_ids(cnx: cnx).include?(val) ? val : nil if key == :id
808 
809   mapped_ids = map_all_ids_to key, cnx: cnx
810   matches = mapped_ids.select { |_id, map_val| val.casecmp? map_val }
811   raise Jamf::AmbiguousError, "Key #{key}: value '#{val}' is not unique for #{self}" if matches.size > 1
812 
813   return nil if matches.size.zero?
814 
815   matches.keys.first
816 end
lookup_keys(no_aliases: false, fetch_rsrc_keys: false) click to toggle source

What are all the lookup keys available for this class, with all their aliases (or optionally not) or with their fetch_rsrc_keys

This method combines the DEFAULT_LOOOKUP_KEYS defined above, with the optional OTHER_LOOKUP_KEYS from a subclass (See ‘Lookup Keys’ in the class comments/docs above)

The hash returned flattens and inverts the two source hashes, so that all possible lookup keys (the keys and their aliases) are hash keys and the non-aliased lookup key is the value.

For example, when

OTHER_LOOKUP_KEYS = {
   serial_number: { aliases: [:serialnumber, :sn], fetch_rsrc_key: :serialnumber },
   udid: { fetch_rsrc_key: :udid },
   wifi_mac_address: { aliases: [:macaddress, :macaddr], fetch_rsrc_key: :macaddress }
}

It is combined with DEFAULT_LOOKUP_KEYS to produce:

{
  id: :id,
  name: :name,
  serial_number: :serial_number,
  serialnumber: :serial_number,
  sn: :serial_number,
  udid: :udid,
  wifi_mac_address: :wifi_mac_address,
  macaddress: :wifi_mac_address,
  macaddr: :wifi_mac_address
}

If the optional parameter no_aliases: is truthy, only the real keynames are returned in an array, so the above would become

[:id, :name, :serial_number, :udid, :wifi_mac_address]

@param no_aliases [Boolean] Only return the real keys, no aliases.

@return [Hash {Symbol: Symbol}] when no_aliases is falsey, the lookup keys

and aliases for this subclass.

@return [Array<Symbol>] when no_aliases is truthy, the lookup keys for this

subclass
    # File lib/jamf/api/classic/base_classes/api_object.rb
403 def self.lookup_keys(no_aliases: false, fetch_rsrc_keys: false)
404   parse_lookup_keys unless @lookup_keys
405   no_aliases ? @lookup_keys.values.uniq : @lookup_keys
406 end
make(**args) click to toggle source

backward compatability @deprecated use .create instead

     # File lib/jamf/api/classic/base_classes/api_object.rb
1154 def self.make(**args)
1155   create(**args)
1156 end
map_all(ident, to:, cnx: Jamf.cnx, refresh: false, cached_list: nil) click to toggle source

A Hash of all members of this collection where the keys are some identifier and values are any other attribute.

@param ident [Symbol] An identifier of this Class, used as the key

for the mapping Hash. Aliases are acceptable, e.g. :sn for :serialNumber

@param to [Symbol] The attribute to which the ident will be mapped.

Aliases are acceptable, e.g. :name for :displayName

@param refresh [Boolean] Re-read the ‘all’ data from the API? otherwise

use the cached data if available.

@param cnx (see .all)

@return [Hash {Symbol: Object}] A Hash of identifier mapped to attribute

    # File lib/jamf/api/classic/base_classes/api_object.rb
559 def self.map_all(ident, to:, cnx: Jamf.cnx, refresh: false, cached_list: nil)
560   orig_ident = ident
561   ident = lookup_keys[ident]
562   raise Jamf::InvalidDataError, "No identifier :#{orig_ident} for class #{self}" unless ident
563 
564   list = cached_list || all(refresh, cnx: cnx)
565   mapped = list.map do |i|
566     [
567       i[ident],
568       i[to]
569     ]
570   end # do i
571 
572   mapped.to_h
573 end
map_all_ids_to(other_key, refresh = false, cached_list: nil, api: nil, cnx: Jamf.cnx) click to toggle source

Return a hash of all objects of this subclass in the JSS where the key is the id, and the value is some other key in the data items returned by the Jamf::APIObject.all.

If the other key doesn’t exist in the API summary data from .all (eg :udid for Jamf::Department) the values will be nil.

Use this method to map ID numbers to other identifiers returned by the API list resources. Invert its result to map the other identfier to ids.

@example

Jamf::Computer.map_all_ids_to(:serial_number)

# Returns, eg {2 => "C02YD3U8JHD3", 5 => "VMMz7xgg8lYZ"}

Jamf::Computer.map_all_ids_to(:serial_number).invert

# Returns, eg {"C02YD3U8JHD3" => 2, "VMMz7xgg8lYZ" => 5}

These hashes are cached separately from the .all data, and when the refresh parameter is truthy, both will be refreshed.

WARNING: Some values in the output of .all are not guaranteed to be unique in Jamf Pro. This is fine in the direct output of this method, each id will be the key for some value and many ids might have the same value. However if you invert that hash, the values become keys, and the ids become the values, and there can be only one id per each new key. Which id becomes associated with a value is undefined, and data about the others is lost. This is especially important if you ‘.map_all_ids_to :name`, since, for some objects, names are not unique.

@param other_key the other data key with which to associate each id

@param refresh should the data 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 [Hash{Integer => Oject}] the associated ids and data

    # File lib/jamf/api/classic/base_classes/api_object.rb
616 def self.map_all_ids_to(other_key, refresh = false, cached_list: nil, api: nil, cnx: Jamf.cnx)
617   cnx = api if api
618 
619   map_all :id, to: other_key, refresh: refresh, cnx: cnx, cached_list: cached_list
620 
621   # # we will accept any key, it'll just return nil if not in the
622   # # .all hashes. However if we're given an alias of a lookup key
623   # # we need to convert it to its real name.
624   # other_key = lookup_keys[other_key] if lookup_keys[other_key]
625   #
626   # cache_key = "#{self::RSRC_LIST_KEY}_map_#{other_key}".to_sym
627   # cache = cnx.c_object_list_cache
628   # cache[cache_key] = nil if refresh
629   # return cache[cache_key] if cache[cache_key]
630   #
631   # map = {}
632   # all(refresh, cnx: cnx).each { |i| map[i[:id]] = i[other_key] }
633   # cache[cache_key] = map
634 end
new(**args) click to toggle source

Disallow direct use of ruby’s .new class method for creating instances. Require use of .fetch or .make

Calls superclass method
     # File lib/jamf/api/classic/base_classes/api_object.rb
1160 def self.new(**args)
1161   validate_not_metaclass(self)
1162 
1163   calling_method = caller_locations(1..1).first.label
1164   raise Jamf::UnsupportedError, 'Use .fetch or .create to instantiate APIObject classes' unless OK_INSTANTIATORS.include? calling_method
1165 
1166   super
1167 end
new(**args) click to toggle source

The args hash must include :id, :name, or :data.

  • :id or :name will be looked up via the API

    • if the subclass includes Jamf::Creatable, :id can be :new, to create a new object in the JSS. and :name is required

  • :data must be the JSON output of a separate {Jamf::Connection} query (a Hash of valid object data)

Some subclasses can accept other options, by pasing their keys in a final Array

@param args the data for looking up, or constructing, a new object.

@option args :id the jss id to look up

@option args :name the name to look up

@option args :fetch_rsrc a non-standard resource for fetching

API data e.g. to limit the data returned
     # File lib/jamf/api/classic/base_classes/api_object.rb
1271 def initialize(**args)
1272   @cnx = args[:cnx]
1273   @cnx ||= args[:api]
1274   @cnx ||= Jamf.cnx
1275 
1276   # we're making a new one in the JSS
1277   if args[:id] == :new
1278     validate_init_for_creation(args)
1279     setup_object_for_creation(args)
1280     @need_to_update = true
1281 
1282   # we're instantiating an existing one in the jss
1283   else
1284     @init_data = look_up_object_data(args)
1285     @need_to_update = false
1286   end ## end arg parsing
1287 
1288   parse_init_data
1289 end
post_raw(xml, api: nil, cnx: Jamf.cnx) click to toggle source

POST some raw XML to the API for a given id in this subclass.

WARNING: You must create or acquire the XML to be sent, and no validation will be performed on it. It must be a String, or something that returns an XML string with to_s, such as a REXML::Document, or a REXML::Element.

This probably isn’t as much of a speed gain as get_raw or put_raw, as opposed to instantiating a ruby object, but might still be useful.

This is really just a wrapper around {APIConnection.c_post} that automatically fills in the RSRC::BASE value for you.

@param xml [String, to_s] The XML to send

@param cnx [Jamf::Connection] the connection thru which to fetch this

object. Defaults to the deault API connection in Jamf.cnx

@return [REXML::Document] the XML response from the API

     # File lib/jamf/api/classic/base_classes/api_object.rb
1111 def self.post_raw(xml, api: nil, cnx: Jamf.cnx)
1112   cnx = api if api
1113 
1114   validate_not_metaclass(self)
1115   rsrc = "#{self::RSRC_BASE}/id/-1"
1116   REXML::Document.new(cnx.c_post(rsrc, xml.to_s))
1117 end
put_raw(id, xml, api: nil, cnx: Jamf.cnx) click to toggle source

PUT some raw XML to the API for a given id in this subclass.

WARNING: You must create or acquire the XML to be sent, and no validation will be performed on it. It must be a String, or something that returns an XML string with to_s, such as a REXML::Document, or a REXML::Element.

In some cases, where you’re making simple changes to simple XML, this can be faster than fetching a full instance and the re-saving it.

This is really just a wrapper around {APIConnection.c_put} that automatically fills in the RSRC::BASE value for you.

@param id [Integer] the id of the object to PUT

@param xml [String, to_s] The XML to send

@param cnx [Jamf::Connection] the connection thru which to fetch this

object. Defaults to the deault API connection in Jamf.cnx

@return [REXML::Document] the XML response from the API

     # File lib/jamf/api/classic/base_classes/api_object.rb
1083 def self.put_raw(id, xml, api: nil, cnx: Jamf.cnx)
1084   cnx = api if api
1085 
1086   validate_not_metaclass(self)
1087   rsrc = "#{self::RSRC_BASE}/id/#{id}"
1088   REXML::Document.new(cnx.c_put(rsrc, xml.to_s))
1089 end
real_lookup_key(key) click to toggle source

get the real lookup key frm a given alias

@param key the key or an aliase of the key

@return [Symbol] the real key for the given key

    # File lib/jamf/api/classic/base_classes/api_object.rb
451 def self.real_lookup_key(key)
452   real_key = lookup_keys[key]
453   raise ArgumentError, "Unknown lookup key '#{key}' for #{self}" unless real_key
454 
455   real_key
456 end
valid_id(identifier = nil, refresh = false, api: nil, cnx: Jamf.cnx, **ident_and_val) click to toggle source

Return the id of the object of this subclass with the given identifier.

Return nil if no object has an identifier that matches.

For all objects the ‘name’ is an identifier. Some objects have more, e.g. udid, mac_address & serial_number. Matches are case-insensitive.

NOTE: while name is an identifier, for Computers and MobileDevices, it need not be unique in Jamf. If name is matched, which one gets returned is undefined. In short - dont’ use names here unless you know they are unique.

NOTE: Integers passed in as strings, e.g. ‘12345’ will be converted to integers and return the matching integer id if it exists.

This means that if you have names that might match ‘12345’ and you use

valid_id '12345'

you will get back the id 12345, if such an id exists, even if it is not the object with the name ‘12345’

To explicitly look for ‘12345’ as a name, use:

   valid_id name: '12345'
See the ident_and_val param below.

@param identfier [String,Integer] An identifier for an object, a value for

one of the available lookup_keys. Omit this and use 'identifier: value'
if you want to limit the search to a specific indentifier key, e.g.
   name: 'somename'
or
   id: 76538

@param refresh [Boolean] Should the data be re-read from the server

@param ident_and_val [Hash] Do not pass in Hash.

This Hash internally holds the arbitrary identifier key
and desired value when you call ".valid_id ident: 'value'", e.g.
".valid_id name: 'somename'" or ".valid_id udid: some_udid"
Using explicit identifier keys like this will speed things up, since
the method doesn't have to search through all available identifiers
for the desired value.

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

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

@return [Integer, nil] the id of the matching object, or nil if it doesn’t exist

    # File lib/jamf/api/classic/base_classes/api_object.rb
711 def self.valid_id(identifier = nil, refresh = false, api: nil, cnx: Jamf.cnx, **ident_and_val)
712   cnx = api if api
713 
714   # refresh the cache if needed
715   all(refresh, cnx: cnx) if refresh
716 
717   # Were we given an explict identifier key, like name: or id:?
718   # If so, just look for that.
719   unless ident_and_val.empty?
720     # only the first k/v pair of the ident_and_val hash is used
721     key = ident_and_val.keys.first
722     val = ident_and_val[key]
723 
724     # if we are explicitly looking for an id, ensure we use an integer
725     # even if we were given an integer in a string.
726     if key == :id
727       val = val.to_i if val.is_a?(String) && val.j_integer?
728       return all_ids(cnx: cnx).include?(val) ? val : nil
729     end
730 
731     # map the identifiers to ids, and return the id if there's
732     # a case-insensitive matching identifire
733     map_all(key, to: :id).each do |ident_val, id|
734       return id if ident_val.to_s.casecmp? val.to_s
735     end
736     nil
737   end
738 
739   # If we are here, we need to seach all available identifier keys
740   # Start by looking for it as an id.
741 
742   # it its a valid integer id, return it
743   return identifier if all_ids(cnx: cnx).include? identifier
744 
745   # if its a valid integer-in-a-string id, return it
746   if identifier.is_a?(String) && identifier.j_integer?
747     int_id = identifier.to_i
748     return int_id if all_ids(cnx: cnx).include? int_id
749   end
750 
751   # Now go through all the other identifier keys
752   keys_to_check = lookup_keys(no_aliases: true)
753   keys_to_check.delete :id # we've already checked :id
754 
755   # loop thru looking for a match
756   keys_to_check.each do |key|
757     mapped_ids = map_all_ids_to key, cnx: cnx
758     matches = mapped_ids.select { |_id, ident| ident.casecmp? identifier }
759     # If exactly one match, return the id
760     return matches.keys.first if matches.size == 1
761   end
762 
763   nil
764 end
validate_not_metaclass(klass) click to toggle source

Can’t use APIObject directly.

     # File lib/jamf/api/classic/base_classes/api_object.rb
1216 def self.validate_not_metaclass(klass)
1217   raise Jamf::UnsupportedError, 'Jamf::APIObject is a metaclass. Do not use it directly' if klass == Jamf::APIObject
1218 end
xml_list(array, content = :name) click to toggle source

Convert an Array of Hashes of API object data to a REXML element.

Given an Array of Hashes of items in the subclass where each Hash has at least an :id or a :name key, (as what comes from the .all class method) return a REXML <classes> element with one <class> element per Hash member.

@example

# for class Jamf::Computer
some_comps = [{:id=>2, :name=>"kimchi"},{:id=>5, :name=>"mantis"}]
xml_names = Jamf::Computer.xml_list some_comps
puts xml_names  # output manually formatted for clarity, xml.to_s has no newlines between elements

<computers>
  <computer>
    <name>kimchi</name>
  </computer>
  <computer>
    <name>mantis</name>
  </computer>
</computers>

xml_ids = Jamf::Computer.xml_list some_comps, :id
puts xml_names  # output manually formatted for clarity, xml.to_s has no newlines between elements

<computers>
  <computer>
    <id>2</id>
  </computer>
  <computer>
    <id>5</id>
  </computer>
</computers>

@param array[Array<Hash{:name=>String, :id =>Integer, Symbol=>#to_s}>] the Array of subclass data to convert

@param content the Hash key to use as the inner element for each member of the Array

@return [REXML::Element] the XML element representing the data

    # File lib/jamf/api/classic/base_classes/api_object.rb
879 def self.xml_list(array, content = :name)
880   JSS.item_list_to_rexml_list self::RSRC_LIST_KEY, self::RSRC_OBJECT_KEY, array, content
881 end

Private Class Methods

parse_lookup_keys() click to toggle source

Used by .lookup_keys

    # File lib/jamf/api/classic/base_classes/api_object.rb
425 def self.parse_lookup_keys
426   @lookup_keys = {}
427   @fetch_rsrc_keys = {}
428 
429   hsh = DEFAULT_LOOKUP_KEYS.dup
430   hsh.merge!(self::OTHER_LOOKUP_KEYS) if defined? self::OTHER_LOOKUP_KEYS
431 
432   hsh.each do |key, info|
433     @lookup_keys[key] = key
434     @fetch_rsrc_keys[key] = info[:fetch_rsrc_key]
435     next unless info[:aliases]
436 
437     info[:aliases].each do |a|
438       @lookup_keys[a] = key
439       @fetch_rsrc_keys[a] = info[:fetch_rsrc_key]
440     end
441   end # self::OTHER_LOOKUP_KEYS.each
442 end

Public Instance Methods

<=>(other) click to toggle source

COMPARABLE APIobjects are == if all of their lookup key values are the same

    # File lib/jamf/api/classic/base_classes/api_object.rb
280 def <=>(other)
281   idents_combined <=> other.idents_combined
282 end
add_object_history_entry(user: nil, notes: nil, details: nil) click to toggle source

Make an entry in this object’s Object History. For this to work, the APIObject subclass must define OBJECT_HISTORY_OBJECT_TYPE, an integer indicating the object type in the OBJECT_HISTORY_TABLE in the database (e.g. for computers, the object type is 1)

NOTE: Object history is not available via the Classic API,

so access is only available through direct MySQL
connections

Also: the ‘details’ column in the table shows up in the

'notes' column of the Web UI.  and the 'object_description'
column of the table shows up in the 'details' column of
the UI, under the 'details' button.

The params below reflect the UI, not the table.

@param user the username creating the entry.

@param notes A string that appears as a ‘note’ in the history

@param details A string that appears as the ‘details’ in the history

@return [void]

     # File lib/jamf/api/classic/base_classes/api_object.rb
1482 def add_object_history_entry(user: nil, notes: nil, details: nil)
1483   validate_object_history_available
1484 
1485   raise Jamf::MissingDataError, 'A user: must be provided to make the entry' unless user
1486 
1487   raise Jamf::MissingDataError, 'notes: must be provided to make the entry' unless notes
1488 
1489   user = "'#{Mysql.quote user.to_s}'"
1490   notes =  "'#{Mysql.quote notes.to_s}'"
1491   obj_type = self.class::OBJECT_HISTORY_OBJECT_TYPE
1492 
1493   field_list = 'object_type, object_id, username, details, timestamp_epoch'
1494   value_list = "#{obj_type}, #{@id}, #{user}, #{notes}, #{Time.now.to_jss_epoch}"
1495 
1496   if details
1497     field_list << ', object_description'
1498     value_list << ", '#{Mysql.quote details.to_s}'"
1499   end # if details
1500 
1501   q = "INSERT INTO #{OBJECT_HISTORY_TABLE}
1502     (#{field_list})
1503   VALUES
1504     (#{value_list})"
1505 
1506   Jamf::DB_CNX.db.query q
1507 end
categorizable?() click to toggle source

@return [Boolean] See {Jamf::Categorizable}

     # File lib/jamf/api/classic/base_classes/api_object.rb
1352 def categorizable?
1353   defined? self.class::CATEGORIZABLE
1354 end
creatable?() click to toggle source

@return [Boolean] See {Jamf::Creatable}

     # File lib/jamf/api/classic/base_classes/api_object.rb
1342 def creatable?
1343   defined? self.class::CREATABLE
1344 end
create() click to toggle source

@deprecated, use save

     # File lib/jamf/api/classic/base_classes/api_object.rb
1322 def create
1323   raise Jamf::UnsupportedError, 'Creating this object in the JSS is currently not supported by ruby-jss' unless creatable?
1324 
1325   create_in_jamf
1326 end
criterable?() click to toggle source

@return [Boolean] See {Jamf::Criteriable}

     # File lib/jamf/api/classic/base_classes/api_object.rb
1367 def criterable?
1368   defined? self.class::CRITERIABLE
1369 end
delete() click to toggle source

Delete this item from the JSS.

@seealso {APIObject.delete} for deleting one or more objects by id without needing to instantiate

Subclasses may want to redefine this method, first calling super, then setting other attributes to nil, false, empty, etc..

@return [void]

     # File lib/jamf/api/classic/base_classes/api_object.rb
1417 def delete
1418   return unless @in_jss
1419 
1420   @cnx.c_delete @rest_rsrc
1421 
1422   @rest_rsrc = "#{self.class::RSRC_BASE}/name/#{CGI.escape @name.to_s}"
1423   @id = nil
1424   @in_jss = false
1425   @need_to_update = false
1426 
1427   # clear any cached all-lists or id-maps for this class
1428   # so they'll re-cache as needed
1429   @cnx.flushcache self.class::RSRC_LIST_KEY
1430   # self.class.all :refresh, cnx: @cnx
1431 
1432   :deleted
1433 end
extendable?() click to toggle source

@return [Boolean] See {Jamf::extendable}

     # File lib/jamf/api/classic/base_classes/api_object.rb
1377 def extendable?
1378   defined? self.class::EXTENDABLE
1379 end
idents_combined() click to toggle source
    # File lib/jamf/api/classic/base_classes/api_object.rb
284 def idents_combined
285   my_keys = self.class.lookup_keys.values.uniq
286   my_keys.map { |k| send(k).to_s }.sort.join
287 end
locatable?() click to toggle source

@return [Boolean] See {Jamf::Locatable}

     # File lib/jamf/api/classic/base_classes/api_object.rb
1387 def locatable?
1388   defined? self.class::LOCATABLE
1389 end
matchable?() click to toggle source

@return [Boolean] See {Jamf::Matchable}

     # File lib/jamf/api/classic/base_classes/api_object.rb
1382 def matchable?
1383   defined? self.class::MATCHABLE
1384 end
object_history() click to toggle source

the object history for this object, an array of hashes one per history entry, in order of creation. Each hash contains:

user: String, the username that created the entry
notes:  String, the notes for the entry
date: Time, the timestamp for the entry
details: String or nil, any details provided for the entry

@return [Array<Hash>] the object history

     # File lib/jamf/api/classic/base_classes/api_object.rb
1519 def object_history
1520   validate_object_history_available
1521 
1522   q = "SELECT username, details, timestamp_epoch, object_description
1523   FROM #{OBJECT_HISTORY_TABLE}
1524   WHERE object_type = #{self.class::OBJECT_HISTORY_OBJECT_TYPE}
1525   AND object_id = #{@id}
1526   ORDER BY object_history_id ASC"
1527 
1528   result = Jamf::DB_CNX.db.query q
1529   history = []
1530   result.each do |entry|
1531     history << {
1532       user: entry[0],
1533       notes: entry[1],
1534       date: JSS.epoch_to_time(entry[2]),
1535       details: entry[3]
1536     }
1537   end # each do entry
1538   history
1539 end
ppx() click to toggle source

Print the rest_xml value of the object to stdout, with indentation. Useful for debugging.

@return [void]

     # File lib/jamf/api/classic/base_classes/api_object.rb
1546 def ppx
1547   return nil unless creatable? || updatable?
1548 
1549   formatter = REXML::Formatters::Pretty.new(2)
1550   formatter.compact = true
1551   formatter.write(REXML::Document.new(rest_xml), $stdout)
1552   puts
1553 end
pretty_print_instance_variables() click to toggle source

Remove the init_data and api object from the instance_variables used to create pretty-print (pp) output.

@return [Array] the desired instance_variables

     # File lib/jamf/api/classic/base_classes/api_object.rb
1449 def pretty_print_instance_variables
1450   vars = instance_variables.sort
1451   vars.delete :@cnx
1452   vars.delete :@init_data
1453   vars.delete :@main_subset
1454   vars
1455 end
purchasable?() click to toggle source

@return [Boolean] See {Jamf::Purchasable}

     # File lib/jamf/api/classic/base_classes/api_object.rb
1392 def purchasable?
1393   defined? self.class::PURCHASABLE
1394 end
save() click to toggle source

Either Create or Update this object in the JSS

If this item is creatable or updatable, then create it if needed, or update it if it already exists.

@return [Integer] the id of the item created or updated

     # File lib/jamf/api/classic/base_classes/api_object.rb
1313 def save
1314   if @in_jss
1315     update
1316   else
1317     create
1318   end
1319 end
scopable?() click to toggle source

@return [Boolean] See {Jamf::Scopable}

     # File lib/jamf/api/classic/base_classes/api_object.rb
1397 def scopable?
1398   defined? self.class::SCOPABLE
1399 end
self_servable?() click to toggle source

@return [Boolean] See {Jamf::SelfServable}

     # File lib/jamf/api/classic/base_classes/api_object.rb
1362 def self_servable?
1363   defined? self.class::SELF_SERVABLE
1364 end
sitable?() click to toggle source

@return [Boolean] See {Jamf::Sitable}

     # File lib/jamf/api/classic/base_classes/api_object.rb
1372 def sitable?
1373   defined? self.class::SITABLE
1374 end
to_s() click to toggle source

A meaningful string representation of this object

@return [String]

     # File lib/jamf/api/classic/base_classes/api_object.rb
1439 def to_s
1440   "#{self.class}@#{cnx.host}, id: #{@id}"
1441 end
updatable?() click to toggle source

@return [Boolean] See {Jamf::Updatable}

     # File lib/jamf/api/classic/base_classes/api_object.rb
1347 def updatable?
1348   defined? self.class::UPDATABLE
1349 end
update() click to toggle source

@deprecated, use save

     # File lib/jamf/api/classic/base_classes/api_object.rb
1329 def update
1330   raise Jamf::UnsupportedError, 'Updating this object in the JSS is currently not supported by ruby-jss' unless updatable?
1331 
1332   update_in_jamf
1333 end
uploadable?() click to toggle source

@return [Boolean] See {Jamf::Uploadable}

     # File lib/jamf/api/classic/base_classes/api_object.rb
1402 def uploadable?
1403   defined? self.class::UPLOADABLE
1404 end
vppable?() click to toggle source

@return [Boolean] See {Jamf::VPPable}

     # File lib/jamf/api/classic/base_classes/api_object.rb
1357 def vppable?
1358   defined? self.class::VPPABLE
1359 end

Private Instance Methods

find_main_subset() click to toggle source

Find which part of the @init_data contains the :id and :name

If they aren’t at the top-level of the init hash they are in a subset hash, usually :general, but sometimes someething else, like ldap servers, which have them in :connection Whereever both :id and :name are, that’s the main subset

@return [Hash] The part of the @init_data containg the :id and :name

     # File lib/jamf/api/classic/base_classes/api_object.rb
1670 def find_main_subset
1671   return @init_data if @init_data[:id] && @init_data[:name]
1672   return @init_data[:general] if @init_data[:general] && @init_data[:general][:id] && @init_data[:general][:name]
1673 
1674   @init_data.each do |_key, value|
1675     next unless value.is_a? Hash
1676     return value if value.keys.include?(:id) && value.keys.include?(:name)
1677   end
1678 end
initialize_category() click to toggle source

parse category data during initialization

@return [void]

     # File lib/jamf/api/classic/base_classes/api_object.rb
1684 def initialize_category
1685   parse_category if categorizable? && @in_jss
1686 end
initialize_criteria() click to toggle source

parse criteria data during initialization

@return [void]

     # File lib/jamf/api/classic/base_classes/api_object.rb
1724 def initialize_criteria
1725   parse_criteria if criterable?
1726 end
initialize_ext_attrs() click to toggle source

parse ext_attrs data during initialization

@return [void]

     # File lib/jamf/api/classic/base_classes/api_object.rb
1732 def initialize_ext_attrs
1733   parse_ext_attrs if extendable?
1734 end
initialize_location() click to toggle source

parse location data during initialization

@return [void]

     # File lib/jamf/api/classic/base_classes/api_object.rb
1700 def initialize_location
1701   parse_location if locatable?
1702 end
initialize_purchasing() click to toggle source

parse purchasing data during initialization

@return [void]

     # File lib/jamf/api/classic/base_classes/api_object.rb
1708 def initialize_purchasing
1709   parse_purchasing if purchasable?
1710 end
initialize_scope() click to toggle source

parse scope data during initialization

@return [void]

     # File lib/jamf/api/classic/base_classes/api_object.rb
1716 def initialize_scope
1717   parse_scope if scopable?
1718 end
initialize_self_service() click to toggle source

parse self_service data during initialization

@return [void]

     # File lib/jamf/api/classic/base_classes/api_object.rb
1748 def initialize_self_service
1749   parse_self_service if self_servable?
1750 end
initialize_site() click to toggle source

parse site data during initialization

@return [void]

     # File lib/jamf/api/classic/base_classes/api_object.rb
1692 def initialize_site
1693   parse_site if sitable?
1694 end
initialize_vpp() click to toggle source

parse vpp data during initialization

@return [void]

     # File lib/jamf/api/classic/base_classes/api_object.rb
1740 def initialize_vpp
1741   parse_vpp if vppable?
1742 end
look_up_object_data(args) click to toggle source

Given initialization args, perform an API lookup for an object.

@param args The args passed to initialize, which must have either

key :id or key :fetch_rsrc

@return [Hash] The parsed JSON data for the object from the API

     # File lib/jamf/api/classic/base_classes/api_object.rb
1606 def look_up_object_data(args)
1607   rsrc = args[:fetch_rsrc]
1608   rsrc ||= "#{self.class::RSRC_BASE}/id/#{args[:id]}"
1609 
1610   # if needed, a non-standard object key can be passed by a subclass.
1611   # e.g. User when loookup is by email.
1612   args[:rsrc_object_key] ||= self.class::RSRC_OBJECT_KEY
1613 
1614   raw_json =
1615     if defined? self.class::USE_XML_WORKAROUND
1616       # if we're here, the API JSON is borked, so use the XML
1617       Jamf::XMLWorkaround.data_via_xml rsrc, self.class::USE_XML_WORKAROUND, @cnx
1618     else
1619       # otherwise
1620       @cnx.c_get(rsrc)
1621     end
1622 
1623   raw_json[args[:rsrc_object_key]]
1624 end
parse_init_data() click to toggle source

Start examining the @init_data recieved from the API

@return [void]

     # File lib/jamf/api/classic/base_classes/api_object.rb
1630 def parse_init_data
1631   @init_data ||= {}
1632   # set empty strings to nil
1633   @init_data.jss_nillify! '', :recurse
1634 
1635   # Find the "main" subset which contains :id and :name
1636   @main_subset = find_main_subset
1637   @name = @main_subset[:name]
1638 
1639   if @main_subset[:id] == :new
1640     @id = 0
1641     @in_jss = false
1642   else
1643     @id = @main_subset[:id].to_i
1644     @in_jss = true
1645   end
1646 
1647   @rest_rsrc = "#{self.class::RSRC_BASE}/id/#{@id}"
1648 
1649   ##### Handle Mix-ins
1650   initialize_category
1651   initialize_site
1652   initialize_location
1653   initialize_purchasing
1654   initialize_scope
1655   initialize_criteria
1656   initialize_ext_attrs
1657   initialize_vpp
1658   initialize_self_service
1659 end
rest_xml() click to toggle source

Return a String with the XML Resource for submitting creation or changes to the JSS via the API via the Creatable or Updatable modules

Most classes will redefine this method.

     # File lib/jamf/api/classic/base_classes/api_object.rb
1774 def rest_xml
1775   doc = REXML::Document.new Jamf::Connection::XML_HEADER
1776   tmpl = doc.add_element self.class::RSRC_OBJECT_KEY.to_s
1777   tmpl.add_element('name').text = @name
1778   doc.to_s
1779 end
setup_object_for_creation(args) click to toggle source

Set the basics for creating a new object in the JSS

@param args describe_args

@return [Type] description_of_returned_object

     # File lib/jamf/api/classic/base_classes/api_object.rb
1758 def setup_object_for_creation(args)
1759   # NOTE: subclasses may want to pre-populate more keys in @init_data when :id == :new
1760   # then parse them into attributes later.
1761   @init_data = args
1762   @name = args[:name]
1763   @in_jss = false
1764   @rest_rsrc = "#{self.class::RSRC_BASE}/name/#{CGI.escape @name.to_s}"
1765   @need_to_update = true
1766 end
validate_init_for_creation(args) click to toggle source

If we’re making a new object in the JSS, make sure we were given valid data to do so, raise exceptions otherwise.

NOTE: some subclasses may do further validation.

TODO: support for objects that can have duplicate names.

@param args The args passed to initialize

@return [void]

     # File lib/jamf/api/classic/base_classes/api_object.rb
1587 def validate_init_for_creation(args)
1588   raise Jamf::UnsupportedError, "Creating #{self.class::RSRC_LIST_KEY} isn't yet supported. Please use other Casper workflows." unless creatable?
1589 
1590   raise Jamf::MissingDataError, "You must provide a :name to create a #{self.class::RSRC_OBJECT_KEY}." unless args[:name]
1591 
1592   return if defined? self.class::NON_UNIQUE_NAMES
1593 
1594   matches = self.class.all_names(:refresh, cnx: @cnx).select { |n| n.casecmp? args[:name] }
1595 
1596   raise Jamf::AlreadyExistsError, "A #{self.class::RSRC_OBJECT_KEY} already exists with the name '#{args[:name]}'" unless matches.empty?
1597 end
validate_object_history_available() click to toggle source

Raise an exception if object history is not available for this object

@return [void]

     # File lib/jamf/api/classic/base_classes/api_object.rb
1565 def validate_object_history_available
1566   raise Jamf::NoSuchItemError, 'Object not yet created' unless @id && @in_jss
1567 
1568   raise Jamf::InvalidConnectionError, 'Not connected to MySQL' unless Jamf::DB_CNX.connected?
1569 
1570   return if defined? self.class::OBJECT_HISTORY_OBJECT_TYPE
1571 
1572   raise Jamf::UnsupportedError,
1573         "Object History access is not supported for #{self.class} objects at this time"
1574 end