xolo-server 1.0.1 → 2.0.3

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/data/client/xolo +182 -82
  3. data/lib/xolo/core/base_classes/title.rb +261 -20
  4. data/lib/xolo/core/base_classes/version.rb +53 -8
  5. data/lib/xolo/core/constants.rb +7 -3
  6. data/lib/xolo/core/security_cmd.rb +128 -0
  7. data/lib/xolo/core/version.rb +1 -1
  8. data/lib/xolo/core.rb +1 -0
  9. data/lib/xolo/server/app.rb +7 -0
  10. data/lib/xolo/server/configuration.rb +243 -37
  11. data/lib/xolo/server/constants.rb +10 -0
  12. data/lib/xolo/server/helpers/auth.rb +19 -2
  13. data/lib/xolo/server/helpers/autopkg.rb +170 -0
  14. data/lib/xolo/server/helpers/client_data.rb +91 -61
  15. data/lib/xolo/server/helpers/file_transfers.rb +412 -82
  16. data/lib/xolo/server/helpers/jamf_pro.rb +30 -7
  17. data/lib/xolo/server/helpers/log.rb +2 -0
  18. data/lib/xolo/server/helpers/maintenance.rb +1 -0
  19. data/lib/xolo/server/helpers/notification.rb +4 -3
  20. data/lib/xolo/server/helpers/pkg_signing.rb +16 -12
  21. data/lib/xolo/server/helpers/progress_streaming.rb +9 -12
  22. data/lib/xolo/server/helpers/subscriptions.rb +119 -0
  23. data/lib/xolo/server/helpers/titles.rb +27 -3
  24. data/lib/xolo/server/helpers/versions.rb +23 -11
  25. data/lib/xolo/server/mixins/changelog.rb +9 -16
  26. data/lib/xolo/server/mixins/title_jamf_access.rb +395 -383
  27. data/lib/xolo/server/mixins/title_ted_access.rb +29 -3
  28. data/lib/xolo/server/mixins/version_jamf_access.rb +185 -159
  29. data/lib/xolo/server/mixins/version_ted_access.rb +25 -0
  30. data/lib/xolo/server/object_locks.rb +2 -1
  31. data/lib/xolo/server/routes/auth.rb +2 -2
  32. data/lib/xolo/server/routes/jamf_pro.rb +11 -1
  33. data/lib/xolo/server/routes/maint.rb +2 -1
  34. data/lib/xolo/server/routes/subscriptions.rb +126 -0
  35. data/lib/xolo/server/routes/title_editor.rb +1 -1
  36. data/lib/xolo/server/routes/titles.rb +30 -17
  37. data/lib/xolo/server/routes/uploads.rb +0 -14
  38. data/lib/xolo/server/routes/versions.rb +22 -17
  39. data/lib/xolo/server/routes.rb +9 -0
  40. data/lib/xolo/server/title.rb +107 -78
  41. data/lib/xolo/server/tmp_overrides.rb +38 -0
  42. data/lib/xolo/server/version.rb +193 -15
  43. data/lib/xolo/server.rb +12 -0
  44. metadata +8 -9
@@ -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,18 @@ 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
+ sleep 5
181
+
182
+ # must delete this group before the patch title
183
+ # since the group criteria references the patch title
191
184
  delete_jamf_installed_group
192
- delete_jamf_normal_ea
185
+ sleep 5
186
+
193
187
  delete_jamf_patch_title
188
+
189
+ # static group deleted last,
190
+ # was used in scopes for patch and normal policies
191
+ delete_jamf_frozen_group
194
192
  end
195
193
 
196
194
  # If any title changes require updates to existing versions in
@@ -211,8 +209,6 @@ module Xolo
211
209
  vers_obj.update_release_groups(ttl_obj: self) if changes_for_update&.key? :release_groups
212
210
  vers_obj.update_excluded_groups(ttl_obj: self) if changes_for_update&.key? :excluded_groups
213
211
  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
212
  end
217
213
  end
218
214
 
@@ -271,12 +267,13 @@ module Xolo
271
267
  ######################
272
268
  def patch_report(vers: nil)
273
269
  vers = Xolo::Server::Helpers::JamfPro::PATCH_REPORT_UNKNOWN_VERSION if vers == Xolo::UNKNOWN
274
- vers &&= CGI.escape vers.to_s
275
270
 
276
271
  page_size = Xolo::Server::Helpers::JamfPro::PATCH_REPORT_JPAPI_PAGE_SIZE
277
272
  page = 0
278
- paged_rsrc = "#{patch_report_rsrc}?page=#{page}&page-size=#{page_size}"
279
- paged_rsrc << "&filter=version%3D%3D#{vers}" if vers
273
+ paged_rsrc = +"#{patch_report_rsrc}?page=#{page}&page-size=#{page_size}"
274
+
275
+ filter = vers ? jp_api_filter_string_for_version(vers) : nil
276
+ paged_rsrc << filter if filter
280
277
 
281
278
  report = []
282
279
  loop do
@@ -287,7 +284,7 @@ module Xolo
287
284
  report += data
288
285
  page += 1
289
286
  paged_rsrc = "#{patch_report_rsrc}?page=#{page}&page-size=#{page_size}"
290
- paged_rsrc << "&filter=version%3D%3D#{vers}" if vers
287
+ paged_rsrc << filter if filter
291
288
  end
292
289
 
293
290
  # log_debug "REPORT: #{report}"
@@ -300,118 +297,25 @@ module Xolo
300
297
  report
301
298
  end
302
299
 
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.
300
+ # given a version, return a patch report filter
301
+ # with it properly quoted and URL escaped
315
302
  #
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)
303
+ # e.g. this string: 7.1.5 (84650)
304
+ # becomes: version=="7.1.5 (84650)"
305
+ # becomes: version%3D%3D%227.1.5+%2884650%29%22
306
+ # becomes: &filter=version%3D%3D%227.1.5+%2884650%29%22
343
307
  #
