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.

@@ -98,6 +98,12 @@ module JSS
98
98
  # Where is the Site data in the API JSON?
99
99
  SITE_SUBSET = :general
100
100
 
101
+ # Where is the Category in the API JSON?
102
+ CATEGORY_SUBSET = :general
103
+
104
+ # How is the category stored in the API data?
105
+ CATEGORY_DATA_TYPE = Hash
106
+
101
107
  # Attributes
102
108
  #####################################
103
109
 
@@ -97,6 +97,13 @@ module JSS
97
97
  # See {APIObject#add_object_history_entry}
98
98
  OBJECT_HISTORY_OBJECT_TYPE = 90
99
99
 
100
+ # Where is the Category in the API JSON?
101
+ CATEGORY_SUBSET = :top
102
+
103
+ # How is the category stored in the API data?
104
+ CATEGORY_DATA_TYPE = String
105
+
106
+
100
107
  ### Class Variables
101
108
  #####################################
102
109
 
@@ -118,7 +125,7 @@ module JSS
118
125
  ### @return [Boolean] does this pkg also get install in the OS user homedir template
119
126
  attr_reader :fill_user_template
120
127
 
121
- ### @return [Boolean] does this item require a reboot after installation? If so, it'll be a puppy-install in d3
128
+ ### @return [Boolean] does this item require a reboot after installation?
122
129
  attr_reader :reboot_required
123
130
 
124
131
  ### @return [Array<String>] the OS versions this can be installed onto. For all minor versions, the format is 10.5.x
@@ -26,13 +26,599 @@
26
26
  ###
27
27
  module JSS
28
28
 
29
- # This is a stub - Patch Policy data in the API is still borked.
30
- # Waiting for fixes from Jamf.
29
+ # A Patch Policy in the JSS
30
+ #
31
+ # When making new Patch Polices :patch_title and :target_version must be
32
+ # provided as well as :name.
33
+ #
34
+ # :patch_title is the name or id of a currently active patch title
35
+ #
36
+ # :target_version is the string identfier of an available version of
37
+ # the title. The target version MUST have a package assigned to it.
38
+ #
39
+ # See {JSS::PatchTitle} and {JSS::PatchSource.available_titles} for methods
40
+ # to acquire such info.
31
41
  #
32
42
  # @see JSS::APIObject
33
43
  #
34
44
  class PatchPolicy < JSS::APIObject
35
45
 
