ruby-jss 1.2.0 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ruby-jss might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a4ab060337594e885ff2fd1022848c5b6fea4ad442a8f531681cf6b347cc8dc4
4
- data.tar.gz: 3bcc0b2eca1c2a650295489d2612bdf42c4fadfd0d5a16aa082c6148aefe8100
3
+ metadata.gz: 83836b3e036658c6254365b47fe0d2b00b03dd3e75e42cd80f27ba29988d6b3b
4
+ data.tar.gz: 2e5b73fd7fd6f76f474170cc40165a156b79e37e96807fcfad0ee987e5df8018
5
5
  SHA512:
6
- metadata.gz: f5895079a629e1cae9c3e5fb41ec4a3c5b36fbd3da4d030f600df9b5d6acc9c5edb2231bc0556e5409aa0bfc6895826a474b91d4d2c484a0382b6863d0ff202f
7
- data.tar.gz: 77fad42135aa6c91ee95f10bafe8815ac7a738b1aa37280ea275ab9fd6167ec28b994e5a2d5586e99e448c83e62aa3c1a7b1914934f183af5a8366efd0f4936d
6
+ metadata.gz: fbf7ba2137f3de2a8aaff77c8c53f84086913f6ee154345d3419e94e874422116405d04ab1448c254a09e1375a734738050f07738d860245dc629f8a157c96d8
7
+ data.tar.gz: 4480768a24e0cd1372f35bafcce1d2effe646e09da4ee44bf195deff2451f46d946c94c8c34aa0ecb74dd5177ad274f4c7f9c7e0c60abf1fe0da55b1271d0b00
data/CHANGES.md CHANGED
@@ -4,6 +4,29 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## \[1.2.2] - 2019-10-31
8
+ ### Added
9
+ - the ManagementHistory mixin module used by the Computer and MobileDevice classes, now has a `last_mdm_contact` class and instance method, which returns a Time object for the timestamp of the most recent completed MDM command. This is useful for MobileDevices, which don't have anything like the `last_checkin` value for comptuers, indicating real communication between the device and Jamf Pro.
10
+ Note that the `last_inventory_update` value does NOT indicate such communication, since that timestamp is updated when values are changed via the API
11
+
12
+ - All APIObject Subclasses (Policy, Computer, MobileDevice, ComputerGroup, etc..) now have `get_raw`, `post_raw` & `put_raw` class methods, which are simpler wrappers for APIConnection#get_rsrc, #post_rsrc, and #put_rsrc.
13
+ - `get_raw` takes an object's id, and returns the 'raw' JSON (parsed into a ruby Hash with symbolized keys) or a REXML::Document (from which you'll probably want to use the `root` element). If you pass `as_string: true` you'll get the un-parsed JSON or XML string directly from the API
14
+ This can be useful when you need to retrieve the full object, to get some data not available in the summary-list, but instantiating the full ruby class is too slow.
15
+ - `post_raw` & `put_raw` can send raw XML to the API without instantiating objects. In some cases, where you're making simple changes to simple XML, this can be faster than fetching a full instance and the re-saving it.
16
+ WARNING You must create or acquire the XML to be sent, and no validation will be performed on it. It must be a String of XML, or something that returns such a string with #to_s, such as a REXML::Document, or a REXML::Element.
17
+
18
+ - APIConnection#get_rsrc now takes the boolean raw_json: parameter (defaults to false). If true, the raw JSON string is
19
+ returned, not parsed into a Hash. When requesting XML, it already comes as a string.
20
+
21
+ - ExtensionAttribute#attribute_mapping getter & setter for EAs that have the 'LDAP Attribute Mapping' input type.
22
+
23
+ ### Fixed
24
+ - DB_CNX.valid_server? now specifies utf8 charset, needed for newer versions of mysql.
25
+
26
+ ### Changed
27
+ - Cleaned up and modernized ExtensionAttribute and its subclasses. Marked a few things as deprecated: recon_display, scripting_language, and platform, all in ComputerExtensionAttribute.
28
+ - Better error message when using `APIObject.fetch :random` on a subclass with no objects
29
+
7
30
  ## \[1.2.0] - 2019-10-17