344
- # If we have one already but are deleting it, that happens elsewhere
308
+ # Use CGI.escape because it encodes all special cars, like parens,
309
+ # which we want here, rather than URI::Parser.new.escape
310
+ # which is meant to parse whole URLs leaving URL functional characters.
345
311
  #
346
- # @return [void]
312
+ # @param vers [String] the version the filter for
313
+ # @return [String] the quoted and escaped filter string
347
314
  ################################
348
- def configure_jamf_normal_ea
349
- # nothing to do if its nil, if we need to delete it, that'll happen later
350
- return if version_script_contents.pix_empty?
351
-
352
- progress "Jamf: Configuring regular extension attribute '#{jamf_normal_ea_name}'", log: :info
353
-
354
- jamf_normal_ea.description = "The version of xolo title '#{title}' installed on the machine"
355
- jamf_normal_ea.data_type = 'String'
356
- jamf_normal_ea.input_type = 'script'
357
- jamf_normal_ea.enable
358
- jamf_normal_ea.script = version_script_contents
359
- jamf_normal_ea.save
360
- end
361
-
362
- # Repair the 'normal' EA in jamf to match our version_script
363
- ########################
364
- def repair_jamf_normal_ea
365
- if version_script_contents.pix_empty?
366
- delete_jamf_normal_ea
367
- else
368
- configure_jamf_normal_ea
369
- end
370
- end
371
-
372
- # Delete the 'normal' computer ext attr matching the Patch EA
373
- # @return [void]
374
- ######################################
375
- def delete_jamf_normal_ea
376
- ea_id = Jamf::ComputerExtensionAttribute.valid_id jamf_normal_ea_name, cnx: jamf_cnx
377
- return unless ea_id
378
-
379
- progress "Jamf: Deleting regular extension attribute '#{jamf_normal_ea_name}'", log: :info
380
- Jamf::ComputerExtensionAttribute.delete ea_id, cnx: jamf_cnx
381
- end
382
-
383
- # do we need to update the normal EA in jamf?
384
- # true if our incoming changes include :version_script
385
- # and the new value is not empty (in which case we'll delete it)
386
- #
387
- # @return [Boolean]
388
- ########################
389
- def need_to_update_jamf_normal_ea?
390
- changes_for_update.key?(:version_script) && !changes_for_update[:version_script][:new].pix_empty?
391
- end
392
-
393
- # the script contents of the Normal Jamf EA that comes from our version_script
394
- # @return [String, nil] nil if there is none
395
- ##############################
396
- def jamf_normal_ea_contents
397
- return unless jamf_normal_ea_exist?
398
-
399
- jamf_normal_ea.script
400
- end
401
-
402
- # @return [String] the URL for the Normal EA in Jamf Pro
403
- ######################
404
- def jamf_normal_ea_url
405
- return @jamf_normal_ea_url if @jamf_normal_ea_url
406
- return unless version_script
407
-
408
- ea_id = Jamf::ComputerExtensionAttribute.valid_id jamf_normal_ea_name, cnx: jamf_cnx
409
- return unless ea_id
410
-
411
- # Jamf Changed the URL!
412
- # @jamf_normal_ea_url = "#{jamf_gui_url}/computerExtensionAttributes.html?id=#{ea_id}&o=r"
413
-
414
- @jamf_normal_ea_url = "#{jamf_gui_url}/view/settings/computer-management/computer-extension-attributes/#{ea_id}"
315
+ def jp_api_filter_string_for_version(vers)
316
+ raw_fiter_query = "version==\"#{vers}\""
317
+ escaped_filter_query = CGI.escape raw_fiter_query
318
+ "&filter=#{escaped_filter_query}"
415
319
  end
416
320
 
417
321
  ####### The Patch Ext Attribute
@@ -432,6 +336,8 @@ module Xolo
432
336
  # @return [Boolean]
433
337
  #########################
434
338
  def need_to_accept_jamf_patch_ea?
339
+ return unless managed?
340
+
435
341
  @need_to_accept_jamf_patch_ea
436
342
  end
437
343
 
@@ -457,16 +363,22 @@ module Xolo
457
363
  # @return [void]
458
364
  ############################
459
365
  def accept_jamf_patch_ea
460
- return unless need_to_accept_jamf_patch_ea?
366
+ # give the server a moment to catch up with the new EA before we check on it
367
+ log_debug "Jamf: Checking if we need to accept the version-script EA for title '#{title}'"
368
+ sleep 5
369
+
370
+ awating_acceptance = jamf_patch_ea_awaiting_acceptance?
461
371
 
462
- # return with warning if we aren't auto-accepting
463
- unless Xolo::Server.config.jamf_auto_accept_xolo_eas
464
- 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') "
465
- progress msg
372
+ # return with warning if we aren't auto-accepting, or for all subscribed titles
373
+ if awating_acceptance && (subscribed? || !Xolo::Server.config.jamf_auto_accept_xolo_eas)
374
+ 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."
375
+ progress msg, log: :warn, alert: true
466
376
  log_debug 'Admin informed about accepting EA/version-script manually'
467
377
  return
468
378
  end
469
379
 
380
+ return unless need_to_accept_jamf_patch_ea?
381
+
470
382
  # this is true if the Jamf server already knows it needs to be accepted
471
383
  # so just do it now
472
384
  if jamf_patch_ea_awaiting_acceptance?
@@ -607,6 +519,8 @@ module Xolo
607
519
  end
608
520
 
609
521
  # API call to accept the version-script EA in Jamf Pro
522
+ # This never happens for subscribed titles
523
+ #
610
524
  # TODO: when this gets implemented in ruby-jss, use that implementation
611
525
  #
612
526
  # @return [void]
@@ -640,7 +554,7 @@ module Xolo
640
554
  ######################
641
555
  def jamf_patch_ea_url
642
556
  return @jamf_patch_ea_url if @jamf_patch_ea_url
