class Jamf::Group
This is the parent class of the smart/static group objects in the JSS
namely, {ComputerGroup}, {MobileDeviceGroup}, and {UserGroup}
It provides methods for working with the membership of static groups and, by including {Jamf::Criteriable}, the criteria for smart groups.
When changing the criteria of a smart group, use the criteria
attribute, which is a {Jamf::Criteria} instance.
Subclasses must define these constants:
-
MEMBER_CLASS: the ruby-jss class to which the group members belong (e.g.
Jamf::MobileDevice
) -
ADD_MEMBERS_ELEMENT: the XML element tag for adding members to the group wuth a PUT call to the API, e.g. ‘computer_additions’
-
REMOVE_MEMBERS_ELEMENT: the XML element tag for removing members from the group wuth a PUT call to the API, e.g. ‘computer_deletions’
@see Jamf::APIObject
@see Jamf::Criteriable
Constants
- GROUP_TYPES
the types of groups allowed for creation
- ID_XML_TAG
the ‘id’ xml element tag
- SITE_SUBSET
Where is the
Site
data in the API JSON?
Attributes
@return [Boolean] is this a smart group
@return [Array<Hash>] the group membership
Each hash contains the identifiers for a member of the group, those being:
-
:id, :name, and possibly :udid, :serial_number, :mac_address, :alt_mac_address, and :wifi_mac_address
@see member_ids
@see member_names
@return [Boolean] does this group send notifications when it changes?
@return [Boolean] does this group send notifications when it changes?
@return [Boolean] does this group send notifications when it changes?
@return [Boolean] is this a smart group
Public Class Methods
Returns an Array
of all the smart groups.
# File lib/jamf/api/classic/base_classes/group.rb 79 def self.all_smart(refresh = false, api: nil, cnx: Jamf.cnx) 80 cnx = api if api 81 82 all(refresh, cnx: cnx).select { |g| g[:is_smart] } 83 end
Returns an Array
of all the static groups.
# File lib/jamf/api/classic/base_classes/group.rb 88 def self.all_static(refresh = false, api: nil, cnx: Jamf.cnx) 89 cnx = api if api 90 91 all(refresh, cnx: cnx).reject { |g| g[:is_smart] } 92 end
Immediatly add and/or remove members in a static group without instantiating it first. Uses the <x_additions> and <x_deletions> XML elements available when sending a PUT request to the API.
@param group [String, Integer] The name or id of the group being changed
@param add_members [String, Integer, Array
<String, Integer>] valid
identifier(s) for members to add
@param remove_members [String, Integer, Array
<String, Integer>] valid
identifier(s) for members to remove
@param cnx [Jamf::Connection] The API connetion to use, uses the default
connection if not specified
@return [void]
# File lib/jamf/api/classic/base_classes/group.rb 111 def self.change_membership(group, add_members: [], remove_members: [], api: nil, cnx: Jamf.cnx) 112 cnx = api if api 113 114 raise Jamf::NoSuchItemError, "No #{self} matching '#{group}'" unless (group_id = valid_id group, cnx: cnx) 115 raise Jamf::UnsupportedError, "Not a static group, can't change membership" if map_all(:id, to: :is_smart, cnx: cnx)[group_id] 116 117 add_members = [add_members].flatten 118 remove_members = [remove_members].flatten 119 return if add_members.empty? && remove_members.empty? 120 121 # we must know the current group membership, because the API 122 # will raise a conflict error if we try to remove a member 123 # that isn't in the group (which is kinda lame - it should just 124 # ignore this, like it does when we add a member that's already 125 # in the group.) 126 # Its even more lame because we have to instantiate the group 127 # and part of the point of this class method is to avoid that. 128 current_member_ids = fetch(id: group_id, cnx: cnx).member_ids 129 130 # nil if no changes to be made 131 xml_doc = change_membership_xml add_members, remove_members, current_member_ids 132 return unless xml_doc 133 134 cnx.c_put "#{self::RSRC_BASE}/id/#{group_id}", xml_doc.to_s 135 end
When creating a new group in the JSS
, you must call .make with a :type key and a value of :smart or :static, as well as a :name
@see Jamf::APIObject
Jamf::APIObject::new
# File lib/jamf/api/classic/base_classes/group.rb 226 def initialize(**args) 227 raise Jamf::InvalidDataError, 'New group creation must specify a :type of :smart or :static' if args[:id] == :new && !(GROUP_TYPES.include? args[:type]) 228 229 super 230 231 @is_smart = @init_data[:is_smart] || (args[:type] == :smart) 232 233 @members = 234 @init_data[self.class::MEMBER_CLASS::RSRC_LIST_KEY] || [] 235 end
Private Class Methods
return [REXML::Document, nil]
# File lib/jamf/api/classic/base_classes/group.rb 139 def self.change_membership_xml(add_members, remove_members, current_member_ids) 140 # these are nil if there are no changes to make 141 addx = member_additions_xml(add_members, current_member_ids) 142 remx = member_removals_xml(remove_members, current_member_ids) 143 return nil unless addx || remx 144 145 doc = REXML::Document.new Jamf::Connection::XML_HEADER 146 groupelem = doc.add_element self::RSRC_OBJECT_KEY.to_s 147 groupelem << addx if addx 148 groupelem << remx if remx 149 doc 150 end
@return [REXML::Element, nil]
# File lib/jamf/api/classic/base_classes/group.rb 155 def self.member_additions_xml(add_members, current_member_ids) 156 return nil if add_members.empty? 157 158 additions = REXML::Element.new self::ADD_MEMBERS_ELEMENT 159 member_added = false 160 add_members.each do |am| 161 am_id = self::MEMBER_CLASS.valid_id am 162 raise Jamf::NoSuchItemError, "No #{self::MEMBER_CLASS} matching '#{am}'" unless am_id 163 next if current_member_ids.include? am_id 164 165 xam = additions.add_element self::MEMBER_CLASS::RSRC_OBJECT_KEY.to_s 166 xam.add_element(ID_XML_TAG).text = am_id.to_s 167 member_added = true 168 end # each 169 170 member_added ? additions : nil 171 end
@return [REXML::Element, nil]
# File lib/jamf/api/classic/base_classes/group.rb 176 def self.member_removals_xml(remove_members, current_member_ids) 177 return nil if remove_members.empty? 178 179 removals = REXML::Element.new self::REMOVE_MEMBERS_ELEMENT 180 member_removed = false 181 remove_members.each do |rm| 182 rm_id = self::MEMBER_CLASS.valid_id rm 183 next unless rm_id && current_member_ids.include?(rm_id) 184 185 xrm = removals.add_element self::MEMBER_CLASS::RSRC_OBJECT_KEY.to_s 186 xrm.add_element(ID_XML_TAG).text = rm_id.to_s 187 member_removed = true 188 end # each 189 190 member_removed ? removals : nil 191 end
Public Instance Methods
Add a member, by name or id
@param m the id or name of the member to add
@return [void]
# File lib/jamf/api/classic/base_classes/group.rb 416 def add_member(mem) 417 raise UnsupportedError, "Smart group members can't be changed." if smart? 418 419 @members << check_member(mem) 420 @need_to_update = true 421 end
Immediatly add and/or remove members in this static group
IMPORTANT: This method changes the group in the JSS
immediately,
there is no need to call #update/#save
@param add_members [String, Integer, Array
<String, Integer>] valid
identifier(s) for members to add
@param remove_members [String, Integer, Array
<String, Integer>] valid
identifier(s) for members to remove
@param cnx [Jamf::Connection] The API connetion to use, uses the default
connection if not specified
@return [void]
# File lib/jamf/api/classic/base_classes/group.rb 473 def change_membership(add_members: [], remove_members: []) 474 self.class.change_membership(@id, add_members: add_members, remove_members: remove_members, cnx: @cnx) 475 refresh_members 476 end
Remove all members
@return [void]
# File lib/jamf/api/classic/base_classes/group.rb 449 def clear 450 raise InvalidDataError, "Smart group members can't be changed." if @is_smart 451 return if @members.empty? 452 453 @members.clear 454 @need_to_update = true 455 end
@see Creatable#create
@param calculate_members [Boolan] should the local membership list be
re-read from the API after the group is created?
@param retries [Integer] If calculate_members is true, refetching the
group to re-read the membership can happen too fast, the JSS won't know it exists yet and will throw a NoSuchItem error. If that happens, try again this many times with a 1 second pause between attempts.
Jamf::APIObject::create
# File lib/jamf/api/classic/base_classes/group.rb 250 def create(calculate_members: true, retries: 10) 251 raise Jamf::MissingDataError, 'No criteria specified for smart group' if @is_smart && !@criteria 252 253 super() 254 255 if calculate_members 256 tries = 0 257 while tries < retries 258 begin 259 refresh_members 260 break 261 rescue 262 sleep 1 263 tries += 1 264 end # begin 265 end # while 266 end # if calc members 267 268 @id 269 end
Apply a new set of criteria to a smart group
@param new_criteria the new criteria for the smart group
Jamf::Criteriable#criteria=
# File lib/jamf/api/classic/base_classes/group.rb 310 def criteria=(new_criteria) 311 raise InvalidDataError, 'Only smart groups have criteria.' unless @is_smart 312 313 super 314 end
@see APIObject#delete
Jamf::APIObject::delete
# File lib/jamf/api/classic/base_classes/group.rb 298 def delete 299 super 300 @is_smart = nil 301 @criteria = nil 302 @site = nil 303 @members = [] 304 end
Change static group to smart group
@param args the options and settings use for switching the computer group from static group to smart group
@option args criteria The criteria to be user for the smart group
@return [void]
# File lib/jamf/api/classic/base_classes/group.rb 323 def make_smart(**params) 324 return if @is_smart 325 326 params[:criteria] = [] if params[:criteria].nil? 327 328 criteria = params[:criteria] 329 330 @is_smart = true 331 @need_to_update = true 332 end
Change smart group to static group
@param args the options and settings use for switching the computer group from smart group to static group
@option args preserve_members Should the smart group preserve it’s current members?
@return [void]
# File lib/jamf/api/classic/base_classes/group.rb 343 def make_static(**params) 344 return unless @is_smart 345 346 preserve_members = params.include? :preserve_members 347 348 @is_smart = false 349 350 clear unless preserve_members 351 end
@return [Array<Integer>] the ids of the group members
# File lib/jamf/api/classic/base_classes/group.rb 378 def member_ids 379 @members.map { |m| m[:id] } 380 end
@return [Array<String>] the names of the group members
# File lib/jamf/api/classic/base_classes/group.rb 372 def member_names 373 @members.map { |m| m[:name] } 374 end
Replace all @members with an array of uniq device identfiers (names, ids, serial numbers, etc) E.g: [ ‘lambic’, 1233, ‘2341’, ‘monkey’]
They must all be in the JSS
or an error is raised before doing anything. See {#check_member}
@param new_members the new group members
@return [void]
# File lib/jamf/api/classic/base_classes/group.rb 392 def members=(new_members) 393 raise UnsupportedError, "Smart group members can't be changed." if @is_smart 394 raise InvalidDataError, 'Arg must be an array of names and/or ids' unless new_members.is_a? Array 395 396 ok_members = [] 397 new_members.each do |m| 398 ok_members << check_member(m) 399 end 400 401 ok_members.uniq! 402 403 # make sure we've actually changed... 404 return if members.map { |m| m[:id] }.sort == ok_members.map { |m| m[:id] }.sort 405 406 @members = ok_members 407 @need_to_update = true 408 end
Refresh the membership from the API
@return [Array<Hash>] the refresh membership
# File lib/jamf/api/classic/base_classes/group.rb 482 def refresh_members 483 @members = @cnx.c_get(@rest_rsrc)[self.class::RSRC_OBJECT_KEY][self.class::MEMBER_CLASS::RSRC_LIST_KEY] 484 end
Remove a member by id, or name
@param m an identifier for the item to remove
@return [void]
# File lib/jamf/api/classic/base_classes/group.rb 429 def remove_member(mem) 430 raise UnsupportedError, "Smart group members can't be changed." if smart? 431 432 # See if we have the identifier in the @members hash 433 id_to_remove = @members.select { |mm| mm.values.include? mem }.first&.dig :id 434 # But the members hash might not have SN, macaddr, etc, and never has udid, so 435 # look at the MEMBER_CLASS if needed 436 id_to_remove ||= self.class::MEMBER_CLASS.valid_id mem 437 438 # nothing to do if that id isn't one of our members 439 return unless id_to_remove && member_ids.include?(id_to_remove) 440 441 @members.delete_if { |k, v| k == :id && v == id_to_remove } 442 @need_to_update = true 443 end
Wrapper/alias for both create and update
# File lib/jamf/api/classic/base_classes/group.rb 280 def save(**params) 281 params[:calculate_members] = true if params[:calculate_members].nil? 282 params[:retries] = 10 if params[:retries].nil? 283 params[:refresh] = true if params[:refresh].nil? 284 285 if @in_jss 286 raise Jamf::UnsupportedError, 'Updating this object in the JSS is currently not supported by ruby-jss' unless updatable? 287 288 update refresh: params[:refresh] 289 else 290 raise Jamf::UnsupportedError, 'Creating this object in the JSS is currently not supported by ruby-jss' unless creatable? 291 292 create calculate_members: params[:calculate_members], retries: params[:retries] 293 end 294 end
How many members of the group?
@return [Integer] the number of members of the group
# File lib/jamf/api/classic/base_classes/group.rb 359 def size 360 @members.count 361 end
@return [Boolean] Is this a static group?
# File lib/jamf/api/classic/base_classes/group.rb 365 def static? 366 !smart? 367 end
@see Updatable#update
Jamf::APIObject#update
# File lib/jamf/api/classic/base_classes/group.rb 273 def update(refresh: true) 274 super() 275 refresh_members if refresh 276 @id 277 end
Private Instance Methods
Check that a potential group member is valid in the JSS
. Arg must be an id or name. An exception is raised if the device doesn’t exist.
@return [Hash{:id=>Integer,:name=>String}] the valid id and name
# File lib/jamf/api/classic/base_classes/group.rb 497 def check_member(m) 498 desired_id = self.class::MEMBER_CLASS.valid_id m, cnx: @cnx 499 raise Jamf::NoSuchItemError, "No #{self.class::MEMBER_CLASS::RSRC_OBJECT_KEY} matching '#{m}' in the JSS." unless desired_id 500 501 desired_name = self.class::MEMBER_CLASS.map_all(:id, to: :name, cnx: @cnx)[desired_id] 502 503 { name: desired_name, id: desired_id } 504 end
the xml formated data for adding or updating this in the JSS
,
# File lib/jamf/api/classic/base_classes/group.rb 508 def rest_xml 509 doc = REXML::Document.new Jamf::Connection::XML_HEADER 510 group = doc.add_element self.class::RSRC_OBJECT_KEY.to_s 511 group.add_element('name').text = @name 512 group.add_element('is_smart').text = @is_smart 513 if @is_smart 514 group << @criteria.rest_xml if @criteria 515 else 516 group << self.class::MEMBER_CLASS.xml_list(@members, :id) 517 end 518 519 add_site_to_xml(doc) 520 521 doc.to_s 522 end