8
31
  ### Added
9
32
  - APIConnection#flushcache can be used to flush all cached data, or just for specific APIObject lists or ExtensionAttribute definitions. This is now used more often throughout ruby-jss.
@@ -327,6 +327,9 @@ module JSS
327
327
  # These classes are extendable, and may need cache flushing for EA definitions
328
328
  EXTENDABLE_CLASSES = [JSS::Computer, JSS::MobileDevice, JSS::User].freeze
329
329
 
330
+ # values for the format param of get_rsrc
331
+ GET_FORMATS = %i[json xml].freeze
332
+
330
333
  # Attributes
331
334
  #####################################
332
335
 
@@ -546,36 +549,40 @@ module JSS
546
549
  # after the 'JSSResource/' ) The resource must be properly URL escaped
547
550
  # beforehand. Note: URL.encode is deprecated, use CGI.escape
548
551
  #
549
- # By default we get the data in JSON, and parse it
550
- # into a ruby data structure (arrays, hashes, strings, etc)
552
+ # By default we get the data in JSON, and parse it into a ruby Hash
551
553
  # with symbolized Hash keys.
552
554
  #
555
+ # If the second parameter is :xml then the XML version is retrieved and
556
+ # returned as a String.
557
+ #
558
+ # To get the raw JSON string as it comes from the API, pass raw_json: true
559
+ #
553
560
  # @param rsrc[String] the resource to get
554
561
  # (the part of the API url after the 'JSSResource/' )
555
562
  #
556
563
  # @param format[Symbol] either ;json or :xml
557
- # If the second argument is :xml, the XML data is returned as a String.
564
+ # If the second argument is :xml, the XML data is returned as a String.
565
+ #
566
+ # @param raw_json[Boolean] When GETting JSON, return the raw unparsed string
567
+ # (the XML is always returned as a raw string)
558
568
  #
559
569
  # @return [Hash,String] the result of the get
560
570
  #
561
- def get_rsrc(rsrc, format = :json)
562
- # puts object_id
571
+ def get_rsrc(rsrc, format = :json, raw_json: false)
563
572
  validate_connected
564
-
565
- raise JSS::InvalidDataError, 'format must be :json or :xml' unless %i[json xml].include? format
573
+ raise JSS::InvalidDataError, 'format must be :json or :xml' unless GET_FORMATS.include? format
566
574
 
567
575
  begin
568
576
  @last_http_response = @cnx[rsrc].get(accept: format)
577
+ return JSON.parse(@last_http_response.body, symbolize_names: true) if format == :json && !raw_json
578
+
569
579
  @last_http_response.body
570
580
  rescue RestClient::ExceptionWithResponse => e
571
581
  handle_http_error e
572
582
  end
573
- # TODO: make sure we're returning the String version of the
574
- # response (i.e. its body) here and in POST, PUT, DELETE.
575
- format == :json ? JSON.parse(@last_http_response, symbolize_names: true) : @last_http_response
576
583
  end
577
584
 
578
- # Change an existing JSS resource
585
+ # Update an existing JSS resource
579
586
  #
580
587
  # @param rsrc[String] the API resource being changed, the URL part after 'JSSResource/'
581
588
  #
@@ -1219,7 +1226,7 @@ module JSS
1219
1226
  raise exception
1220
1227
  when RestClient::Conflict
1221
1228
  err = JSS::ConflictError
1222
- msg_matcher = /<p>Error:(.*)(<|$)/m
1229
+ msg_matcher = /<p>Error:(.*?)(<|$)/m
1223
1230
  when RestClient::BadRequest
1224
1231
  err = JSS::BadRequestError
1225
1232
  msg_matcher = %r{>Bad Request</p>\n<p>(.*?)</p>\n<p>You can get technical detail}m
@@ -1227,7 +1234,7 @@ module JSS
1227
1234
  raise
1228
1235
  else
1229
1236
  err = JSS::APIRequestError