643
- return unless version_script
557
+ return unless version_script || (subscribed? && jamf_patch_ea_data)
644
558
 
645
559
  @jamf_patch_ea_url = "#{jamf_patch_title_url}?tab=extension"
646
560
  end
@@ -786,7 +700,10 @@ module Xolo
786
700
  pol.add_script jamf_uninstall_script_name
787
701
  pol.set_trigger_event :checkin, false
788
702
  pol.set_trigger_event :custom, jamf_uninstall_policy_name
789
- pol.scope.add_target(:computer_group, jamf_installed_group_name)
703
+ # pol.scope.add_target(:computer_group, jamf_installed_group_name)
704
+ # all targets, cus jamf doesn't like some policies with scoped
705
+ # smart groups using 'latest version'
706
+ pol.scope.set_all_targets
790
707
  pol.scope.set_exclusions :computer_groups, [valid_forced_exclusion_group_name] if valid_forced_exclusion_group_name
791
708
  pol.frequency = :ongoing
792
709
  pol.enable
@@ -804,7 +721,6 @@ module Xolo
804
721
  end
805
722
 
806
723
  # delete the policy first if it exists
807
-
808
724
  #########################
809
725
  def delete_jamf_uninstall_policy
810
726
  return unless jamf_uninstall_policy_exist?
@@ -861,9 +777,9 @@ module Xolo
861
777
  )
862
778
  @jamf_installed_group.save
863
779
  configure_jamf_installed_group
864
- log_debug 'Jamf: Sleeping to let Jamf server see change to the Installed smart group.'
865
- sleep 10
780
+
866
781
  end
782
+
867
783
  @jamf_installed_group
868
784
  end
869
785
 
@@ -876,75 +792,34 @@ module Xolo
876
792
 
877
793
  jamf_installed_group.criteria = Jamf::Criteriable::Criteria.new(jamf_installed_group_criteria)
878
794
  jamf_installed_group.save
795
+ log_debug 'Jamf: Sleeping 30 secs to let Jamf server see changes to Installed smart group.'
796
+ sleep 30
879
797
  end
880
798
 
881
799
  # The criteria for the smart group in Jamf that contains all Macs
882
800
  # with any version of this title installed
883
801
  #
884
- # If we have, or are about to update to, a version_script (EA) then use it,
885
- # otherwise use the app_name and app_bundle_id.
802
+ # We use the "Patch Reporting: #{display_name}" criterion so that we don't
803
+ # care whether the title uses a version-script or app data.
886
804
  #
887
805
  # @return [Array<Jamf::Criteriable::Criterion>]
888
806
  ###################################
889
807
  def jamf_installed_group_criteria
890
- have_vers_script =
891
- if changes_for_update&.dig :version_script
892
- changes_for_update[:version_script][:new]
893
- else
894
- version_script
895
- end
896
-
897
- # If we have a version_script, use the ea
898
- if have_vers_script
899
- [
900
- Jamf::Criteriable::Criterion.new(
901
- and_or: :and,
902
- name: jamf_normal_ea_name,
903
- search_type: 'is not',
904
- value: Xolo::BLANK
905
- )
906
- ]
907
-
908
- # No version script, so we must be using app data
909
- else
910
- aname = changes_for_update.dig(:app_name, :new) || app_name
911
- abundle = changes_for_update.dig(:app_bundle_id, :new) || app_bundle_id
912
-
913
- [
914
- Jamf::Criteriable::Criterion.new(
915
- and_or: :and,
916
- name: 'Application Title',
917
- search_type: 'is',
918
- value: aname
919
- ),
920
-
921
- Jamf::Criteriable::Criterion.new(
922
- and_or: :and,
923
- name: 'Application Bundle ID',
924
- search_type: 'is',
925
- value: abundle
926
- )
927
- ]
928
- end
929
- end
930
-
931
- # do we need to update the 'installed' smart group?
932
- # true if our incoming changes include the app_name or app_bundle_id
933
- #
934
- # If they changed at all, we need to update no matter what:
935
- # - if they are now nil, we switched to a version script
936
- #
937
- # - if they aren't nil but are different, we need to update
938
- # the group criteria to reflect that.
939
- #
940
- # Changes to the version script, if it was in use before, don't
941
- # require us to change the smart group
942
- #
943
- #
944
- # @return [Boolean]
945
- #########################
946
- def need_to_update_jamf_installed_group?
947
- changes_for_update[:app_name] || changes_for_update[:app_bundle_id]
808
+ [
809
+ Jamf::Criteriable::Criterion.new(
810
+ and_or: :and,
811
+ name: "Patch Reporting: #{display_name}",
812
+ search_type: 'less than or equal',
813
+ value: 'Latest Version'
814
+ ),
815
+
816
+ Jamf::Criteriable::Criterion.new(
817
+ and_or: :or,
818
+ name: "Patch Reporting: #{display_name}",
819
+ search_type: 'is',
820
+ value: 'Unknown Version'
821
+ )
822
+ ]
948
823
  end
949
824
 
950
825
  # Delete the 'installed' smart group
@@ -1239,14 +1114,15 @@ module Xolo
1239
1114
  #
1240
1115
  # @return [Jamf::PatchSource] The Jamf Patch Source
1241
1116
  #########################
1242
- def jamf_ted_patch_source
1243
- @jamf_ted_patch_source ||=
1117
+ def jamf_managed_patch_source
1118
+ @jamf_managed_patch_source ||=
1244
1119
  Jamf::PatchSource.fetch(name: Xolo::Server.config.ted_patch_source, cnx: jamf_cnx)
1245
1120
  end
1246
1121
 
1247
1122
  # The titles available from the Title Editor via its
1248
- # Jamf Patch Source. These are titles have have been enabled
1249
- # in the Title Editor
1123
+ # Jamf Patch Source.
1124
+ # These are titles have have been enabled in the Title Editor
1125
+ # but have not yet been activated in Jamf Patch.
1250
1126
  #
