# Copyright 2020 Pixar # # Licensed under the Apache License, Version 2.0 (the "Apache License") # with the following modification; you may not use this file except in # compliance with the Apache License and the following modification to it: # Section 6. Trademarks. is deleted and replaced with: # # 6. Trademarks. This License does not grant permission to use the trade # names, trademarks, service marks, or product names of the Licensor # and its affiliates, except as required to comply with Section 4(c) of # the License and to reproduce the content of the NOTICE file. # # You may obtain a copy of the Apache License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the Apache License with the above modification is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the Apache License for the specific # language governing permissions and limitations under the Apache License. # # # The module module Jamf # Classes ##################################### # # Jamf::JSONObject # # In JSON & Javascript, an 'object' is a data structure equivalent to a # Hash in Ruby. Much of the JSON data exchaged with the API is formatted as # these JSON objects. # # Jamf::JSONObject is a meta class that provides a way to convert those JSON # 'objects' into not just Hashes (that's done by the Jamf::Connection) but # into full-fledged ruby Classes. Once implemented in ruby-jss, all JSON # objects (Hashes) used anywhere in the Jamf Pro API have a matching Class in # ruby-jss which is a subclass of Jamf::JSONObject # # The Jamf::JSONObject class is abstract, and cannot be instantiated or used # directly. It merely provides the common functionality needed for dealing # with all JSON objects in the API. # # # ## Subclassing # # When implementing a JSON object in the API as a class in ruby-jss, # you will make a subclass of either Jamf::JSONObject, Jamf::SingletonResource # or Jamf::CollectionResource. # # Here's the relationship between these abstract classes: # # Jamf::JSONObject # (abstract) # | # | # ----------------------- # | | # Jamf::Resource | # (abstract) | # | | # | | # | Jamf::Computer::Reference # | Jamf::Location # | Jamf::ChangeLog::Entry # | (more non-resource JSON object classes) # | # |---------------------------------------- # | | # | | # Jamf::SingletonResource Jamf::CollectionResource # (abstract) (abstract) # | | # | | # Jamf::Settings::ReEnrollment Jamf::Computer # Jamf::Settings::SelfService Jamf::Building # Jamf::SystemInfo Jamf::PatchPolicy # (more singleton resource classes) (more collection resource classes) # # # Direct descendents of Jamf::JSONObject are arbitrary JSON objects that # appear inside other objects, e.g. the Location data for a computer, # or a reference to a building. # # {Jamf::Resource} classes represent direct resources of the API, i.e. items # accessible with a URL. The ability to interact with those URLs is defined in # the metaclass Jamf::Resource, and all resources must define a RSRC_VERSION # and a RSRC_PATH. See {Jamf::Resource} for more info. # # There are two kinds of resources in the API: # # {Jamf::SingletonResource} classes represent objects in the API that have # only one instance, such as various settings, or server-wide state. These # objects cannot be created or deleted, only fetched and updated. # # {Jamf::CollectionResource} classes represent collections of objects in the # API. These resources can list all of their members, and individual members # can be retrieved, updated, created and deleted. # # Subclasses need to meet the requirements for all of their ancestors, # so once you decide which one you're subclassing, be sure to read the docs # for each one. E.g. to implement Jamf::Package, it will be a # {Jamf::CollectionResource}, which is a {Jamf::Resource}, which is a # {Jamf::JSONObject}, and the requirements for all must be met. # # The remainder of this page documents the requirements and details of # Jamf::JSONObject. # # # NOTES: # # - subclasses may define more methods, include mix-ins, and if # needed can override methods defined in metaclasses. Please read the # docs before overriding. # # - Throughout the documentation 'parsed JSON object' means the result of running # a raw JSON string thru `JSON.parse raw_json, symbolize_names: true`. This # is performed in the {Jamf::Connection} methods which interact with the API: # {Jamf::Connection#get}, {Jamf::Connection#post}, {Jamf::Connection#put} # {Jamf::Connection#patch} and {Jamf::Connection#delete}. # # - Related to the above, the {Jamf::Connection} methods # {Jamf::Connection#post} and {Jamf::Connection#put} call `#to_json` on the # data passed to them, before sending it to the API. Subclasses and # application code should never call #to_json anywhere. The data passed # to put and post should be the output of `#to_jamf` on a Jamf::JSONObject, # which is handled by the the #update and #create methods as needed. # # # ### # # ### Required Constant: OBJECT_MODEL & call to parse_object_model # # Each descendent of JSONObject must define the constant OBJECT_MODEL, which # is a Hash of Hashes that collectively define the top-level keys of the JSON # object as attributes of the matching ruby class. # # Immediately after the definition of OBJECT_MODEL, the subclass *MUST* call # `self.parse_object_model` to convert the model into actual ruby attributes # with getters and setters. # # The OBJECT_MODEL Hash directly implements the matching JSON object model # defined at https://developer.jamf.com/apis/jamf-pro-api/index and is used # to automatically create attributes & accessor methods mirroring those # in the API. # # The keys of the main hash are the symbolized names of the attributes as they # come from the JSON fetched from the API. # # _ATTRIBUTE NAMES:_ # # The attribute names in the Jamf Pro API JSON data are in 'lowerCamelCase' # (https://en.wikipedia.org/wiki/Camel_case), and are used that way # throughout the Jamf module in order to maintain consistency with the API # itself. This differs from the ruby standard of using 'snake_case' # (https://en.wikipedia.org/wiki/Snake_case) for attributes, # methods, & local variables. I believe that maintaining consistency with the # API we are mirroring is more important (and simpler) than conforming with # ruby's community standards. I also believe that doing so is in-line with the # ruby community's larger philosophy. # # "There's more than one way to do it" - because context matters. # If that weren't true, I'd be writing Python. # # Each attribute key has a Hash of details defining how the attribute is # used in the class. Getters and setters are created from these details, and # they are used to parse incoming, and generate outgoing JSON data # # The possible keys of the details Hash for each attribute are: # # - class: # - identfier: # - required: # - readonly: # - multi: # - enum: # - validator: # - aliases: # - filter_key: # # For an example of an OBJECT_MODEL hash, see {Jamf::MobileDeviceDetails::OBJECT_MODEL} # # The details for each key's value are as follows. Note that omitting a # boolean key is the same as setting it to false. # # class: \[Symbol or Class] # ----------------- # This is the only required key for all attributes. # # --- # Symbol is one of :string, :integer, :float, :boolean, or :j_id # # The first four are the JSON data types that don't need parsing into ruby # beyond that done by `JSON.parse`. When processing an attribute with one of # these symbols as the `class:`, the JSON value is used as-is. # # The ':j_id' symbol means this value is an id used to reference an object in # a collection resource of the API - all such objects have an 'id' attribute # which is a String containing an Integer. # # These ids are used not only as the id attribute of the object itself, but # if an object contains references to one or more other objects, those # references are also ':j_id' values. # In setters and .create, :j_id values can take either an integer or an # integer-in-a-string, and are stored as integer-in-a-string/ # # When 'class:' is not a Symbol, it must be an actual class, such as # Jamf::Timestamp or Jamf::PurchasingData. # # Actual classes used this way _must_: # # - Have an #initialize method that takes two parameters and performs # validation on them: # # A first positional parameter, the value used to create the instance, # which accepts, at the very least, the Parsed JSON data for the attribute. # This can be a single value (e.g. a string for Jamf::Timestamp), or a Hash # (e.g. for Jamf::Location), or whatever. Other values are # allowed if your initialize method handles them properly. # # A keyword parameter `cnx:`. This can be ignored if not needed, but # #initialize must accept it. If used, it will contain a Jamf::Connection # object, either the one from which the first param came, or the one # to which we'll be validating or creating a new object # # - Define a #to_jamf method that returns a value that can be used # in the data sent back to the API. Subclasses of JSONObject already # have this requirement, and the value is a Hash. # # # Classes used in the class: value of an attribute definition are often # also subclasses of JSONObject (e.g. Jamf::Location) but do not have to be # as long as they conform to the standards above, e.g. Jamf::Timestamp. # # See also: [Data Validation](#data_validation) below. # # # identifier: \[Boolean or Symbol :primary] # ----------------- # Only applicable to descendents of Jamf::CollectionResource # # If true, this value must be unique among all members of the class in # the JAMF, and can be used to look up objects. # # If the symbol :primary, this is the primary identifier, used in API # resource paths for this particular object. Usually its the :id attribute, # but for some objects may be some other attribute, e.g. for config- # profiles, it would be a uuid. # # # required: \[Boolean] # ----------------- # If true, this attribute must be provided when creating a new local instance # and cannot be set to nil or empty # # # readonly: \[Boolean] # ----------------- # If true, no setter method(s) will be created, and the value is not # sent to the API with #create or #update # # # multi: \[Boolean] # ----------------- # When true, this value comes as a JSON array and its items are defined by # the 'class:' setting described above. The JSON array is used # to contstruct an attribute array of the correct kind of item. # # Example: # > When `class:` is Jamf::Computer::Reference the incoming JSON array # > of Hashes (computer references) will become an array of # > Jamf::Computer::Reference instances. # # The stored array is not directly accessible, the getter will return a # frozen duplicate of it. # # If not readonly, several setters are created: # # - a direct setter which takes an Array of 'class:', replacing the original # - a \_append method, appends a new value to the array, # aliased as `<<` # - a \_prepend method, prepends a new value to the array # - a \_insert method, inserts a new value to the array # at the given index # - a \_delete\_at method, deletes a value at the given index # # This protection of the underlying array is needed for two reasons: # # 1. so ruby-jss knows when changes are made and need to be saved # 2. so that validation can be performed on values added to the array. # # # enum: \[Constant -> Array ] # ----------------- # This is a constant defined somewhere in the Jamf module. The constant # must contain an Array of values, usually Strings. You may or may not choose # to define the array members as constants themselves. # # Example: # > Attribute `:type` has enum: Jamf::ExtentionAttribute::DATA_TYPES # > # > The constant Jamf::ExtentionAttribute::DATA_TYPES is defined thus: # > # > DATA_TYPE_STRING = 'STRING'.freeze # > DATA_TYPE_INTEGER = 'INTEGER'.freeze # > DATA_TYPE_DATE = 'DATE'.freeze # > # > DATA_TYPES = [ # > DATA_TYPE_STRING, # > DATA_TYPE_INTEGER, # > DATA_TYPE_DATE, # > ] # > # > When setting the type attribute via `#type = newval`, # > `Jamf::ExtentionAttribute::DATA_TYPES.include? newval` must be true # > # # Setters for attributes with an enum require that the new value is # a member of the array as seen above. When using such setters, If you defined # the array members as constants themselves, it is wise to use those rather # than a different but identical string, however either will work. # In other words, this: # # my_ea.dataType = Jamf::ExtentionAttribute::DATA_TYPE_INTEGER # # is preferred over: # # my_ea.dataType = 'INTEGER' # # since the second version creates a new string in memory, but the first uses # the one already stored in a constant. # # See also: [Data Validation](#data_validation) below. # # validator: \[Symbol] # ----------------- # (ignored if readonly: is true, or if enum: is set) # # The symbol is the name of a Jamf::Validators class method used in the # setter to validate new values for this attribute. It only is used when # class: is :string, :integer, :boolean, and :float # # If omitted, the setter will take any value passed to it, which is # generally unwise. # # When the class: is an actual class, the setter will instantiate a new one # with the value to be set, and validation is handled by the class itself. # # Example: # > If the `class:` for an attrib named ':releaseDate' is class: Jamf::Timestamp # > then the setter method will look like this: # > # > def releaseDate=(newval) # > newval = Jamf::Timestamp.new newval unless newval.is_a? Jamf::Timestamp # > # ^^^ This will validate newval # > return if newval == @releaseDate # > @releaseDate = newval # > @need_to_update = true # > end # # see also: [Data Validation](#data_validation) below. # # # aliases: \[Array of Symbols] # ----------------- # Other names for this attribute. If provided, getters, and setters will # be made for all aliases. Should be used very sparingly. # # Attributes of class :boolean automatically have a getter alias ending with a '?'. # # filter_key: \[Boolean] # ----------------- # For subclasses of CollectionResource, GETting the main endpoint will return # the entire collection. Some of these endpoints support RSQL filters to return # only those objects that match the filter. If this attribute can be used as # a field for filtering, set filter_key: to true, and filters will be used # where possible to optimize GET requests. # # Documenting your code # --------------------- # For documenting attributes with YARD, put this above each # attribute name key: # # ``` # # @!attribute # # @param [Class] # # @return [Class] # ``` # # If the value is readonly, remove the @param line, and add \[r], like this: # # ``` # # @!attribute [r]