ruby-jss 1.2.0 → 1.2.2

Sign up to get free protection for your applications and to get access to all the features.

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