1251
1127
  # available_titles returns a Hash for each available title, with these keys:
1252
1128
  #
@@ -1266,93 +1142,79 @@ module Xolo
1266
1142
  #
1267
1143
  # @return [Array<String>] info about the available titles
1268
1144
  #########################
1269
- def jamf_ted_available_titles
1145
+ def jamf_available_managed_titles
1270
1146
  # Don't cache this in an instance var, it changes during the
1271
1147
  # life of our title instance
1272
- # jamf_ted_patch_source.available_titles.map { |t| t[:name_id] }
1148
+ # jamf_managed_patch_source.available_titles.map { |t| t[:name_id] }
1273
1149
  # Also NOTE: "available" means not only enabled
1274
1150
  # in the title editor, but also not already active in jamf.
1275
1151
  # So any given title will either be here or in
1276
- # jamf_active_ted_titles, but never both.
1277
- jamf_ted_patch_source.available_name_ids
1152
+ # jamf_active_managed_titles, but never both.
1153
+ jamf_managed_patch_source.available_name_ids
1278
1154
  end
1279
1155
 
1280
1156
  # @return [Boolean] Is this xolo title available in Jamf?
1281
1157
  ########################
1282
- def jamf_ted_title_available?
1283
- jamf_ted_available_titles.include? title
1284
- end
1285
-
1286
- # The titles active in Jamf Patch Management from the Title Editor
1287
- # This takes into account that other Patch Sources may have titles with the
1288
- # same 'name_id' (the xolo 'title')
1289
- # A hash keyed by the title, with values of the jamf title id
1290
- #
1291
- # @return [Hash {String => Integer}] The xolo titles that are active in Jamf Patch Management
1292
- ########################
1293
- def jamf_active_ted_titles(refresh: false)
1294
- @jamf_active_ted_titles = nil if refresh
1295
- return @jamf_active_ted_titles if @jamf_active_ted_titles
1296
-
1297
- @jamf_active_ted_titles = {}
1298
- active_from_ted = Jamf::PatchTitle.all(:refresh, cnx: jamf_cnx).select do |t|
1299
- t[:source_id] == jamf_ted_patch_source.id
1300
- end
1301
- active_from_ted.each { |t| @jamf_active_ted_titles[t[:name_id]] = t[:id] }
1302
- @jamf_active_ted_titles
1158
+ def jamf_managed_title_available?
1159
+ jamf_available_managed_titles.include? title
1303
1160
  end
1304
1161
 
1305
1162
  # @return [Boolean] Is this xolo title currently active in Jamf?
1306
1163
  ########################
1307
- def jamf_ted_title_active?
1308
- jamf_active_ted_titles(refresh: true).key? title
1164
+ def jamf_title_active?
1165
+ !jamf_patch_title_id.pix_empty?
1166
+ # if subscribed?
1167
+ # jamf_active_subscribed_titles(refresh: true).key? title
1168
+ # else
1169
+ # jamf_active_managed_titles(refresh: true).key? title
1170
+ # end
1309
1171
  end
1310
1172
 
1311
- # The Jamf ID of the Patch Title for this xolo title
1312
- # if it has been activated in jamf.
1173
+ # The managed titles active in Jamf Patch Management from the Title Editor
1174
+ # A hash keyed by the title, with values of the jamf patch title id
1313
1175
  #
1314
- # @return [Integer, nil] The Jamf ID of this title, if it is active in Jamf
1176
+ # @return [Hash {String => Integer}] The managed xolo titles that are active in Jamf Patch Management
1177
+ # and their Jamf::PatchTitle id
1315
1178
  ########################
1316
- def jamf_patch_title_id
1317
- @jamf_patch_title_id ||= jamf_active_ted_titles(refresh: true)[title]
1318
- end
1179
+ def jamf_active_managed_titles(refresh: false)
1180
+ @jamf_active_managed_titles = nil if refresh
1181
+ return @jamf_active_managed_titles if @jamf_active_managed_titles
1319
1182
 
1320
- # create/activate the patch title in Jamf Pro, if not already done.
1321
- #
1322
- # This 'subscribes' Jamf to the title in the title editor
1323
- # It must be enabled in the Title Editor first, meaning
1324
- # it has at least one requirement, and at least one enabled patch/version.
1325
- #
1326
- # The 'stub' patch version should allow this when we create the title.
1327
- #
1328
- # Xolo should have enabled it in the Title editor before we
1329
- # reach this point.
1330
- #
1331
- ##########################
1332
- def activate_jamf_patch_title
1333
- if jamf_ted_title_active?
1334
- log_debug "Jamf: Title '#{display_name}' (#{title}) is already active in Jamf"
1335
- return
1336
- end
1183
+ @jamf_active_managed_titles = {}
1184
+ all_patch_titles = Jamf::PatchTitle.all(cnx: jamf_cnx)
1337
1185
 
1338
- # wait up to 60secs for the title to become available
1339
- counter = 0
1340
- until jamf_ted_title_available? || counter == 12
1341
- log_debug "Jamf: Waiting for title '#{display_name}' (#{title}) to become available from the Title Editor"
1342
- sleep 5
1343
- counter += 1
1186
+ active_from_ted = all_patch_titles.select do |t|
1187
+ # log_debug "Jamf: Checking if active patch title '#{t[:name]}' with id #{t[:id]} is from the Title Editor's patch source"
1188
+ t[:source_id] == jamf_managed_patch_source.id
1344
1189
  end
1345
1190
 
1346
- unless jamf_ted_title_available?
1347
- msg = "Jamf: Title '#{title}' is not yet available to Jamf. Make sure it has at least one version enabled in the Title Editor"
1348
- log_error msg
1349
- raise Xolo::NoSuchItemError, msg
1191
+ managed_titles = server_app_instance.managed_title_objects.select(&:managed?).map(&:title)
1192
+
1193
+ active_from_ted.each do |t|
1194
+ @jamf_active_managed_titles[t[:name_id]] = t[:id] if managed_titles.include? t[:name_id]
1350
1195
  end
