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:
-
A list of target objects with a certain value for the
ExtensionAttribute
instance. See the {#all_with_result} method for details -
A list of the most recent value for this
ExtensionAttribute
in all targets in theJSS
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:
-
TARGET_CLASS - the {APIObject} subclass to which the extention attribute applies. e.g. {Jamf::Computer}
-
ALL_TARGETS_CRITERION - a {Jamf::Criteriable::Criterion} instance that will be used in an {AdvancedSearch} to find all of members of the TARGET_CLASS
@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
@return [String] the LDAP attribute for the User’s ldap entry that maps to this EA, when input type is INPUT_TYPE_LDAP
@return [String] the type of data created by the EA. Must be one of DATA_TYPES
@return [String] description of the ext attrib
@return [String] description of the ext attrib
@return [String] where does this data come from? Must be one of the INPUT_TYPES
.
@return [Array<String>] the choices available in the UI when the @input_type is “Pop-up Menu”
@return [String] In which part of the web UI does the data appear?
Public Class Methods
@see Jamf::APIObject#initialize
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
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
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
@see Jamf::Creatable#create
Jamf::APIObject::create
# File lib/jamf/api/classic/base_classes/extension_attribute.rb 193 def create 194 validate_for_save 195 super 196 end
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
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
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
# File lib/jamf/api/classic/base_classes/extension_attribute.rb 231 def from_ldap? 232 @input_type == INPUT_TYPE_LDAP 233 end
# File lib/jamf/api/classic/base_classes/extension_attribute.rb 235 def from_script? 236 @input_type == INPUT_TYPE_SCRIPT 237 end
# File lib/jamf/api/classic/base_classes/extension_attribute.rb 223 def from_text_field? 224 @input_type == INPUT_TYPE_FIELD 225 end
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
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
# 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
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]
# File lib/jamf/api/classic/base_classes/extension_attribute.rb 331 def popup_choices=(new_val) 332 return if @popup_choices == new_val 333 raise Jamf::InvalidDataError, 'popup_choices must be an Array' unless new_val.is_a?(Array) 334 335 # convert each one to a String, 336 # and check that it matches the @data_type 337 new_val.map! do |v| 338 v = v.to_s.strip 339 case @data_type 340 when DATA_TYPE_DATE 341 raise Jamf::InvalidDataError, "data_type is Date, but '#{v}' is not formatted 'YYYY-MM-DD hh:mm:ss'" unless v =~ /^\d{4}(-\d\d){2} (\d\d:){2}\d\d$/ 342 when 'Integer' 343 raise Jamf::InvalidDataError, "data_type is Integer, but '#{v}' is not one" unless v =~ /^\d+$/ 344 end 345 v 346 end 347 348 self.input_type = INPUT_TYPE_POPUP 349 @popup_choices = new_val 350 @need_to_update = true 351 end
@see Jamf::Updatable#update
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
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
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
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