xolo-server 1.0.0 → 2.0.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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +42 -4
  3. data/bin/xoloserver +3 -0
  4. data/data/client/xolo +1233 -0
  5. data/lib/optimist_with_insert_blanks.rb +1216 -0
  6. data/lib/xolo/core/base_classes/configuration.rb +238 -0
  7. data/lib/xolo/core/base_classes/server_object.rb +112 -0
  8. data/lib/xolo/core/base_classes/title.rb +884 -0
  9. data/lib/xolo/core/base_classes/version.rb +641 -0
  10. data/lib/xolo/core/constants.rb +85 -0
  11. data/lib/xolo/core/exceptions.rb +52 -0
  12. data/lib/xolo/core/json_wrappers.rb +43 -0
  13. data/lib/xolo/core/loading.rb +59 -0
  14. data/lib/xolo/core/output.rb +292 -0
  15. data/lib/xolo/core/security_cmd.rb +128 -0
  16. data/lib/xolo/core/version.rb +21 -0
  17. data/lib/xolo/core.rb +47 -0
  18. data/lib/xolo/server/app.rb +7 -0
  19. data/lib/xolo/server/configuration.rb +243 -38
  20. data/lib/xolo/server/constants.rb +10 -0
  21. data/lib/xolo/server/helpers/auth.rb +19 -2
  22. data/lib/xolo/server/helpers/autopkg.rb +157 -0
  23. data/lib/xolo/server/helpers/client_data.rb +90 -60
  24. data/lib/xolo/server/helpers/file_transfers.rb +412 -82
  25. data/lib/xolo/server/helpers/jamf_pro.rb +31 -7
  26. data/lib/xolo/server/helpers/log.rb +2 -0
  27. data/lib/xolo/server/helpers/maintenance.rb +1 -0
  28. data/lib/xolo/server/helpers/notification.rb +4 -3
  29. data/lib/xolo/server/helpers/pkg_signing.rb +16 -12
  30. data/lib/xolo/server/helpers/progress_streaming.rb +9 -12
  31. data/lib/xolo/server/helpers/subscriptions.rb +119 -0
  32. data/lib/xolo/server/helpers/titles.rb +27 -3
  33. data/lib/xolo/server/helpers/versions.rb +23 -11
  34. data/lib/xolo/server/mixins/changelog.rb +9 -16
  35. data/lib/xolo/server/mixins/title_jamf_access.rb +375 -390
  36. data/lib/xolo/server/mixins/title_ted_access.rb +50 -8
  37. data/lib/xolo/server/mixins/version_jamf_access.rb +118 -129
  38. data/lib/xolo/server/mixins/version_ted_access.rb +34 -4
  39. data/lib/xolo/server/object_locks.rb +2 -1
  40. data/lib/xolo/server/routes/auth.rb +2 -2
  41. data/lib/xolo/server/routes/jamf_pro.rb +11 -1
  42. data/lib/xolo/server/routes/maint.rb +2 -1
  43. data/lib/xolo/server/routes/subscriptions.rb +126 -0
  44. data/lib/xolo/server/routes/title_editor.rb +1 -1
  45. data/lib/xolo/server/routes/titles.rb +26 -11
  46. data/lib/xolo/server/routes/uploads.rb +0 -14
  47. data/lib/xolo/server/routes/versions.rb +14 -13
  48. data/lib/xolo/server/routes.rb +15 -23
  49. data/lib/xolo/server/title.rb +100 -77
  50. data/lib/xolo/server/version.rb +178 -18
  51. data/lib/xolo/server.rb +8 -0
  52. metadata +20 -11
@@ -60,13 +60,16 @@ module Xolo
60
60
  # @return [void]
61
61
  ################################
62
62
  def create_title_in_jamf
63
- # ORDER MATTERS
63
+ log_debug "Display Name in create_title_in_jamf: #{display_name}"
64
64
 
65
- # create the normal ea if needed
66
- configure_jamf_normal_ea if version_script
65
+ # create/activate the Jamf::PatchTitle
66
+ # this notes the jamf id of the PatchTitle
67
+ # and sets the display name if needed for subbed titles
68
+ jamf_patch_title
67
69
 
68
- # must happen after the normal ea is created
69
- configure_jamf_installed_group
70
+ # This creates the installed group
71
+ # must happen after activating the title in jamf
72
+ jamf_installed_group
70
73
 
71
74
  if uninstall_script || !uninstall_ids.pix_empty?
72
75
  configure_jamf_uninstall_script
@@ -82,7 +85,10 @@ module Xolo
82
85
  # Just calling this will create it if it doesn't exist.
83
86
  jamf_frozen_group
84
87
 
85
- activate_jamf_patch_title
88
+ # This either notifies, or does it
89
+ # TODO: EAs for subscribed titles can change at any time and need
90
+ # re-accepted - Notifications must happen.
91
+ accept_jamf_patch_ea
86
92
  end
87
93
 
88
94
  # Apply any changes to Jamf as needed
@@ -94,14 +100,6 @@ module Xolo
94
100
  def update_title_in_jamf
95
101
  # ORDER MATTERS
96
102
 
97
- # do we have a version_script? if so we maintain a 'normal' EA
98
- # this has to happen before updating the installed_group
99
- configure_jamf_normal_ea if need_to_update_jamf_normal_ea?
100
-
101
- # this smart group might use the normal-EA or might use app data
102
- # If those have changed, we need to update it.
103
- configure_jamf_installed_group if need_to_update_jamf_installed_group?
104
-
105
103
  # if the exclusions have changed update the manual install released policy
106
104
  if changes_for_update[:excluded_groups]
107
105
  progress "Jamf: Updating excluded groups for Manual Released Policy '#{jamf_manual_install_released_policy_name}'."
@@ -110,7 +108,6 @@ module Xolo
110
108
 
111
109
  # Do we need to update (vs delete) the uninstall script?
112
110
  if need_to_update_jamf_uninstall_script?
113
-
114
111
  configure_jamf_uninstall_script
115
112
  configure_jamf_uninstall_policy
116
113
 
@@ -122,7 +119,6 @@ module Xolo
122
119
  elsif need_to_delete_jamf_uninstall_script?
123
120
  delete_jamf_uninstall_policy
124
121
  delete_jamf_uninstall_script
125
- # can't expire without
126
122
  end
127
123
 
128
124
  # Do we need to add or delete the expire policy?
@@ -132,16 +128,11 @@ module Xolo
132
128
  changes_for_update.dig(:expiration, :new).to_i.positive? ? jamf_expire_policy : delete_jamf_expire_policy
133
129
  end
134
130
 
