Class: Jamf::APIObject

Inherits:
Object show all
Includes:
Comparable
Defined in:
lib/jamf/api/classic/base_classes/api_object.rb

Overview

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 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 Creatable

The constructor should verify any extra required data in the args

See Creatable for more details.

Object Modification

If a subclass should be modifiable in the JSS, include 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:

  • creating list-methods: For each lookup key, a class method ‘.all_<key>s` is created automatically, e.g. `.all_serial_numbers`. The aliases are used to make alises of those methods, e.g. `.all_sns`

  • finding valid ids: The APIObject.valid_id class method looks at the known lookup keys to find an object’s id.

  • fetching: When an indentifier is given to ‘.fetch`, the fetch_rsrc_key is used to build the resource URL for fetching the object. If there is no fetch_rsrc_key, the lookup_keys and aliases are used to find the matching id, which is used to build the URL.

    When no identifier is specified, .fetch uses .valid_id, described above.

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.

Constant Summary collapse

API_SOURCE =

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

:classic
OK_INSTANTIATORS =

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

['make', 'create', 'fetch', 'block in fetch'].freeze
DEFAULT_LOOKUP_KEYS =

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

{
  id: { fetch_rsrc_key: :id },
  name: { fetch_rsrc_key: :name }
}.freeze
OBJECT_HISTORY_TABLE =

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

'object_history'.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(**args) ⇒ APIObject

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 Connection query (a Hash of valid object data)

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

Parameters:

  • args (Hash)

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

Options Hash (**args):

  • :id (Integer)

    the jss id to look up

  • :name (String)

    the name to look up

  • :fetch_rsrc (String)

    a non-standard resource for fetching API data e.g. to limit the data returned



1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1271

def initialize(**args)
  @cnx = args[:cnx]
  @cnx ||= args[:api]
  @cnx ||= Jamf.cnx

  # we're making a new one in the JSS
  if args[:id] == :new
    validate_init_for_creation(args)
    setup_object_for_creation(args)
    @need_to_update = true

  # we're instantiating an existing one in the jss
  else
    @init_data = look_up_object_data(args)
    @need_to_update = false
  end ## end arg parsing

  parse_init_data
end

Instance Attribute Details

#cnxJamf::Connection (readonly) Also known as: api

Returns the API connection thru which we deal with this object.

Returns:

  • (Jamf::Connection)

    the API connection thru which we deal with this object.



1225
1226
1227
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1225

def cnx
  @cnx
end

#idInteger (readonly)

Returns the JSS id number.

Returns:

  • (Integer)

    the JSS id number



1234
1235
1236
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1234

def id
  @id
end

#in_jssBoolean (readonly) Also known as: in_jss?

Returns is it in the JSS?.

Returns:

  • (Boolean)

    is it in the JSS?



1240
1241
1242
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1240

def in_jss
  @in_jss
end

#init_dataObject (readonly)

Returns the parsed JSON data retrieved from the API when this object was fetched.

Returns:

  • the parsed JSON data retrieved from the API when this object was fetched



1231
1232
1233
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1231

def init_data
  @init_data
end

#nameString (readonly)

Returns the name.

Returns:



1237
1238
1239
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1237

def name
  @name
end

#rest_rsrcString (readonly)

Returns the Rest resource for API access (the part after “JSSResource/” ).

Returns:

  • (String)

    the Rest resource for API access (the part after “JSSResource/” )



1243
1244
1245
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1243

def rest_rsrc
  @rest_rsrc
end

Class Method Details

.all(refresh = false, api: nil, cnx: Jamf.cnx) ⇒ Array<Hash{:name=>String, :id=> Integer}>

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 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.

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:



510
511
512
513
514
515
516
517
518
519
520
521
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 510

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

  validate_not_metaclass(self)

  cache = cnx.c_object_list_cache
  cache_key = self::RSRC_LIST_KEY
  cnx.flushcache(cache_key) if refresh
  return cache[cache_key] if cache[cache_key]

  cache[cache_key] = cnx.c_get(self::RSRC_BASE)[cache_key]
end

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

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.

Parameters:

  • refresh (Boolean) (defaults to: false)

    should the data 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:



