Class: Jamf::CollectionResource Abstract

Inherits:
Resource show all
Extended by:
Abstract
Includes:
Comparable
Defined in:
lib/jamf/api/abstract_classes/collection_resource.rb

Overview

This class is abstract.

A Collection Resource in Jamf Pro

See Resource for general info about API resources.

Collection resources have more than one resource within them, and those can (usually) be created and deleted as well as fetched and updated. The entire collection (or a part of it) can also be fetched as an Array. When the whole collection is fetched, the result is cached for future use.

# Subclassing

## Creatability, & Deletability

Sometimes the API doesn’t support creation of new members of the collection. If that’s the case, just extend the subclass with Jamf::UnCreatable and the ‘.create’ class method will raise an error.

Similarly for deletion of members: if the API doesn’t have a way to delete them, extend the subclass with Jamf::UnDeletable

See also Jamf::JSONObject, which talks about extending subclasses with Jamf::Immutable

## Bulk Deletion

Some collection resources have a resource for bulk deletion, passing in a JSON array of ids to delete.

If so, just define a BULK_DELETE_RSRC, and the .delete class method will use it, rather than making multiple calls to delete individual items. See Jamf::Category::BULK_DELETE_RSRC for an example

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

This class inherits a constructor from Jamf::JSONObject

Class Method Details

.all(refresh = false, cnx: Jamf.cnx, instantiate: false) ⇒ Array<Object>

The Collection members Array for this class, retrieved from the RSRC_PATH as Parsed JSON, but not instantiated into instances unless instantiate: is truthy.

E.g. for Settings::Building, this would be the Array of Hashes returned by GETing the resource …/settings/obj/building

This Array is cached in the Jamf::Connection instance used to retrieve it, and future calls to .all will return the cached Array unless refresh is truthy.

TODO: Investigate www.rubydoc.info/github/mloughran/api_cache

Parameters:

  • refresh (Boolean) (defaults to: false)

    re-read the data from the API?

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

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

  • instantiate (Boolean) (defaults to: false)

    The Array contains instances of this class rather than the JSON Hashes from the API.

Returns:

  • (Array<Object>)

    An Array of all objects of this class in the JSS.



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/jamf/api/abstract_classes/collection_resource.rb', line 100

def self.all(refresh = false, cnx: Jamf.cnx, instantiate: false)
  validate_not_abstract
  cnx.collection_cache[self] = nil if refresh
  if cnx.collection_cache[self]
    return cnx.collection_cache[self] unless instantiate

    return cnx.collection_cache[self].map { |m| new m }
  end

  raw = cnx.get rsrc_path
  cnx.collection_cache[self] =
    if raw.is_a?(Hash) && raw[:results]
      raw[:results]
    else
      raw
    end

  return cnx.collection_cache[self] unless instantiate

  cnx.collection_cache[self].map { |m| new m }
end

.all_ids(refresh = false, cnx: Jamf.cnx) ⇒ Array<Integer>

An array of the ids for all collection members. According to the specs ALL collection resources must have an ID, which is used in the resource path.

Parameters:

  • refresh (Boolean) (defaults to: false)

    re-read the data from the API?

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

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

Returns:



132
133
134
# File 'lib/jamf/api/abstract_classes/collection_resource.rb', line 132

def self.all_ids(refresh = false, cnx: Jamf.cnx)
  all(refresh, cnx: cnx).map { |m|  m[:id] }
end

.creatable?Boolean

Bu default, subclasses are creatable, i.e. new instances can be created with .create, and added to the JSS with .save If a subclass is NOT creatble for any reason, just add

extend Jamf::UnCreatable

and this method will return false

Returns:

  • (Boolean)


239
240
241
# File 'lib/jamf/api/abstract_classes/collection_resource.rb', line 239

def self.creatable?
  true
end

.create(**params) ⇒ Object

Make a new thing to be added to the API



244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/jamf/api/abstract_classes/collection_resource.rb', line 244