135
- # If we don't use a version script anymore, delete the normal EA
136
- # this has to happen after updating the installed_group
137
- delete_jamf_normal_ea unless version_script_contents
138
-
139
131
  update_description_in_jamf
140
132
  update_ssvc
141
- update_ssvc_category
142
133
  # TODO: deal with icon changes: if changes_for_update&.key? :self_service_icon
143
134
 
144
- if jamf_ted_title_active?
135
+ if jamf_title_active?
145
136
  update_versions_for_title_changes_in_jamf
146
137
  else
147
138
  log_debug "Jamf: Title '#{display_name}' (#{title}) is not yet active to Jamf, nothing to update in versions."
@@ -151,7 +142,6 @@ module Xolo
151
142
  # Repair this title in Jamf Pro
152
143
  # - TODO: activate title in patch mgmt
153
144
  # - TODO: Accept Patch EA
154
- # - Normal EA 'xolo-<title>-installed-version'
155
145
  # - title-installed smart group 'xolo-<title>-installed'
156
146
  # - frozen static group 'xolo-<title>-frozen'
157
147
  # - manual/SSvc install-current-release policy 'xolo-<title>-install'
@@ -169,7 +159,7 @@ module Xolo
169
159
  ###############################################
170
160
  def repair_jamf_title_objects
171
161
  progress "Jamf: Repairing Jamf objects for title '#{title}'", log: :info
172
- repair_jamf_normal_ea
162
+
173
163
  configure_jamf_installed_group
174
164
  repair_jamf_uninstall_policy
175
165
  repair_jamf_uninstall_script
@@ -187,10 +177,32 @@ module Xolo
187
177
  delete_jamf_uninstall_policy
188
178
  delete_jamf_manual_install_released_policy
189
179
  delete_jamf_uninstall_script
190
- delete_jamf_frozen_group
180
+ delete_lingering_policies_for_title
181
+ sleep 5
182
+
183
+ # must delete this group before the patch title
184
+ # since the group criteria references the patch title
191
185
  delete_jamf_installed_group
192
- delete_jamf_normal_ea
186
+ sleep 5
187
+
193
188
  delete_jamf_patch_title
189
+
190
+ # static group deleted last,
191
+ # was used in scopes for patch and normal policies
192
+ delete_jamf_frozen_group
193
+ end
194
+
195
+ ################################
196
+ def delete_lingering_policies_for_title
197
+ Jamf::Policy.all_names(:refresh, cnx: jamf_cnx).each do |polname|
198
+ next unless polname.start_with? jamf_obj_name_pfx
199
+
200
+ polid = Jamf::Policy.valid_id(polname, cnx: jamf_cnx)
201
+ next unless polid
202
+
203
+ progress "Jamf: Deleting lingering policy #{polname}, possibly from a failed version action.", log: :info
204
+ Jamf::Policy.delete(polid, cnx: jamf_cnx)
205
+ end
194
206
  end
195
207
 
196
208
  # If any title changes require updates to existing versions in
@@ -211,8 +223,6 @@ module Xolo
211
223
  vers_obj.update_release_groups(ttl_obj: self) if changes_for_update&.key? :release_groups
212
224
  vers_obj.update_excluded_groups(ttl_obj: self) if changes_for_update&.key? :excluded_groups
213
225
  vers_obj.update_jamf_package_notes(ttl_obj: self) if need_to_update_description?
214
- # vers_obj.update_ssvc(ttl_obj: self) if changes_for_update&.key? :self_service
215
- # vers_obj.update_ssvc_category(ttl_obj: self) if changes_for_update&.key? :self_service_category
216
226
  end
217
227
  end
218
228
 
@@ -300,125 +310,6 @@ module Xolo
300
310
  report
301
311
  end
302
312
 
303
- ####### The'Normal" Extension Attribute
304
- ###########################################
305
- ###########################################
306
-
307
- # @return [Boolean] Does the 'normal' EA exist in jamf?
308
- #########################
309
- def jamf_normal_ea_exist?
310
- Jamf::ComputerExtensionAttribute.all_names(:refresh, cnx: jamf_cnx).include? jamf_normal_ea_name
311
- end
312
-
313
- # Create or fetch the 'normal' EA in jamf
314
- # If we are deleting and it doesn't exist, return nil.
315
- #
316
- # @return [Jamf::ComputerExtensionAttribute] The 'normal' Jamf ComputerExtensionAttribute for this title
317
- ########################
318
- def jamf_normal_ea
319
- return @jamf_normal_ea if @jamf_normal_ea
320
-
321
- if jamf_normal_ea_exist?
322
- @jamf_normal_ea = Jamf::ComputerExtensionAttribute.fetch(name: jamf_normal_ea_name, cnx: jamf_cnx)
323
-
324
- else
325
- return if deleting?
326
-
327
- msg = "Jamf: Creating regular extension attribute '#{jamf_normal_ea_name}' for use in smart groups"
328
- progress msg, log: :info
329
-
330
- @jamf_normal_ea = Jamf::ComputerExtensionAttribute.create(
331
- name: jamf_normal_ea_name,
332
- cnx: jamf_cnx
333
- )
334
- @jamf_normal_ea.save
335
-
336
- end
337
- @jamf_normal_ea
338
- end
339
-
340
- # Configure the 'normal' EA that matches the Patch EA for this title,
341
- # so that it can be used in smart groups and adv. searches.
342
- # (Patch EAs aren't available for use in smart group critera)
343
- #
344
- # If we have one already but are deleting it, that happens elsewhere
345
- #
346
- # @return [void]
347
- ################################
348
- def configure_jamf_normal_ea
349
- progress "Jamf: Configuring regular extension attribute '#{jamf_normal_ea_name}'", log: :info
350
-
351
- jamf_normal_ea.description = "The version of xolo title '#{title}' installed on the machine"
352
- jamf_normal_ea.data_type = :string
353
-
354
- # this is our incoming or already-existing EA script
355
- if version_script_contents.pix_empty?
356
- # nothing to do if its nil, if we need to delete it, that'll happen later
357
- else
358
- jamf_normal_ea.enabled = true
359
- jamf_normal_ea.input_type = 'script'
360
- jamf_normal_ea.script = version_script_contents
361
- end
362
-
363
- jamf_normal_ea.script = scr
364
- jamf_normal_ea.save
365
- end
366
-
367
- # Repair the 'normal' EA in jamf to match our version_script
368
- ########################
369
- def repair_jamf_normal_ea
370
- if version_script_contents.pix_empty?
371
- delete_jamf_normal_ea
372
- else
373
- configure_jamf_normal_ea
374
- end
375
- end
376
-
377
- # Delete the 'normal' computer ext attr matching the Patch EA
378
- # @return [void]
379
- ######################################
380
- def delete_jamf_normal_ea
381
- ea_id = Jamf::ComputerExtensionAttribute.valid_id jamf_normal_ea_name, cnx: jamf_cnx
382
- return unless ea_id
383
-
384
- progress "Jamf: Deleting regular extension attribute '#{jamf_normal_ea_name}'", log: :info
385
- Jamf::ComputerExtensionAttribute.delete ea_id, cnx: jamf_cnx
386
- end
387
-
388
- # do we need to update the normal EA in jamf?
389
- # true if our incoming changes include :version_script
390
- # and the new value is not empty (in which case we'll delete it)
391
- #
392
- # @return [Boolean]
393
- ########################
394
- def need_to_update_jamf_normal_ea?
395
- changes_for_update.key?(:version_script) && !changes_for_update[:version_script][:new].pix_empty?
396
- end
397
-
398
- # the script contents of the Normal Jamf EA that comes from our version_script
399
- # @return [String, nil] nil if there is none
400
- ##############################
401
- def jamf_normal_ea_contents
402
- return unless jamf_normal_ea_exist?
403
-
404
- jamf_normal_ea.script
405
- end
406
-
407
- # @return [String] the URL for the Normal EA in Jamf Pro
408
- ######################
409
- def jamf_normal_ea_url
410
- return @jamf_normal_ea_url if @jamf_normal_ea_url
411
- return unless version_script
412
-
413
- ea_id = Jamf::ComputerExtensionAttribute.valid_id jamf_normal_ea_name, cnx: jamf_cnx
414
- return unless ea_id
415
-
416
- # Jamf Changed the URL!
417
- # @jamf_normal_ea_url = "#{jamf_gui_url}/computerExtensionAttributes.html?id=#{ea_id}&o=r"
418
-
419
- @jamf_normal_ea_url = "#{jamf_gui_url}/view/settings/computer-management/computer-extension-attributes/#{ea_id}"
420
- end
421
-
422
313
  ####### The Patch Ext Attribute