1196
+ @jamf_active_managed_titles
1197
+ end
1351
1198
 
1352
- # This creates/activates the title if needed
1353
- jamf_patch_title
1199
+ # @return [Hash {String => Integer}] The subscribed xolo titles that are active in Jamf Patch Management
1200
+ # and their Jamf::PatchTitle id
1201
+ ########################
1202
+ def jamf_active_subscribed_titles(refresh: false)
1203
+ @jamf_active_subscribed_titles = nil if refresh
1204
+ return @jamf_active_subscribed_titles if @jamf_active_subscribed_titles
1354
1205
 
1355
- accept_jamf_patch_ea
1206
+ @jamf_active_subscribed_titles = {}
1207
+
1208
+ all_active_patchtitles = Jamf::PatchTitle.all :refresh, cnx: jamf_cnx
1209
+
1210
+ server_app_instance.subscribed_title_objects.each do |st|
1211
+ next unless all_active_patchtitles.any? do |pt|
1212
+ pt[:source_id] == st.jamf_patch_source_id && pt[:name_id] == st.title_id
1213
+ end
1214
+
1215
+ @jamf_active_subscribed_titles[st.title] = pt[:id]
1216
+ end
1217
+ @jamf_active_subscribed_titles
1356
1218
  end
1357
1219
 
1358
1220
  # Create or fetch the patch title object for this xolo title.
@@ -1365,52 +1227,118 @@ module Xolo
1365
1227
  @jamf_patch_title = nil if refresh
1366
1228
  return @jamf_patch_title if @jamf_patch_title
1367
1229
 
1368
- breaktime = Time.now + Xolo::Server::MAX_JAMF_WAIT_FOR_TITLE_EDITOR
1369
- until jamf_ted_title_available? || jamf_ted_title_active?
1370
- if Time.now > breaktime
1371
- raise Xolo::ServerError,
1372
- "Jamf: Title '#{title}' is not available in Jamf after waiting #{Xolo::Server::MAX_JAMF_WAIT_FOR_TITLE_EDITOR} seconds"
1373
- end
1374
-
1375
- log_debug "Waiting a long time for title '#{title}' to become available from TEd. Checking every 10 secs"
1376
- sleep 10
1377
- end
1230
+ # wait up to 60secs for a managed title to become available.
1231
+ # subscribed titles are already available
1232
+ wait_for_managed_title_to_become_available
1378
1233
 
1379
- if jamf_ted_title_active?
1234
+ # Jamf::PatchTitle object already active, just fetch it
1235
+ if jamf_patch_title_id # jamf_title_active? || subscribed?
1380
1236
  @jamf_patch_title = Jamf::PatchTitle.fetch(id: jamf_patch_title_id, cnx: jamf_cnx)
1381
1237
 
1238
+ # not yet active, so activate/create the Jamf::PatchTitle object
1382
1239
  else
1383
1240
  return if deleting?
1384
1241
 
1385
- msg = "Jamf: Activating Patch Title '#{display_name}' (#{title}) from the Title Editor Patch Source '#{Xolo::Server.config.ted_patch_source}'"
1386
- progress msg, log: :info
1242
+ @jamf_patch_title = activate_jamf_patch_title
1243
+ end
1244
+ end
1245
+
1246
+ # activate the patch title in jamf, whatever it's source
1247
+ ############################
1248
+ def activate_jamf_patch_title
1249
+ if jamf_title_active?
1250
+ log_debug "Jamf: Title '#{title}' is already active in Jamf Patch"
1251
+ return
1252
+ end
1253
+
1254
+ # patch_source and title_id are required when adding subbed titles
1255
+ # but pre-defined for managed
1256
+ if managed?
1257
+ log_debug "Jamf: Title '#{title}' is managed, so using pre-defined patch source and title_id"
1258
+ log_debug "Jamf: Display Name in managed?: #{display_name}"
1259
+ self.patch_source = Xolo::Server.config.ted_patch_source
1260
+ self.title_id = title
1261
+ log_debug "Jamf: Display Name in managed? after setting patch_source and title_id: #{display_name}"
1262
+
1263
+ # display name is required for managed titles, but will be empty for subbed
1264
+ # so for subbed, we look it up from the patch source
1265
+ else
1266
+ log_debug "Jamf: Title '#{title}' is subscribed, so looking up patch source and title_id from"
1267
+ log_debug "Jamf: Display Name in NOT managed?: #{display_name}"
1268
+ if display_name.pix_empty?
1269
+ self.display_name = Jamf::PatchSource.available_titles(patch_source, cnx: jamf_cnx).select { |t| t[:name_id] == title_id }.first&.dig :app_name
1270
+ end
1271
+ end # if managed
1272
+
1273
+ log_debug "Jamf: class: #{self.class}, display_name: #{display_name}, patch_source: #{patch_source}, title_id: #{title_id}"
1274
+
1275
+ msg = "Jamf: Activating Patch Title '#{display_name}' (#{title}) from the Patch Source '#{patch_source}'"
1276
+ progress msg, log: :info
1277
+
1278
+ jamf_patch_title =
1279
+ Jamf::PatchTitle.create(
1280
+ name: display_name,
1281
+ source: patch_source,
1282
+ name_id: title_id,
1283
+ cnx: jamf_cnx
1284
+ )
1285
+ jamf_patch_title.category = Xolo::Server::JAMF_XOLO_CATEGORY
1286
+ self.jamf_patch_title_id = jamf_patch_title.save
1287
+ log_debug "Activated Jamf Patch Title '#{display_name}' (#{title}) with id #{jamf_patch_title_id}"
1288
+
1289
+ jamf_patch_title
1290
+ end
1387
1291
 