650
651
652
653
654
655
656
657
658
659
660
661
662
663
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 650

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

  objects_cache_key ||= "#{self::RSRC_LIST_KEY}_objects".to_sym
  api_cache = cnx.c_object_list_cache
  api_cache[objects_cache_key] = nil if refresh

  return api_cache[objects_cache_key] if api_cache[objects_cache_key]

  all_result = all(refresh, cnx: cnx)
  api_cache[objects_cache_key] = all_result.map do |o|
    fetch id: o[:id], cnx: cnx, refresh: false
  end
end

.create(**args) ⇒ APIObject

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 fetch

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

Parameters:

  • name (String)

    The name of this object, generally must be uniqie

  • cnx (Jamf::Connection)

    the connection thru which to make this object. Defaults to the deault API connection in Jamf.cnx

  • args (Hash)

    The data for creating an object, such as name: See #initialize

Returns:

  • (APIObject)

    The un-created ruby-instance of a JSS object

Raises:

  • (ArgumentError)


1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1139

def self.create(**args)
  validate_not_metaclass(self)
  unless constants.include?(:CREATABLE)
    raise Jamf::UnsupportedError, "Creating #{self::RSRC_LIST_KEY} isn't yet supported. Please use other Casper workflows."
  end
  raise ArgumentError, "Use '#{self.class}.fetch id: xx' to retrieve existing JSS objects" if args[:id]

  args[:cnx] ||= args[:api] # deprecated
  args[:cnx] ||= Jamf.cnx
  args[:id] = :new
  new(**args)
end

.define_identifier_list_methodsObject

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



297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 297

def self.define_identifier_list_methods
  Jamf.load_msg "Defining list-methods for APIObject subclass #{self}"

  lookup_keys.each do |als, key|
    meth_name = key.to_s.end_with?('s') ? "all_#{key}es" : "all_#{key}s"
    if als == key
      # the all_ method - skip if defined in the class
      next if singleton_methods.include? meth_name.to_sym

      define_singleton_method meth_name do |refresh = false, cached_list: nil, api: nil, cnx: Jamf.cnx|
        cnx = api if api
        list = cached_list || all(refresh, cnx: cnx)
        list.map { |i| i[key] }
      end
      Jamf.load_msg "Defined method #{self}##{meth_name}"

    else
      # an alias - skip if defined in the class
      als_name = als.to_s.end_with?('s') ? "all_#{als}es" : "all_#{als}s"

      next if singleton_methods.include? als_name.to_sym

      define_singleton_method als_name do |refresh = false, api: nil, cnx: Jamf.cnx|
        cnx = api if api
        send meth_name, refresh, cnx: cnx
      end
      Jamf.load_msg "Defined alias '#{als_name}' of #{self}##{meth_name}"

    end # if
  end # lookup_keys.each

  true
end

.delete(victims, refresh = true, api: nil, cnx: Jamf.cnx) ⇒ Array<Integer>

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.

Parameters:

  • victims (Integer, Array<Integer>)

    An object id or an array of them to be deleted

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

    the API connection to use. Defaults to the corrently active API. See Connection

Returns:

  • (Array<Integer>)

    The id’s that didn’t exist when we tried to delete them.

Raises:



1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1183

def self.delete(victims, refresh = true, api: nil, cnx: Jamf.cnx)
  cnx = api if api

  validate_not_metaclass(self)

  raise Jamf::InvalidDataError, 'Parameter must be an Integer ID or an Array of them' unless victims.is_a?(Integer) || victims.is_a?(Array)

  case victims
  when Integer
    victims = [victims]
  when Array
    victims.uniq!
  end

  skipped = []
  current_ids = all_ids refresh, cnx: cnx
  victims.each do |vid|
    if current_ids.include? vid
      cnx.c_delete "#{self::RSRC_BASE}/id/#{vid}"
    else
      skipped << vid
    end # if current_ids include vid
  end # each victim

  # clear any cached all-lists or id-maps for this class
  # so they'll re-cache as needed
  cnx.flushcache self::RSRC_LIST_KEY
  # all :refresh, cnx: cnx

  skipped
end

.duplicate_names(refresh = false, api: nil, cnx: Jamf.cnx) ⇒ Hash {String => Integer}

Returns name => number of occurances.

Returns:

  • (Hash {String => Integer})

    name => number of occurances



525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 525

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

  return {} unless defined? self::NON_UNIQUE_NAMES

  dups = {}
  all(refresh, cnx: cnx).each do |obj|
    if dups[obj[:name]]
      dups[obj[:name]] += 1
    else
      dups[obj[:name]] = 1
    end # if
  end # all(refresh, cnx: cnx).each
  dups.delete_if { |_k, v| v == 1 }
  dups