423
314
  ###########################################
424
315
  ###########################################
@@ -437,6 +328,8 @@ module Xolo
437
328
  # @return [Boolean]
438
329
  #########################
439
330
  def need_to_accept_jamf_patch_ea?
331
+ return unless managed?
332
+
440
333
  @need_to_accept_jamf_patch_ea
441
334
  end
442
335
 
@@ -462,16 +355,22 @@ module Xolo
462
355
  # @return [void]
463
356
  ############################
464
357
  def accept_jamf_patch_ea
465
- return unless need_to_accept_jamf_patch_ea?
358
+ # give the server a moment to catch up with the new EA before we check on it
359
+ log_debug "Jamf: Checking if we need to accept the version-script EA for title '#{title}'"
360
+ sleep 5
361
+
362
+ awating_acceptance = jamf_patch_ea_awaiting_acceptance?
466
363
 
467
- # return with warning if we aren't auto-accepting
468
- unless Xolo::Server.config.jamf_auto_accept_xolo_eas
469
- msg = "Jamf: IMPORTANT: Before adding any versions, the Extension Attribute (version-script) for this title must be accepted manually in Jamf Pro at #{jamf_patch_ea_url} under the 'Extension Attribute' tab (click 'Edit') "
470
- progress msg
364
+ # return with warning if we aren't auto-accepting, or for all subscribed titles
365
+ if awating_acceptance && (subscribed? || !Xolo::Server.config.jamf_auto_accept_xolo_eas)
366
+ msg = "Jamf: IMPORTANT: The Extension Attribute (version-script) for title '#{title}' must be accepted manually in Jamf Pro at #{jamf_patch_ea_url} under the 'Extension Attribute' tab (click 'Edit'). If you cannot do this yourself, please contact your Xolo server admins for assistance. Deployment will not happen until this is done."
367
+ progress msg, log: :warn, alert: true
471
368
  log_debug 'Admin informed about accepting EA/version-script manually'
472
369
  return
473
370
  end
474
371
 
372
+ return unless need_to_accept_jamf_patch_ea?
373
+
475
374
  # this is true if the Jamf server already knows it needs to be accepted
476
375
  # so just do it now
477
376
  if jamf_patch_ea_awaiting_acceptance?
@@ -612,6 +511,8 @@ module Xolo
612
511
  end
613
512
 
614
513
  # API call to accept the version-script EA in Jamf Pro
514
+ # This never happens for subscribed titles
515
+ #
615
516
  # TODO: when this gets implemented in ruby-jss, use that implementation
616
517
  #
617
518
  # @return [void]
@@ -645,7 +546,7 @@ module Xolo
645
546
  ######################
646
547
  def jamf_patch_ea_url
647
548
  return @jamf_patch_ea_url if @jamf_patch_ea_url
648
- return unless version_script
549
+ return unless version_script || (subscribed? && jamf_patch_ea_data)
649
550
 
650
551
  @jamf_patch_ea_url = "#{jamf_patch_title_url}?tab=extension"
651
552
  end
@@ -791,7 +692,10 @@ module Xolo
791
692
  pol.add_script jamf_uninstall_script_name
792
693
  pol.set_trigger_event :checkin, false
793
694
  pol.set_trigger_event :custom, jamf_uninstall_policy_name
794
- pol.scope.add_target(:computer_group, jamf_installed_group_name)
695
+ # pol.scope.add_target(:computer_group, jamf_installed_group_name)
696
+ # all targets, cus jamf doesn't like some policies with scoped
697
+ # smart groups using 'latest version'
698
+ pol.scope.set_all_targets
795
699
  pol.scope.set_exclusions :computer_groups, [valid_forced_exclusion_group_name] if valid_forced_exclusion_group_name
796
700
  pol.frequency = :ongoing
797
701
  pol.enable
@@ -809,7 +713,6 @@ module Xolo
809
713
  end
810
714
 
811
715
  # delete the policy first if it exists
812
-
813
716
  #########################
814
717
  def delete_jamf_uninstall_policy
815
718
  return unless jamf_uninstall_policy_exist?
@@ -866,9 +769,9 @@ module Xolo
866
769
  )
867
770
  @jamf_installed_group.save
868
771
  configure_jamf_installed_group
869
- log_debug 'Jamf: Sleeping to let Jamf server see change to the Installed smart group.'
870
- sleep 10
772
+
871
773
  end
774
+
872
775
  @jamf_installed_group
873
776
  end
874
777
 
@@ -881,75 +784,34 @@ module Xolo
881
784
 
882
785
  jamf_installed_group.criteria = Jamf::Criteriable::Criteria.new(jamf_installed_group_criteria)