46
+ include JSS::SelfServable
47
+ include JSS::Scopable
48
+ include JSS::Creatable
49
+ include JSS::Updatable
50
+
51
+ RSRC_BASE = 'patchpolicies'.freeze
52
+
53
+ RSRC_LIST_KEY = :patch_policies
54
+
55
+ RSRC_OBJECT_KEY = :patch_policy
56
+
57
+ RSRC_BY_PATCH_TITLE = 'patchpolicies/softwaretitleconfig/id/'.freeze
58
+
59
+ # TODO: complain to jamf about this - should be the same as RSRC_LIST_KEY
60
+ RSRC_BY_PATCH_TITLE_LIST_KEY = :"patch policies"
61
+
62
+ SCOPE_TARGET_KEY = :computers
63
+
64
+ AUTO_INSTALL_GRACE_PERIOD_MESSAGE = '$APP_NAMES will quit in $DELAY_MINUTES minutes so that $SOFTWARE_TITLE can be updated. Save anything you are working on and quit the app(s).'.freeze
65
+
66
+ DFT_ENABLED = false
67
+
68
+ # the default dist method - not in ssvc
69
+ DFT_DISTRIBUTION = 'prompt'.freeze
70
+
71
+ # the value of #deadline when there is no deadline
72
+ NO_DEADLINE = :none
73
+
74
+ DFT_DEADLINE = 7
75
+
76
+ # The valud of #grace_period when not defined
77
+ DFT_GRACE_PERIOD = 15
78
+
79
+ DFT_GRACE_PERIOD_SUBJECT = 'Important'.freeze
80
+
81
+ DFT_GRACE_PERIOD_MESSAGE = '$APP_NAMES will quit in $DELAY_MINUTES minutes so that $SOFTWARE_TITLE can be updated. Save anything you are working on and quit the app(s).'.freeze
82
+
83
+ # See {JSS::XMLWorkaround}
84
+ USE_XML_WORKAROUND = {
85
+ patch_policy: {
86
+ general: {
87
+ id: -1,
88
+ name: JSS::BLANK,
89
+ enabled: nil,
90
+ target_version: JSS::BLANK,
91
+ release_date: 0,
92
+ incremental_update: nil,
93
+ reboot: nil,
94
+ minimum_os: JSS::BLANK,
95
+ kill_apps: [
96
+ {
97
+ kill_app_name: JSS::BLANK,
98
+ kill_app_bundle_id: JSS::BLANK
99
+ }
100
+ ],
101
+ distribution_method: JSS::BLANK,
102
+ allow_downgrade: nil,
103
+ patch_unknown: nil
104
+ },
105
+ scope: {
106
+ all_computers: nil,
107
+ computers: [
108
+ {
109
+ id: -1,
110
+ name: JSS::BLANK,
111
+ udid: JSS::BLANK
112
+ }
113
+ ],
114
+ computer_groups: [
115
+ {
116
+ id: -1,
117
+ name: JSS::BLANK
118
+ }
119
+ ],
120
+ users: [
121
+ {
122
+ id: -1,
123
+ username: JSS::BLANK
124
+ }
125
+ ],
126
+ user_groups: [
127
+ {
128
+ id: -1,
129
+ name: JSS::BLANK
130
+ }
131
+ ],
132
+ buildings: [
133
+ {
134
+ id: -1,
135
+ name: JSS::BLANK
136
+ }
137
+ ],
138
+ departments: [
139
+ {
140
+ id: -1,
141
+ name: JSS::BLANK
142
+ }
143
+ ],
144
+ limitations: {
145
+ network_segments: [
146
+ {
147
+ id: -1,
148
+ name: JSS::BLANK
149
+ }
150
+ ],
151
+ ibeacons: [
152
+ {
153
+ id: -1,
154
+ name: JSS::BLANK
155
+ }
156
+ ]
157
+ },
158
+ exclusions: {
159
+ computers: [
160
+ {
161
+ id: -1,
162
+ name: JSS::BLANK,
163
+ udid: JSS::BLANK
164
+ }
165
+ ],
166
+ computer_groups: [
167
+ {
168
+ id: -1,
169
+ name: JSS::BLANK
170
+ }
171
+ ],
172
+ users: [
173
+ {
174
+ id: -1,
175
+ username: JSS::BLANK
176
+ }
177
+ ],
178
+ user_groups: [
179
+ {
180
+ id: -1,
181
+ name: JSS::BLANK
182
+ }
183
+ ],
184
+ buildings: [
185
+ {
186
+ id: -1,
187
+ name: JSS::BLANK
188
+ }
189
+ ],
190
+ departments: [
191
+ {
192
+ id: -1,
193
+ name: JSS::BLANK
194
+ }
195
+ ],
196
+ network_segments: [
197
+ {
198
+ id: -1,
199
+ name: JSS::BLANK
200
+ }
201
+ ],
202
+ ibeacons: [
203
+ {
204
+ id: -1,
205
+ name: JSS::BLANK
206
+ }
207
+ ]
208
+ }
209
+ },
210
+ user_interaction: {
211
+ install_button_text: JSS::BLANK,
212
+ self_service_description: JSS::BLANK,
213
+ self_service_icon: {
214
+ id: -1,
215
+ filename: JSS::BLANK,
216
+ uri: JSS::BLANK
217
+ },
218
+ notifications: {
219
+ notification_enabled: nil,
220
+ notification_type: JSS::BLANK,
221
+ notification_subject: JSS::BLANK,
222
+ notification_message: JSS::BLANK,
223
+ reminders: {
224
+ notification_reminders_enabled: nil,
225
+ notification_reminder_frequency: 1
226
+ }
227
+ },
228
+ deadlines: {
229
+ deadline_enabled: nil,
230
+ deadline_period: 7
231
+ },
232
+ grace_period: {
233
+ grace_period_duration: 15,
234
+ notification_center_subject: 'Important',
235
+ message: AUTO_INSTALL_GRACE_PERIOD_MESSAGE
236
+ }
237
+ },
238
+ software_title_configuration_id: 2
239
+ }
240
+ }.freeze
241
+
242
+ # Class Methods
243
+ ################################
244
+
245
+ # Fetch name and id of all PatchPolicies tied to a given PatchTitle
246
+ #
247
+ # @param title[String,Integer] the name or id of the PatchTitle for which
248
+ # to retrieve a list of patch policies
249
+ #
250
+ # @return [Array<Hash>] the :id and :name of each policy for the title
251
+ #
252
+ def self.all_for_title(title, api: JSS.api)
253
+ title_id = JSS::PatchTitle.valid_id title
254
+ raise JSS::NoSuchItemError, "No PatchTitle matching '#{title}'" unless title_id
255
+ api.get_rsrc("#{RSRC_BY_PATCH_TITLE}#{title_id}")[RSRC_BY_PATCH_TITLE_LIST_KEY]
256
+ end
257
+
258
+ # Attributes
259
+ ################################
260
+
261
+ # @return [Boolean] is this patch policy enabled?
262
+ attr_reader :enabled
263
+ alias enabled? enabled
264
+
265
+ # When setting, the version must exist in the policy's PatchTitle,
266
+ # and have a package assigned to it.
267
+ #
268
+ # @param new_tgt_vers[String] the new version for this Patch Policy.
269
+ #
270
+ # @return [String] The version deployed by this policy
271
+ #
272
+ attr_reader :target_version
273
+ alias version target_version
274
+
275
+ # @return [Time] when the target_version was released
276
+ attr_reader :release_date
277
+
278
+ # @return [Boolean] must this patch be installed only over the prev. version?
279
+ attr_reader :incremental_update
280
+ alias incremental_update? incremental_update
281
+
282
+ # @return [Boolean] does this patch require a reboot after installation?
283
+ attr_reader :reboot
284
+ alias reboot_required reboot
285
+ alias reboot? reboot
286
+ alias reboot_required? reboot
287
+
288
+ # @return [String] The min. OS version require to install this patch
289
+ attr_reader :minimum_os
290
+
291
+ # @return [Array<Hash>] The apps that cannot be running when this is installed.
292
+ # each Hash contains :kill_app_name and :kill_app_bundle_id, both Strings
293
+ attr_reader :kill_apps
294
+
295
+ # Can this title be downgraded to this version?
296
+ # @param new_val [Boolean]
297
+ # @return [Boolean]
298
+ attr_reader :allow_downgrade
299
+ alias allow_downgrade? allow_downgrade
300
+ alias downgradable? allow_downgrade
301
+
302
+ # Can this policy run when we don't know the prev. version?
303
+ # @param new_val [Boolean]
304
+ # @return [Boolean]
305
+ attr_reader :patch_unknown
306
+ alias patch_unknown? patch_unknown
307
+
308
+ # How many days is the install deadline?
309
+ # @param days [Integer, Symbol] :none, or a positive integer. Integers < 1
310
+ # have the same meaning as :none
311
+ #
312
+ # @return [Integer, Symnol] :none, or a positive integer
313
+ attr_reader :deadline
314
+
315
+ # @param new_period [Integer] Negative integers will be saved as 0
316
+ #
317
+ # @return [Integer] How many minutes does the user have to quit the killapps?
318
+ #
319
+ attr_reader :grace_period
320
+ alias grace_period_duration grace_period
321
+
322
+ # @param subj [String] the new subject
323
+ #
324
+ # @return [String] The Subject of the message displayed asking the user to
325
+ # quit the killapps within @grace_period minutes
326
+ attr_reader :grace_period_subject
327
+ alias grace_period_notification_center_subject grace_period_subject
328
+
329
+ # @param subj [String] the new message
330
+ #
331
+ # @return [String] The message displayed asking the user to quit the killapps
332
+ # within @grace_period minutes
333
+ attr_reader :grace_period_message
334
+
335
+ # @return [Integer] the id of the JSS::PatchTitle for this policy.
336
+ # Can be set with the patch_title: param of .make, but is read-only after
337
+ # that.
338
+ attr_reader :patch_title_id
339
+ alias software_title_id patch_title_id
340
+ alias software_title_configuration_id patch_title_id
341
+
342
+ # When making new Patch Polices :patch_title is required and is
343
+ # a JSS::PatchTitle or the name or id of one
344
+ #
345
+ # If target_version: is provided, it must exist in the PatchTitle,
346
+ # and must have a package assigned to it.
347
+ #
348
+ def initialize(data = {})
349
+ super
350
+
351
+ # creation...
352
+ unless in_jss
353
+ @init_data[:general] ||= {}
354
+ @init_data[:software_title_configuration_id] = validate_patch_title @init_data[:patch_title]
355
+
356
+ # were we given target_version in the make params?
357
+ validate_target_version @init_data[:target_version] if @init_data[:target_version]
358
+ @init_data[:general][:target_version] = @init_data[:target_version]
359
+
360
+ # other defaults
361
+ @init_data[:general][:enabled] = false
362
+ @init_data[:general][:allow_downgrade] = false
363
+ @init_data[:general][:patch_unknown] = false
364
+ @init_data[:general][:distribution_method] = DFT_DISTRIBUTION
365
+ end
366
+
367
+ @patch_title_id = @init_data[:software_title_configuration_id]
368
+
369
+ gen = @init_data[:general]
370
+ @enabled = gen[:enabled]
371
+ @target_version = gen[:target_version]
372
+ @allow_downgrade = gen[:allow_downgrade]
373
+ @patch_unknown = gen[:patch_unknown]
374
+
375
+ @init_data[:user_interaction] ||= {}
376
+
377
+ deadlines = @init_data[:user_interaction][:deadlines]
378
+ deadlines ||= {}
379
+ deadlines[:deadline_period] = DFT_DEADLINE if deadlines[:deadline_period].to_s.empty?
380
+ @deadline = deadlines[:deadline_enabled] ? deadlines[:deadline_period] : NO_DEADLINE
381
+
382
+ grace = @init_data[:user_interaction][:grace_period]
383
+ grace ||= {}
384
+
385
+ @grace_period = grace[:grace_period_duration]
386
+ @grace_period = DFT_GRACE_PERIOD if @grace_period.to_s.empty?
387
+
388
+ @grace_period_subject = grace[:notification_center_subject]
389
+ @grace_period_subject = DFT_GRACE_PERIOD_SUBJECT if @grace_period_subject.to_s.empty?
390
+
391
+ @grace_period_message = grace[:message]
392
+ @grace_period_message = DFT_GRACE_PERIOD_MESSAGE if @grace_period_message.to_s.empty?
393
+
394
+
395
+ # read-only values, they come from the version.
396
+ @release_date = JSS.epoch_to_time gen[:release_date]
397
+ @incremental_update = gen[:incremental_update]
398
+ @reboot = gen[:reboot]
399
+ @minimum_os = gen[:minimum_os]
400
+ @kill_apps = gen[:kill_apps]
401
+ end
402
+
403
+ # The JSS::PatchTitle to for this PatchPolicy
404
+ #
405
+ # @param refresh [Boolean] Should the Title be re-fetched from the API?
406
+ #
407
+ # @return [JSS::PatchTitle, nil]
408
+ #
409
+ def patch_title(refresh = false)
410
+ @patch_title = nil if refresh
411
+ @patch_title ||= JSS::PatchTitle.fetch id: patch_title_id
412
+ end
413
+
414
+ # @return [String] the name of the PatchTitle for this patch policy
415
+ #
416
+ def patch_title_name
417
+ return @patch_title.name if @patch_title
418
+ JSS::PatchTitle.map_all_ids_to(:name)[software_title_configuration_id]
419
+ end
420
+
421
+ # See attr_reader :target_version
422
+ #
423
+ def target_version=(new_tgt_vers)
424
+ return if new_tgt_vers == target_version
425
+ @target_version = validate_target_version new_tgt_vers
426
+ @need_to_update = true
427
+ @refetch_for_new_version = true
428
+ end
429
+
430
+ # enable this policy
431
+ #
432
+ # @return [void]
433
+ #
434
+ def enable
435
+ return if enabled
436
+ @enabled = true
437
+ @need_to_update = true
438
+ end
439
+
440
+ # disable this policy
441
+ #
442
+ # @return [void]
443
+ #
444
+ def disable
445
+ return unless enabled
446
+ @enabled = false
447
+ @need_to_update = true
448
+ end
449
+
450
+ # see attr_reader :allow_downgrade
451
+ #
452
+ def allow_downgrade=(new_val)
453
+ return if new_val == allow_downgrade
454
+ @allow_downgrade = JSS::Validate.boolean new_val
455
+ @need_to_update = true
456
+ end
457
+
458
+ # see attr_reader :patch_unknown
459
+ #
460
+ def patch_unknown=(new_val)
461
+ return if new_val == patch_unknown
462
+ @patch_unknown = JSS::Validate.boolean new_val
463
+ @need_to_update = true
464
+ end
465
+
466
+ # see attr_reader :deadline
467
+ #
468
+ def deadline=(days)
469
+ unless days == NO_DEADLINE
470
+ days = JSS::Validate.integer(days)
471
+ days = NO_DEADLINE unless days.positive?
472
+ end
473
+ return if days == deadline
474
+ @deadline = days
475
+ @need_to_update = true
476
+ end
477
+
478
+ # see attr_reader :grace_period
479
+ #
480
+ def grace_period=(mins)
481
+ mins = JSS::Validate.integer(mins)
482
+ mins = 0 if mins.negative?
483
+ return if mins == grace_period
484
+ @grace_period = mins
485
+ @need_to_update = true
486
+ end
487
+
488
+ # see attr_reader :grace_period_subject
489
+ #
490
+ def grace_period_subject=(subj)
491
+ return if grace_period_subject == subj.to_s
492
+ @grace_period_subject = subj.to_s
493
+ @need_to_update = true
494
+ end
495
+
496
+ # see attr_reader :grace_period_message
497
+ #
498
+ def grace_period_message=(msg)
499
+ return if grace_period_message == msg
500
+ @grace_period_message = msg
501
+ @need_to_update = true
502
+ end
503
+
504
+ # Create a new PatchPolicy in the JSS
505
+ #
506
+ # @return [Integer] the id of the new policy
507
+ #
508
+ def create
509
+ validate_for_saving
510
+ # TODO: prepare for more cases where the POST rsrc is
511
+ # different from the PUT/GET/DELETE.
512
+ orig_rsrc = @rest_rsrc
513
+ @rest_rsrc = "#{RSRC_BY_PATCH_TITLE}#{patch_title_id}"
514
+ super
515
+ @rest_rsrc = orig_rsrc
516
+ refetch_version_info
517
+ id
518
+ end
519
+
520
+ # Update an existing PatchPolicy with changes from ruby
521
+ #
522
+ # @return [Integer] the id of the policy
523
+ #
524
+ def update
525
+ validate_for_saving
526
+ super
527
+ refetch_version_info if @refetch_for_new_version
528
+ @refetch_for_new_version = false
529
+ id
530
+ end
531
+
532
+ # Private Instance Methods
533
+ #####################################
534
+ private
535
+
536
+ # raise an error if the patch title we're trying to use isn't available in
537
+ # the jss. If handed a PatchTitle instance, we assume it came from the JSS
538
+ #
539
+ ## @param new_title[String,Integer,JSS::PatchTitle] the title to validate
540
+ #
541
+ # @return [Integer] the id of the valid title
542
+ #
543
+ def validate_patch_title(a_title)
544
+ if a_title.is_a? JSS::PatchTitle
545
+ @patch_title = a_title
546
+ return a_title.id
547
+ end
548
+ raise JSS::MissingDataError, ':patch_title is required' unless a_title
549
+ title_id = JSS::PatchTitle.valid_id a_title
550
+ return title_id if title_id
551
+ raise JSS::NoSuchItemError, "No Patch Title matches '#{a_title}'"
552
+ end
553
+
554
+ # raise an exception if a given target version is not valid for this policy
555
+ # Otherwise return it
556
+ #
557
+ def validate_target_version(tgt_vers)
558
+ raise JSS::MissingDataError, "target_version can't be nil" unless tgt_vers
559
+
560
+ JSS::Validate.non_empty_string tgt_vers
561
+
562
+ unless patch_title(:refresh).versions.key? tgt_vers
563
+ errmsg = "Version '#{tgt_vers}' does not exist for title: #{patch_title_name}."
564
+ raise JSS::NoSuchItemError, errmsg
565
+ end
566
+
567
+ return tgt_vers if patch_title.versions_with_packages.key? tgt_vers
568
+
569
+ errmsg = "Version '#{tgt_vers}' cannot be used in Patch Policies until a package is assigned to it."
570
+ raise JSS::UnsupportedError, errmsg
571
+ end
572
+
573
+ def validate_for_saving
574
+ validate_target_version target_version
575
+ end
576
+
577
+ # Update our local version data after the target_version is changed
578
+ #
579
+ def refetch_version_info
580
+ tmp = self.class.fetch id: id
581
+ @release_date = tmp.release_date
582
+ @incremental_update = tmp.incremental_update
583
+ @reboot = tmp.reboot
584
+ @minimum_os = tmp.minimum_os
585
+ @kill_apps = tmp.kill_apps
586
+ end
587
+
588
+ def rest_xml
589
+ doc = REXML::Document.new JSS::APIConnection::XML_HEADER
590
+ obj = doc.add_element RSRC_OBJECT_KEY.to_s
591
+
592
+ general = obj.add_element 'general'
593
+ general.add_element('target_version').text = target_version
594
+ general.add_element('name').text = name
595
+ general.add_element('enabled').text = enabled?.to_s
596
+ general.add_element('allow_downgrade').text = allow_downgrade
597
+ general.add_element('patch_unknown').text = patch_unknown
598
+
599
+ obj << scope.scope_xml
600
+
601
+ add_self_service_xml doc
602
+
603
+ # self svc xml gave us the user_interaction section
604
+ user_int = obj.elements['user_interaction']
605
+
606
+ dlines = user_int.add_element 'deadlines'
607
+ if deadline == NO_DEADLINE
608
+ dlines.add_element('deadline_enabled').text = 'false'
609
+ else
610
+ dlines.add_element('deadline_enabled').text = 'true'
611
+ dlines.add_element('deadline_period').text = deadline.to_s
612
+ end
613
+
614
+ grace = user_int.add_element 'grace_period'
615
+ grace.add_element('grace_period_duration').text = grace_period.to_s
616
+ grace.add_element('notification_center_subject').text = grace_period_subject.to_s
617
+ grace.add_element('message').text = grace_period_message.to_s
618
+
619
+ doc.to_s
620
+ end
621
+
36
622
  end # class PatchPolicy
37
623
 
38
624
  end # module JSS