Class: JSS::APIObject
- Inherits:
-
Object
- Object
- JSS::APIObject
- Defined in:
- lib/jss.rb,
lib/jss/api_object.rb
Overview
This class is the parent to all JSS API objects. It provides standard methods and structures that apply to all API resouces.
See the README.md file for general info about using subclasses of JSS::APIObject
Subclassing
Constructor
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. @id, @name, @rest_rsrc, and @in_jss are handled here.
If a subclass can be looked up by some key other than :name or :id, the subclass must pass the keys as an Array in the second argument when calling super from #initialize. See Computer#initialize for an example of how to implement this feature.
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 (aside from :name) in the args before or after calling super.
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 “casper.mycompany.com:8443/JSSResource/computergroups/id/12”
RSRC_LIST_KEY = [Symbol] The Hash key for the JSON list output of all objects of this class in the JSS.
e.g. the JSON output of resource “JSSResource/computergroups” is a hash with one item (an Array of computergroups). That item’s key is the Symbol :computer_groups
RSRC_OBJECT_KEY = [Symbol] The Hash key used for individual JSON object output.
It’s also used in various error messages
e.g. the JSON output of the resource “JSSResource/computergroups/id/436” is a hash with one item (another hash with details of one computergroup). That item’s key is the Symbol :computer_group
VALID_DATA_KEYS = [Array<Symbol>] The Hash keys used to verify validity of :data
When instantiating a subclass using :data => somehash, some minimal checks are performed to ensure the data is valid for the subclass
The Symbols in this Array are compared to the keys of the hash provided. If any of these don’t exist in the hash’s keys, then the :data is not valid and an exception is raised.
The keys :id and :name must always exist in the hash. If only :id and :name are valid, VALID_DATA_KEYS should be an empty array.
e.g. for a department, only :id and :name are valid, so VALID_DATA_KEYS is an empty Array ([]) but for a computer group, the keys :computers and :is_smart must be present as well. so VALID_DATA_KEYS will be [:computers, :is_smart]
NOTE Some API objects have data broken into subsections, in which case the VALID_DATA_KEYS are expected in the section :general.
Direct Known Subclasses
Account, AdvancedSearch, Building, Category, Computer, ComputerInvitation, Department, DistributionPoint, ExtensionAttribute, Group, LDAPServer, MobileDevice, NetBootServer, NetworkSegment, OSXConfigurationProfile, Package, Peripheral, PeripheralType, Policy, RemovableMacAddress, RestrictedSoftware, Script, Site, SoftwareUpdateServer, User
Constant Summary collapse
- REQUIRED_DATA_KEYS =
These Symbols are added to VALID_DATA_KEYS for performing the :data validity test described above.
[:id, :name].freeze
- DEFAULT_LOOKUP_KEYS =
By default, these keys are available for object lookups Others can be added by subclasses using an array of them as the second argument to super(initialize) The keys must be Symbols that match the keyname in the resource url. e.g. :serialnumber for JSSResource/computers/serialnumber/xxxxx
[:id, :name].freeze
- @@all_items =
This Hash holds the most recent API query for a list of all items in any subclass, keyed by the subclass’s RSRC_LIST_KEY. See the self.all class method.
When the .all method is called without an argument, and this hash has a matching value, the value is returned, rather than requerying the API. The first time a class calls .all, or whnever refresh is not false, the API is queried and the value in this hash is updated.
{}
Instance Attribute Summary collapse
-
#id ⇒ Integer
readonly
The JSS id number.
-
#in_jss ⇒ Boolean
(also: #in_jss?)
readonly
Is it in the JSS?.
-
#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) ⇒ Array<Hash{:name=>String, :id=> Integer}>
Return an Array of Hashes for all objects of this subclass in the JSS.
-
.all_ids(refresh = false) ⇒ Array<Integer>
Returns an Array of the JSS id numbers of all the members of the subclass.
-
.all_names(refresh = false) ⇒ Array<String>
Returns an Array of the JSS names of all the members of the subclass.
-
.all_objects(refresh = false) ⇒ Hash{Integer => Object}
Return an Array of JSS::APIObject subclass instances e.g when called on JSS::Package, return all JSS::Package objects in the JSS.
-
.exist?(identfier, refresh = false) ⇒ Boolean
Return true or false if an object of this subclass with the given name or id exists on the server.
-
.get_name(a_thing) ⇒ String
Some API objects contain references to other API objects.
-
.map_all_ids_to(other_key, refresh = false) ⇒ 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.
-
.valid_id(identfier, refresh = false) ⇒ Integer?
Return an id or nil if an object of this subclass with the given name or id exists on the server.
-
.xml_list(array, content = :name) ⇒ REXML::Element
Convert an Array of Hashes of API object data to a REXML element.
Instance Method Summary collapse
-
#delete ⇒ void
Delete this item from the JSS.
-
#initialize(args = {}, other_lookup_keys = []) ⇒ APIObject
constructor
The args hash must include :id, :name, or :data.
-
#save ⇒ Integer
Either Create or Update this object in the JSS.
-
#to_s ⇒ String
A meaningful string representation of this object.
Constructor Details
#initialize(args = {}, other_lookup_keys = []) ⇒ 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
424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 |
# File 'lib/jss/api_object.rb', line 424 def initialize(args = {}, other_lookup_keys = []) ####### Previously looked-up JSON data if args[:data] @init_data = args[:data] ### Does this data come in subsets? @got_subsets = @init_data[:general].is_a?(Hash) ### data must include all they keys in REQUIRED_DATA_KEYS + VALID_DATA_KEYS ### in either the main hash keys or the :general sub-hash, if it exists hash_to_check = @got_subsets ? @init_data[:general] : @init_data combined_valid_keys = self.class::REQUIRED_DATA_KEYS + self.class::VALID_DATA_KEYS keys_ok = (hash_to_check.keys & combined_valid_keys).count == combined_valid_keys.count unless keys_ok raise( JSS::InvalidDataError, ":data is not valid JSON for a #{self.class::RSRC_OBJECT_KEY} from the API. It needs at least the keys :#{combined_valid_keys.join ', :'}" ) end ### and the id must be in the jss raise NoSuchItemError, "No #{self.class::RSRC_OBJECT_KEY} with JSS id: #{@init_data[:id]}" unless \ self.class.all_ids.include? hash_to_check[:id] ###### Make a new one in the JSS, but only if we've included the Creatable module elsif args[:id] == :new raise JSS::UnsupportedError, "Creating #{self.class::RSRC_LIST_KEY} isn't yet supported. Please use other Casper workflows." \ unless defined? self.class::CREATABLE raise JSS::MissingDataError, "You must provide a :name for a new #{self.class::RSRC_OBJECT_KEY}." \ unless args[:name] raise JSS::AlreadyExistsError, "A #{self.class::RSRC_OBJECT_KEY} already exists with the name '#{args[:name]}'" \ if self.class.all_names.include? args[:name] ### NOTE: subclasses may want to pre-populate more keys in @init_data when :id == :new ### then parse them into attributes later. @name = args[:name] @init_data = { name: args[:name] } @in_jss = false @rest_rsrc = "#{self.class::RSRC_BASE}/name/#{CGI.escape @name}" @need_to_update = true return ###### Look up the data via the API else ### what lookup key are we using? combined_lookup_keys = self.class::DEFAULT_LOOKUP_KEYS + other_lookup_keys lookup_key = (combined_lookup_keys & args.keys)[0] raise JSS::MissingDataError, "Args must include :#{combined_lookup_keys.join(', :')}, or :data" unless lookup_key rsrc = "#{self.class::RSRC_BASE}/#{lookup_key}/#{args[lookup_key]}" begin @init_data = JSS::API.get_rsrc(rsrc)[self.class::RSRC_OBJECT_KEY] ### If we're looking up by id or name and we're here, ### then we have it regardless of which subset it's in ### otherwise, first assume they are in the init hash @id = lookup_key == :id ? args[lookup_key] : @init_data[:id] @name = lookup_key == :name ? args[lookup_key] : @init_data[:name] rescue RestClient::ResourceNotFound raise NoSuchItemError, "No #{self.class::RSRC_OBJECT_KEY} found matching: #{args[:name] ? args[:name] : args[:id]}" end end ## end arg parsing # Find the "main" subset which contains :id and :name # # If they aren't at the top-level of the init hash they are in a subset hash, # usually :general, but sometimes someething else, # like ldap servers, which have them in :connection # Whereever both :id and :name are, that's the main subset @init_data.keys.each do |subset| @main_subset = @init_data[subset] if @init_data[subset].is_a?(Hash) && @init_data[subset][:id] && @init_data[subset][:name] break if @main_subset end @main_subset ||= @init_data @id ||= @main_subset[:id] @name ||= @main_subset[:name] # many things have a :site if @main_subset[:site] @site = JSS::APIObject.get_name(@main_subset[:site]) end # many things have a :category if @main_subset[:category] @category = JSS::APIObject.get_name(@main_subset[:category]) end # set empty strings to nil @init_data.jss_nillify! '', :recurse @in_jss = true @rest_rsrc = "#{self.class::RSRC_BASE}/id/#{@id}" @need_to_update = false end |
Instance Attribute Details
#id ⇒ Integer (readonly)
391 392 393 |
# File 'lib/jss/api_object.rb', line 391 def id @id end |
#in_jss ⇒ Boolean (readonly) Also known as: in_jss?
397 398 399 |
# File 'lib/jss/api_object.rb', line 397 def in_jss @in_jss end |
#name ⇒ String (readonly)
394 395 396 |
# File 'lib/jss/api_object.rb', line 394 def name @name end |
#rest_rsrc ⇒ String (readonly)
400 401 402 |
# File 'lib/jss/api_object.rb', line 400 def rest_rsrc @rest_rsrc end |
Class Method Details
.all(refresh = false) ⇒ 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 data as mapped Arrays.
Some API classes provide other data in each Hash, e.g. :udid (for computers and mobile devices) or :is_smart (for groups).
Subclasses implementing those API classes should provide .all_xxx class methods for accessing those other values as mapped Arrays, e.g. JSS::Computer.all_udids
The results of the first query for each subclass is stored in @@all_items and returned at every future call, so as to not requery the server every time.
To force requerying to get updated data, provided a non-false 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.
161 162 163 164 165 166 |
# File 'lib/jss/api_object.rb', line 161 def self.all(refresh = false) raise JSS::UnsupportedError, '.all can only be called on subclasses of JSS::APIObject' if self == JSS::APIObject @@all_items[self::RSRC_LIST_KEY] = nil if refresh return @@all_items[self::RSRC_LIST_KEY] if @@all_items[self::RSRC_LIST_KEY] @@all_items[self::RSRC_LIST_KEY] = JSS::API.get_rsrc(self::RSRC_BASE)[self::RSRC_LIST_KEY] end |
.all_ids(refresh = false) ⇒ Array<Integer>
Returns an Array of the JSS id numbers of all the members of the subclass.
e.g. When called from subclass JSS::Computer, returns the id’s of all computers in the JSS
178 179 180 |
# File 'lib/jss/api_object.rb', line 178 def self.all_ids(refresh = false) all(refresh).map { |i| i[:id] } end |
.all_names(refresh = false) ⇒ Array<String>
Returns an Array of the JSS names of all the members of the subclass.
e.g. When called from subclass JSS::Computer, returns the names of all computers in the JSS
192 193 194 |
# File 'lib/jss/api_object.rb', line 192 def self.all_names(refresh = false) all(refresh).map { |i| i[:name] } end |
.all_objects(refresh = false) ⇒ Hash{Integer => Object}
Return an Array of JSS::APIObject subclass instances e.g when called on JSS::Package, return all JSS::Package objects in the JSS.
NOTE: This may be slow as it has to look up each object individually! use it wisely.
238 239 240 241 242 243 |
# File 'lib/jss/api_object.rb', line 238 def self.all_objects(refresh = false) objects_key = "#{self::RSRC_LIST_KEY}_objects".to_sym @@all_items[objects_key] = nil if refresh return @@all_items[objects_key] if @@all_items[objects_key] @@all_items[objects_key] = all(refresh = false).map { |o| new id: o[:id] } end |
.exist?(identfier, refresh = false) ⇒ Boolean
Return true or false if an object of this subclass with the given name or id exists on the server
254 255 256 257 258 259 260 261 262 263 |
# File 'lib/jss/api_object.rb', line 254 def self.exist?(identfier, refresh = false) case identfier when Integer all_ids(refresh).include? identfier when String all_names(refresh).include? identfier else raise ArgumentError, 'Identifier must be a name (String) or id (Integer)' end 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
360 361 362 363 364 365 366 367 368 369 |
# File 'lib/jss/api_object.rb', line 360 def self.get_name(a_thing) case a_thing when String a_thing when Hash a_thing[:name] when nil nil end end |
.map_all_ids_to(other_key, refresh = false) ⇒ 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 data, (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.
222 223 224 225 226 |
# File 'lib/jss/api_object.rb', line 222 def self.map_all_ids_to(other_key, refresh = false) h = {} all(refresh).each { |i| h[i[:id]] = i[other_key] } h end |
.valid_id(identfier, refresh = false) ⇒ Integer?
Return an id or nil if an object of this subclass with the given name or id exists on the server
Subclasses may want to override this method to support more identifiers than name and id.
277 278 279 280 281 282 283 284 285 286 |
# File 'lib/jss/api_object.rb', line 277 def self.valid_id(identfier, refresh = false) case identfier when Integer return identfier if all_ids(refresh).include? identfier when String return map_all_ids_to(:name).invert[identfier] if all_names(refresh).include? identfier else raise ArgumentError, 'Identifier must be a name (String) or id (Integer)' end 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.
330 331 332 |
# File 'lib/jss/api_object.rb', line 330 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
#delete ⇒ void
This method returns an undefined value.
Delete this item from the JSS.
Subclasses may want to redefine this method, first calling super, then setting other attributes to nil, false, empty, etc..
558 559 560 561 562 563 564 565 566 |
# File 'lib/jss/api_object.rb', line 558 def delete return nil unless @in_jss JSS::API.delete_rsrc @rest_rsrc @rest_rsrc = "#{self.class::RSRC_BASE}/name/#{CGI.escape @name}" @id = nil @in_jss = false @need_to_update = false :deleted 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.
538 539 540 541 542 543 544 545 546 547 548 |
# File 'lib/jss/api_object.rb', line 538 def save if @in_jss raise JSS::UnsupportedError, 'Updating this object in the JSS is currently not supported' \ unless defined? self.class::UPDATABLE update else raise JSS::UnsupportedError, 'Creating this object in the JSS is currently not supported' \ unless defined? self.class::CREATABLE create end end |
#to_s ⇒ String
A meaningful string representation of this object
573 574 575 |
# File 'lib/jss/api_object.rb', line 573 def to_s "#{self.class}, name: #{@name}, id: #{@id}" end |