883
786
  jamf_installed_group.save
787
+ log_debug 'Jamf: Sleeping 30 secs to let Jamf server see changes to Installed smart group.'
788
+ sleep 30
884
789
  end
885
790
 
886
791
  # The criteria for the smart group in Jamf that contains all Macs
887
792
  # with any version of this title installed
888
793
  #
889
- # If we have, or are about to update to, a version_script (EA) then use it,
890
- # otherwise use the app_name and app_bundle_id.
794
+ # We use the "Patch Reporting: #{display_name}" criterion so that we don't
795
+ # care whether the title uses a version-script or app data.
891
796
  #
892
797
  # @return [Array<Jamf::Criteriable::Criterion>]
893
798
  ###################################
894
799
  def jamf_installed_group_criteria
895
- have_vers_script =
896
- if changes_for_update&.dig :version_script
897
- changes_for_update[:version_script][:new]
898
- else
899
- version_script
900
- end
901
-
902
- # If we have a version_script, use the ea
903
- if have_vers_script
904
- [
905
- Jamf::Criteriable::Criterion.new(
906
- and_or: :and,
907
- name: jamf_normal_ea_name,
908
- search_type: 'is not',
909
- value: Xolo::BLANK
910
- )
911
- ]
912
-
913
- # No version script, so we must be using app data
914
- else
915
- aname = changes_for_update.dig(:app_name, :new) || app_name
916
- abundle = changes_for_update.dig(:app_bundle_id, :new) || app_bundle_id
917
-
918
- [
919
- Jamf::Criteriable::Criterion.new(
920
- and_or: :and,
921
- name: 'Application Title',
922
- search_type: 'is',
923
- value: aname
924
- ),
925
-
926
- Jamf::Criteriable::Criterion.new(
927
- and_or: :and,
928
- name: 'Application Bundle ID',
929
- search_type: 'is',
930
- value: abundle
931
- )
932
- ]
933
- end
934
- end
935
-
936
- # do we need to update the 'installed' smart group?
937
- # true if our incoming changes include the app_name or app_bundle_id
938
- #
939
- # If they changed at all, we need to update no matter what:
940
- # - if they are now nil, we switched to a version script
941
- #
942
- # - if they aren't nil but are different, we need to update
943
- # the group criteria to reflect that.
944
- #
945
- # Changes to the version script, if it was in use before, don't
946
- # require us to change the smart group
947
- #
948
- #
949
- # @return [Boolean]
950
- #########################
951
- def need_to_update_jamf_installed_group?
952
- changes_for_update[:app_name] || changes_for_update[:app_bundle_id]
800
+ [
801
+ Jamf::Criteriable::Criterion.new(
802
+ and_or: :and,
803
+ name: "Patch Reporting: #{display_name}",
804
+ search_type: 'less than or equal',
805
+ value: 'Latest Version'
806
+ ),
807
+
808
+ Jamf::Criteriable::Criterion.new(
809
+ and_or: :or,
810
+ name: "Patch Reporting: #{display_name}",
811
+ search_type: 'is',
812
+ value: 'Unknown Version'
813
+ )
814
+ ]
953
815
  end
954
816
 
955
817
  # Delete the 'installed' smart group
@@ -1244,14 +1106,15 @@ module Xolo
1244
1106
  #
1245
1107
  # @return [Jamf::PatchSource] The Jamf Patch Source
1246
1108
  #########################
1247
- def jamf_ted_patch_source
1248
- @jamf_ted_patch_source ||=
1109
+ def jamf_managed_patch_source
1110
+ @jamf_managed_patch_source ||=
1249
1111
  Jamf::PatchSource.fetch(name: Xolo::Server.config.ted_patch_source, cnx: jamf_cnx)
1250
1112
  end
1251
1113
 
1252
1114
  # The titles available from the Title Editor via its
1253
- # Jamf Patch Source. These are titles have have been enabled
1254
- # in the Title Editor
1115
+ # Jamf Patch Source.
1116
+ # These are titles have have been enabled in the Title Editor
1117
+ # but have not yet been activated in Jamf Patch.
1255
1118
  #
1256
1119
  # available_titles returns a Hash for each available title, with these keys:
1257
1120
  #
@@ -1271,93 +1134,79 @@ module Xolo
1271
1134
  #
1272
1135
  # @return [Array<String>] info about the available titles
1273
1136
  #########################
1274
- def jamf_ted_available_titles
1137
+ def jamf_available_managed_titles
1275
1138
  # Don't cache this in an instance var, it changes during the
1276
1139
  # life of our title instance
1277
- # jamf_ted_patch_source.available_titles.map { |t| t[:name_id] }
1140
+ # jamf_managed_patch_source.available_titles.map { |t| t[:name_id] }
1278
1141
  # Also NOTE: "available" means not only enabled
1279
1142
  # in the title editor, but also not already active in jamf.
1280
1143
  # So any given title will either be here or in
1281
- # jamf_active_ted_titles, but never both.
1282
- jamf_ted_patch_source.available_name_ids
1144
+ # jamf_active_managed_titles, but never both.
1145
+ jamf_managed_patch_source.available_name_ids
1283
1146
  end
1284
1147
 
1285
1148
  # @return [Boolean] Is this xolo title available in Jamf?
1286
1149
  ########################
1287
- def jamf_ted_title_available?
1288
- jamf_ted_available_titles.include? title
1289
- end
1290
-
1291
- # The titles active in Jamf Patch Management from the Title Editor
1292
- # This takes into account that other Patch Sources may have titles with the
1293
- # same 'name_id' (the xolo 'title')
1294
- # A hash keyed by the title, with values of the jamf title id
1295
- #
1296
- # @return [Hash {String => Integer}] The xolo titles that are active in Jamf Patch Management
1297
- ########################
1298
- def jamf_active_ted_titles(refresh: false)
1299
- @jamf_active_ted_titles = nil if refresh
1300
- return @jamf_active_ted_titles if @jamf_active_ted_titles
1301
-
1302
- @jamf_active_ted_titles = {}
1303
- active_from_ted = Jamf::PatchTitle.all(:refresh, cnx: jamf_cnx).select do |t|
1304
- t[:source_id] == jamf_ted_patch_source.id
1305
- end
1306
- active_from_ted.each { |t| @jamf_active_ted_titles[t[:name_id]] = t[:id] }
1307
- @jamf_active_ted_titles
1150
+ def jamf_managed_title_available?
1151
+ jamf_available_managed_titles.include? title
1308
1152
  end
1309
1153
 
1310
1154
  # @return [Boolean] Is this xolo title currently active in Jamf?
1311
1155
  ########################