end

.exist?(identifier, refresh = false, api: nil, cnx: Jamf.cnx) ⇒ Boolean

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

one of the available lookup_keys

Parameters:

  • identfier (String, Integer)

    An identifier for an object, a value for

  • refresh (Boolean) (defaults to: false)

    Should the data be re-read from the server

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

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

Returns:

  • (Boolean)

    does an object with the given identifier exist?



831
832
833
834
835
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 831

def self.exist?(identifier, refresh = false, api: nil, cnx: Jamf.cnx)
  cnx = api if api

  !valid_id(identifier, refresh, cnx: cnx).nil?
end

.fetch(searchterm = nil, **args) ⇒ APIObject

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

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 make

Examples:

# 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'

Parameters:

  • searchterm (String, Integer) (defaults to: nil)

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

  • args (Hash)

    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’‘

Options Hash (**args):

  • cnx (Jamf::Connection)

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

  • refresh (Boolean)

    should the summary list of all objects be reloaded from the API before being used to look for this object.

Returns:

  • (APIObject)

    The ruby-instance of a JSS object



961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 961

def self.fetch(searchterm = nil, **args)
  validate_not_metaclass(self)

  # which connection?
  cnx = args.delete :cnx
  cnx ||= args.delete :api # backward compatibility, deprecated
  cnx ||= Jamf.cnx

  # refresh the .all list if needed
  if args.delete(:refresh) || searchterm == :random
    all(:refresh, cnx: cnx)
    just_refreshed = true
  else
    just_refreshed = false
  end

  # a random object?
  if searchterm == :random || args[:random]
    rnd_thing = all(cnx: cnx).sample
    raise Jamf::NoSuchItemError, "No #{self::RSRC_LIST_KEY} found" unless rnd_thing

    return new id: rnd_thing[:id], cnx: cnx
  end

  # get the lookup key and value, if given
  fetch_key, fetch_val = args.to_a.first
  fetch_rsrc_key = fetch_rsrc_key(fetch_key)

  # names should raise an error if more than one exists,
  # so we always have to do id_for_identifier, which will do so.
  if fetch_rsrc_key == :name
    id = id_for_identifier fetch_key, fetch_val, !just_refreshed, cnx: cnx
    fetch_rsrc = id ? "#{self::RSRC_BASE}/name/#{CGI.escape fetch_val.to_s}" : nil

  # if the fetch rsrc key exists, it can be used directly in an endpoint path
  # so, use it directly, rather than looking up the id first.
  elsif fetch_rsrc_key
    fetch_rsrc = "#{self::RSRC_BASE}/#{fetch_rsrc_key}/#{CGI.escape fetch_val.to_s}"

  # it has an OTHER_LOOKUP_KEY but that key doesn't have a fetch_rsrc
  # so we look in the .map_all_ids_to_* hash for it.
  elsif fetch_key
    id = id_for_identifier fetch_key, fetch_val, !just_refreshed, cnx: cnx
    fetch_rsrc = id ? "#{self::RSRC_BASE}/id/#{id}" : nil

  # no fetch key was given in the args, so try a search term
  elsif searchterm
    id = valid_id searchterm, cnx: cnx
    fetch_rsrc = id ? "#{self::RSRC_BASE}/id/#{id}" : nil

  else
    raise ArgumentError, 'Missing searchterm or fetch key'
  end

  new fetch_rsrc: fetch_rsrc, cnx: cnx
end

.fetch_rsrc_key(lookup_key) ⇒ Symbol?

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.

Parameters:

  • lookup_key (Symbol)

    A lookup key, or an aliases of one, for this subclass.

Returns:

  • (Symbol, nil)

    the fetch_rsrc_key for that lookup key.



418
419
420
421
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 418

def self.fetch_rsrc_key(lookup_key)
  parse_lookup_keys unless @fetch_rsrc_keys
  @fetch_rsrc_keys[lookup_key]
end

.get_name(a_thing) ⇒ String

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

Parameters:

  • a_thing (String, Array)

    the api data from which we’re extracting the name

Returns:

  • (String)

    the name extracted from a_thing



908
909
910
911
912
913
914
915
916
917
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 908

def self.get_name(a_thing)
  case a_thing
  when String
    a_thing
  when Hash
    a_thing[:name]
  when nil
    nil
  end
end