1230
- msg_matcher = %r{<body.*?>(.*)</body>}m
1237
+ msg_matcher = %r{<body.*?>(.*?)</body>}m
1231
1238
  end
1232
1239
  exception.http_body =~ msg_matcher
1233
1240
  msg = Regexp.last_match(1)
@@ -809,7 +809,12 @@ module JSS
809
809
  end
810
810
 
811
811
  # a random object?
812
- return new id: all.sample[:id], api: api if searchterm == :random
812
+ if searchterm == :random
813
+ rnd_thing = all.sample
814
+ raise JSS::NoSuchItemError, "No #{self::RSRC_LIST_KEY} found" unless rnd_thing
815
+
816
+ return new id: rnd_thing[:id], api: api
817
+ end
813
818
 
814
819
  # get the lookup key and value, if given
815
820
  fetch_key, fetch_val = args.to_a.first
@@ -851,6 +856,105 @@ module JSS
851
856
  end
852
857
  end # fetch
853
858
 
859
+ # Fetch the mostly- or fully-raw JSON or XML data for an object of this
860
+ # subclass.
861
+ #
862
+ # By default, returns the JSON data parsed into a Hash.
863
+ #
864
+ # When format: is anything but :json, returns the XML data parsed into
865
+ # a REXML::Document
866
+ #
867
+ # When as_string: is truthy, returns an unparsed JSON String (or XML String
868
+ # if format: is not :json) as it comes directly from the API.
869
+ #
870
+ # When fetching raw JSON, the returned Hash will have its keys symbolized.
871
+ #
872
+ # This can be substantialy faster than instantiating, especially when you don't need
873
+ # all the ruby goodness of a full instance, but just want a few values for
874
+ # an object that aren't available in the `all` data
875
+ #
876
+ # This is really just a wrapper around {APIConnection.get_rsrc} that
877
+ # automatically fills in the RSRC::BASE value for you.
878
+ #
879
+ # @param id [Integer] the id of the object to fetch
880
+ #
881
+ # @param format[Symbol] :json or :xml, defaults to :json
882
+ #
883
+ # @param as_string[Boolean] return the raw JSON or XML string as it comes
884
+ # from the API, do not parse into a Hash or REXML::Document
885
+ #
886
+ # @param api[JSS::APIConnection] the connection thru which to fetch this
887
+ # object. Defaults to the deault API connection in JSS.api
888
+ #
889
+ # @return [Hash, REXML::Document, String] the raw data for the object
890
+ #
891
+ def self.get_raw(id, format: :json, as_string: false, api: JSS.api)
892
+ validate_not_metaclass(self)
893
+ rsrc = "#{self::RSRC_BASE}/id/#{id}"
894
+ data = api.get_rsrc rsrc, format, raw_json: as_string
895
+ return data if format == :json || as_string
896
+
897
+ REXML::Document.new(data)
898
+ rescue RestClient::NotFound
899
+ raise JSS::NoSuchItemError, "No #{self} with id #{id}"
900
+ end
901
+
902
+ # PUT some raw XML to the API for a given id in this subclass.
903
+ #
904
+ # WARNING: You must create or acquire the XML to be sent, and no validation
905
+ # will be performed on it. It must be a String, or something that returns
906
+ # an XML string with #to_s, such as a REXML::Document, or
907
+ # a REXML::Element.
908
+ #
909
+ # In some cases, where you're making simple changes to simple XML,
910
+ # this can be faster than fetching a full instance and the re-saving it.
911
+ #
912
+ # This is really just a wrapper around {APIConnection.put_rsrc} that
913
+ # automatically fills in the RSRC::BASE value for you.
914
+ #
915
+ # @param id [Integer] the id of the object to PUT
916
+ #
917
+ # @param xml [String, #to_s] The XML to send
918
+ #
919
+ # @param api[JSS::APIConnection] the connection thru which to fetch this
920
+ # object. Defaults to the deault API connection in JSS.api
921
+ #
922
+ # @return [REXML::Document] the XML response from the API
923
+ #
924
+ def self.put_raw(id, xml, api: JSS.api)
925
+ validate_not_metaclass(self)
926
+ rsrc = "#{self::RSRC_BASE}/id/#{id}"
927
+ REXML::Document.new(api.put_rsrc rsrc, xml.to_s)
928
+ rescue RestClient::NotFound
929
+ raise JSS::NoSuchItemError, "No #{self} with id #{id}"
930
+ end
931
+
932
+ # POST some raw XML to the API for a given id in this subclass.
933
+ #
934
+ # WARNING: You must create or acquire the XML to be sent, and no validation
935
+ # will be performed on it. It must be a String, or something that returns
936
+ # an XML string with #to_s, such as a REXML::Document, or
937
+ # a REXML::Element.
938
+ #
939
+ # This probably isn't as much of a speed gain as get_raw or put_raw, as
940
+ # opposed to instantiating a ruby object, but might still be useful.
941
+ #
942
+ # This is really just a wrapper around {APIConnection.post_rsrc} that
943
+ # automatically fills in the RSRC::BASE value for you.
944
+ #
945
+ # @param xml [String, #to_s] The XML to send
946
+ #
947
+ # @param api[JSS::APIConnection] the connection thru which to fetch this
948
+ # object. Defaults to the deault API connection in JSS.api
949
+ #
950
+ # @return [REXML::Document] the XML response from the API
951
+ #
952
+ def self.post_raw( xml, api: JSS.api)
953
+ validate_not_metaclass(self)
954
+ rsrc = "#{self::RSRC_BASE}/id/-1"
955
+ REXML::Document.new(api.post_rsrc rsrc, xml.to_s)
956
+ end
957
+
854
958
  # Make a ruby instance of a not-yet-existing APIObject.