1388
- @jamf_patch_title =
1389
- Jamf::PatchTitle.create(
1390
- name: display_name,
1391
- source: Xolo::Server.config.ted_patch_source,
1392
- name_id: title,
1393
- cnx: jamf_cnx
1394
- )
1395
- @jamf_patch_title.category = Xolo::Server::JAMF_XOLO_CATEGORY
1396
- @jamf_patch_title.save
1292
+ # wait up to 60secs for a managed title to become available to be activated
1293
+ # subscribed titles are already available
1294
+ ####################
1295
+ def wait_for_managed_title_to_become_available
1296
+ return unless managed?
1297
+ return if jamf_title_active?
1397
1298
 
1299
+ counter = 0
1300
+ until jamf_title_active? || jamf_managed_title_available? || counter == 12
1301
+ log_debug "Jamf: Waiting for title '#{display_name}' (#{title}) to become available from the Title Editor"
1302
+ sleep 5
1303
+ counter += 1
1398
1304
  end
1399
- @jamf_patch_title
1305
+
1306
+ return if jamf_managed_title_available? || jamf_title_active?
1307
+
1308
+ msg = "Jamf: Title '#{title}' is not yet available to Jamf. Make sure it has at least one version enabled in the Title Editor"
1309
+ log_error msg
1310
+ raise Xolo::NoSuchItemError, msg
1400
1311
  end
1401
1312
 
1402
1313
  # Delete the patch title
1403
1314
  # NOTE: jamf api user must have 'delete computer ext. attribs' permmissions
1404
1315
  ##############################
1405
1316
  def delete_jamf_patch_title
1406
- return unless jamf_ted_title_active?
1317
+ log_debug "Jamf: Deleting patch title '#{display_name}' (#{title})"
1318
+ unless jamf_title_active?
1319
+ log_debug "Jamf: Patch title '#{display_name}' (#{title}) is not active in Jamf, nothing to delete"
1320
+ return
1321
+ end
1322
+
1323
+ # # subscribed titles often have name_id's like "1B7"
1324
+ # # whereas managed titles have name_id's that are the same as the title,
1325
+ # # so we have to look up the id differently for subbed vs managed
1326
+ # key = subscribed? ? title_id : title
1327
+ # pt_id = Jamf::PatchTitle.map_all(:id, to: :name_id, cnx: jamf_cnx).invert[key]
1407
1328
 
1408
- pt_id = Jamf::PatchTitle.map_all(:id, to: :name_id, cnx: jamf_cnx).invert[title]
1409
- return unless pt_id
1329
+ # unless pt_id
1330
+ # log_debug "Jamf: Cannot find patch title '#{display_name}' (#{title}) in Jamf, cannot delete"
1331
+ # return
1332
+ # end
1410
1333
 
1411
- msg = "Jamf: Deleting (unsubscribing) title '#{display_name}' (#{title}}) in Jamf Patch Management"
1334
+ # # 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
1335
+ # unless pt_id.to_s == jamf_patch_title_id.to_s
1336
+ # 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."
1337
+ # end
1338
+
1339
+ msg = "Jamf: Deleting (deactivating) title '#{display_name}' (#{title}}) in Jamf Patch Management"
1412
1340
  progress msg, log: :info
1413
- Jamf::PatchTitle.delete pt_id, cnx: jamf_cnx
1341
+ Jamf::PatchTitle.delete jamf_patch_title_id, cnx: jamf_cnx
1414
1342
  end
1415
1343
 
1416
1344
  # @return [String] the URL for the Patch Title in Jamf Pro
@@ -1476,6 +1404,7 @@ module Xolo
1476
1404
  end
1477
1405
 
1478
1406
  # Configure the settings for the manual_install_released_policy
1407
+ # Be sure to call .save on the policy after this to save the changes.
1479
1408
  # @param pol [Jamf::Policy] the policy we are configuring
1480
1409
  # @return [void]
1481
1410
  ###################
@@ -1490,15 +1419,7 @@ module Xolo
1490
1419
  # clear any existing packages
1491
1420
  pol.package_names.each { |pkg_name| pol.remove_package pkg_name }
1492
1421
 
1493
- # don't add a package or enable the pol if there's no released version
1494
- desired_vers = releasing_version || released_version
1495
- if desired_vers
1496
- rel_vers = version_object(desired_vers)
1497
- pol.add_package rel_vers.jamf_pkg_id
1498
- pol.enable
1499
- else
1500
- pol.disable
1501
- end
1422
+ toggle_jamf_manual_install_released_policy pol
1502
1423
 
1503
1424
  # figure out the exclusions.
1504
1425
  #
@@ -1514,12 +1435,36 @@ module Xolo
1514
1435
 
1515
1436
  pol.scope.set_exclusions :computer_groups, excls
1516
1437
 
1517
- return unless self_service
1438
+ if self_service?
1439
+ add_title_to_self_service(pol)
1440
+ else
1441
+ remove_title_from_self_service(pol)
1442
+ end
1443
+ end
1518
1444
 
1519
- if pol.in_self_service?
1520
- configure_pol_for_self_service(pol)
1445
+ # enable or disable the manual install policy for the current release, depending on
1446
+ # whether or not we have a released version
1447
+ # @param pol [Jamf::Policy] the manual install policy for the current release, which may or may not be saved yet.
1448
+ # return [void]
1449
+ ############################
1450
+ def toggle_jamf_manual_install_released_policy(pol, vobj = nil)
1451
+ # remove any current pkg payload
1452
+ pol.package_ids.each { |pid| pol.remove_package pid }
1453
+
1454
+ unless vobj
1455
+ desired_vers = releasing_version || released_version
1456
+ vobj = desired_vers ? version_object(desired_vers) : nil
1457
+ end
1458
+
1459
+ if vobj
1460
+ progress "Jamf: Setting policy #{jamf_manual_install_released_policy_name} to install the package for version '#{vobj.version}'", log: :info
1461
+ pol.add_package vobj.jamf_pkg_id
1462
+
1463
+ progress "Jamf: Enabling policy #{jamf_manual_install_released_policy_name}", log: :info
1464
+ pol.enable
1521
1465
  else