.get_raw(id, format: :json, as_string: false, api: nil, cnx: Jamf.cnx) ⇒ Hash, ...

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 Connection.c_get that automatically fills in the RSRC::BASE value for you.

Parameters:

  • id (Integer)

    the id of the object to fetch

  • format (Symbol) (defaults to: :json)

    :json or :xml, defaults to :json

  • as_string (Boolean) (defaults to: false)

    return the raw JSON or XML string as it comes from the API, do not parse into a Hash or REXML::Document

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

    the connection thru which to fetch this object. Defaults to the deault API connection in Jamf.cnx

Returns:

  • (Hash, REXML::Document, String)

    the raw data for the object



1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1050

def self.get_raw(id, format: :json, as_string: false, api: nil, cnx: Jamf.cnx)
  cnx = api if api

  validate_not_metaclass(self)
  rsrc = "#{self::RSRC_BASE}/id/#{id}"
  data = cnx.c_get rsrc, format, raw_json: as_string
  return data if format == :json || as_string

  REXML::Document.new(**data)
end

.id_for_identifier(key, val, refresh = false, api: nil, cnx: Jamf.cnx) ⇒ Integer?

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

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

Examples:

# 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

Parameters:

  • key (Symbol)

    they key in which to look for the identifier. Must be a valid lookup key for this subclass.

  • identfier (String, Integer)

    An identifier for an object, a value for one of the available lookup_keys

  • refresh (Boolean) (defaults to: false)

    Should the cached summary data be re-read from the server first?

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

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

Returns:

  • (Integer, nil)

    the id of the matching object, or nil if it doesn’t exist

Raises:



797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 797

def self.id_for_identifier(key, val, refresh = false, api: nil, cnx: Jamf.cnx)
  cnx = api if api

  # refresh if needed
  all(refresh, cnx: cnx) if refresh

  # get the real key if an alias was used
  key = real_lookup_key key

  # do id's expicitly, they are integers
  return all_ids(cnx: cnx).include?(val) ? val : nil if key == :id

  mapped_ids = map_all_ids_to key, cnx: cnx
  matches = mapped_ids.select { |_id, map_val| val.casecmp? map_val }
  raise Jamf::AmbiguousError, "Key #{key}: value '#{val}' is not unique for #{self}" if matches.size > 1

  return nil if matches.size.zero?

  matches.keys.first
end

.lookup_keys(no_aliases: false, fetch_rsrc_keys: false) ⇒ Hash {Symbol: Symbol}, Array<Symbol>

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]

Parameters:

  • no_aliases (Boolean) (defaults to: false)

    Only return the real keys, no aliases.

Returns:

  • (Hash {Symbol: Symbol})

    when no_aliases is falsey, the lookup keys and aliases for this subclass.

  • (Array<Symbol>)

    when no_aliases is truthy, the lookup keys for this subclass



403
404
405
406
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 403

def self.lookup_keys(no_aliases: false, fetch_rsrc_keys: false)
  parse_lookup_keys unless @lookup_keys
  no_aliases ? @lookup_keys.values.uniq : @lookup_keys
end

.make(**args) ⇒ Object

Deprecated.

use .create instead

backward compatability



1154
1155
1156
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1154

def self.make(**args)
  create(**args)
end

.map_all(ident, to:, cnx: Jamf.cnx, refresh: false, cached_list: nil) ⇒ Hash {Symbol: Object}

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

Parameters:

  • ident (Symbol)

    An identifier of this Class, used as the key for the mapping Hash. Aliases are acceptable, e.g. :sn for :serialNumber

  • to (Symbol)

    The attribute to which the ident will be mapped. Aliases are acceptable, e.g. :name for :displayName

  • refresh (Boolean) (defaults to: false)

    Re-read the ‘all’ data from the API? otherwise use the cached data if available.

  • 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 {Symbol: Object})

    A Hash of identifier mapped to attribute

Raises:



559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 559

def self.map_all(ident, to:, cnx: Jamf.cnx, refresh: false, cached_list: nil)
  orig_ident = ident
  ident = lookup_keys[ident]
  raise Jamf::InvalidDataError, "No identifier :#{orig_ident} for class #{self}" unless ident

  list = cached_list || all(refresh, cnx: cnx)
  mapped = list.map do |i|
    [
      i[ident],
      i[to]
    ]
  end # do i

  mapped.to_h
end

.map_all_ids_to(other_key, refresh = false, cached_list: nil, api: nil, cnx: Jamf.cnx) ⇒ Hash{Integer => Oject}

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.

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.