855
959
  #
856
960
  # This is how to create new objects in the JSS. A name: must be provided,
@@ -20,10 +20,7 @@
20
20
  # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21
21
  # KIND, either express or implied. See the Apache License for the specific
22
22
  # language governing permissions and limitations under the Apache License.
23
- #
24
- #
25
23
 
26
- #
27
24
  module JSS
28
25
 
29
26
  # Classes
@@ -89,20 +86,33 @@ module JSS
89
86
  INPUT_TYPE_SCRIPT = 'script'.freeze
90
87
  INPUT_TYPE_LDAP = 'LDAP Attribute Mapping'.freeze
91
88
 
92
- INPUT_TYPES = [INPUT_TYPE_FIELD, INPUT_TYPE_POPUP, INPUT_TYPE_SCRIPT, INPUT_TYPE_LDAP].freeze
89
+ INPUT_TYPES = [
90
+ INPUT_TYPE_FIELD,
91
+ INPUT_TYPE_POPUP,
92
+ INPUT_TYPE_SCRIPT,
93
+ INPUT_TYPE_LDAP
94
+ ].freeze
95
+
93
96
  DEFAULT_INPUT_TYPE = INPUT_TYPE_FIELD
94
97
 
95
98
  # Where can it be displayed in the WebApp?
96
- # subclasses can add to this list
99
+ WEB_DISPLAY_CHOICE_GENERAL = 'General'.freeze
100
+ WEB_DISPLAY_CHOICE_OS = 'Operating System'.freeze
101
+ WEB_DISPLAY_CHOICE_HW = 'Hardware'.freeze
102
+ WEB_DISPLAY_CHOICE_USER_LOC = 'User and Location'.freeze
103
+ WEB_DISPLAY_CHOICE_PURCHASING = 'Purchasing'.freeze
104
+ WEB_DISPLAY_CHOICE_EAS = 'Extension Attributes'.freeze
105
+
97
106
  WEB_DISPLAY_CHOICES = [
98
- 'General',
99
- 'Operating System',
100
- 'Hardware',
101
- 'User and Location',
102
- 'Purchasing',
103
- 'Extension Attributes'
107
+ WEB_DISPLAY_CHOICE_GENERAL,
108
+ WEB_DISPLAY_CHOICE_OS,
109
+ WEB_DISPLAY_CHOICE_HW,
110
+ WEB_DISPLAY_CHOICE_USER_LOC,
111
+ WEB_DISPLAY_CHOICE_PURCHASING,
112
+ WEB_DISPLAY_CHOICE_EAS
104
113
  ].freeze