1312
- def jamf_ted_title_active?
1313
- jamf_active_ted_titles(refresh: true).key? title
1156
+ def jamf_title_active?
1157
+ !jamf_patch_title_id.pix_empty?
1158
+ # if subscribed?
1159
+ # jamf_active_subscribed_titles(refresh: true).key? title
1160
+ # else
1161
+ # jamf_active_managed_titles(refresh: true).key? title
1162
+ # end
1314
1163
  end
1315
1164
 
1316
- # The Jamf ID of the Patch Title for this xolo title
1317
- # if it has been activated in jamf.
1165
+ # The managed titles active in Jamf Patch Management from the Title Editor
1166
+ # A hash keyed by the title, with values of the jamf patch title id
1318
1167
  #
1319
- # @return [Integer, nil] The Jamf ID of this title, if it is active in Jamf
1168
+ # @return [Hash {String => Integer}] The managed xolo titles that are active in Jamf Patch Management
1169
+ # and their Jamf::PatchTitle id
1320
1170
  ########################
1321
- def jamf_patch_title_id
1322
- @jamf_patch_title_id ||= jamf_active_ted_titles(refresh: true)[title]
1323
- end
1171
+ def jamf_active_managed_titles(refresh: false)
1172
+ @jamf_active_managed_titles = nil if refresh
1173
+ return @jamf_active_managed_titles if @jamf_active_managed_titles
1324
1174
 
1325
- # create/activate the patch title in Jamf Pro, if not already done.
1326
- #
1327
- # This 'subscribes' Jamf to the title in the title editor
1328
- # It must be enabled in the Title Editor first, meaning
1329
- # it has at least one requirement, and at least one enabled patch/version.
1330
- #
1331
- # The 'stub' patch version should allow this when we create the title.
1332
- #
1333
- # Xolo should have enabled it in the Title editor before we
1334
- # reach this point.
1335
- #
1336
- ##########################
1337
- def activate_jamf_patch_title
1338
- if jamf_ted_title_active?
1339
- log_debug "Jamf: Title '#{display_name}' (#{title}) is already active in Jamf"
1340
- return
1341
- end
1175
+ @jamf_active_managed_titles = {}
1176
+ all_patch_titles = Jamf::PatchTitle.all(cnx: jamf_cnx)
1342
1177
 
1343
- # wait up to 60secs for the title to become available
1344
- counter = 0
1345
- until jamf_ted_title_available? || counter == 12
1346
- log_debug "Jamf: Waiting for title '#{display_name}' (#{title}) to become available from the Title Editor"
1347
- sleep 5
1348
- counter += 1
1178
+ active_from_ted = all_patch_titles.select do |t|
1179
+ # log_debug "Jamf: Checking if active patch title '#{t[:name]}' with id #{t[:id]} is from the Title Editor's patch source"
1180
+ t[:source_id] == jamf_managed_patch_source.id
1349
1181
  end
1350
1182
 
1351
- unless jamf_ted_title_available?
1352
- msg = "Jamf: Title '#{title}' is not yet available to Jamf. Make sure it has at least one version enabled in the Title Editor"
1353
- log_error msg
1354
- raise Xolo::NoSuchItemError, msg
1183
+ managed_titles = server_app_instance.managed_title_objects.select(&:managed?).map(&:title)
1184
+
1185
+ active_from_ted.each do |t|
1186
+ @jamf_active_managed_titles[t[:name_id]] = t[:id] if managed_titles.include? t[:name_id]
1355
1187
  end
1188
+ @jamf_active_managed_titles
1189
+ end
1356
1190
 
1357
- # This creates/activates the title if needed
1358
- jamf_patch_title
1191
+ # @return [Hash {String => Integer}] The subscribed xolo titles that are active in Jamf Patch Management
1192
+ # and their Jamf::PatchTitle id
1193
+ ########################
1194
+ def jamf_active_subscribed_titles(refresh: false)
1195
+ @jamf_active_subscribed_titles = nil if refresh
1196
+ return @jamf_active_subscribed_titles if @jamf_active_subscribed_titles
1359
1197
 
1360
- accept_jamf_patch_ea
1198
+ @jamf_active_subscribed_titles = {}
1199
+
1200
+ all_active_patchtitles = Jamf::PatchTitle.all :refresh, cnx: jamf_cnx
1201
+
1202
+ server_app_instance.subscribed_title_objects.each do |st|
1203
+ next unless all_active_patchtitles.any? do |pt|
1204
+ pt[:source_id] == st.jamf_patch_source_id && pt[:name_id] == st.title_id
1205
+ end
1206
+
1207
+ @jamf_active_subscribed_titles[st.title] = pt[:id]
1208
+ end
1209
+ @jamf_active_subscribed_titles
1361
1210
  end
1362
1211
 
1363
1212
  # Create or fetch the patch title object for this xolo title.
@@ -1370,52 +1219,118 @@ module Xolo
1370
1219
  @jamf_patch_title = nil if refresh
1371
1220
  return @jamf_patch_title if @jamf_patch_title
1372
1221
 
1373
- breaktime = Time.now + Xolo::Server::MAX_JAMF_WAIT_FOR_TITLE_EDITOR
1374
- until jamf_ted_title_available? || jamf_ted_title_active?
1375
- if Time.now > breaktime
1376
- raise Xolo::ServerError,
1377
- "Jamf: Title '#{title}' is not available in Jamf after waiting #{Xolo::Server::MAX_JAMF_WAIT_FOR_TITLE_EDITOR} seconds"
1378
- end
1379
-
1380
- log_debug "Waiting a long time for title '#{title}' to become available from TEd. Checking every 10 secs"
1381
- sleep 10
1382
- end
1222
+ # wait up to 60secs for a managed title to become available.
1223
+ # subscribed titles are already available
1224
+ wait_for_managed_title_to_become_available
1383
1225
 
1384
- if jamf_ted_title_active?
1226
+ # Jamf::PatchTitle object already active, just fetch it
1227
+ if jamf_patch_title_id # jamf_title_active? || subscribed?
1385
1228
  @jamf_patch_title = Jamf::PatchTitle.fetch(id: jamf_patch_title_id, cnx: jamf_cnx)
1386
1229
 
1230
+ # not yet active, so activate/create the Jamf::PatchTitle object
1387
1231
  else
1388
1232
  return if deleting?
1389
1233
 
1390
- msg = "Jamf: Activating Patch Title '#{display_name}' (#{title}) from the Title Editor Patch Source '#{Xolo::Server.config.ted_patch_source}'"
1391
- progress msg, log: :info
1234
+ @jamf_patch_title = activate_jamf_patch_title
1235
+ end
1236
+ end
1392
1237
 