Examples:

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}

Parameters:

  • other_key (Symbol)

    the other data key with which to associate each id

  • refresh (Boolean) (defaults to: false)

    should the data 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:

  • (Hash{Integer => Oject})

    the associated ids and data



616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 616

def self.map_all_ids_to(other_key, refresh = false, cached_list: nil, api: nil, cnx: Jamf.cnx)
  cnx = api if api

  map_all :id, to: other_key, refresh: refresh, cnx: cnx, cached_list: cached_list

  # # we will accept any key, it'll just return nil if not in the
  # # .all hashes. However if we're given an alias of a lookup key
  # # we need to convert it to its real name.
  # other_key = lookup_keys[other_key] if lookup_keys[other_key]
  #
  # cache_key = "#{self::RSRC_LIST_KEY}_map_#{other_key}".to_sym
  # cache = cnx.c_object_list_cache
  # cache[cache_key] = nil if refresh
  # return cache[cache_key] if cache[cache_key]
  #
  # map = {}
  # all(refresh, cnx: cnx).each { |i| map[i[:id]] = i[other_key] }
  # cache[cache_key] = map
end

.new(**args) ⇒ Object

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



1160
1161
1162
1163
1164
1165
1166
1167
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1160

def self.new(**args)
  validate_not_metaclass(self)

  calling_method = caller_locations(1..1).first.label
  raise Jamf::UnsupportedError, 'Use .fetch or .create to instantiate APIObject classes' unless OK_INSTANTIATORS.include? calling_method

  super
end

.post_raw(xml, api: nil, cnx: Jamf.cnx) ⇒ REXML::Document

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 Connection.c_post that automatically fills in the RSRC::BASE value for you.