105
- DEFAULT_WEB_DISPLAY_CHOICE = 'Extension Attributes'.freeze
114
+
115
+ DEFAULT_WEB_DISPLAY_CHOICE = WEB_DISPLAY_CHOICE_EAS
106
116
 
107
117
  LAST_RECON_FIELD = 'Last Inventory Update'.freeze
108
118
  LAST_RECON_FIELD_SYM = LAST_RECON_FIELD.tr(' ', '_').to_sym
@@ -117,6 +127,7 @@ module JSS
117
127
 
118
128
  # @return [String] description of the ext attrib
119
129
  attr_reader :description
130
+ alias desc description
120
131
 
121
132
  # @return [String] the type of data created by the EA. Must be one of DATA_TYPES
122
133
  attr_reader :data_type
@@ -127,14 +138,15 @@ module JSS
127
138
  # @return [Array<String>] the choices available in the UI when the @input_type is "Pop-up Menu"
128
139
  attr_reader :popup_choices
129
140
 
141
+ # @return [String] the LDAP attribute for the User's ldap entry that maps to this EA, when input type is INPUT_TYPE_LDAP
142
+ attr_reader :attribute_mapping
143
+
130
144
  # @return [String] In which part of the web UI does the data appear?
131
145
  attr_reader :web_display
132
146
 
133
-
134
147
  # Constructor
135
148
  ###################################
136
149
 
137
-
138
150
  # @see JSS::APIObject#initialize
139
151
  #
140
152
  def initialize(args = {})
@@ -142,19 +154,30 @@ module JSS
142
154
 
143
155
  # @init_data now has the raw data
144
156
  # so fill in our attributes or set defaults
145
-
146
157
  @description = @init_data[:description]
147
158
  @data_type = @init_data[:data_type] || DEFAULT_DATA_TYPE
148
- @web_display = @init_data[:inventory_display] || DEFAULT_WEB_DISPLAY_CHOICE
149
159
 
150
160
  if @init_data[:input_type]
151
- @input_type = @init_data[:input_type][:type] || DEFAULT_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]
152
166
  @popup_choices = @init_data[:input_type][:popup_choices]
153
167
  # popups can always contain blank
154
168
  @popup_choices << JSS::BLANK if @popup_choices
155
- else
156
- @input_type = DEFAULT_INPUT_TYPE
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]
157
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
158
181
 
159
182
  # the name of the EA might have spaces and caps, which the will come to us as symbols with the spaces
160
183
  # as underscores, like this.
@@ -167,19 +190,16 @@ module JSS
167
190
  # @see JSS::Creatable#create
168
191
  #
169
192
  def create
170
- if @input_type == INPUT_TYPE_POPUP
171
- raise MissingDataError, 'No popup_choices set for Pop-up Menu input_type.' unless @popup_choices.is_a?(Array) && !@popup_choices.empty?
172
- end
193
+ validate_for_save
173
194
  super
174
195
  end
175
196
 
176
197
  # @see JSS::Updatable#update
177
198
  #
178
199
  def update
179
- if @input_type == INPUT_TYPE_POPUP
180
- raise MissingDataError, 'No popup_choices set for Pop-up Menu input_type.' unless @popup_choices.is_a?(Array) && !@popup_choices.empty?
181
- end
200
+ validate_for_save
182
201
  super
202
+ # this flushes the cached EA itself, used for validating ea values.
183
203
  @api.flushcache self.class
184
204
  end
185
205
 
@@ -222,10 +242,12 @@ module JSS
222
242
  # @return [void]
223
243
  #
224
244
  def description=(new_val)
225
- return nil if @description == new_val
245
+ new_val = new_val.to_s
246
+ return if @description == new_val
247
+
226
248
  @description = new_val
227
249
  @need_to_update = true