1393
- @jamf_patch_title =
1394
- Jamf::PatchTitle.create(
1395
- name: display_name,
1396
- source: Xolo::Server.config.ted_patch_source,
1397
- name_id: title,
1398
- cnx: jamf_cnx
1399
- )
1400
- @jamf_patch_title.category = Xolo::Server::JAMF_XOLO_CATEGORY
1401
- @jamf_patch_title.save
1238
+ # activate the patch title in jamf, whatever it's source
1239
+ ############################
1240
+ def activate_jamf_patch_title
1241
+ if jamf_title_active?
1242
+ log_debug "Jamf: Title '#{title}' is already active in Jamf Patch"
1243
+ return
1244
+ end
1245
+
1246
+ # patch_source and title_id are required when adding subbed titles
1247
+ # but pre-defined for managed
1248
+ if managed?
1249
+ log_debug "Jamf: Title '#{title}' is managed, so using pre-defined patch source and title_id"
1250
+ log_debug "Jamf: Display Name in managed?: #{display_name}"
1251
+ self.patch_source = Xolo::Server.config.ted_patch_source
1252
+ self.title_id = title
1253
+ log_debug "Jamf: Display Name in managed? after setting patch_source and title_id: #{display_name}"
1254
+
1255
+ # display name is required for managed titles, but will be empty for subbed
1256
+ # so for subbed, we look it up from the patch source
1257
+ else
1258
+ log_debug "Jamf: Title '#{title}' is subscribed, so looking up patch source and title_id from"
1259
+ log_debug "Jamf: Display Name in NOT managed?: #{display_name}"
1260
+ if display_name.pix_empty?
1261
+ self.display_name = Jamf::PatchSource.available_titles(patch_source, cnx: jamf_cnx).select { |t| t[:name_id] == title_id }.first&.dig :app_name
1262
+ end
1263
+ end # if managed
1264
+
1265
+ log_debug "Jamf: class: #{self.class}, display_name: #{display_name}, patch_source: #{patch_source}, title_id: #{title_id}"
1266
+
1267
+ msg = "Jamf: Activating Patch Title '#{display_name}' (#{title}) from the Patch Source '#{patch_source}'"
1268
+ progress msg, log: :info
1402
1269
 
1270
+ jamf_patch_title =
1271
+ Jamf::PatchTitle.create(
1272
+ name: display_name,
1273
+ source: patch_source,
1274
+ name_id: title_id,
1275
+ cnx: jamf_cnx
1276
+ )
1277
+ jamf_patch_title.category = Xolo::Server::JAMF_XOLO_CATEGORY
1278
+ self.jamf_patch_title_id = jamf_patch_title.save
1279
+ log_debug "Activated Jamf Patch Title '#{display_name}' (#{title}) with id #{jamf_patch_title_id}"
1280
+
1281
+ jamf_patch_title
1282
+ end
1283
+
1284
+ # wait up to 60secs for a managed title to become available to be activated
1285
+ # subscribed titles are already available
1286
+ ####################
1287
+ def wait_for_managed_title_to_become_available
1288
+ return unless managed?
1289
+ return if jamf_title_active?
1290
+
1291
+ counter = 0
1292
+ until jamf_title_active? || jamf_managed_title_available? || counter == 12
1293
+ log_debug "Jamf: Waiting for title '#{display_name}' (#{title}) to become available from the Title Editor"
1294
+ sleep 5
1295
+ counter += 1
1403
1296
  end
1404
- @jamf_patch_title
1297
+
1298
+ return if jamf_managed_title_available? || jamf_title_active?
1299
+
1300
+ msg = "Jamf: Title '#{title}' is not yet available to Jamf. Make sure it has at least one version enabled in the Title Editor"
1301
+ log_error msg
1302
+ raise Xolo::NoSuchItemError, msg
1405
1303
  end
1406
1304
 
1407
1305
  # Delete the patch title
1408
1306
  # NOTE: jamf api user must have 'delete computer ext. attribs' permmissions
1409
1307
  ##############################
1410
1308
  def delete_jamf_patch_title
1411
- return unless jamf_ted_title_active?
1309
+ log_debug "Jamf: Deleting patch title '#{display_name}' (#{title})"
1310
+ unless jamf_title_active?
1311
+ log_debug "Jamf: Patch title '#{display_name}' (#{title}) is not active in Jamf, nothing to delete"
1312
+ return
1313
+ end
1412
1314
 
1413
- pt_id = Jamf::PatchTitle.map_all(:id, to: :name_id, cnx: jamf_cnx).invert[title]
1414
- return unless pt_id
1315
+ # # subscribed titles often have name_id's like "1B7"
1316
+ # # whereas managed titles have name_id's that are the same as the title,
1317
+ # # so we have to look up the id differently for subbed vs managed
1318
+ # key = subscribed? ? title_id : title
1319
+ # pt_id = Jamf::PatchTitle.map_all(:id, to: :name_id, cnx: jamf_cnx).invert[key]
1415
1320
 
1416
- msg = "Jamf: Deleting (unsubscribing) title '#{display_name}' (#{title}}) in Jamf Patch Management"
1321
+ # unless pt_id
1322
+ # log_debug "Jamf: Cannot find patch title '#{display_name}' (#{title}) in Jamf, cannot delete"
1323
+ # return
1324
+ # end
1325
+
1326
+ # # the pt_id SHOULD match the jamf_patch_title_id we have stored, but just in case, we'll use the one we looked up here
1327
+ # unless pt_id.to_s == jamf_patch_title_id.to_s
1328
+ # log_warn "Jamf: Stored patch title id #{jamf_patch_title_id} does not match id #{pt_id} looked up for '#{display_name}' (#{title}). We are deleting the stored id."
1329
+ # end
1330
+
1331
+ msg = "Jamf: Deleting (deactivating) title '#{display_name}' (#{title}}) in Jamf Patch Management"
1417
1332
  progress msg, log: :info
1418
- Jamf::PatchTitle.delete pt_id, cnx: jamf_cnx
1333
+ Jamf::PatchTitle.delete jamf_patch_title_id, cnx: jamf_cnx
1419
1334
  end
1420
1335
 
1421
1336
  # @return [String] the URL for the Patch Title in Jamf Pro
@@ -1481,6 +1396,7 @@ module Xolo
1481
1396
  end
1482
1397
 
1483
1398
  # Configure the settings for the manual_install_released_policy
1399
+ # Be sure to call .save on the policy after this to save the changes.
1484
1400
  # @param pol [Jamf::Policy] the policy we are configuring
1485
1401
  # @return [void]
1486
1402
  ###################