1522
- add_title_to_self_service(pol)
1466
+ progress "Jamf: Disabling policy #{jamf_manual_install_released_policy_name}, no released version", log: :info
1467
+ pol.disable
1523
1468
  end
1524
1469
  end
1525
1470
 
@@ -1549,111 +1494,178 @@ module Xolo
1549
1494
  # @return [void]
1550
1495
  ##################################
1551
1496
  def add_title_to_self_service(pol = nil)
1552
- return unless self_service
1553
-
1554
1497
  pol ||= jamf_manual_install_released_policy
1555
1498
 
1556
- msg = "Jamf: Adding Manual Install Policy '#{pol.name}' to Self Service."
1557
- progress msg, log: :info
1499
+ unless pol.in_self_service?
1500
+ msg = "Jamf: Adding Manual Install Policy '#{pol.name}' to Self Service."
1501
+ progress msg, log: :info
1502
+ pol.add_to_self_service
1503
+ end
1558
1504
 
1559
- pol.add_to_self_service
1560
1505
  configure_pol_for_self_service(pol)
1561
- end
1506
+ pol.save
1562
1507
 
1563
- # Update whether or not we are in self service, based on the setting in the title
1564
- #
1565
- #########################
1566
- def update_ssvc
1567
- return unless changes_for_update.key? :self_service
1508
+ # loop thru versions to make any SSvc changes
1509
+ version_objects.each do |vobj|
1510
+ ppol = vobj.jamf_patch_policy
1511
+ vobj.configure_patch_pol_for_self_service ppol
1512
+ ppol.save
1513
+ end
1514
+ end
1568
1515
 
1569
- # Update the manual install policy
1570
- pol = jamf_manual_install_released_policy
1571
- return unless pol
1516
+ # Add the jamf_manual_install_released_policy to self service if needed
1517
+ # @param pol [Jamf::Policy] The jamf_manual_install_released_policy, which may not be saved yet.
1518
+ # @return [void]
1519
+ ##################################
1520
+ def remove_title_from_self_service(pol = nil)
1521
+ pol ||= jamf_manual_install_released_policy
1522
+ return unless pol.in_self_service?
1572
1523
 
1573
- # we should be in SSvc - changes_for_update[:self_service][:new] is a boolean
1574
- if changes_for_update[:self_service][:new]
1575
- add_title_to_self_service(pol) unless pol.in_self_service?
1524
+ msg = "Jamf: Removing Manual Install Policy '#{pol.name}' from Self Service."
1525
+ progress msg, log: :info
1526
+ pol.remove_from_self_service
1576
1527
 
1577
- # we should not be in SSvc
1578
- elsif pol.in_self_service?
1579
- msg = "Jamf: Removing Manual Install Policy '#{pol.name}' from Self Service."
1580
- progress msg, log: :info
1581
- pol.remove_from_self_service
1582
- end
1583
1528
  pol.save
1584
1529
 
1585
- # TODO: if we decide to use ssvc in patch policies, loop thru versions to make any changes
1530
+ # loop thru versions to make any SSvc changes
1531
+ version_objects.each do |vobj|
1532
+ progress "Jamf: Removing patch policy for version '#{vobj.version}' of title '#{title}' from Self Service'", log: :info
1533
+ ppol = vobj.jamf_patch_policy
1534
+ ppol.remove_from_self_service
1535
+ ppol.save
1536
+ end
1586
1537
  end
1587
1538
 
1588
- # Update our self service category, based on the setting in the title
1589
- # TODO: Allow multiple categories, and 'featuring' ?
1539
+ # Update whether or not we are in self service, based on the setting in the title
1590
1540
  #
1591
1541
  #########################
1592
- def update_ssvc_category
1593
- return unless changes_for_update.key? :self_service_category
1594
-
1542
+ def update_ssvc
1595
1543
  pol = jamf_manual_install_released_policy
1596
- return unless pol
1597
1544
 
1598
- new_cat = changes_for_update[:self_service_category][:new]
1545
+ adding_to_ssvc = changes_for_update.dig :self_service, :new
1599
1546
 
1600
- progress(
1601
- "Jamf: Updating Self Service Category to '#{new_cat}' for Manual Install Policy '#{pol.name}'.",
1602
- log: :info
1603
- )
1547
+ updating_config = changes_for_update.dig :self_service_category, :new
1548
+ updating_config ||= changes_for_update.dig :description, :new
1549
+ updating_config ||= changes_for_update.dig :display_name, :new
1604
1550
 
1605
- old_cats = pol.self_service_categories.map { |c| c[:name] }
1606
- old_cats.each { |c| pol.remove_self_service_category c }
1607
- pol.add_self_service_category new_cat
1608
- pol.save
1551
+ # if adding_to_ssvc is nil, we aren't changing it at all
1552
+ # it must be false to indicate removal.
1553
+ removing_from_ssvc = adding_to_ssvc == false
1609
1554
 
1610
- # TODO: if we decide to use ssvc in patch policies, loop thru versions to make any changes
1611
- end
1612
-
1613
- # Update the SSvc Icon for the policies used by this version
1614
- #
1615
- # @param ttl_obj [Xolo::Server::Title] The pre-instantiated title for ths version.
1616
- # if nil, we'll instantiate it now
1617
- #
1618
- # @return [void]
1619
- ###############################
1620
- def update_ssvc_icon(ttl_obj: nil)
1621
- ttl_obj ||= title_object
1622
- # update manual install policy
1555
+ if adding_to_ssvc
1556
+ add_title_to_self_service(pol)
1623
1557
 
1624
- log_debug "Jamf: Updating SSvc Icon for Manual Install Policy '#{jamf_manual_install_policy_name}'"
1625
- pol = jamf_manual_install_policy
1626
- return unless pol
1558
+ elsif updating_config
1559
+ configure_pol_for_self_service(pol)
1627
1560
 
