ruby-jss 0.14.0 → 1.0.0b2

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.

@@ -26,13 +26,30 @@
26
26
  ###
27
27
  module JSS
28
28
 
29
- # This is a stub - Patch data in the API is still borked.
30
- # Waiting for fixes from Jamf.
29
+ # An 'Internal' patch source. These sources are defined by
30
+ # Jamf themselves, as a part of the JSS, and cannot be created, modified
31
+ # or deleted.
31
32
  #
32
33
  # @see JSS::APIObject
33
34
  #
34
- class Patch < JSS::APIObject
35
+ class PatchInternalSource < JSS::PatchSource
35
36
 
36
- end # class Patch
37
+ # Constants
38
+ #####################################
39
+
40
+ ### The base for REST resources of this class
41
+ RSRC_BASE = 'patchinternalsources'.freeze
42
+
43
+ ### the hash key used for the JSON list output of all objects in the JSS
44
+ RSRC_LIST_KEY = :patch_internal_sources
45
+
46
+ # The hash key used for the JSON object output.
47
+ # It's also used in various error messages
48
+ RSRC_OBJECT_KEY = :patch_internal_source
49
+
50
+ ### these keys, as well as :id and :name, are present in valid API JSON data for this class
51
+ VALID_DATA_KEYS = %i[enabled endpoint].freeze
52
+
53
+ end # class PatchInternalSource
37
54
 
38
55
  end # module JSS