def self.create(**params)
  validate_not_abstract
  raise Jamf::UnsupportedError, "#{self}'s are not currently creatable via the API" unless creatable?

  cnx = params.delete :cnx
  cnx ||= Jamf.cnx

  params.delete :id # no such animal when .creating

  params.keys.each do |param|
    raise ArgumentError, "Unknown parameter: #{param}" unless self::OBJECT_MODEL.key? param

    params[param] = validate_attr param, params[param], cnx: cnx
  end

  params[:creating_from_create] = true
  new params, cnx: cnx
end

.deletable?Boolean

By default, CollectionResource subclass instances are deletable. If not, just extend the subclass with Jamf::UnDeletable, and this will return false, and .delete & #delete will raise errors

Returns:

  • (Boolean)


309
310
311
# File 'lib/jamf/api/abstract_classes/collection_resource.rb', line 309

def self.deletable?
  true
end

.delete(*idents, cnx: Jamf.cnx) ⇒ Array

Delete one or more objects by identifier Any valid identifier for the class can be used (id, name, udid, etc) Identifiers can be provided as an array or as separate parameters

e.g. .delete [1,3, 34, 4] or .delete ‘myComputer’, ‘that-computer’, ‘OtherComputer’

Parameters:

Returns:

  • (Array)

    the identifiers that were not found, so couldn’t be deleted

Raises:



326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
# File 'lib/jamf/api/abstract_classes/collection_resource.rb', line 326

def self.delete(*idents, cnx: Jamf.cnx)
  raise Jamf::UnsupportedError, "Deleting #{self} objects is not currently supported" unless deletable?

  idents.flatten!
  no_valid_ids = []

  idents.map do |ident|
    id = valid_id ident
    no_valid_ids << ident unless id
    id
  end
  idents.compact!

  # TODO: some rsrcs have a 'bulk delete' version...
  idents.each { |id| cnx.delete "#{rsrc_path}/#{id}" }

  no_valid_ids
end

.fetch(ident_value = nil, cnx: Jamf.cnx, **ident_hash) ⇒ CollectionResource

Retrieve a member of a CollectionResource from the API

To create new members to be added to the JSS, use create

If you know the specific identifier attribute you’re looking up, e.g. :id or :name or :udid, (or an aliase thereof) then you can specify it like ‘.fetch name: ’somename’‘, or `.fetch udid: ’someudid’‘

If you don’t know if (or don’t want to type it) you can just use ‘.fetch ’somename’‘, or `.fetch ’someudid’‘ and all identifiers will be searched for a match.

Parameters:

  • ident_value (Object) (defaults to: nil)

    A value for any identifier for this subclass. All identifier attributes will be searched for a match.

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

    the connection to use to fetch the object

  • ident_hash (Hash)

    an identifier attribute key and a search value

Returns:

Raises:



285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
# File 'lib/jamf/api/abstract_classes/collection_resource.rb', line 285

def self.fetch(ident_value = nil, cnx: Jamf.cnx, **ident_hash)
  validate_not_abstract

  id =
    if ident_value == :random
      all_ids.sample
    elsif ident_value
      valid_id ident_value, cnx: cnx
    elsif ident_hash.empty?
      nil
    else
      ident, lookup_value = ident_hash.first
      valid_id ident => lookup_value, cnx: cnx
    end

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

  data = cnx.get "#{rsrc_path}/#{id}"
  new data, cnx: cnx
end

.identifiersArray<Symbol>

Returns the attribute names that are marked as identifiers.

Returns:

  • (Array<Symbol>)

    the attribute names that are marked as identifiers



73
74
75
# File 'lib/jamf/api/abstract_classes/collection_resource.rb', line 73

def self.identifiers
  self::OBJECT_MODEL.select { |_attr, deets| deets[:identifier] }.keys
end

.map_all(ident, to:, cnx: Jamf.cnx, refresh: false) ⇒ 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 data from the API?

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

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