@@ -1495,15 +1411,7 @@ module Xolo
1495
1411
  # clear any existing packages
1496
1412
  pol.package_names.each { |pkg_name| pol.remove_package pkg_name }
1497
1413
 
1498
- # don't add a package or enable the pol if there's no released version
1499
- desired_vers = releasing_version || released_version
1500
- if desired_vers
1501
- rel_vers = version_object(desired_vers)
1502
- pol.add_package rel_vers.jamf_pkg_id
1503
- pol.enable
1504
- else
1505
- pol.disable
1506
- end
1414
+ toggle_jamf_manual_install_released_policy pol
1507
1415
 
1508
1416
  # figure out the exclusions.
1509
1417
  #
@@ -1519,12 +1427,36 @@ module Xolo
1519
1427
 
1520
1428
  pol.scope.set_exclusions :computer_groups, excls
1521
1429
 
1522
- return unless self_service
1430
+ if self_service?
1431
+ add_title_to_self_service(pol)
1432
+ else
1433
+ remove_title_from_self_service(pol)
1434
+ end
1435
+ end
1523
1436
 
1524
- if pol.in_self_service?
1525
- configure_pol_for_self_service(pol)
1437
+ # enable or disable the manual install policy for the current release, depending on
1438
+ # whether or not we have a released version
1439
+ # @param pol [Jamf::Policy] the manual install policy for the current release, which may or may not be saved yet.
1440
+ # return [void]
1441
+ ############################
1442
+ def toggle_jamf_manual_install_released_policy(pol, vobj = nil)
1443
+ # remove any current pkg payload
1444
+ pol.package_ids.each { |pid| pol.remove_package pid }
1445
+
1446
+ unless vobj
1447
+ desired_vers = releasing_version || released_version
1448
+ vobj = desired_vers ? version_object(desired_vers) : nil
1449
+ end
1450
+
1451
+ if vobj
1452
+ progress "Jamf: Setting policy #{jamf_manual_install_released_policy_name} to install the package for version '#{vobj.version}'", log: :info
1453
+ pol.add_package vobj.jamf_pkg_id
1454
+
1455
+ progress "Jamf: Enabling policy #{jamf_manual_install_released_policy_name}", log: :info
1456
+ pol.enable
1526
1457
  else
1527
- add_title_to_self_service(pol)
1458
+ progress "Jamf: Disabling policy #{jamf_manual_install_released_policy_name}, no released version", log: :info
1459
+ pol.disable
1528
1460
  end
1529
1461
  end
1530
1462
 
@@ -1554,111 +1486,164 @@ module Xolo
1554
1486
  # @return [void]
1555
1487
  ##################################
1556
1488
  def add_title_to_self_service(pol = nil)
1557
- return unless self_service
1558
-
1559
1489
  pol ||= jamf_manual_install_released_policy
1560
1490
 
1561
- msg = "Jamf: Adding Manual Install Policy '#{pol.name}' to Self Service."
1562
- progress msg, log: :info
1491
+ unless pol.in_self_service?
1492
+ msg = "Jamf: Adding Manual Install Policy '#{pol.name}' to Self Service."
1493
+ progress msg, log: :info
1494
+ pol.add_to_self_service
1495
+ end
1563
1496
 
1564
- pol.add_to_self_service
1565
1497
  configure_pol_for_self_service(pol)
1498
+ pol.save
1566
1499
  end
1567
1500
 
1568
- # Update whether or not we are in self service, based on the setting in the title
1569
- #
1570
- #########################
1571
- def update_ssvc
1572
- return unless changes_for_update.key? :self_service
1573
-
1574
- # Update the manual install policy
1575
- pol = jamf_manual_install_released_policy
1576
- return unless pol
1501
+ # Add the jamf_manual_install_released_policy to self service if needed
1502
+ # @param pol [Jamf::Policy] The jamf_manual_install_released_policy, which may not be saved yet.
1503
+ # @return [void]
1504
+ ##################################
1505
+ def remove_title_from_self_service(pol = nil)
1506
+ pol ||= jamf_manual_install_released_policy
1507
+ return unless pol.in_self_service?
1577
1508
 
1578
- # we should be in SSvc - changes_for_update[:self_service][:new] is a boolean
1579
- if changes_for_update[:self_service][:new]
1580
- add_title_to_self_service(pol) unless pol.in_self_service?
1509
+ msg = "Jamf: Removing Manual Install Policy '#{pol.name}' from Self Service."
1510
+ progress msg, log: :info
1511
+ pol.remove_from_self_service
1581
1512
 
1582
- # we should not be in SSvc
1583
- elsif pol.in_self_service?
1584
- msg = "Jamf: Removing Manual Install Policy '#{pol.name}' from Self Service."
1585
- progress msg, log: :info
1586
- pol.remove_from_self_service
1587
- end
1588
1513
  pol.save
1589
-
1590
- # TODO: if we decide to use ssvc in patch policies, loop thru versions to make any changes
1591
1514
  end
1592
1515
 
1593
- # Update our self service category, based on the setting in the title
1594
- # TODO: Allow multiple categories, and 'featuring' ?
1516
+ # Update whether or not we are in self service, based on the setting in the title
1595
1517
  #
1596
1518
  #########################
1597
- def update_ssvc_category
1598
- return unless changes_for_update.key? :self_service_category
1599
-
1519
+ def update_ssvc
1600
1520
  pol = jamf_manual_install_released_policy
1601
- return unless pol
1602
1521
 
1603
- new_cat = changes_for_update[:self_service_category][:new]
1522
+ adding_to_ssvc = changes_for_update.dig :self_service, :new
1604
1523
 
1605
- progress(
1606
- "Jamf: Updating Self Service Category to '#{new_cat}' for Manual Install Policy '#{pol.name}'.",
1607
- log: :info
1608
- )
1524
+ updating_config = changes_for_update.dig :self_service_category, :new
1525
+ updating_config ||= changes_for_update.dig :description, :new
1526
+ updating_config ||= changes_for_update.dig :display_name, :new
1609
1527
 
1610
- old_cats = pol.self_service_categories.map { |c| c[:name] }
1611
- old_cats.each { |c| pol.remove_self_service_category c }
1612
- pol.add_self_service_category new_cat
1613
- pol.save
1528
+ # if adding_to_ssvc is nil, we aren't changing it at all
1529
+ # it must be false to indicate removal.
1530
+ removing_from_ssvc = adding_to_ssvc == false
1614
1531
 
1615
- # TODO: if we decide to use ssvc in patch policies, loop thru versions to make any changes
1616
- end
1532
+ if adding_to_ssvc
1533
+ add_title_to_self_service(pol)
1617
1534
 