228
- end # name=(newname)
250
+ end # name=(newname)
229
251
 
230
252
  # Change the data type of this EA
231
253
  #
@@ -234,24 +256,28 @@ module JSS
234
256
  # @return [void]
235
257
  #
236
258
  def data_type=(new_val)
237
- return nil if @data_type == new_val
259
+ return if @data_type == new_val
238
260
  raise JSS::InvalidDataError, "data_type must be a string, one of: '#{DATA_TYPES.join("', '")}'" unless DATA_TYPES.include? new_val
261
+
239
262
  @data_type = new_val
240
263
  @need_to_update = true
241
- end #
264
+ end
242
265
 
243
266
  # Change the inventory_display of this EA
244
267
  #
245
- # @param new_val[String] the new value, which must be a member of INVENTORY_DISPLAY_CHOICES
268
+ # @param new_val[String] the new value, which must be a member of WEB_DISPLAY_CHOICES
246
269
  #
247
270
  # @return [void]
248
271
  #
249
272
  def web_display=(new_val)
250
- return nil if @web_display == new_val
251
- raise JSS::InvalidDataError, "inventory_display must be a string, one of: #{INVENTORY_DISPLAY_CHOICES.join(', ')}" unless WEB_DISPLAY_CHOICES.include? new_val
273
+ return if @web_display == new_val
274
+ unless WEB_DISPLAY_CHOICES.include? new_val
275
+ raise JSS::InvalidDataError, "inventory_display must be a string, one of: #{WEB_DISPLAY_CHOICES.join(', ')}"
276
+ end
277
+
252
278
  @web_display = new_val
253
279
  @need_to_update = true
254
- end #
280
+ end
255
281
 
256
282
  # Change the input type of this EA
257
283
  #
@@ -260,17 +286,39 @@ module JSS
260
286
  # @return [void]
261
287
  #
262
288
  def input_type=(new_val)
263
- return nil if @input_type == new_val
289
+ return if @input_type == new_val
264
290
  raise JSS::InvalidDataError, "input_type must be a string, one of: #{INPUT_TYPES.join(', ')}" unless INPUT_TYPES.include? new_val
291
+
265
292
  @input_type = new_val
266
- @popup_choices = nil if @input_type == INPUT_TYPE_FIELD
293
+ case @input_type
294
+ when INPUT_TYPE_FIELD
295
+ @script = nil
296
+ @scripting_language = nil
297
+ @platform = nil
298
+ @popup_choices = nil
299
+ @attribute_mapping = nil
300
+ when INPUT_TYPE_POPUP
301
+ @script = nil
302
+ @scripting_language = nil
303
+ @platform = nil
304
+ @attribute_mapping = nil
305
+ when INPUT_TYPE_SCRIPT
306
+ @popup_choices = nil
307
+ @attribute_mapping = nil
308
+ when INPUT_TYPE_LDAP
309
+ @script = nil
310
+ @scripting_language = nil
311
+ @platform = nil
312
+ @popup_choices = nil
313
+ end # case
314
+
267
315
  @need_to_update = true
268
- end #
316
+ end
269
317
 
270
318
  # Change the Popup Choices of this EA
271
319
  # New value must be an Array, the items will be converted to Strings.
272
320
  #
273
- # This automatically sets input_type to "Pop-up Menu"
321
+ # This automatically sets input_type to INPUT_TYPE_POPUP
274
322
  #
275
323
  # Values are checked to ensure they match the @data_type
276
324
  # Note, Dates must be in the format "YYYY-MM-DD hh:mm:ss"
@@ -280,7 +328,7 @@ module JSS
280
328
  # @return [void]
281
329
  #
282
330
  def popup_choices=(new_val)
283
- return nil if @popup_choices == new_val
331
+ return if @popup_choices == new_val
284
332
  raise JSS::InvalidDataError, 'popup_choices must be an Array' unless new_val.is_a?(Array)
285
333
 
286
334
  # convert each one to a String,
@@ -295,10 +343,29 @@ module JSS
295
343
  end
296
344
  v