Parameters:

  • xml (String, #to_s)

    The XML to send

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

    the connection thru which to fetch this object. Defaults to the deault API connection in Jamf.cnx

Returns:

  • (REXML::Document)

    the XML response from the API



1111
1112
1113
1114
1115
1116
1117
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1111

def self.post_raw(xml, api: nil, cnx: Jamf.cnx)
  cnx = api if api

  validate_not_metaclass(self)
  rsrc = "#{self::RSRC_BASE}/id/-1"
  REXML::Document.new(cnx.c_post(rsrc, xml.to_s))
end

.put_raw(id, xml, api: nil, cnx: Jamf.cnx) ⇒ REXML::Document

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 Connection.c_put that automatically fills in the RSRC::BASE value for you.

Parameters:

  • id (Integer)

    the id of the object to PUT

  • xml (String, #to_s)

    The XML to send

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

    the connection thru which to fetch this object. Defaults to the deault API connection in Jamf.cnx

Returns:

  • (REXML::Document)

    the XML response from the API



1083
1084
1085
1086
1087
1088
1089
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1083

def self.put_raw(id, xml, api: nil, cnx: Jamf.cnx)
  cnx = api if api

  validate_not_metaclass(self)
  rsrc = "#{self::RSRC_BASE}/id/#{id}"
  REXML::Document.new(cnx.c_put(rsrc, xml.to_s))
end

.real_lookup_key(key) ⇒ Symbol

get the real lookup key frm a given alias

Parameters:

  • key (Symbol)

    the key or an aliase of the key

Returns:

  • (Symbol)

    the real key for the given key

Raises:

  • (ArgumentError)


451
452
453
454
455
456
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 451

def self.real_lookup_key(key)
  real_key = lookup_keys[key]
  raise ArgumentError, "Unknown lookup key '#{key}' for #{self}" unless real_key

  real_key
end

.valid_id(identifier = nil, refresh = false, api: nil, cnx: Jamf.cnx, **ident_and_val) ⇒ Integer?

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.

Parameters:

  • 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
    
  • refresh (Boolean) (defaults to: false)

    Should the data be re-read from the server

  • 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.

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

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

Returns:

  • (Integer, nil)

    the id of the matching object, or nil if it doesn’t exist



711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 711

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

  # refresh the cache if needed
  all(refresh, cnx: cnx) if refresh

  # Were we given an explict identifier key, like name: or id:?
  # If so, just look for that.
  unless ident_and_val.empty?
    # only the first k/v pair of the ident_and_val hash is used
    key = ident_and_val.keys.first
    val = ident_and_val[key]

    # if we are explicitly looking for an id, ensure we use an integer
    # even if we were given an integer in a string.
    if key == :id
      val = val.to_i if val.is_a?(String) && val.j_integer?
      return all_ids(cnx: cnx).include?(val) ? val : nil
    end

    # map the identifiers to ids, and return the id if there's
    # a case-insensitive matching identifire
    map_all(key, to: :id).each do |ident_val, id|
      return id if ident_val.to_s.casecmp? val.to_s
    end
    nil
  end

  # If we are here, we need to seach all available identifier keys
  # Start by looking for it as an id.

  # it its a valid integer id, return it
  return identifier if all_ids(cnx: cnx).include? identifier

  # if its a valid integer-in-a-string id, return it
  if identifier.is_a?(String) && identifier.j_integer?
    int_id = identifier.to_i
    return int_id if all_ids(cnx: cnx).include? int_id
  end

  # Now go through all the other identifier keys
  keys_to_check = lookup_keys(no_aliases: true)
  keys_to_check.delete :id # we've already checked :id

  # loop thru looking for a match
  keys_to_check.each do |key|
    mapped_ids = map_all_ids_to key, cnx: cnx
    matches = mapped_ids.select { |_id, ident| ident.casecmp? identifier }
    # If exactly one match, return the id
    return matches.keys.first if matches.size == 1
  end

  nil
end

.validate_not_metaclass(klass) ⇒ Object

Can’t use APIObject directly.



1216
1217
1218
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1216

def self.validate_not_metaclass(klass)
  raise Jamf::UnsupportedError, 'Jamf::APIObject is a metaclass. Do not use it directly' if klass == Jamf::APIObject
end

.xml_list(array, content = :name) ⇒ REXML::Element

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.

Examples:

# 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>

Parameters:

  • array (Array<Hash{:name=>String, :id =>Integer, Symbol=>#to_s}>)

    the Array of subclass data to convert

  • content (Symbol) (defaults to: :name)

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

Returns:

  • (REXML::Element)

    the XML element representing the data



879
880
881
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 879

def self.xml_list(array, content = :name)
  JSS.item_list_to_rexml_list self::RSRC_LIST_KEY, self::RSRC_OBJECT_KEY, array, content
end

Instance Method Details

#<=>(other) ⇒ Object

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



280
281
282
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 280

def <=>(other)
  idents_combined <=> other.idents_combined
end

#add_object_history_entry(user: nil, notes: nil, details: nil) ⇒ void

This method returns an undefined value.

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.

Parameters:

  • user (String) (defaults to: nil)

    the username creating the entry.

  • notes (String) (defaults to: nil)

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

  • details (String) (defaults to: nil)

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

Raises:



1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1482

def add_object_history_entry(user: nil, notes: nil, details: nil)
  validate_object_history_available

  raise Jamf::MissingDataError, 'A user: must be provided to make the entry' unless user

  raise Jamf::MissingDataError, 'notes: must be provided to make the entry' unless notes

  user = "'#{Mysql.quote user.to_s}'"
  notes =  "'#{Mysql.quote notes.to_s}'"
  obj_type = self.class::OBJECT_HISTORY_OBJECT_TYPE

  field_list = 'object_type, object_id, username, details, timestamp_epoch'
  value_list = "#{obj_type}, #{@id}, #{user}, #{notes}, #{Time.now.to_jss_epoch}"

  if details
    field_list << ', object_description'
    value_list << ", '#{Mysql.quote details.to_s}'"
  end # if details

  q = "INSERT INTO #{OBJECT_HISTORY_TABLE}
    (#{field_list})
  VALUES
    (#{value_list})"

  Jamf::DB_CNX.db.query q
end

#categorizable?Boolean

Returns See Categorizable.

Returns:



1352
1353
1354
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1352

def categorizable?
  defined? self.class::CATEGORIZABLE
end

#creatable?Boolean

Returns See Creatable.

Returns:



1342
1343
1344
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1342

def creatable?
  defined? self.class::CREATABLE
end

#createObject

@deprecated, use #save



1322
1323
1324
1325
1326
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1322

def create
  raise Jamf::UnsupportedError, 'Creating this object in the JSS is currently not supported by ruby-jss' unless creatable?

  create_in_jamf
end

#criterable?Boolean

Returns See Criteriable.

Returns:



1367
1368
1369
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1367

def criterable?
  defined? self.class::CRITERIABLE
end

#deletevoid

This method returns an undefined value.

Delete this item from the JSS.

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..



1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1417

def delete
  return unless @in_jss

  @cnx.c_delete @rest_rsrc

  @rest_rsrc = "#{self.class::RSRC_BASE}/name/#{CGI.escape @name.to_s}"
  @id = nil
  @in_jss = false
  @need_to_update = false

  # clear any cached all-lists or id-maps for this class
  # so they'll re-cache as needed
  @cnx.flushcache self.class::RSRC_LIST_KEY
  # self.class.all :refresh, cnx: @cnx

  :deleted
end

#extendable?Boolean

Returns See extendable.

Returns:

  • (Boolean)

    See extendable



1377
1378
1379
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1377

def extendable?
  defined? self.class::EXTENDABLE
end

#idents_combinedObject



284
285
286
287
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 284

def idents_combined
  my_keys = self.class.lookup_keys.values.uniq
  my_keys.map { |k| send(k).to_s }.sort.join
end

#locatable?Boolean

Returns See Locatable.

Returns:



1387
1388
1389
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1387

def locatable?
  defined? self.class::LOCATABLE
end

#matchable?Boolean

Returns See Matchable.

Returns:



1382
1383
1384
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1382

def matchable?
  defined? self.class::MATCHABLE
end

#object_historyArray<Hash>

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

Returns:



1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1519

def object_history
  validate_object_history_available

  q = "SELECT username, details, timestamp_epoch, object_description
  FROM #{OBJECT_HISTORY_TABLE}
  WHERE object_type = #{self.class::OBJECT_HISTORY_OBJECT_TYPE}
  AND object_id = #{@id}
  ORDER BY object_history_id ASC"

  result = Jamf::DB_CNX.db.query q
  history = []
  result.each do |entry|
    history << {
      user: entry[0],
      notes: entry[1],
      date: JSS.epoch_to_time(entry[2]),
      details: entry[3]
    }
  end # each do entry
  history
end

#ppxvoid

This method returns an undefined value.

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



1546
1547
1548
1549
1550
1551
1552
1553
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1546

def ppx
  return nil unless creatable? || updatable?

  formatter = REXML::Formatters::Pretty.new(2)
  formatter.compact = true
  formatter.write(REXML::Document.new(rest_xml), $stdout)
  puts
end

#pretty_print_instance_variablesArray

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

Returns:

  • (Array)

    the desired instance_variables



1449
1450
1451
1452
1453
1454
1455
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1449

def pretty_print_instance_variables
  vars = instance_variables.sort
  vars.delete :@cnx
  vars.delete :@init_data
  vars.delete :@main_subset
  vars
end

#purchasable?Boolean

Returns See Purchasable.

Returns:



1392
1393
1394
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1392

def purchasable?
  defined? self.class::PURCHASABLE
end

#saveInteger

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.

Returns:

  • (Integer)

    the id of the item created or updated



1313
1314
1315
1316
1317
1318
1319
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1313

def save
  if @in_jss
    update
  else
    create
  end
end

#scopable?Boolean

Returns See Scopable.

Returns:



1397
1398
1399
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1397

def scopable?
  defined? self.class::SCOPABLE
end

#self_servable?Boolean

Returns See SelfServable.

Returns:



1362
1363
1364
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1362

def self_servable?
  defined? self.class::SELF_SERVABLE
end

#sitable?Boolean

Returns See Sitable.

Returns:



1372
1373
1374
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1372

def sitable?
  defined? self.class::SITABLE
end

#to_sString

A meaningful string representation of this object

Returns:



1439
1440
1441
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1439

def to_s
  "#{self.class}@#{cnx.host}, id: #{@id}"
end

#updatable?Boolean

Returns See Updatable.

Returns:



1347
1348
1349
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1347

def updatable?
  defined? self.class::UPDATABLE
end

#updateObject

@deprecated, use #save



1329
1330
1331
1332
1333
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1329

def update
  raise Jamf::UnsupportedError, 'Updating this object in the JSS is currently not supported by ruby-jss' unless updatable?

  update_in_jamf
end

#uploadable?Boolean

Returns See Uploadable.

Returns:



1402
1403
1404
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1402

def uploadable?
  defined? self.class::UPLOADABLE
end

#vppable?Boolean

Returns See VPPable.

Returns:



1357
1358
1359
# File 'lib/jamf/api/classic/base_classes/api_object.rb', line 1357

def vppable?
  defined? self.class::VPPABLE
end