class Jamf::ExtensionAttribute

The parent class of ExtensionAttribute objects in the JSS.

The API extension attribute objects work with the definitions of extension attributes, not the resulting values stored in the JSS with the inventory reports.

This superclass, however, uses the {AdvancedSearch} subclasses to provide access to the reported values in two ways:

The {ComputerExtensionAttribute} subclass offers a {ComputerExtensionAttribute#history} method providing the history of values for the EA for one computer. This requires MySQL access to the JSS database since that history isn’t available via the API.

Subclasses of ExtensionAttribute must define these constants:

@see Jamf::APIObject

Constants

DATA_TYPES
DATA_TYPE_DATE
DATA_TYPE_INTEGER
DATA_TYPE_NUMBER
DATA_TYPE_STRING

What kinds of data can be created by EAs? Note, Dates must be in the format “YYYY-MM-DD hh:mm:ss”

DEFAULT_DATA_TYPE
DEFAULT_INPUT_TYPE
DEFAULT_WEB_DISPLAY_CHOICE
INPUT_TYPES
INPUT_TYPE_FIELD

Where does the data come from?

INPUT_TYPE_LDAP
INPUT_TYPE_POPUP
INPUT_TYPE_SCRIPT
LAST_RECON_FIELD
LAST_RECON_FIELD_SYM
NUMERIC_TYPES

ExtensionAttributes refer to the numeric data type as “Integer” but the ext. attr values that come with extendable objects refer to that data type as “Number”. Here’s an array with both, so we can work with ether more easily.

USERNAME_FIELD
USERNAME_FIELD_SYM
WEB_DISPLAY_CHOICES
WEB_DISPLAY_CHOICE_EAS
WEB_DISPLAY_CHOICE_GENERAL

Where can it be displayed in the WebApp?

WEB_DISPLAY_CHOICE_HW
WEB_DISPLAY_CHOICE_OS
WEB_DISPLAY_CHOICE_PURCHASING
WEB_DISPLAY_CHOICE_USER_LOC

Attributes

attribute_mapping[R]

@return [String] the LDAP attribute for the User’s ldap entry that maps to this EA, when input type is INPUT_TYPE_LDAP

data_type[R]

@return [String] the type of data created by the EA. Must be one of DATA_TYPES

desc[R]

@return [String] description of the ext attrib

description[R]

@return [String] description of the ext attrib

input_type[R]

@return [String] where does this data come from? Must be one of the INPUT_TYPES.

popup_choices[R]

@return [Array<String>] the choices available in the UI when the @input_type is “Pop-up Menu”

web_display[R]

@return [String] In which part of the web UI does the data appear?

Public Class Methods

new(**args) click to toggle source

@see Jamf::APIObject#initialize

Calls superclass method Jamf::APIObject::new
    # File lib/jamf/api/classic/base_classes/extension_attribute.rb
152 def initialize(**args)
153   super
154 
155   # @init_data now has the raw data
156   # so fill in our attributes or set defaults
157   @description = @init_data[:description]
158   @data_type = @init_data[:data_type] || DEFAULT_DATA_TYPE
159 
160   if @init_data[:input_type]
161     @input_type = @init_data[:input_type][:type]
162 
163     @script = @init_data[:input_type][:script]
164 
165     @attribute_mapping = @init_data[:input_type][:attribute_mapping]
166     @popup_choices = @init_data[:input_type][:popup_choices]
167     # popups can always contain blank
168     @popup_choices << Jamf::BLANK if @popup_choices
169 
170     # These two are deprecated - windows won't be supported for long
171     @platform = @init_data[:input_type][:platform]
172     @scripting_language = @init_data[:input_type][:scripting_language]
173   end
174   @input_type ||= DEFAULT_INPUT_TYPE
175 
176   @enabled = @init_data[:enabled]
177 
178   @web_display = @init_data[:inventory_display] || DEFAULT_WEB_DISPLAY_CHOICE
179   # deprecated - no longer in the UI
180   @recon_display = @init_data[:recon_display] || @web_display
181 
182   # When used in Advanced Search results, the EA name
183   # has colons removed, spaces & dashes turned to underscores.
184   # and then ruby-jss symbolizes the name.
185   @symbolized_name = @name.gsub(':', '').gsub(/-| /, '_').to_sym
186 end

Public Instance Methods

all_with_result(search_type, desired_value) click to toggle source

Get an Array of Hashes for all inventory objects with a desired result in their latest report for this EA.

Each Hash is one inventory object (computer, mobile device, user), with these keys:

:id - the computer id
:name - the computer name
:value - the matching ext attr value for the objects latest report.

This is done by creating a temprary {AdvancedSearch} for objects with matching values in the EA field, then getting the search_results hash from it.

The AdvancedSearch is then deleted.

@param search_type how are we comparing the stored value with the desired value. must be a member of Jamf::Criterion::SEARCH_TYPES

@param desired_value the value to compare with the stored value to determine a match.

@return [Array<Hash{:id=>Integer,:name=>String,:value=>String,Integer,Time}>] the items that match the result.

    # File lib/jamf/api/classic/base_classes/extension_attribute.rb
391 def all_with_result(search_type, desired_value)
392   raise Jamf::NoSuchItemError, "EA Not In JSS! Use #create to create this #{self.class::RSRC_OBJECT_KEY}." unless @in_jss
393   unless Jamf::Criteriable::Criterion::SEARCH_TYPES.include? search_type.to_s
394     raise Jamf::InvalidDataError, 'Invalid search_type, see Jamf::Criteriable::Criterion::SEARCH_TYPES'
395   end
396 
397   begin
398     search_class = self.class::TARGET_CLASS::SEARCH_CLASS
399     acs = search_class.make cnx: @cnx, name: "ruby-jss-EA-result-search-#{Time.now.to_jss_epoch}"
400     acs.display_fields = [@name]
401     crit_list = [Jamf::Criteriable::Criterion.new(and_or: 'and', name: @name, search_type: search_type.to_s, value: desired_value)]
402     acs.criteria = Jamf::Criteriable::Criteria.new crit_list
403 
404     acs.create :get_results
405 
406     results = []
407 
408     acs.search_results.each do |i|
409       value =
410         case @data_type
411         when 'Date' then Jamf.parse_time i[@symbolized_name]
412         when 'Integer' then i[@symbolized_name].to_i
413         else i[@symbolized_name]
414         end # case
415       results << { id: i[:id], name: i[:name], value: value }
416     end # each
417   ensure
418     acs.delete if acs.is_a? self.class::TARGET_CLASS::SEARCH_CLASS
419   end
420   results
421 end
attribute_mapping=(new_mapping) click to toggle source

Change the LDAP Attribute Mapping of this EA New value must be a String, or respond correctly to to_s

This automatically sets input_type to INPUT_TYPE_LDAP

@param new_val[String, to_s] the new value

@return [void]

    # File lib/jamf/api/classic/base_classes/extension_attribute.rb
362 def attribute_mapping=(new_mapping)
363   new_mapping = Jamf::Validate.non_empty_string new_mapping.to_s
364   return if new_mapping == @attribute_mapping
365 
366   self.input_type = INPUT_TYPE_LDAP
367   @attribute_mapping = new_mapping
368   @need_to_update = true
369 end
create() click to toggle source

@see Jamf::Creatable#create

Calls superclass method Jamf::APIObject::create
    # File lib/jamf/api/classic/base_classes/extension_attribute.rb
193 def create
194   validate_for_save
195   super
196 end
data_type=(new_val) click to toggle source

Change the data type of this EA

@param new_val the new value, which must be a member of DATA_TYPES

@return [void]

    # File lib/jamf/api/classic/base_classes/extension_attribute.rb
259 def data_type=(new_val)
260   return if @data_type == new_val
261   raise Jamf::InvalidDataError, "data_type must be a string, one of: '#{DATA_TYPES.join("', '")}'" unless DATA_TYPES.include? new_val
262 
263   @data_type = new_val
264   @need_to_update = true
265 end
delete() click to toggle source

@see Jamf::APIObject#delete

Calls superclass method Jamf::APIObject::delete
    # File lib/jamf/api/classic/base_classes/extension_attribute.rb
209 def delete
210   orig_open_timeout = @cnx.open_timeout
211   orig_timeout = @cnx.timeout
212   @cnx.timeout = orig_timeout + 1800
213   @cnx.open_timeout = orig_open_timeout + 1800
214   begin
215     super
216     @cnx.flushcache self.class
217   ensure
218     @cnx.timeout = orig_timeout
219     @cnx.open_timeout = orig_open_timeout
220   end
221 end
description=(new_val) click to toggle source

Change the description of this EA

@param new_val the new value

@return [void]

    # File lib/jamf/api/classic/base_classes/extension_attribute.rb
245 def description=(new_val)
246   new_val = new_val.to_s
247   return if @description == new_val
248 
249   @description = new_val
250   @need_to_update = true
251 end
from_ldap?() click to toggle source
    # File lib/jamf/api/classic/base_classes/extension_attribute.rb
231 def from_ldap?
232   @input_type == INPUT_TYPE_LDAP
233 end
from_popup_menu?() click to toggle source
    # File lib/jamf/api/classic/base_classes/extension_attribute.rb
227 def from_popup_menu?
228   @input_type == INPUT_TYPE_POPUP
229 end
from_script?() click to toggle source
    # File lib/jamf/api/classic/base_classes/extension_attribute.rb
235 def from_script?
236   @input_type == INPUT_TYPE_SCRIPT
237 end
from_text_field?() click to toggle source
    # File lib/jamf/api/classic/base_classes/extension_attribute.rb
223 def from_text_field?
224   @input_type == INPUT_TYPE_FIELD
225 end
input_type=(new_val) click to toggle source

Change the input type of this EA

@param new_val the new value, which must be a member of INPUT_TYPES

@return [void]

    # File lib/jamf/api/classic/base_classes/extension_attribute.rb
289 def input_type=(new_val)
290   return if @input_type == new_val
291   raise Jamf::InvalidDataError, "input_type must be a string, one of: #{INPUT_TYPES.join(', ')}" unless INPUT_TYPES.include? new_val
292 
293   @input_type = new_val
294   case @input_type
295   when INPUT_TYPE_FIELD
296     @script = nil
297     @scripting_language = nil
298     @platform = nil
299     @popup_choices = nil
300     @attribute_mapping = nil
301   when INPUT_TYPE_POPUP
302     @script = nil
303     @scripting_language = nil
304     @platform = nil
305     @attribute_mapping = nil
306   when INPUT_TYPE_SCRIPT
307     @popup_choices = nil
308     @attribute_mapping = nil
309   when INPUT_TYPE_LDAP
310     @script = nil
311     @scripting_language = nil
312     @platform = nil
313     @popup_choices = nil
314   end # case
315 
316   @need_to_update = true
317 end
latest_values() click to toggle source

for this EA on all inventory objects in the JSS.

Each Hash is one inventory object (computer, mobile device, user), with these keys:

:id - the jss id
:name - the object (computer, user, mobiledevice) name
:value - the most recent ext attr value for the object.
:as_of - the timestamp of when the value was collected (nil for User EAs, or for devices that have never collected inventory)
:username - the username associated with the object

This is done by creating a temporary {AdvancedSearch} for all objects, with the EA as a display field. The search_result then contains the desired data.

The AdvancedSearch is then deleted.

@return [Array<Hash{:id=>Integer,:name=>String,:value=>String,Integer,Time,:as_of=>Time}>]

@see Jamf::AdvancedSearch

@see Jamf::AdvancedComputerSearch

@see Jamf::AdvancedMobileDeviceSearch

@see Jamf::AdvancedUserSearch

    # File lib/jamf/api/classic/base_classes/extension_attribute.rb
448 def latest_values
449   raise Jamf::NoSuchItemError, "EA Not In JSS! Use #create to create this #{self.class::RSRC_OBJECT_KEY}." unless @in_jss
450 
451   tmp_advsrch = "ruby-jss-EA-latest-search-#{Time.now.to_jss_epoch}"
452 
453   begin
454     search_class = self.class::TARGET_CLASS::SEARCH_CLASS
455     acs = search_class.make name: tmp_advsrch, cnx: @cnx
456     acs.display_fields = self.class::TARGET_CLASS == Jamf::User ? [@name, USERNAME_FIELD] : [@name, USERNAME_FIELD, LAST_RECON_FIELD]
457 
458     # search for 'Username like "" ' because all searchable object classes have a "Username" value
459     crit = Jamf::Criteriable::Criterion.new(and_or: 'and', name: 'Username', search_type: 'like', value: '')
460     # crit = self.class::ALL_TARGETS_CRITERION
461     acs.criteria = Jamf::Criteriable::Criteria.new [crit]
462     acs.create :get_results
463 
464     results = []
465 
466     acs.search_results.each do |i|
467       value =
468         case @data_type
469         when 'Date' then Jamf.parse_time i[@symbolized_name]
470         when 'Integer' then i[@symbolized_name].to_i
471         else i[@symbolized_name]
472         end # case
473 
474       as_of = Jamf.parse_time(i[LAST_RECON_FIELD_SYM])
475 
476       results << { id: i[:id], name: i[:name], username: i[USERNAME_FIELD_SYM], value: value, as_of: as_of }
477     end # acs.search_results.each
478   ensure
479     if defined? acs
480       acs.delete if acs
481     elsif search_class.all_names(:refresh, cnx: @cnx).include? tmp_advsrch
482       search_class.fetch(name: tmp_advsrch, cnx: @cnx).delete
483     end
484   end
485 
486   results
487 end
popup_choices=(new_val) click to toggle source

Change the Popup Choices of this EA New value must be an Array, the items will be converted to Strings.

This automatically sets input_type to INPUT_TYPE_POPUP

Values are checked to ensure they match the @data_type Note, Dates must be in the format “YYYY-MM-DD hh:mm:ss”

@param new_val the new values

@return [void]

update() click to toggle source

@see Jamf::Updatable#update

Calls superclass method Jamf::APIObject#update
    # File lib/jamf/api/classic/base_classes/extension_attribute.rb
200 def update
201   validate_for_save
202   super
203   # this flushes the cached EA itself, used for validating ea values.
204   @cnx.flushcache self.class
205 end
web_display=(new_val) click to toggle source

Change the inventory_display of this EA

@param new_val the new value, which must be a member of WEB_DISPLAY_CHOICES

@return [void]

    # File lib/jamf/api/classic/base_classes/extension_attribute.rb
273 def web_display=(new_val)
274   return if @web_display == new_val
275   unless WEB_DISPLAY_CHOICES.include? new_val
276     raise Jamf::InvalidDataError, "inventory_display must be a string, one of: #{WEB_DISPLAY_CHOICES.join(', ')}"
277   end
278 
279   @web_display = new_val
280   @need_to_update = true
281 end

Private Instance Methods

rest_xml() click to toggle source

Return a REXML doc string for this ext attr, with the current values.

    # File lib/jamf/api/classic/base_classes/extension_attribute.rb
511 def rest_xml
512   ea = REXML::Element.new self.class::RSRC_OBJECT_KEY.to_s
513   ea.add_element('name').text = @name
514   ea.add_element('description').text = @description if @description
515   ea.add_element('data_type').text = @data_type
516 
517   unless self.class == Jamf::UserExtensionAttribute
518     ea.add_element('inventory_display').text = @web_display if @web_display
519     ea.add_element('recon_display').text = @recon_display if @recon_display
520   end
521 
522   it = ea.add_element('input_type')
523   it.add_element('type').text = @input_type
524 
525   case @input_type
526   when INPUT_TYPE_POPUP
527     pcs = it.add_element('popup_choices')
528     @popup_choices.each { |pc| pcs.add_element('choice').text = pc } if @popup_choices.is_a? Array
529   when INPUT_TYPE_LDAP
530     it.add_element('attribute_mapping').text = @attribute_mapping
531   when INPUT_TYPE_SCRIPT
532     ea.add_element('enabled').text = @enabled ? 'true' : 'false'
533     it.add_element('script').text = @script
534     it.add_element('platform').text = @platform || 'Mac'
535     it.add_element('scripting_language').text = @scripting_language if @scripting_language
536   end
537 
538   doc = REXML::Document.new Jamf::Connection::XML_HEADER
539   doc << ea
540   doc.to_s
541 end
validate_for_save() click to toggle source

make sure things are OK for saving to Jamf

    # File lib/jamf/api/classic/base_classes/extension_attribute.rb
495 def validate_for_save
496   case @input_type
497   when INPUT_TYPE_POPUP
498     raise MissingDataError, "No popup_choices set for input type: #{INPUT_TYPE_POPUP}" unless @popup_choices.is_a?(Array) && !@popup_choices.empty?
499   when INPUT_TYPE_LDAP
500     raise MissingDataError, "No attribute_mapping set for input type: #{INPUT_TYPE_LDAP}" if @attribute_mapping.to_s.empty?
501   when INPUT_TYPE_SCRIPT
502     raise MissingDataError, "No script set for input_type: #{INPUT_TYPE_SCRIPT}" unless @script
503 
504     # Next two lines DEPRECATED
505     @platform ||= 'Mac'
506     raise MissingDataError, "No scripting_language set for Windows '#{INPUT_TYPE_SCRIPT}' input_type." if @platform == 'Windows' && !@scripting_language
507   end
508 end