@@ -0,0 +1,480 @@
1
+ ### Copyright 2018 Pixar
2
+
3
+ ###
4
+ ### Licensed under the Apache License, Version 2.0 (the "Apache License")
5
+ ### with the following modification; you may not use this file except in
6
+ ### compliance with the Apache License and the following modification to it:
7
+ ### Section 6. Trademarks. is deleted and replaced with:
8
+ ###
9
+ ### 6. Trademarks. This License does not grant permission to use the trade
10
+ ### names, trademarks, service marks, or product names of the Licensor
11
+ ### and its affiliates, except as required to comply with Section 4(c) of
12
+ ### the License and to reproduce the content of the NOTICE file.
13
+ ###
14
+ ### You may obtain a copy of the Apache License at
15
+ ###
16
+ ### http://www.apache.org/licenses/LICENSE-2.0
17
+ ###
18
+ ### Unless required by applicable law or agreed to in writing, software
19
+ ### distributed under the Apache License with the above modification is
20
+ ### distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21
+ ### KIND, either express or implied. See the Apache License for the specific
22
+ ### language governing permissions and limitations under the Apache License.
23
+ ###
24
+ ###
25
+
26
+ ###
27
+ module JSS
28
+
29
+ # An active Patch Software Title in the JSS.
30
+ #
31
+ # This class provides access to titles that have been added to Jamf Pro
32
+ # via a PatchInternalSource or a PatchExternalSource, and the versions
33
+ # contained therein.
34
+ #
35
+ # Patch versions for the title are available in the #versions read-only
36
+ # attribute, a Hash of versions keyed by the version string. The values are
37
+ # JSS::PatchTitle::Version objects.
38
+ #
39
+ # Use the patch_report method on the PatchTitle class, an instance of it, or
40
+ # a PatchTitle::Version, to retrieve a report of computers with a
41
+ # specific version of the title installed, or :all, :latest, or :unknown
42
+ # versions. Reports called on the class or an instance default to :all
43
+ # versions, and are slower to retrieve than a specific version,
44
+ #
45
+ # @see JSS::APIObject
46
+ #
47
+ class PatchTitle < JSS::APIObject
48
+
49
+ include JSS::Sitable
50
+ include JSS::Categorizable
51
+ include JSS::Creatable
52
+ include JSS::Updatable
53
+
54
+ # TODO: remove this and adjust parsing when jamf fixes the JSON
55
+ # Data map for PatchTitle XML data parsing cuz Borked JSON
56
+ # @see {JSS::XMLWorkaround} for details
57
+ USE_XML_WORKAROUND = {
58
+ patch_software_title: {
59
+ id: -1,
60
+ name: JSS::BLANK,
61
+ name_id: JSS::BLANK,
62
+ source_id: -1,
63
+ notifications: {
64
+ email_notification: nil,
65
+ web_notification: nil
66
+ },
67
+ category: {
68
+ id: -1,
69
+ name: JSS::BLANK
70
+ },
71
+ site: {
72
+ id: -1,
73
+ name: JSS::BLANK
74
+ },
75
+ versions: [
76
+ {
77
+ software_version: JSS::BLANK,
78
+ package: {
79
+ id: -1,
80
+ name: JSS::BLANK
81
+ }
82
+ }
83
+ ]
84
+ }
85
+ }.freeze
86
+
87
+ # TODO: remove this and adjust parsing when jamf fixes the JSON
88
+ # Data map for PatchReport XML data parsing cuz Borked JSON
89
+ # @see {JSS::XMLWorkaround} for details
90
+ PATCH_REPORT_DATA_MAP = {
91
+ patch_report: {
92
+ name: JSS::BLANK,
93
+ patch_software_title_id: -1,
94
+ total_computers: 0,
95
+ total_versions: 0,
96
+ versions: [
97
+ {
98
+ software_version: JSS::BLANK,
99
+ computers: [
100
+ {
101
+ id: -1,
102
+ name: JSS::BLANK,
103
+ mac_address: JSS::BLANK,
104
+ alt_mac_address: JSS::BLANK,
105
+ serial_number: JSS::BLANK
106
+ }
107
+ ]
108
+ }
109
+ ]
110
+ }
111
+ }.freeze
112
+
113
+ ### The base for REST resources of this class
114
+ RSRC_BASE = 'patchsoftwaretitles'.freeze
115
+
116
+ ### the hash key used for the JSON list output of all objects in the JSS
117
+ RSRC_LIST_KEY = :patch_software_titles
118
+
119
+ # The hash key used for the JSON object output.
120
+ # It's also used in various error messages
121
+ RSRC_OBJECT_KEY = :patch_software_title
122
+
123
+ ### these keys, as well as :id and :name, are present in valid API JSON data for this class
124
+ VALID_DATA_KEYS = %i[notifications name_id source_id].freeze
125
+
126
+ # the object type for this object in
127
+ # the object history table.
128
+ # See {APIObject#add_object_history_entry}
129
+ # TODO: comfirm this in 10.4
130
+ OBJECT_HISTORY_OBJECT_TYPE = 604
131
+
132
+ SITE_SUBSET = :top
133
+
134
+ # Where is the Category in the API JSON?
135
+ CATEGORY_SUBSET = :top
136
+
137
+ # How is the category stored in the API data?
138
+ CATEGORY_DATA_TYPE = Hash
139
+
140
+ # when fetching a specific version, this is a valid version
141
+ LATEST_VERSION_ID = 'Latest'.freeze
142
+
143
+ # when fetching a specific version, this is a valid version
144
+ UNKNOWN_VERSION_ID = 'Unknown'.freeze
145
+
146
+ REPORTS_RSRC_BASE = '/patchreports/patchsoftwaretitleid'.freeze
147
+
148
+ # Class Methods
149
+ #######################################
150
+
151
+ # The same as @see APIObject.all but also takes an optional
152
+ # source_id: parameter, which limites the results to
153
+ # patch titles with the specified source_id.
154
+ #
155
+ # ALSO, JAMF BUG: More broken json - the id is coming as a string.
156
+ # so here we turn it into an integer manually :-(
157
+ # Ditto for source_id
158
+ #
159
+ def self.all(refresh = false, source_id: nil, api: JSS.api)
160
+ data = super refresh, api: api
161
+ data.each do |info|
162
+ info[:id] = info[:id].to_i
163
+ info[:source_id] = info[:source_id].to_i
164
+ end
165
+ return data unless source_id
166
+ data.select { |p| p[:source_id] == source_id }
167
+ end
168
+
169
+ # The same as @see APIObject.all_names but also takes an optional
170
+ # source_id: parameter, which limites the results to
171
+ # patch titles with the specified source_id.
172
+ #
173
+ def self.all_names(refresh = false, source_id: nil, api: JSS.api)
174
+ all(refresh, source_id: source_id, api: api).map { |i| i[:name] }
175
+ end
176
+
177
+ # The same as @see APIObject.all_ids but also takes an optional
178
+ # source_id: parameter, which limites the results to
179
+ # patch titles with the specified source_id.
180
+ #
181
+ def self.all_ids(refresh = false, source_id: nil, api: JSS.api)
182
+ all(refresh, source_id: source_id, api: api).map { |i| i[:id] }
183
+ end
184
+
185
+ # @return [Array<String>] all 'name_id' values for active patches
186
+ #
187
+ def self.all_name_ids(refresh = false, source_id: nil, api: JSS.api)
188
+ all(refresh, source_id: source_id, api: api).map { |i| i[:name_id] }
189
+ end
190
+
191
+ # Returns an Array of unique source_ids used by active Patches
192
+ #
193
+ # e.g. if there are patches that come from one internal source
194
+ # and two external sources this might return [1,3,4].
195
+ #
196
+ # Regardless of how many patches come from each source, the
197
+ # source id appears only once in this array.
198
+ #
199
+ # @param refresh[Boolean] should the data be re-queried from the API?
200
+ #
201
+ # @param api[JSS::APIConnection] an API connection to use for the query.
202
+ # Defaults to the corrently active API. See {JSS::APIConnection}
203
+ #
204
+ # @return [Array<Integer>] the ids of the patch sources used in the JSS
205
+ #
206
+ def self.all_source_ids(refresh = false, api: JSS.api)
207
+ all(refresh, api: api).map { |i| i[:source_id] }.sort.uniq
208
+ end
209
+
210
+ # Get a patch report for a softwaretitle, withouth fetching an instance.
211
+ # Defaults to reporting all versions. Specifiying a version will be faster.
212
+ #
213
+ # The Hash returned has 3 keys:
214
+ # - :total_comptuters [Integer] total computers found for the requested version(s)
215
+ # - :total versions [Integer] How many versions does this title have?
216
+ # Always 1 if you report a specific version
217
+ # - :versions [Hash {String => Array<Hash>}] Keys are the version(s) requested
218
+ # values are Arrays of Hashes, one per computer with the keyed version
219
+ # installed. Computer Hashes have identifiers as keys.
220
+ #
221
+ # PatchTitle#patch_report calls this method, as does
222
+ # PatchTitle::Version.patch_report.
223
+ #
224
+ # @param title[Integer, String] The name or id of the software title to
225
+ # report.
226
+ #
227
+ # @param version[String,Symbol] Limit the report to this version.
228
+ # Can be a string version number like '8.13.2' or :latest, :unknown,
229
+ # or :all. Defaults to :all
230
+ #
231
+ # @param api[JSS::APIConnection] an API connection to use for the query.
232
+ # Defaults to the corrently active API. See {JSS::APIConnection}
233
+ #
234
+ # @return [Hash] the patch report for the version(s) specified.
235
+ #
236
+ def self.patch_report(title, version: :all, api: JSS.api)
237
+ title_id = valid_id title, api: api
238
+ raise JSS::NoSuchItemError, "No PatchTitle matches '#{title}'" unless title_id
239
+
240
+ rsrc = patch_report_rsrc title_id, version
241
+
242
+ # TODO: remove this and adjust parsing when jamf fixes the JSON
243
+ raw_report = XMLWorkaround.data_via_xml(rsrc, PATCH_REPORT_DATA_MAP, api)[:patch_report]
244
+ report = {}
245
+ report[:total_computers] = raw_report[:total_computers]
246
+ report[:total_versions] = raw_report[:total_versions]
247
+
248
+ if raw_report[:versions].is_a? Hash
249
+ vs = raw_report[:versions][:version][:software_version].to_s
250
+ comps = raw_report[:versions][:version][:computers]
251
+ comps = [] if comps.empty?
252
+ report[:versions] = { vs => comps }
253
+ return report
254
+ end
255
+
256
+ report[:versions] = {}
257
+ raw_report[:versions].each do |v|
258
+ report[:versions][v[:software_version].to_s] = v[:computers].empty? ? [] : v[:computers]
259
+ end
260
+ report
261
+ end
262
+
263
+ # aliases of patch_report
264
+ singleton_class.send(:alias_method, :version_report, :patch_report)
265
+ singleton_class.send(:alias_method, :report, :patch_report)
266
+
267
+ # given a requested version, return the rest rsrc for getting
268
+ # a patch report for it.
269
+ def self.patch_report_rsrc(id, vers)
270
+ case vers
271
+ when :all
272
+ "#{REPORTS_RSRC_BASE}/#{id}"
273
+ when :latest
274
+ "#{REPORTS_RSRC_BASE}/#{id}/version/#{LATEST_VERSION_ID}"
275
+ when :unknown
276
+ "#{REPORTS_RSRC_BASE}/#{id}/version/#{UNKNOWN_VERSION_ID}"
277
+ else
278
+ "#{REPORTS_RSRC_BASE}/#{id}/version/#{vers}"
279
+ end
280
+ end
281
+ private_class_method :patch_report_rsrc
282
+
283
+ # for some reason, patch titles can't be fetched by name.
284
+ # only by id. SO, look up the id if given a name.
285
+ #
286
+ def self.fetch(id: nil, name: nil, api: JSS.api)
287
+ unless id
288
+ id = JSS::PatchTitle.map_all_ids_to(:name).invert[name]
289
+ raise NoSuchItemError, "No matching #{self::RSRC_OBJECT_KEY} found" unless id
290
+ end
291
+
292
+ super id: id, api: api
293
+ end
294
+
295
+ # Attributes
296
+ #####################################
297
+
298
+ # @return [String] the 'name_id' for this patch title. name_id is a unique
299
+ # identfier provided by the patch source
300
+ attr_reader :name_id
301
+
302
+ # @return [Integer] the id of the patch source from which we get patches
303
+ # for this title
304
+ attr_reader :source_id
305
+
306
+ # @return [Boolean] Are new patches announced in the JSS web ui?
307
+ attr_reader :web_notification
308
+ alias web_notification? web_notification
309
+
310
+ # @return [Boolean] Are new patches announced by email?
311
+ attr_reader :email_notification
312
+ alias email_notification? email_notification
313
+
314
+ # @return [Hash{String => JSS::PatchTitle::Version}] The JSS::PatchVersions fetched for
315
+ # this title, keyed by version string
316
+ attr_reader :versions
317
+
318
+ # PatchTitles may be fetched by name: or id:
319
+ #
320
+ def initialize(**args)
321
+ super
322
+
323
+ @name_id = @init_data[:name_id]
324
+ @source_id = @init_data[:source_id]
325
+
326
+ @init_data[:notifications] ||= {}
327
+ notifs = @init_data[:notifications]
328
+ @web_notification = notifs[:web_notification].nil? ? false : notifs[:web_notification]
329
+ @email_notification = notifs[:email_notification].nil? ? false : notifs[:email_notification]
330
+
331
+ @versions = {}
332
+ @init_data[:versions] ||= []
333
+ @init_data[:versions].each do |vers|
334
+ @versions[vers[:software_version]] = JSS::PatchTitle::Version.new(self, vers)
335
+ end # each do vers
336
+
337
+ @changed_pkgs = []
338
+ end
339
+
340
+ # @return [Hash] Subset of @versions, containing those which have packages
341
+ # assigned
342
+ #
343
+ def versions_with_packages
344
+ versions.select { |_ver_string, vers| vers.package_assigned? }
345
+ end
346
+
347
+ # Set email notifications on or off
348
+ #
349
+ # @param new_setting[Boolean] Should email notifications be on or off?
350
+ #
351
+ # @return [void]
352
+ #
353
+ def email_notification=(new_setting)
354
+ return if email_notification == new_setting
355
+ raise JSS::InvalidDataError, 'New Setting must be boolean true or false' unless JSS::TRUE_FALSE.include? @email_notification = new_setting
356
+ @need_to_update = true
357
+ end
358
+
359
+ # Set web notifications on or off
360
+ #
361
+ # @param new_setting[Boolean] Should email notifications be on or off?
362
+ #
363
+ # @return [void]
364
+ #
365
+ def web_notification=(new_setting)
366
+ return if web_notification == new_setting
367
+ raise JSS::InvalidDataError, 'New Setting must be boolean true or false' unless JSS::TRUE_FALSE.include? @web_notification = new_setting
368
+ @need_to_update = true
369
+ end
370
+
371
+ # this is called by JSS::PatchTitle::Version#package= to update @changed_pkgs which
372
+ # is used by #rest_xml to change the package assigned to a patch version
373
+ # in this title.
374
+ def changed_pkg_for_version(version)
375
+ @changed_pkgs << version
376
+ @need_to_update = true
377
+ end
378
+
379
+ def source_id=(new_id)
380
+ sid = JSS::PatchSource.valid_patch_source_id new_id
381
+ raise JSS::NoSuchItemError, "No active Patch Sources matche '#{new_id}'" unless sid
382
+ return if sid == source_id
383
+ @source_id = sid
384
+ @need_to_update = true
385
+ end
386
+
387
+ def name_id=(new_id)
388
+ return if new_id == name_id
389
+ raise JSS::MissingDataError, 'source_id must be set before setting name_id' if source_id.to_s.empty?
390
+ raise JSS::NoSuchItemError, "source_id #{source_id} doesn't offer name_id '#{new_id}'" unless JSS::PatchSource.available_name_ids(source_id).include? new_id
391
+ @name_id = new_id
392
+ @need_to_update = true
393
+ end
394
+
395
+ # wrapper to fetch versions after creating
396
+ def create
397
+ validate_for_saving
398
+ response = super
399
+ @versions = self.class.fetch(id: id).versions
400
+ response
401
+ end
402
+
403
+ # wrapper to clear @changed_pkgs after updating
404
+ def update
405
+ validate_for_saving
406
+ response = super
407
+ @changed_pkgs.clear
408
+ response
409
+ end
410
+
411
+ # Get a patch report for this title.
412
+ #
413
+ # See the class method JSS::PatchTitle.patch_report
414
+ #
415
+ def patch_report(vers = :all)
416
+ JSS::PatchTitle.patch_report id, version: vers, api: @api
417
+ end
418
+ alias version_report patch_report
419
+ alias report patch_report
420
+
421
+ # Remove the various cached data
422
+ # from the instance_variables used to create
423
+ # pretty-print (pp) output.
424
+ #
425
+ # @return [Array] the desired instance_variables
426
+ #
427
+ def pretty_print_instance_variables
428
+ vars = super
429
+ vars.delete :@versions
430
+ vars
431
+ end
432
+
433
+ #################################
434
+ private
435
+
436
+ def validate_for_saving
437
+ raise JSS::MissingDataError, 'PatchTitles must have valid source_id and name_id' if source_id.to_s.empty? || name_id.to_s.empty?
438
+ end
439
+
440
+ # Return the REST XML for this title, with the current values,
441
+ # for saving or updating.
442
+ #
443
+ def rest_xml
444
+ doc = REXML::Document.new # JSS::APIConnection::XML_HEADER
445
+ obj = doc.add_element RSRC_OBJECT_KEY.to_s
446
+
447
+ obj.add_element('name').text = name
448
+ obj.add_element('name_id').text = name_id
449
+ obj.add_element('source_id').text = source_id
450
+
451
+ notifs = obj.add_element 'notifications'
452
+ notifs.add_element('web_notification').text = web_notification?.to_s
453
+ notifs.add_element('email_notification').text = email_notification?.to_s
454
+
455
+ add_changed_pkg_xml obj unless @changed_pkgs.empty?
456
+
457
+ add_category_to_xml doc
458
+ add_site_to_xml doc
459
+
460
+ doc.to_s
461
+ end # rest_xml
462
+
463
+ # add xml for any package changes to patch versions
464
+ def add_changed_pkg_xml(obj)
465
+ versions_elem = obj.add_element 'versions'
466
+ @changed_pkgs.each do |vers|
467
+ velem = versions_elem.add_element 'version'
468
+ velem.add_element('software_version').text = vers.to_s
469
+ pkg = velem.add_element 'package'
470
+ # leave am empty package element to remove the pkg assignement
471
+ next if versions[vers].package_id == :none
472
+ pkg.add_element('id').text = versions[vers].package_id.to_s
473
+ end # do vers
474
+ end
475
+
476
+ end # class Patch
477
+
478
+ end # module JSS
479
+
480
+ require 'jss/api_object/patch_title/version'