1628
- pol.upload :icon, ttl_obj.ssvc_icon_file
1629
- progress "Jamf: Updated Icon for Manual Install Policy '#{jamf_manual_install_policy_name}'",
1630
- log: :debug
1561
+ # we should not be in SSvc, remove it, but leave all the settings
1562
+ elsif removing_from_ssvc
1563
+ remove_title_from_self_service(pol)
1564
+ end
1631
1565
 
1632
- # TODO: When we figure out if we want patch policies to use
1633
- # ssvc - they will need to be updated also
1566
+ pol.save
1634
1567
  end
1635
1568
 
1636
1569
  # configure the self-service settings of the manual_install_released_policy
1570
+ # NOTE this doesn't actually add it to self service, just configures the settings
1571
+ # See add_title_to_self_service
1572
+ #
1637
1573
  # @param pol [Jamf::Policy] The jamf_manual_install_released_policy, which may not be saved yet.
1638
1574
  # @return [void]
1639
1575
  ############################
1640
1576
  def configure_pol_for_self_service(pol = nil)
1641
1577
  pol ||= jamf_manual_install_released_policy
1642
1578
 
1643
- # clear existing categories, re-add correct one
1644
- pol.self_service_categories.each { |cat| pol.remove_self_service_category cat }
1645
- pol.add_self_service_category self_service_category
1579
+ new_cat = changes_for_update&.dig(:self_service_category, :new) || self_service_category
1580
+ if new_cat
1581
+ progress "Jamf: Setting Self Service category to '#{new_cat}'", log: :debug
1582
+ # clear existing categories, re-add correct one
1583
+ pol.self_service_categories.each { |cat| pol.remove_self_service_category cat }
1584
+ pol.add_self_service_category new_cat if new_cat
1585
+ end
1586
+
1587
+ new_display_name = changes_for_update&.dig(:display_name, :new) || display_name
1588
+ progress "Jamf: Setting Self Service display name to '#{new_display_name}'", log: :debug
1589
+ pol.self_service_display_name = new_display_name
1590
+
1591
+ new_desc = changes_for_update&.dig(:description, :new) || description
1592
+ progress "Jamf: Setting Self Service description to '#{new_desc}'", log: :debug
1593
+ pol.self_service_description = new_desc
1646
1594
 
1647
- pol.self_service_description = description
1648
- pol.self_service_display_name = display_name
1649
1595
  pol.self_service_install_button_text = Xolo::Server::Title::SELF_SERVICE_INSTALL_BTN_TEXT
1650
1596
  return unless ssvc_icon_file
1651
1597
 
1598
+ # TODO: Is this needed if we already have a ssvc_icon_id ??
1599
+ progress 'Jamf: Uploading Self Service icon', log: :debug
1652
1600
  pol.save # won't do anything unless needed, but has to exist before we can upload icons
1653
1601
  pol.upload :icon, ssvc_icon_file
1602
+ # re-fetch the pol to get the icon id
1654
1603
  self.ssvc_icon_id = Jamf::Policy.fetch(id: pol.id, cnx: jamf_cnx).icon.id
1655
1604
  end
1656
1605
 
1606
+ # run the autopkg recipe if defined
1607
+ # See Helpers::AutoPkg for more details
1608
+ ############################
1609
+ def run_autopkg_recipe
1610
+ server_app_instance.run_autopkg_recipe self
1611
+ end
1612
+
1613
+ # Methods used by subscription stuff
1614
+ ##############################
1615
+
1616
+ # Returns an array of known patch versions for this title from Jamf
1617
+ # These are not necessarily in xolo, but they are available from the Patch Source.
1618
+ #
1619
+ # One or more Hashes like this:
1620
+ # {
1621
+ # "version": "143.0.7499.170",
1622
+ # "releaseDate": "2025-12-18T20:31:01Z",
1623
+ # "standalone": true,
1624
+ # "minimumOperatingSystem": "12.0",
1625
+ # "rebootRequired": false,
1626
+ # "killApps": [
1627
+ # {
1628
+ # "appName": "Google Chrome"
1629
+ # }
1630
+ # ],
1631
+ # "absoluteOrderId": "0"
1632
+ # }
1633
+ #
1634
+ # @param version [String, Symbol, nil] If a string, only return info about that version, if :latest,
1635
+ # only the latest version, otherwise all available versions known by the patch source.
1636
+ #
1637
+ # @param refresh [Boolean] re-read the data from the Jamf Pro API
1638
+ #
1639
+ # @return [Array<Hash>] Data about available patch versions for this title
1640
+ ################################
1641
+ def patch_versions(version: nil, refresh: false)
1642
+ @patch_versions = nil if refresh
1643
+ @patch_versions ||= []
1644
+
1645
+ if @patch_versions.empty?
1646
+ page = 0
1647
+ loop do
1648
+ jpapi_path = "#{Xolo::Server::JPAPI_PATCH_TITLE_ENDPOINT}/#{jamf_patch_title_id}/definitions?page=#{page}&page-size=1000&sort=absoluteOrderId%3Aasc"
1649
+ log_debug "Jamf: Fetching patch versions for title '#{title}' from Jamf API endpoint '#{jpapi_path}'"
1650
+
1651
+ data = jamf_cnx.jp_get jpapi_path
1652
+ break if data[:results].empty?
1653
+
1654
+ @patch_versions += data[:results]
1655
+ page += 1
1656
+ end # loop
1657
+ end # if
1658
+
1659
+ case version
1660
+ when nil
1661
+ @patch_versions
1662
+ when :latest
1663
+ @patch_versions.select { |p| p[:absoluteOrderId] == '0' }
1664
+ else
1665
+ @patch_versions.select { |p| p[:version] == version }
1666
+ end # case
1667
+ end # patch_versions
1668
+
1657
1669
  end # TitleJamfPro
1658
1670
 
1659
1671
  end # Mixins