lib/jss/api_object/group.rb in ruby-jss-1.0.3b3 vs lib/jss/api_object/group.rb in ruby-jss-1.0.3b4
- old
+ new
@@ -35,12 +35,17 @@
# including {JSS::Criteriable}, the criteria for smart groups.
#
# When changing the criteria of a smart group, use the #criteria attribute,
# which is a {JSS::Criteria} instance.
#
- # Subclasses must define the constant MEMBER_CLASS which indicates Ruby class
- # to which the group members belong (e.g. JSS::MobileDevice)
+ # Subclasses must define these constants:
+ # - MEMBER_CLASS: the ruby-jss class to which the group
+ # members belong (e.g. JSS::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 JSS::APIObject
#
# @see JSS::Criteriable
#
@@ -60,10 +65,13 @@
GROUP_TYPES = %i[smart static].freeze
# Where is the Site data in the API JSON?
SITE_SUBSET = :top
+ # the 'id' xml element tag
+ ID_XML_TAG = 'id'.freeze
+
# Class Methods
#####################################
# Returns an Array of all the smart
# groups.
@@ -77,10 +85,107 @@
#
def self.all_static(refresh = false, api: JSS.api)
all(refresh, api: api).select { |g| (g[:is_smart]) }
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 api [JSS::APIConnection] The API connetion to use, uses the default
+ # connection if not specified
+ #
+ # @return [void]
+ #
+ def self.change_membership(group, add_members: [], remove_members: [], api: JSS.api)
+ raise JSS::NoSuchItemError, "No #{self} matching '#{ident}'" unless (group_id = valid_id group, api: api)
+ raise JSS::UnsupportedError, "Not a static group, can't change membership directly" if map_all_ids_to(:is_smart, api: api)[group_id]
+
+ add_members = [add_members].flatten
+ remove_members = [remove_members].flatten
+ return if add_members.empty? && remove_members.empty?
+
+ # we must know the current group membership, because the API
+ # will raise a conflict error if we try to remove a member
+ # that isn't in the group (which is kinda lame - it should just
+ # ignore this, like it does when we add a member that's already
+ # in the group.)
+ current_member_ids = fetch(id: group_id).member_ids
+
+ # nil if no changes to be made
+ xml_doc = change_membership_xml add_members, remove_members, current_member_ids
+ return unless xml_doc
+
+ api.put_rsrc "#{self::RSRC_BASE}/id/#{group_id}", xml_doc.to_s
+ end
+
+ # return [REXML::Document, nil]
+ #
+ def self.change_membership_xml(add_members, remove_members, current_member_ids)
+ # these are nil if there are no changes to make
+ addx = member_additions_xml(add_members, current_member_ids)
+ remx = member_removals_xml(remove_members, current_member_ids)
+ return nil unless addx || remx
+
+ doc = REXML::Document.new JSS::APIConnection::XML_HEADER
+ groupelem = doc.add_element self::RSRC_OBJECT_KEY.to_s
+ groupelem << addx if addx
+ groupelem << remx if remx
+ doc
+ end
+ private_class_method :change_membership_xml
+
+ # @return [REXML::Element, nil]
+ #
+ def self.member_additions_xml(add_members, current_member_ids)
+ return nil if add_members.empty?
+
+ additions = REXML::Element.new self::ADD_MEMBERS_ELEMENT
+ member_added = false
+ add_members.each do |am|
+ am_id = self::MEMBER_CLASS.valid_id am
+ raise JSS::NoSuchItemError, "No #{self::MEMBER_CLASS} matching '#{am}'" unless am_id
+ next if current_member_ids.include? am_id
+
+ xam = additions.add_element self::MEMBER_CLASS::RSRC_OBJECT_KEY.to_s
+ xam.add_element(ID_XML_TAG).text = am_id.to_s
+ member_added = true
+ end # each
+
+ member_added ? additions : nil
+ end
+ private_class_method :member_additions_xml
+
+ # @return [REXML::Element, nil]
+ #
+ def self.member_removals_xml(remove_members, current_member_ids)
+ return nil if remove_members.empty?
+
+ removals = REXML::Element.new self::REMOVE_MEMBERS_ELEMENT
+ member_removed = false
+ remove_members.each do |rm|
+ rm_id = self::MEMBER_CLASS.valid_id rm
+ next unless rm_id && current_member_ids.include?(rm_id)
+
+ xrm = removals.add_element self::MEMBER_CLASS::RSRC_OBJECT_KEY.to_s
+ xrm.add_element(ID_XML_TAG).text = rm_id.to_s
+ member_removed = true
+ end # each
+
+ member_removed ? removals : nil
+ end
+ private_class_method :member_removals_xml
+
+
# Attributes
#####################################
# @return [Array<Hash>] the group membership
#
@@ -98,12 +203,10 @@
attr_reader :is_smart
# @return [Boolean] does this group send notifications when it changes?
attr_reader :notify_on_change
- # @return [String] the :name of the site for this group
- attr_reader :site
# Constructor
#####################################
# When creating a new group in the JSS, you must call .make with a :type key
@@ -124,12 +227,10 @@
if @init_data[self.class::MEMBER_CLASS::RSRC_LIST_KEY]
@init_data[self.class::MEMBER_CLASS::RSRC_LIST_KEY]
else
[]
end
-
- @site = JSS::APIObject.get_name(@init_data[:site])
end # init
# Public Instance Methods
#####################################
@@ -147,11 +248,11 @@
# @see Updatable#update
#
def update
super
refresh_members
- true
+ @id
end
# @see APIObject#delete
#
def delete
@@ -251,27 +352,36 @@
return if @members.empty?
@members.clear
@need_to_update = true
end
- # Refresh the membership from the API
+ # Immediatly add and/or remove members in this static group
#
- # @return [Array<Hash>] the refresh membership
+ # IMPORTANT: This method changes the group in the JSS immediately,
+ # there is no need to call #update/#save
#
- def refresh_members
- @members = @api.get_rsrc(@rest_rsrc)[self.class::RSRC_OBJECT_KEY][self.class::MEMBER_CLASS::RSRC_LIST_KEY]
- end
-
- # Change the site for this group
+ # @param add_members [String, Integer, Array<String, Integer>] valid
+ # identifier(s) for members to add
#
- # @param new_val[String] the name of the new site
+ # @param remove_members [String, Integer, Array<String, Integer>] valid
+ # identifier(s) for members to remove
#
+ # @param api [JSS::APIConnection] The API connetion to use, uses the default
+ # connection if not specified
+ #
# @return [void]
#
- def site=(new_val)
- raise JSS::NoSuchItemError, "No site named #{new_val} in the JSS" unless JSS::Site.all_names(api: @api).include? new_val
- @site = new_val
- @need_to_update = true
+ def change_membership(add_members: [], remove_members: [])
+ self.class.change_membership(@id, add_members: add_members, remove_members: remove_members, api: @api)
+ refresh_members
+ end
+
+ # Refresh the membership from the API
+ #
+ # @return [Array<Hash>] the refresh membership
+ #
+ def refresh_members
+ @members = @api.get_rsrc(@rest_rsrc)[self.class::RSRC_OBJECT_KEY][self.class::MEMBER_CLASS::RSRC_LIST_KEY]
end
# aliases
alias smart? is_smart