Class: JSS::APIObject
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 JSS::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 JSS::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 JSS::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 JSS::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.
Direct Known Subclasses
Account, AdvancedSearch, Building, Category, Computer, ComputerInvitation, ConfigurationProfile, Department, DirectoryBinding, DiskEncryptionConfiguration, DistributionPoint, DockItem, EBook, ExtensionAttribute, Group, IBeacon, LDAPServer, MacApplication, MobileDevice, MobileDeviceApplication, NetBootServer, NetworkSegment, Package, PatchPolicy, PatchSource, PatchTitle, Peripheral, PeripheralType, Policy, Printer, RemovableMacAddress, RestrictedSoftware, Script, Site, SoftwareUpdateServer, User, VPPAccount, WebHook
Constant Summary collapse
- OK_INSTANTIATORS =
'.new' can only be called from these methods:
['make', 'fetch', 'block in fetch'].freeze
- DEFAULT_LOOKUP_KEYS =
See the discussion of 'Lookup Keys' in the comments/docs for JSS::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
-
#api ⇒ JSS::APIConnection
readonly
The API connection thru which we deal with this object.
-
#id ⇒ Integer
readonly
The JSS id number.
-
#in_jss ⇒ Boolean
(also: #in_jss?)
readonly
Is it in the JSS?.
-
#init_data ⇒ Object
readonly
The parsed JSON data retrieved from the API when this object was fetched.
-
#name ⇒ String
readonly
The name.
-
#rest_rsrc ⇒ String
readonly
The Rest resource for API access (the part after “JSSResource/” ).
Class Method Summary collapse
-
.all(refresh = false, api: JSS.api) ⇒ Array<Hash{:name=>String, :id=> Integer}>
Return an Array of Hashes for all objects of this subclass in the JSS.
-
.all_objects(refresh = false, api: JSS.api) ⇒ Array<APIObject>
Return an Array of JSS::APIObject subclass instances e.g when called on JSS::Package, return a hash of JSS::Package instancesa for every package in the JSS.
-
.delete(victims, refresh = true, api: JSS.api) ⇒ Array<Integer>
Delete one or more API objects by jss_id without instantiating them.
-
.duplicate_names(refresh = false, api: JSS.api) ⇒ Hash {String => Integer}
Name => number of occurances.
-
.exist?(identifier, refresh = false, api: JSS.api) ⇒ Boolean
Return true or false if an object of this subclass with the given Identifier exists on the server.
-
.fetch(searchterm = nil, **args) ⇒ APIObject
Retrieve an object from the API and return an instance of this APIObject subclass.
-
.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.
-
.get_name(a_thing) ⇒ String
Some API objects contain references to other API objects.
-
.get_raw(id, format: :json, as_string: false, api: JSS.api) ⇒ Hash, ...
Fetch the mostly- or fully-raw JSON or XML data for an object of this subclass.
-
.id_for_identifier(key, val, refresh = false, api: JSS.api) ⇒ Integer?
Return the id of the object of this subclass with the given lookup key == a given identifier.
-
.inherited(subclass) ⇒ Object
Builtin ruby callback, whenver a subclass is created.
-
.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.
-
.make(**args) ⇒ APIObject
Make a ruby instance of a not-yet-existing APIObject.
-
.map_all_ids_to(other_key, refresh = false, api: JSS.api) ⇒ 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 JSS::APIObject.all.
-
.new(**args) ⇒ Object
Disallow direct use of ruby's .new class method for creating instances.
-
.post_raw(xml, api: JSS.api) ⇒ REXML::Document
POST some raw XML to the API for a given id in this subclass.
-
.put_raw(id, xml, api: JSS.api) ⇒ REXML::Document
PUT some raw XML to the API for a given id in this subclass.
-
.real_lookup_key(key) ⇒ Symbol
get the real lookup key frm a given alias.
-
.valid_id(identifier, refresh = false, api: JSS.api) ⇒ Integer?
Return the id of the object of this subclass with the given identifier.
-
.validate_not_metaclass(klass) ⇒ Object
Can't use APIObject directly.
-
.xml_list(array, content = :name) ⇒ REXML::Element
Convert an Array of Hashes of API object data to a REXML element.
Instance Method Summary collapse
-
#add_object_history_entry(user: nil, notes: nil, details: nil) ⇒ void
Make an entry in this object's Object History.
-
#categorizable? ⇒ Boolean
See Categorizable.
-
#creatable? ⇒ Boolean
See Creatable.
-
#criterable? ⇒ Boolean
See Criteriable.
-
#delete ⇒ void
Delete this item from the JSS.
-
#extendable? ⇒ Boolean
See extendable.
-
#initialize(**args) ⇒ APIObject
constructor
The args hash must include :id, :name, or :data.
-
#locatable? ⇒ Boolean
See Locatable.
-
#matchable? ⇒ Boolean
See Matchable.
-
#object_history ⇒ Array<Hash>
the object history for this object, an array of hashes one per history entry, in order of creation.
-
#ppx ⇒ void
Print the rest_xml value of the object to stdout, with indentation.
-
#pretty_print_instance_variables ⇒ Array
Remove the init_data and api object from the instance_variables used to create pretty-print (pp) output.
-
#purchasable? ⇒ Boolean
See Purchasable.
-
#save ⇒ Integer
Either Create or Update this object in the JSS.
-
#scopable? ⇒ Boolean
See Scopable.
-
#self_servable? ⇒ Boolean
See SelfServable.
-
#sitable? ⇒ Boolean
See Sitable.
-
#to_s ⇒ String
A meaningful string representation of this object.
-
#updatable? ⇒ Boolean
See Updatable.
-
#uploadable? ⇒ Boolean
See Uploadable.
-
#vppable? ⇒ Boolean
See VPPable.
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 JSS::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 JSS::APIConnection query (a Hash of valid object data)
Some subclasses can accept other options, by pasing their keys in a final Array
1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 |
# File 'lib/jss/api_object.rb', line 1093 def initialize(**args) @api = args[:api] @api ||= JSS.api # 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
#api ⇒ JSS::APIConnection (readonly)
Returns the API connection thru which we deal with this object.
1049 1050 1051 |
# File 'lib/jss/api_object.rb', line 1049 def api @api end |
#id ⇒ Integer (readonly)
Returns the JSS id number.
1056 1057 1058 |
# File 'lib/jss/api_object.rb', line 1056 def id @id end |
#in_jss ⇒ Boolean (readonly) Also known as: in_jss?
Returns is it in the JSS?.
1062 1063 1064 |
# File 'lib/jss/api_object.rb', line 1062 def in_jss @in_jss end |
#init_data ⇒ Object (readonly)
Returns the parsed JSON data retrieved from the API when this object was fetched.
1053 1054 1055 |
# File 'lib/jss/api_object.rb', line 1053 def init_data @init_data end |
#name ⇒ String (readonly)
Returns the name.
1059 1060 1061 |
# File 'lib/jss/api_object.rb', line 1059 def name @name end |
#rest_rsrc ⇒ String (readonly)
Returns the Rest resource for API access (the part after “JSSResource/” ).
1065 1066 1067 |
# File 'lib/jss/api_object.rb', line 1065 def rest_rsrc @rest_rsrc end |
Class Method Details
.all(refresh = false, api: JSS.api) ⇒ 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 JSS::APIObject, and is the parsed JSON output of an API query for the resource defined in the subclass's RSRC_BASE
e.g. for JSS::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. JSS::Computer.all_managed
– Caching
The results of the first call to .all for each subclass is cached in the .object_list_cache of the given JSS::APIConnection 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 api: named parameter.
450 451 452 453 454 455 456 457 458 459 |
# File 'lib/jss/api_object.rb', line 450 def self.all(refresh = false, api: JSS.api) (self) cache = api.object_list_cache cache_key = self::RSRC_LIST_KEY api.flushcache(cache_key) if refresh return cache[cache_key] if cache[cache_key] cache[cache_key] = api.get_rsrc(self::RSRC_BASE)[cache_key] end |
.all_objects(refresh = false, api: JSS.api) ⇒ Array<APIObject>
Return an Array of JSS::APIObject subclass instances e.g when called on JSS::Package, return a hash of JSS::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.
549 550 551 552 553 554 555 556 557 558 559 |
# File 'lib/jss/api_object.rb', line 549 def self.all_objects(refresh = false, api: JSS.api) objects_cache_key ||= "#{self::RSRC_LIST_KEY}_objects".to_sym api_cache = api.object_list_cache api_cache[objects_cache_key] = nil if refresh return api_cache[objects_cache_key] if api_cache[objects_cache_key] all = all(refresh, api: api) api_cache[objects_cache_key] = all.map do |o| fetch id: o[:id], api: api, refresh: false end end |
.delete(victims, refresh = true, api: JSS.api) ⇒ 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.
1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 |
# File 'lib/jss/api_object.rb', line 1009 def self.delete(victims, refresh = true, api: JSS.api) (self) raise JSS::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, api: api victims.each do |vid| if current_ids.include? vid api.delete_rsrc "#{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 api.flushcache self::RSRC_LIST_KEY # all :refresh, api: api skipped end |
.duplicate_names(refresh = false, api: JSS.api) ⇒ Hash {String => Integer}
Returns name => number of occurances.
463 464 465 466 467 468 469 470 471 472 473 474 475 476 |
# File 'lib/jss/api_object.rb', line 463 def self.duplicate_names(refresh = false, api: JSS.api) return {} unless defined? self::NON_UNIQUE_NAMES dups = {} all(refresh, api: api).each do |obj| if dups[obj[:name]] dups[obj[:name]] += 1 else dups[obj[:name]] = 1 end # if end # all(refresh, api: api).each dups.delete_if { |k,v| v == 1 } dups end |
.exist?(identifier, refresh = false, api: JSS.api) ⇒ 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
668 669 670 |
# File 'lib/jss/api_object.rb', line 668 def self.exist?(identifier, refresh = false, api: JSS.api) !valid_id(identifier, refresh, api: api).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
796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 |
# File 'lib/jss/api_object.rb', line 796 def self.fetch(searchterm = nil, **args) (self) # which connection? api = args.delete :api api ||= JSS.api # refresh the .all list if needed if args.delete(:refresh) || searchterm == :random all(:refresh, api: api) just_refreshed = true else just_refreshed = false end # a random object? if searchterm == :random rnd_thing = all.sample raise JSS::NoSuchItemError, "No #{self::RSRC_LIST_KEY} found" unless rnd_thing return new id: rnd_thing[:id], api: api 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) err_detail = "where #{fetch_key} = #{fetch_val}" # 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, api: api 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, api: api 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, api: api fetch_rsrc = id ? "#{self::RSRC_BASE}/id/#{id}" : nil err_detail = "matching #{searchterm}" else raise ArgumentError, 'Missing searchterm or fetch key' end new fetch_rsrc: fetch_rsrc, api: api 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.
358 359 360 361 |
# File 'lib/jss/api_object.rb', line 358 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
743 744 745 746 747 748 749 750 751 752 |
# File 'lib/jss/api_object.rb', line 743 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: JSS.api) ⇒ 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 JSS::APIConnection#get_rsrc that automatically fills in the RSRC::BASE value for you.
887 888 889 890 891 892 893 894 |
# File 'lib/jss/api_object.rb', line 887 def self.get_raw(id, format: :json, as_string: false, api: JSS.api) (self) rsrc = "#{self::RSRC_BASE}/id/#{id}" data = api.get_rsrc 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: JSS.api) ⇒ 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 JSS::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
636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 |
# File 'lib/jss/api_object.rb', line 636 def self.id_for_identifier(key, val, refresh = false, api: JSS.api) # refresh if needed all(refresh, api: api) 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.include?(val) ? val : nil if key == :id mapped_ids = map_all_ids_to key, api: api matches = mapped_ids.select { |_id, map_val| val.casecmp? map_val } raise JSS::AmbiguousError, "Key #{key}: value '#{val}' is not unique for #{self}" if matches.size > 1 return nil if matches.size.zero? matches.keys.first end |
.inherited(subclass) ⇒ Object
Builtin ruby callback, whenver a subclass is created.
Just store the subclass name, at the end of all the requires, we'll call define_identifier_list_methods on everything we stored here.
159 160 161 162 |
# File 'lib/jss.rb', line 159 def self.inherited(subclass) @subclasses ||= [] @subclasses << subclass 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]
343 344 345 346 |
# File 'lib/jss/api_object.rb', line 343 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) ⇒ 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 APIObject#create
970 971 972 973 974 975 976 977 978 979 980 |
# File 'lib/jss/api_object.rb', line 970 def self.make(**args) (self) unless constants.include?(:CREATABLE) raise JSS::UnsupportedError, "Creating #{self.class::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[:api] ||= JSS.api args[:id] = :new new args end |
.map_all_ids_to(other_key, refresh = false, api: JSS.api) ⇒ 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 JSS::APIObject.all.
If the other key doesn't exist in the API summary data from .all (eg :udid for JSS::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.
519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 |
# File 'lib/jss/api_object.rb', line 519 def self.map_all_ids_to(other_key, refresh = false, api: JSS.api) # 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 = api.object_list_cache cache[cache_key] = nil if refresh return cache[cache_key] if cache[cache_key] map = {} all(refresh, api: api).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
984 985 986 987 988 989 990 991 992 993 |
# File 'lib/jss/api_object.rb', line 984 def self.new(**args) (self) calling_method = caller_locations(1..1).first.label unless OK_INSTANTIATORS.include? calling_method raise JSS::UnsupportedError, 'Use .fetch or .make to instantiate APIObject classes' end super end |
.post_raw(xml, api: JSS.api) ⇒ 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 JSS::APIConnection#post_rsrc that automatically fills in the RSRC::BASE value for you.
944 945 946 947 948 |
# File 'lib/jss/api_object.rb', line 944 def self.post_raw( xml, api: JSS.api) (self) rsrc = "#{self::RSRC_BASE}/id/-1" REXML::Document.new(api.post_rsrc rsrc, xml.to_s) end |
.put_raw(id, xml, api: JSS.api) ⇒ 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 JSS::APIConnection#put_rsrc that automatically fills in the RSRC::BASE value for you.
918 919 920 921 922 |
# File 'lib/jss/api_object.rb', line 918 def self.put_raw(id, xml, api: JSS.api) (self) rsrc = "#{self::RSRC_BASE}/id/#{id}" REXML::Document.new(api.put_rsrc rsrc, xml.to_s) end |
.real_lookup_key(key) ⇒ Symbol
get the real lookup key frm a given alias
391 392 393 394 395 396 |
# File 'lib/jss/api_object.rb', line 391 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, refresh = false, api: JSS.api) ⇒ 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.
one of the available lookup_keys
584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 |
# File 'lib/jss/api_object.rb', line 584 def self.valid_id(identifier, refresh = false, api: JSS.api) # refresh if needed all(refresh, api: api) if refresh # it its a valid id, return it return identifier if all_ids(api: api).include? identifier keys_to_check = lookup_keys(no_aliases: true) keys_to_check.delete :id # we've already checked :id keys_to_check.each do |key| mapped_ids = map_all_ids_to key, api: api 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.
1040 1041 1042 |
# File 'lib/jss/api_object.rb', line 1040 def self.(klass) raise JSS::UnsupportedError, 'JSS::APIObject is a metaclass. Do not use it directly' if klass == JSS::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.
714 715 716 |
# File 'lib/jss/api_object.rb', line 714 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
#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 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.
1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 |
# File 'lib/jss/api_object.rb', line 1281 def add_object_history_entry(user: nil, notes: nil, details: nil) validate_object_history_available raise JSS::MissingDataError, 'A user: must be provided to make the entry' unless user raise JSS::MissingDataError, 'Either 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})" JSS::DB_CNX.db.query q end |
#categorizable? ⇒ Boolean
Returns See Categorizable.
1151 1152 1153 |
# File 'lib/jss/api_object.rb', line 1151 def categorizable? defined? self.class::CATEGORIZABLE end |
#creatable? ⇒ Boolean
Returns See Creatable.
1141 1142 1143 |
# File 'lib/jss/api_object.rb', line 1141 def creatable? defined? self.class::CREATABLE end |
#criterable? ⇒ Boolean
Returns See Criteriable.
1166 1167 1168 |
# File 'lib/jss/api_object.rb', line 1166 def criterable? defined? self.class::CRITERIABLE end |
#delete ⇒ void
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..
1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 |
# File 'lib/jss/api_object.rb', line 1216 def delete return unless @in_jss @api.delete_rsrc @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 @api.flushcache self.class::RSRC_LIST_KEY # self.class.all :refresh, api: @api :deleted end |
#extendable? ⇒ Boolean
Returns See extendable.
1176 1177 1178 |
# File 'lib/jss/api_object.rb', line 1176 def extendable? defined? self.class::EXTENDABLE end |
#locatable? ⇒ Boolean
Returns See Locatable.
1186 1187 1188 |
# File 'lib/jss/api_object.rb', line 1186 def locatable? defined? self.class::LOCATABLE end |
#matchable? ⇒ Boolean
Returns See Matchable.
1181 1182 1183 |
# File 'lib/jss/api_object.rb', line 1181 def matchable? defined? self.class::MATCHABLE end |
#object_history ⇒ Array<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
1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 |
# File 'lib/jss/api_object.rb', line 1318 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 = JSS::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 |
#ppx ⇒ void
This method returns an undefined value.
Print the rest_xml value of the object to stdout, with indentation. Useful for debugging.
1345 1346 1347 1348 1349 1350 1351 1352 |
# File 'lib/jss/api_object.rb', line 1345 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_variables ⇒ Array
Remove the init_data and api object from the instance_variables used to create pretty-print (pp) output.
1248 1249 1250 1251 1252 1253 1254 |
# File 'lib/jss/api_object.rb', line 1248 def pretty_print_instance_variables vars = instance_variables.sort vars.delete :@api vars.delete :@init_data vars.delete :@main_subset vars end |
#purchasable? ⇒ Boolean
Returns See Purchasable.
1191 1192 1193 |
# File 'lib/jss/api_object.rb', line 1191 def purchasable? defined? self.class::PURCHASABLE end |
#save ⇒ Integer
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.
1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 |
# File 'lib/jss/api_object.rb', line 1122 def save if @in_jss raise JSS::UnsupportedError, 'Updating this object in the JSS is currently not supported by ruby-jss' unless updatable? update else raise JSS::UnsupportedError, 'Creating this object in the JSS is currently not supported by ruby-jss' unless creatable? create end end |
#scopable? ⇒ Boolean
Returns See Scopable.
1196 1197 1198 |
# File 'lib/jss/api_object.rb', line 1196 def scopable? defined? self.class::SCOPABLE end |
#self_servable? ⇒ Boolean
Returns See SelfServable.
1161 1162 1163 |
# File 'lib/jss/api_object.rb', line 1161 def self_servable? defined? self.class::SELF_SERVABLE end |
#sitable? ⇒ Boolean
Returns See Sitable.
1171 1172 1173 |
# File 'lib/jss/api_object.rb', line 1171 def sitable? defined? self.class::SITABLE end |
#to_s ⇒ String
A meaningful string representation of this object
1238 1239 1240 |
# File 'lib/jss/api_object.rb', line 1238 def to_s "#{self.class}, name: #{@name}, id: #{@id}" end |
#updatable? ⇒ Boolean
Returns See Updatable.
1146 1147 1148 |
# File 'lib/jss/api_object.rb', line 1146 def updatable? defined? self.class::UPDATABLE end |
#uploadable? ⇒ Boolean
Returns See Uploadable.
1201 1202 1203 |
# File 'lib/jss/api_object.rb', line 1201 def uploadable? defined? self.class::UPLOADABLE end |
#vppable? ⇒ Boolean
Returns See VPPable.
1156 1157 1158 |
# File 'lib/jss/api_object.rb', line 1156 def vppable? defined? self.class::VPPABLE end |