module Jamf::Extendable

A mix-in module for handling extension attribute data for objects in the JSS.

This module provides standardized ways to deal with Extension Attribute data in objects that gather that data ({Jamf::Computer}s, {Jamf::MobileDevice}s, and {Jamf::User}s). For working with the Extension Attributes themselves, see {Jamf::ExtensionAttribute} and its subclasses.

API objects that have Extension Attribute data return it in an Array of Hashes, one for each defined ExtensionAttribute for the class; i.e. a {Jamf::Computer}‘s Array has one Hash for each {Jamf::ComputerExtensionAttribute} defined in the JSS.

The Hash keys are:

Classes including this module must define the constant EXT_ATTRIB_CLASS specifying which {Jamf::ExtensionAttribute} subclass defines the relevant extension attributes. For Example, {Jamf::Computer} sets this:

EXT_ATTRIB_CLASS = Jamf::ComputerExtensionAttribute

Parsing also populates @ext_attrs which is a Hash of name => value for each EA.

When updating or creating, those classes must add the REXML output of {#ext_attr_xml} to their rest_xml output.

Constants

EXTENDABLE

Constants

INVALID_DATE

Attributes

extension_attributes[R]

@return [Array<Hash>] The extension attribute values for the object

Public Instance Methods

ea_names() click to toggle source

@return [Array<String>] the names of all known EAs

   # File lib/jamf/api/classic/api_objects/extendable.rb
96 def ea_names
97   ea_types.keys
98 end
ea_types() click to toggle source

@return [Hash{String => String}] EA names => data type

(one of 'String', 'Number', or 'Date')
    # File lib/jamf/api/classic/api_objects/extendable.rb
102 def ea_types
103   return @ea_types if @ea_types
104 
105   @ea_types = {}
106   extension_attributes.each { |ea| @ea_types[ea[:name]] = ea[:type] }
107   @ea_types
108 end
ext_attr_xml() click to toggle source

@api private

TODO: make this (and all XML amending) method take the in-progress XML doc and add (or not) the EA xml to it. See how Sitable#add_site_to_xml works, as called from Computer.rest_xml

@return [REXML::Element] An <extension_attribute> element to be

included in the rest_xml of objects that mix-in this module.
    # File lib/jamf/api/classic/api_objects/extendable.rb
218 def ext_attr_xml
219   @changed_eas ||= []
220   eaxml = REXML::Element.new('extension_attributes')
221   @extension_attributes.each do |ea|
222     next unless @changed_eas.include? ea[:name]
223 
224     ea_el = eaxml.add_element('extension_attribute')
225     ea_el.add_element('name').text = ea[:name]
226 
227     if ea[:type] == 'Date'
228       begin
229         ea_el.add_element('value').text = ea[:value].to_jss_date
230       rescue
231         ea_el.add_element('value').text = ea[:value].to_s
232       end
233     else
234       ea_el.add_element('value').text = ea[:value].to_s
235     end # if
236   end # each do ea
237 
238   eaxml
239 end
ext_attrs() click to toggle source

An easier-to-use hash of EA name to EA value. This isn’t created until its needed, to speed up instantiation.

    # File lib/jamf/api/classic/api_objects/extendable.rb
113 def ext_attrs
114   return @ext_attrs if @ext_attrs
115 
116   @ext_attrs = {}
117   @extension_attributes.each do |ea|
118     @ext_attrs[ea[:name]] =
119       case ea[:type]
120 
121       when 'Date'
122         begin # if there's random non-date data, the parse will fail
123           Jamf.parse_time ea[:value]
124         rescue
125           INVALID_DATE
126         end
127 
128       when *Jamf::ExtensionAttribute::NUMERIC_TYPES
129         ea[:value].to_i unless ea[:value].to_s.empty?
130 
131       else # String
132         ea[:value]
133       end # case
134   end # each do ea
135 
136   @ext_attrs
137 end
parse_ext_attrs() click to toggle source

Populate @extension_attributes (the Array of Hashes that comes from the API) and @ext_attr_names, which is a Hash mapping the EA names to their values. This is called during initialization for all objects that mix in this module

@return [void]

   # File lib/jamf/api/classic/api_objects/extendable.rb
85 def parse_ext_attrs
86   @extension_attributes = @init_data[:extension_attributes]
87   @extension_attributes ||= []
88 
89   # remember changes as they happen so
90   # we only send changes back to the server.
91   @changed_eas = []
92 end
set_ext_attr(ea_name, value, validate_popup_choice: true, refresh: false) click to toggle source

Set the value of an extension attribute

The new value is validated based on the data type of the Ext. Attrib:

  • If the ext. attrib. is defined with a data type of Integer/Number, the value must be an Integer.

  • If defined with a data type of Date, the value will be parsed as a timestamp, and parsing may raise an exception. Dates can’t be blank.

  • If defined wth data type of String, ‘to_s` will be called on the value.

By default, the full EA definition object is fetched to see if the EA’s input type is ‘popup menu’, and if so, the new value must be one of the defined popup choices, or blank.

The EA definitions used for popup validation are cached, so we don’t have to reach out to the server every time. If you expect the definition to have changed since it was cached, provide a truthy value to the refresh: parameter

To bypass popup validation complepletely, provide a falsey value to the validate_popup_choice: parameter. WARNING: beware that your value is the correct type and format, or you might get errors when saving back to the API.

Note that while the Jamf Pro Web interface does not allow editing the values of Extension Attributes populated by Scripts or LDAP, the API does allow it. Bear in mind however that those values will be reset again at the next recon.

@param name the name of the extension attribute to set

@param value the new value for the extension

attribute for this user

@param validate_popup_choice validate the new value against the E.A. definition.

Defaults to true.

@param refresh Re-read the ext. attrib definition from the API,

for popup validation.

@return [void]

    # File lib/jamf/api/classic/api_objects/extendable.rb
182 def set_ext_attr(ea_name, value, validate_popup_choice: true, refresh: false)
183   raise ArgumentError, "Unknown Extension Attribute Name: '#{ea_name}'" unless ea_types.key? ea_name
184 
185   value = validate_ea_value(ea_name, value, validate_popup_choice, refresh)
186 
187   # update this ea hash in the @extension_attributes array
188   ea_hash = @extension_attributes.find { |ea| ea[:name] == ea_name }
189 
190   raise Jamf::NoSuchItemError, "#{self.class} '#{name}'(id #{id}) does not know about ExtAttr '#{ea_name}'. Please re-fetch and try again." unless ea_hash
191 
192   ea_hash[:value] = value
193 
194   # update the shortcut hash too
195   @ext_attrs[ea_name] = value if @ext_attrs
196   @changed_eas << ea_name
197   @need_to_update = true
198 end
unsaved_eas?() click to toggle source

are there any changes in the EAs needing to be saved?

@return [Boolean]

    # File lib/jamf/api/classic/api_objects/extendable.rb
204 def unsaved_eas?
205   @need_to_update && @changed_eas && !@changed_eas.empty?
206 end
validate_ea_value(ea_name, value, validate_popup_choice, refresh) click to toggle source

is the value being passed to set_ext_attr valid? Converts values as needed (e.g. strings to integers or Times)

If the EA is defined to hold a string, any value is accepted and converted with to_s

Note: All EAs can be blank

@param name the name of the extension attribute to set

@param value the new value for the extension

attribute for this user

@param validate_popup_choice validate the new value against the E.A. definition.

Defaults to true.

@param refresh Re-read the ext. attrib definition from the API,

for popup validation.

@return [Object] the possibly modified valid value

    # File lib/jamf/api/classic/api_objects/extendable.rb
262 def validate_ea_value(ea_name, value, validate_popup_choice, refresh)
263   return Jamf::BLANK if value.to_s == Jamf::BLANK
264 
265   value =
266     case ea_types[ea_name]
267     when Jamf::ExtensionAttribute::DATA_TYPE_DATE
268       Jamf.parse_time(value).to_s
269     when *Jamf::ExtensionAttribute::NUMERIC_TYPES
270       validate_integer_ea_value ea_name, value
271     else
272       value.to_s
273     end # case
274 
275   validate_popup_value(ea_name, value, refresh) if validate_popup_choice
276 
277   value
278 end
validate_integer_ea_value(ea_name, value) click to toggle source

raise error if the value isn’t an integer

    # File lib/jamf/api/classic/api_objects/extendable.rb
281 def validate_integer_ea_value(ea_name, value)
282   if value.is_a? Integer
283     value
284   elsif value.to_s.jss_integer?
285     value.to_s.to_i
286   else
287     raise Jamf::InvalidDataError, "The value for #{ea_name} must be an integer"
288   end # if
289 end
validate_popup_value(ea_name, value, refresh) click to toggle source

Raise an error if the named EA has a popup menu, but the provided value isn’t one of the menu items

    # File lib/jamf/api/classic/api_objects/extendable.rb
294 def validate_popup_value(ea_name, value, refresh)
295   # get the ea def. instance from the api cache, or the api
296   cnx.c_ext_attr_definition_cache[self.class] ||= {}
297   cnx.c_ext_attr_definition_cache[self.class][ea_name] = nil if refresh
298   cnx.c_ext_attr_definition_cache[self.class][ea_name] ||= self.class::EXT_ATTRIB_CLASS.fetch name: ea_name, cnx: cnx
299 
300   ea_def = cnx.c_ext_attr_definition_cache[self.class][ea_name]
301   return unless ea_def.from_popup_menu?
302 
303   return if ea_def.popup_choices.include? value.to_s
304 
305   raise Jamf::UnsupportedError, "The value for #{ea_name} must be one of: '#{ea_def.popup_choices.join("' '")}'"
306 end