297
345
  end
346
+
298
347
  self.input_type = INPUT_TYPE_POPUP
299
348
  @popup_choices = new_val
300
349
  @need_to_update = true
301
- end #
350
+ end
351
+
352
+ # Change the LDAP Attribute Mapping of this EA
353
+ # New value must be a String, or respond correctly to #to_s
354
+ #
355
+ # This automatically sets input_type to INPUT_TYPE_LDAP
356
+ #
357
+ # @param new_val[String, #to_s] the new value
358
+ #
359
+ # @return [void]
360
+ #
361
+ def attribute_mapping=(new_mapping)
362
+ new_mapping = JSS::Validate.non_empty_string new_mapping.to_s
363
+ return if new_mapping == @attribute_mapping
364
+
365
+ self.input_type = INPUT_TYPE_LDAP
366
+ @attribute_mapping = new_mapping
367
+ @need_to_update = true
368
+ end
302
369
 
303
370
  # Get an Array of Hashes for all inventory objects
304
371
  # with a desired result in their latest report for this EA.
@@ -322,7 +389,10 @@ module JSS
322
389
  #
323
390
  def all_with_result(search_type, desired_value)
324
391
  raise JSS::NoSuchItemError, "EA Not In JSS! Use #create to create this #{self.class::RSRC_OBJECT_KEY}." unless @in_jss
325
- raise JSS::InvalidDataError, 'Invalid search_type, see JSS::Criteriable::Criterion::SEARCH_TYPES' unless JSS::Criteriable::Criterion::SEARCH_TYPES.include? search_type.to_s
392
+ unless JSS::Criteriable::Criterion::SEARCH_TYPES.include? search_type.to_s
393
+ raise JSS::InvalidDataError, 'Invalid search_type, see JSS::Criteriable::Criterion::SEARCH_TYPES'
394
+ end
395
+
326
396
  begin
327
397
  search_class = self.class::TARGET_CLASS::SEARCH_CLASS
328
398
  acs = search_class.make api: @api, name: "ruby-jss-EA-result-search-#{Time.now.to_jss_epoch}"
@@ -335,13 +405,14 @@ module JSS
335
405
  results = []
336
406
 
337
407
  acs.search_results.each do |i|
338
- value = case @data_type
339
- when 'Date' then JSS.parse_datetime i[@symbolized_name]
340
- when 'Integer' then i[@symbolized_name].to_i
341
- else i[@symbolized_name]
342
- end # case
408
+ value =
409
+ case @data_type
410
+ when 'Date' then JSS.parse_datetime i[@symbolized_name]
411
+ when 'Integer' then i[@symbolized_name].to_i
412
+ else i[@symbolized_name]
413
+ end # case
343
414
  results << { id: i[:id], name: i[:name], value: value }
344
- end
415
+ end # each
345
416
  ensure
346
417
  acs.delete if acs.is_a? self.class::TARGET_CLASS::SEARCH_CLASS
347
418
  end
@@ -375,6 +446,7 @@ module JSS
375
446
  #
376
447
  def latest_values
377
448
  raise JSS::NoSuchItemError, "EA Not In JSS! Use #create to create this #{self.class::RSRC_OBJECT_KEY}." unless @in_jss
449
+
378
450
  tmp_advsrch = "ruby-jss-EA-latest-search-#{Time.now.to_jss_epoch}"
379
451
 
380
452
  begin
@@ -391,11 +463,12 @@ module JSS
391
463
  results = []
392
464
 
393
465
  acs.search_results.each do |i|
394
- value = case @data_type
395
- when 'Date' then JSS.parse_datetime i[@symbolized_name]
396
- when 'Integer' then i[@symbolized_name].to_i
397
- else i[@symbolized_name]
398
- end # case
466
+ value =
467
+ case @data_type
468
+ when 'Date' then JSS.parse_datetime i[@symbolized_name]
469
+ when 'Integer' then i[@symbolized_name].to_i
470
+ else i[@symbolized_name]
471
+ end # case
399
472
 
