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
121
122
123
124
125
126
127
128
# 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

  # TODO:  make sure all collection resources use this format
  # for paging. Also -ask Jamf about a  url that returns
  # ALL objects in one query, regardless of number.
  page = 0
  raw = cnx.get "#{rsrc_path}?page=#{page}&size=1000000&sort=id%3Aasc"
  results = raw[:results]

  until results.size >= raw[:totalCount]
    page += 1
    raw = cnx.get "#{rsrc_path}?page=#{page}&size=1000000&sort=id%3Aasc"
    results += raw[:results]
  end


  cnx.collection_cache[self] = results

  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:



140
141
142
# File 'lib/jamf/api/abstract_classes/collection_resource.rb', line 140

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)


247
248
249
# File 'lib/jamf/api/abstract_classes/collection_resource.rb', line 247

def self.creatable?
  true
end

.create(**params) ⇒ Object

Make a new thing to be added to the API



252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'lib/jamf/api/abstract_classes/collection_resource.rb', line 252

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)


317
318
319
# File 'lib/jamf/api/abstract_classes/collection_resource.rb', line 317

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:



334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
# File 'lib/jamf/api/abstract_classes/collection_resource.rb', line 334

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:



293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
# File 'lib/jamf/api/abstract_classes/collection_resource.rb', line 293

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:



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/jamf/api/abstract_classes/collection_resource.rb', line 161

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



218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/jamf/api/abstract_classes/collection_resource.rb', line 218

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



418
419
420
# File 'lib/jamf/api/abstract_classes/collection_resource.rb', line 418

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

#deleteObject



412
413
414
415
# File 'lib/jamf/api/abstract_classes/collection_resource.rb', line 412

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)


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

def exist?
  !@id.nil?
end

#rsrc_pathObject



407
408
409
410
# File 'lib/jamf/api/abstract_classes/collection_resource.rb', line 407

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