1618
- # Update the SSvc Icon for the policies used by this version
1619
- #
1620
- # @param ttl_obj [Xolo::Server::Title] The pre-instantiated title for ths version.
1621
- # if nil, we'll instantiate it now
1622
- #
1623
- # @return [void]
1624
- ###############################
1625
- def update_ssvc_icon(ttl_obj: nil)
1626
- ttl_obj ||= title_object
1627
- # update manual install policy
1535
+ elsif updating_config
1536
+ configure_pol_for_self_service(pol)
1628
1537
 
1629
- log_debug "Jamf: Updating SSvc Icon for Manual Install Policy '#{jamf_manual_install_policy_name}'"
1630
- pol = jamf_manual_install_policy
1631
- return unless pol
1538
+ # we should not be in SSvc, remove it, but leave all the settings
1539
+ elsif removing_from_ssvc
1540
+ remove_title_from_self_service(pol)
1541
+ end
1632
1542
 
1633
- pol.upload :icon, ttl_obj.ssvc_icon_file
1634
- progress "Jamf: Updated Icon for Manual Install Policy '#{jamf_manual_install_policy_name}'",
1635
- log: :debug
1543
+ pol.save
1636
1544
 
1637
- # TODO: When we figure out if we want patch policies to use
1638
- # ssvc - they will need to be updated also
1545
+ # TODO: if we decide to use ssvc in patch policies, loop thru versions to make any changes
1639
1546
  end
1640
1547
 
1641
1548
  # configure the self-service settings of the manual_install_released_policy
1549
+ # NOTE this doesn't actually add it to self service, just configures the settings
1550
+ # See add_title_to_self_service
1551
+ #
1642
1552
  # @param pol [Jamf::Policy] The jamf_manual_install_released_policy, which may not be saved yet.
1643
1553
  # @return [void]
1644
1554
  ############################
1645
1555
  def configure_pol_for_self_service(pol = nil)
1646
1556
  pol ||= jamf_manual_install_released_policy
1647
1557
 
1648
- # clear existing categories, re-add correct one
1649
- pol.self_service_categories.each { |cat| pol.remove_self_service_category cat }
1650
- pol.add_self_service_category self_service_category
1558
+ new_cat = changes_for_update&.dig(:self_service_category, :new) || self_service_category
1559
+ if new_cat
1560
+ progress "Jamf: Setting Self Service category to '#{new_cat}'", log: :debug
1561
+ # clear existing categories, re-add correct one
1562
+ pol.self_service_categories.each { |cat| pol.remove_self_service_category cat }
1563
+ pol.add_self_service_category new_cat if new_cat
1564
+ end
1565
+
1566
+ new_display_name = changes_for_update&.dig(:self_service_display_name, :new) || display_name
1567
+ progress "Jamf: Setting Self Service display name to '#{new_display_name}'", log: :debug
1568
+ pol.self_service_display_name = new_display_name
1569
+
1570
+ new_desc = changes_for_update&.dig(:self_service_description, :new) || description
1571
+ progress "Jamf: Setting Self Service description to '#{new_desc}'", log: :debug
1572
+ pol.self_service_description = new_desc
1651
1573
 
1652
- pol.self_service_description = description
1653
- pol.self_service_display_name = display_name
1654
1574
  pol.self_service_install_button_text = Xolo::Server::Title::SELF_SERVICE_INSTALL_BTN_TEXT
1655
1575
  return unless ssvc_icon_file
1656
1576
 
1577
+ progress 'Jamf: Uploading Self Service icon', log: :debug
1657
1578
  pol.save # won't do anything unless needed, but has to exist before we can upload icons
1658
1579
  pol.upload :icon, ssvc_icon_file
1580
+ # re-fetch the pol to get the icon id
1659
1581
  self.ssvc_icon_id = Jamf::Policy.fetch(id: pol.id, cnx: jamf_cnx).icon.id
1660
1582
  end
1661
1583
 
1584
+ # run the autopkg recipe if defined
1585
+ # See Helpers::AutoPkg for more details
1586
+ ############################
1587
+ def run_autopkg_recipe
1588
+ server_app_instance.run_autopkg_recipe self
1589
+ end
1590
+
1591
+ # Methods used by subscription stuff
1592
+ ##############################
1593
+
1594
+ # Returns an array of known patch versions for this title from Jamf
1595
+ # These are not necessarily in xolo, but they are available from the Patch Source.
1596
+ #
1597
+ # One or more Hashes like this:
1598
+ # {
1599
+ # "version": "143.0.7499.170",
1600
+ # "releaseDate": "2025-12-18T20:31:01Z",
1601
+ # "standalone": true,
1602
+ # "minimumOperatingSystem": "12.0",
1603
+ # "rebootRequired": false,
1604
+ # "killApps": [
1605
+ # {
1606
+ # "appName": "Google Chrome"
1607
+ # }
1608
+ # ],
1609
+ # "absoluteOrderId": "0"
1610
+ # }
1611
+ #
1612
+ # @param version [String, Symbol, nil] If a string, only return info about that version, if :latest,
1613
+ # only the latest version, otherwise all available versions known by the patch source.
1614
+ #
1615
+ # @param refresh [Boolean] re-read the data from the Jamf Pro API
1616
+ #
1617
+ # @return [Array<Hash>] Data about available patch versions for this title
1618
+ ################################
1619
+ def patch_versions(version: nil, refresh: false)
1620
+ @patch_versions = nil if refresh
1621
+ @patch_versions ||= []
1622
+
1623
+ if @patch_versions.empty?
1624
+ page = 0
1625
+ loop do
1626
+ jpapi_path = "#{Xolo::Server::JPAPI_PATCH_TITLE_ENDPOINT}/#{jamf_patch_title_id}/definitions?page=#{page}&page-size=1000&sort=absoluteOrderId%3Aasc"
1627
+ log_debug "Jamf: Fetching patch versions for title '#{title}' from Jamf API endpoint '#{jpapi_path}'"
1628
+
1629
+ data = jamf_cnx.jp_get jpapi_path
1630
+ break if data[:results].empty?
1631
+
1632
+ @patch_versions += data[:results]
1633
+ page += 1
1634
+ end # loop
1635
+ end # if
1636
+
1637
+ case version
1638
+ when nil
1639
+ @patch_versions
1640
+ when :latest
1641
+ @patch_versions.select { |p| p[:absoluteOrderId] == '0' }
1642
+ else
1643
+ @patch_versions.select { |p| p[:version] == version }
1644
+ end # case
1645
+ end # patch_versions
1646
+
1662
1647
  end # TitleJamfPro
1663
1648
 
1664
1649
  end # Mixins