400
473
  as_of = Time.parse(i[LAST_RECON_FIELD_SYM]) if i[LAST_RECON_FIELD_SYM] != ''
401
474
 
@@ -404,42 +477,66 @@ module JSS
404
477
  ensure
405
478
  if defined? acs
406
479
  acs.delete if acs
407
- else
408
- search_class.fetch(name: tmp_advsrch, api: @api).delete if search_class.all_names(:refresh, api: @api).include? tmp_advsrch
480
+ elsif search_class.all_names(:refresh, api: @api).include? tmp_advsrch
481
+ search_class.fetch(name: tmp_advsrch, api: @api).delete
409
482
  end
410
483
  end
411
484
 
412
485
  results
413
486
  end
414
487
 
415
- # aliases
416
-
417
- alias desc description
418
-
419
488
  # Private Instance Methods
420
489
  ###################
421
490
 
422
491
  private
423
492
 
424
- #
425
- # Return a REXML object for this ext attr, with the current values.
426
- # Subclasses should augment this in their rest_xml methods
427
- # then return it .to_s, for saving or updating
428
- #
429
- def rest_rexml
493
+ # make sure things are OK for saving to Jamf
494
+ def validate_for_save
495
+ case @input_type
496
+ when INPUT_TYPE_POPUP
497
+ raise MissingDataError, "No popup_choices set for input type: #{INPUT_TYPE_POPUP}" unless @popup_choices.is_a?(Array) && !@popup_choices.empty?
498
+ when INPUT_TYPE_LDAP
499
+ raise MissingDataError, "No attribute_mapping set for input type: #{INPUT_TYPE_LDAP}" if @attribute_mapping.to_s.empty?
500
+ when INPUT_TYPE_SCRIPT
501
+ raise MissingDataError, "No script set for input_type: #{INPUT_TYPE_SCRIPT}" unless @script
502
+
503
+ # Next two lines DEPRECATED
504
+ @platform ||= 'Mac'
505
+ raise MissingDataError, "No scripting_language set for Windows '#{INPUT_TYPE_SCRIPT}' input_type." if @platform == 'Windows' && !@scripting_language
506
+ end
507
+ end
508
+
509
+ # Return a REXML doc string for this ext attr, with the current values.
510
+ def rest_xml
430
511
  ea = REXML::Element.new self.class::RSRC_OBJECT_KEY.to_s
431
512
  ea.add_element('name').text = @name
432
- ea.add_element('description').text = @description
513
+ ea.add_element('description').text = @description if @description
433
514
  ea.add_element('data_type').text = @data_type
434
- ea.add_element('inventory_display').text = @web_display
515
+
516
+ unless self.class == JSS::UserExtensionAttribute
517
+ ea.add_element('inventory_display').text = @web_display if @web_display
518
+ ea.add_element('recon_display').text = @recon_display if @recon_display
519
+ end
435
520
 
436
521
  it = ea.add_element('input_type')
437
522
  it.add_element('type').text = @input_type
438
- if @input_type == 'Pop-up Menu'
523
+
524
+ case @input_type
525
+ when INPUT_TYPE_POPUP
439
526
  pcs = it.add_element('popup_choices')
440
- @popup_choices.each { |pc| pcs.add_element('choice').text = pc }
527
+ @popup_choices.each { |pc| pcs.add_element('choice').text = pc } if @popup_choices.is_a? Array
528
+ when INPUT_TYPE_LDAP
529
+ it.add_element('attribute_mapping').text = @attribute_mapping
530
+ when INPUT_TYPE_SCRIPT
531
+ ea.add_element('enabled').text = @enabled ? 'true' : 'false'
532
+ it.add_element('script').text = @script
533
+ it.add_element('platform').text = @platform || 'Mac'
534
+ it.add_element('scripting_language').text = @scripting_language if @scripting_language
441
535
  end
442
- ea
536
+
537
+ doc = REXML::Document.new APIConnection::XML_HEADER
538
+ doc << ea
539
+ doc.to_s
443
540
  end # rest xml
444
541
 
445
542
  end # class ExtAttrib