Returns:

  • (Hash {Symbol: Object})

    A Hash of identifier mapped to attribute

Raises:



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/jamf/api/abstract_classes/collection_resource.rb', line 153

def self.map_all(ident, to:, cnx: Jamf.cnx, refresh: false)
  real_ident = attr_key_for_alias ident
  raise Jamf::InvalidDataError, "No identifier #{ident} for class #{self}" unless
  identifiers.include? real_ident

  real_to = attr_key_for_alias to
  raise Jamf::NoSuchItemError, "No attribute #{to} for class #{self}" unless self::OBJECT_MODEL.key? real_to

  ident = real_ident
  to = real_to
  list = all refresh, cnx: cnx
  to_class = self::OBJECT_MODEL[to][:class]
  mapped = list.map do |i|
    [
      i[ident],
      to_class.is_a?(Symbol) ? i[to] : to_class.new(i[to])
    ]
  end # do i
  mapped.to_h
end

.new(*args, &block) ⇒ Object Originally defined in module Abstract

when any extended class or subclass of an extended class is instntiated check that it isn’t in the abstract list.

.valid_id(value = nil, refresh: true, cnx: Jamf.cnx, **ident_hash) ⇒ Object?

Given any identfier value for this collection, return the id of the object that has such an identifier.

Return nil if there’s no match for the given value.

If you know the value is a certain identifier, e.g. a serialNumber, then you can specify the identifier for a faster search:

valid_id serialNumber: 'AB12DE34' # => Int or nil

If you don’t know wich identifier you have, just pass the value and all identifiers are searched

valid_id 'AB12DE34' # => Int or nil
valid_id 'SomeComputerName' # => Int or nil

When the value is a string, the seach is case-insensitive

TODO: When ‘Searchability’ is more dialed in via the searchable mixin, which implements enpoints like ‘POST /v1/search-mobile-devices’ then use that before using the ‘all’ list.

Parameters:

  • value (Object) (defaults to: nil)

    A value to search for as an identifier.

  • refresh (Boolean) (defaults to: true)

    Reload the list data from the API

  • ident: (Symbol)

    Restrict the search to this identifier. E.g. if :serialNumber, then the value must be a known serial number, it is not checked against other identifiers

Returns:

  • (Object, nil)

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



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/jamf/api/abstract_classes/collection_resource.rb', line 210

def self.valid_id(value = nil, refresh: true, cnx: Jamf.cnx, **ident_hash)
  unless ident_hash.empty?
    ident, value = ident_hash.first
    return id_from_other_ident ident, value, refresh, cnx: cnx
  end

  # check the id itself first
  return value if all_ids(refresh, cnx: cnx).include? value

  idents = identifiers - [:id]
  val_is_str = value.is_a? String

  idents.each do |ident|
    match = all(refresh: refresh, cnx: cnx).select do |m|
      val_is_str ? m[ident].to_s.casecmp?(value) : m[ident] == value
    end.first
    return match[:id] if match
  end # identifiers.each do |ident|

  nil
end

Instance Method Details

#<=>(other) ⇒ Object

Two collection resource objects are the same if their id’s are the same



410
411
412
# File 'lib/jamf/api/abstract_classes/collection_resource.rb', line 410

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

#deleteObject



404
405
406
407
# File 'lib/jamf/api/abstract_classes/collection_resource.rb', line 404

def delete
  raise Jamf::UnsupportedError, "Deleting #{self} objects is not currently supported" unless self.class.deletable?
  @cnx.delete rsrc_path
end

#exist?Boolean

Instance Methods

Returns:

  • (Boolean)


395
396
397
# File 'lib/jamf/api/abstract_classes/collection_resource.rb', line 395

def exist?
  !@id.nil?
end

#rsrc_pathObject



399
400
401
402
# File 'lib/jamf/api/abstract_classes/collection_resource.rb', line 399

def rsrc_path
  return unless exist?
  "#{self.class.rsrc_path}/#{@id}"
end