xolo-server 1.0.0

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 (42) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +177 -0
  3. data/README.md +7 -0
  4. data/bin/xoloserver +106 -0
  5. data/data/com.pixar.xoloserver.plist +29 -0
  6. data/data/uninstall-pkgs-by-id.zsh +103 -0
  7. data/lib/xolo/server/app.rb +133 -0
  8. data/lib/xolo/server/command_line.rb +216 -0
  9. data/lib/xolo/server/configuration.rb +739 -0
  10. data/lib/xolo/server/constants.rb +70 -0
  11. data/lib/xolo/server/helpers/auth.rb +257 -0
  12. data/lib/xolo/server/helpers/client_data.rb +415 -0
  13. data/lib/xolo/server/helpers/file_transfers.rb +265 -0
  14. data/lib/xolo/server/helpers/jamf_pro.rb +156 -0
  15. data/lib/xolo/server/helpers/log.rb +97 -0
  16. data/lib/xolo/server/helpers/maintenance.rb +401 -0
  17. data/lib/xolo/server/helpers/notification.rb +145 -0
  18. data/lib/xolo/server/helpers/pkg_signing.rb +141 -0
  19. data/lib/xolo/server/helpers/progress_streaming.rb +252 -0
  20. data/lib/xolo/server/helpers/title_editor.rb +92 -0
  21. data/lib/xolo/server/helpers/titles.rb +145 -0
  22. data/lib/xolo/server/helpers/versions.rb +160 -0
  23. data/lib/xolo/server/log.rb +286 -0
  24. data/lib/xolo/server/mixins/changelog.rb +315 -0
  25. data/lib/xolo/server/mixins/title_jamf_access.rb +1668 -0
  26. data/lib/xolo/server/mixins/title_ted_access.rb +519 -0
  27. data/lib/xolo/server/mixins/version_jamf_access.rb +1541 -0
  28. data/lib/xolo/server/mixins/version_ted_access.rb +373 -0
  29. data/lib/xolo/server/object_locks.rb +102 -0
  30. data/lib/xolo/server/routes/auth.rb +89 -0
  31. data/lib/xolo/server/routes/jamf_pro.rb +89 -0
  32. data/lib/xolo/server/routes/maint.rb +174 -0
  33. data/lib/xolo/server/routes/title_editor.rb +71 -0
  34. data/lib/xolo/server/routes/titles.rb +285 -0
  35. data/lib/xolo/server/routes/uploads.rb +93 -0
  36. data/lib/xolo/server/routes/versions.rb +261 -0
  37. data/lib/xolo/server/routes.rb +168 -0
  38. data/lib/xolo/server/title.rb +1143 -0
  39. data/lib/xolo/server/version.rb +902 -0
  40. data/lib/xolo/server.rb +205 -0
  41. data/lib/xolo-server.rb +8 -0
  42. metadata +243 -0
@@ -0,0 +1,373 @@
1
+ # Copyright 2025 Pixar
2
+ #
3
+ # Licensed under the terms set forth in the LICENSE.txt file available at
4
+ # at the root of this project.
5
+ #
6
+ #
7
+
8
+ # frozen_string_literal: true
9
+
10
+ # main module
11
+ module Xolo
12
+
13
+ module Server
14
+
15
+ module Mixins
16
+
17
+ # This is mixed in to Xolo::Server::Version
18
+ # to define Version/Patch-related access to the Title Edit server
19
+ #
20
+ module VersionTedAccess
21
+
22
+ # Module methods
23
+ #
24
+ # These are available as module methods but not as 'helper'
25
+ # methods in sinatra routes & views.
26
+ #
27
+ ##############################
28
+ ##############################
29
+
30
+ # when this module is included
31
+ ##############################
32
+ def self.included(includer)
33
+ Xolo.verbose_include includer, self
34
+ end
35
+
36
+ # Instance methods
37
+ #
38
+ # These are available directly in sinatra routes and views
39
+ #
40
+ ##############################
41
+ ##############################
42
+
43
+ # Create a new version in the title editor
44
+ #
45
+ # TODO: allow specification of version_order, probably by accepting a value
46
+ # for the 'previous_version'?
47
+ #
48
+ # @return [void]
49
+ ##########################
50
+ def create_patch_in_ted
51
+ progress "Title Editor: Creating Patch '#{version}' of SoftwareTitle '#{title}'", log: :info
52
+ ted_title.patches.add_patch(
53
+ version: version,
54
+ minimumOperatingSystem: min_os,
55
+ releaseDate: publish_date,
56
+ reboot: reboot,
57
+ standalone: standalone
58
+ )
59
+ new_patch = ted_title.patches.patch version
60
+
61
+ set_patch_killapps
62
+ set_patch_capabilites
63
+ set_ted_patch_component_criteria ttl_obj: title_object
64
+
65
+ self.ted_id_number = new_patch.patchId
66
+ end
67
+
68
+ # Update version/patch in the title editor directly.
69
+ # This never called when updating versions via changes to
70
+ # the title - that process calls the sub-methods directly.
71
+ #
72
+ # @return [void]
73
+ ##########################
74
+ def update_patch_in_ted
75
+ progress "Title Editor: Updating Patch '#{version}' SoftwareTitle '#{title}'", log: :info
76
+
77
+ Xolo::Server::Version::ATTRIBUTES.each do |attr, deets|
78
+ ted_attribute = deets[:ted_attribute]
79
+ next unless ted_attribute
80
+
81
+ new_val = new_data_for_update[attr]
82
+ old_val = send(attr)
83
+ next if new_val == old_val
84
+
85
+ # These changes happen in real time on the Title Editor server, no need to #save
86
+ log_debug "Title Editor: Updating patch attribute '#{ted_attribute}': #{old_val} -> #{new_val}"
87
+ ted_patch.send "#{ted_attribute}=", new_val
88
+ end
89
+
90
+ set_patch_killapps if changes_for_update[:killapps]
91
+ set_patch_capabilites if changes_for_update[:min_os] || changes_for_update[:max_os]
92
+
93
+ # This should not be needed here - its only affected by changes to the title, and
94
+ # those are handled by the title object calling the same method
95
+ #
96
+ # set_ted_patch_component_criteria ttl_obj: title_object
97
+ end
98
+
99
+ # Set any killapps for this version in the title editor.
100
+ #
101
+ # @return [void]
102
+ ##########################
103
+ def set_patch_killapps
104
+ # creating a new patch
105
+ if creating?
106
+ kapps = killapps
107
+
108
+ # updating - delete and replace with any new ones
109
+ elsif updating?
110
+ return unless changes_for_update.key? :killapps
111
+
112
+ kapps = changes_for_update[:killapps][:new]
113
+ ted_patch.killApps.delete_all_killApps
114
+
115
+ # repairing delete and replace with current, to ensure values are correct.
116
+ elsif repairing?
117
+ kapps = killapps
118
+ ted_patch.killApps.delete_all_killApps
119
+ else
120
+ return
121
+ end
122
+ return if kapps.pix_empty?
123
+
124
+ # set the current values
125
+ kapps.each do |ka_str|
126
+ msg = "Title Editor: Setting killApp '#{ka_str}' for Patch '#{version}' of SoftwareTitle '#{title}'"
127
+ progress msg, log: :info
128
+
129
+ name, bundleid = ka_str.split(Xolo::SEMICOLON_SEP_RE)
130
+ ted_patch.killApps.add_killApp(
131
+ appName: name,
132
+ bundleId: bundleid
133
+ )
134
+ end
135
+ end
136
+
137
+ # Set the capabilities for this version in the title editor.
138
+ # This is a collection of criteria that define which computers
139
+ # can install this version.
140
+ #
141
+ # At the very least we enforce the required minimum OS.
142
+ # and optional maximim OS.
143
+ #
144
+ # TODO: Allow xadm to specify other capability criteria?
145
+ #
146
+ # @return [void]
147
+ ##########################
148
+ def set_patch_capabilites
149
+ # creating a new patch? Just use the current values
150
+ if creating?
151
+ min = min_os
152
+ max = max_os
153
+
154
+ # updating an existing patch? Use the new values if they exist
155
+ # noting that a nil new value for max_os means its being removed
156
+ elsif updating?
157
+ return unless changes_for_update&.key?(:max_os) || changes_for_update&.key?(:min_os)
158
+
159
+ # min gets reset even if it didn't change and it can't be empty.
160
+ min = changes_for_update.dig(:min_os, :new) || min_os
161
+
162
+ # if max is changing and its nil, its being removed, so won't be re-added
163
+ # if its not changing, keep the current value
164
+ max = changes_for_update&.key?(:max_os) ? changes_for_update[:max_os][:new] : max_os
165
+
166
+ # delete the existing criteria
167
+ ted_patch.capabilities.delete_all_criteria
168
+
169
+ elsif repairing?
170
+ min = min_os
171
+ max = max_os
172
+ # delete the existing criteria
173
+ ted_patch.capabilities.delete_all_criteria
174
+
175
+ else
176
+ return
177
+ end
178
+ return unless min || max
179
+
180
+ msg = "Title Editor: Setting min_os capability for Patch '#{version}' of SoftwareTitle '#{title}' to '#{min}'"
181
+ progress msg, log: :info
182
+
183
+ # min os - one is always required
184
+ ted_patch.capabilities.add_criterion(
185
+ name: 'Operating System Version',
186
+ operator: 'greater than or equal',
187
+ value: min
188
+ )
189
+
190
+ return unless max
191
+
192
+ msg = "Title Editor: Setting max_os capability for Patch '#{version}' of SoftwareTitle '#{title}' to '#{max}'"
193
+ progress msg, log: :debug
194
+
195
+ ted_patch.capabilities.add_criterion(
196
+ name: 'Operating System Version',
197
+ operator: 'less than or equal',
198
+ value: max
199
+ )
200
+ end
201
+
202
+ # Set the component criteria for this version in the title editor.
203
+ #
204
+ # @param app_name [String] the name of the app to use in app-based requirements,
205
+ # must be used with app_bundle_id, cannot be used with ea_name
206
+ #
207
+ # @param app_bundle_id [String] the bundle id of the app to use in app-based requirements
208
+ # must be used with app_name, cannot be used with ea_name
209
+ #
210
+ # @param ea_name [String] the name of the EA to use in EA-based requirements (the ted_ea_key)
211
+ # Cannot be used with app_name or app_bundle_id
212
+ #
213
+ # @param ttl_obj [Xolo::Server::Title] the title object with the values
214
+ #
215
+ # This is a collection of criteria that define which computers
216
+ # have this version installed
217
+ #
218
+ # @return [void]
219
+ ##########################
220
+ def set_ted_patch_component_criteria(app_name: nil, app_bundle_id: nil, ea_name: nil, ttl_obj: nil)
221
+ if ttl_obj
222
+ app_name, app_bundle_id, ea_name = get_patch_component_criteria_params(ttl_obj)
223
+
224
+ elsif !((app_name && app_bundle_id) || ea_name)
225
+ raise Xolo::MissingDataError, 'Must provide either ea_name or app_name & app_bundle_id, or ttl_obj'
226
+ end
227
+
228
+ type = ea_name ? 'Extension Attribute (version_script)' : 'App'
229
+ msg = "Title Editor: Setting #{type}-based component criteria for Patch '#{version}' of SoftwareTitle '#{title}'"
230
+ progress msg, log: :info
231
+
232
+ # delete any already there and make a new one
233
+ ted_patch.delete_component
234
+ ted_patch.add_component name: title, version: version
235
+ comp = ted_patch.component
236
+
237
+ ea_name ? set_ea_component(comp, ea_name) : set_app_component(comp, app_name, app_bundle_id)
238
+
239
+ # SHouldn't be needed here, is called in both create and update
240
+ # enable_ted_patch
241
+ end
242
+
243
+ # get the param values for patch component criteria from the title object,
244
+ # which may be setting them via an update
245
+ #
246
+ # @param ttl_obj [Xolo::Server::Title] the title object with the values
247
+ # @return [Array] the values for the component criteria
248
+ ##########################
249
+ def get_patch_component_criteria_params(ttl_obj)
250
+ app_name, app_bundle_id, ea_name = nil
251
+
252
+ if ttl_obj.updating?
253
+ if ttl_obj.changes_for_update.dig :app_name, :new
254
+ app_name = ttl_obj.changes_for_update[:app_name][:new]
255
+ app_bundle_id = ttl_obj.changes_for_update[:app_bundle_id][:new]
256
+ else
257
+ ea_name = ttl_obj.ted_ea_key
258
+ end
259
+ elsif ttl_obj.app_name
260
+ app_name = ttl_obj.app_name
261
+ app_bundle_id = ttl_obj.app_bundle_id
262
+ else
263
+ ea_name = ttl_obj.ted_ea_key
264
+ end
265
+
266
+ [app_name, app_bundle_id, ea_name]
267
+ end
268
+
269
+ # @return [void]
270
+ ##############################
271
+ def set_ea_component(comp, ea_name)
272
+ comp.criteria.add_criterion(
273
+ type: 'extensionAttribute',
274
+ name: ea_name,
275
+ operator: 'is',
276
+ value: version
277
+ )
278
+ end
279
+
280
+ # @return [void]
281
+ ##############################
282
+ def set_app_component(comp, app_name, app_bundle_id)
283
+ comp.criteria.add_criterion(
284
+ name: 'Application Title',
285
+ operator: 'is',
286
+ value: app_name
287
+ )
288
+
289
+ comp.criteria.add_criterion(
290
+ name: 'Application Bundle ID',
291
+ operator: 'is',
292
+ value: app_bundle_id
293
+ )
294
+
295
+ comp.criteria.add_criterion(
296
+ name: 'Application Version',
297
+ operator: 'is',
298
+ value: version
299
+ )
300
+ end
301
+
302
+ # For a patch to be enabled in the Title Editor, it needs at least a component criterion
303
+ # and one capability. Xolo enforces those when the patch is created, so from the title
304
+ # editor's view it should be OK from the start.
305
+ #
306
+ # But Xolo can't really do anything with it until there's a Jamf Package object and
307
+ # an uploaded installer.
308
+ # So once we have those, this method is called to enable the patch.
309
+ #
310
+ # @param version [Xolo::Server::Version] the version who's patch to enable
311
+ #
312
+ # @return [void]
313
+ ##############################
314
+ def enable_ted_patch
315
+ progress "Title Editor: (Re-)Enabling Patch '#{version}' of SoftwareTitle '#{title}'", log: :info
316
+ ted_patch(refresh: true).enable
317
+ end
318
+
319
+ # Delete from the title editor
320
+ # @return [Integer] title editor id
321
+ ###########################
322
+ def delete_patch_from_ted
323
+ patch_id = ted_title.patches.versions_to_patchIds[version]
324
+ if patch_id
325
+ progress "Title Editor: Deleting Patch '#{version}' of SoftwareTitle '#{title}'", log: :info
326
+ ted_title.patches.delete_patch patch_id
327
+
328
+ else
329
+ log_debug "Title Editor: No id for Patch '#{version}' of SoftwareTitle '#{title}', nothing to delete"
330
+ end
331
+
332
+ ted_id_number
333
+ rescue Windoo::NoSuchItemError
334
+ ted_id_number
335
+ end
336
+
337
+ # @return [String] the URL for the Title Editor Web App page for this patch
338
+ ###################################
339
+ def ted_patch_url
340
+ "https://#{Xolo::Server.config.ted_hostname}/patches/#{ted_id_number}"
341
+ end
342
+
343
+ # Ensure all the TEd items for this version are correct based on the server data.
344
+ #########################
345
+ def repair_ted_patch
346
+ progress "Title Editor: Repairing Patch '#{version}' of SoftwareTitle '#{title}'", log: :info
347
+
348
+ Xolo::Server::Version::ATTRIBUTES.each do |attr, deets|
349
+ ted_attribute = deets[:ted_attribute]
350
+ # if there's no ted_attribute, this isn't a value stored in the Title Editor
351
+ next unless ted_attribute
352
+
353
+ real_val = send(attr)
354
+ ted_val = ted_patch.send(ted_attribute)
355
+ next if ted_val == real_val
356
+
357
+ # These changes happen in real time on the Title Editor server, no need to #save
358
+ progress "Title Editor: Repairing patch attribute '#{ted_attribute}': #{ted_val} -> #{real_val}"
359
+ ted_patch.send "#{ted_attribute}=", real_val
360
+ end
361
+ set_patch_killapps
362
+ set_patch_capabilites
363
+ set_ted_patch_component_criteria ttl_obj: title_object
364
+ enable_ted_patch unless ted_patch(refresh: true).enabled?
365
+ end
366
+
367
+ end # VersionTedAccess
368
+
369
+ end # Mixins
370
+
371
+ end # Server
372
+
373
+ end # module Xolo
@@ -0,0 +1,102 @@
1
+ # Copyright 2025 Pixar
2
+ #
3
+ # Licensed under the terms set forth in the LICENSE.txt file available at
4
+ # at the root of this project.
5
+ #
6
+
7
+ # frozen_string_literal: true
8
+
9
+ # main module
10
+ module Xolo
11
+
12
+ # Server Module
13
+ module Server
14
+
15
+ # Code for locking titles and versions as they are being modified
16
+ ##################################
17
+ module ObjectLocks
18
+
19
+ # when this module is included
20
+ def self.included(includer)
21
+ Xolo.verbose_include includer, self
22
+ end
23
+
24
+ # when this module is extended
25
+ def self.extended(extender)
26
+ Xolo.verbose_extend extender, self
27
+ end
28
+
29
+ # How many seconds are update locks valid for?
30
+ OBJECT_LOCK_LIMIT = 60 * 60
31
+
32
+ # The locks for titles and versions - when they are being created or updated
33
+ # they appear in this data structure with an expiration timestamp.
34
+ # During that time, no other updates can be made to the same title or version.
35
+ #
36
+ # Keys are the title names, values are hashes with these sub-keys:
37
+ # - expires: the time the title lock expires, time locked plus OBJECT_LOCK_LIMIT
38
+ # - versions: Hash of versions_that_are_locked => expiration_time
39
+ #
40
+ # @return [Concurrent::Hash] The locks
41
+ ################################
42
+ def object_locks
43
+ @object_locks ||= Concurrent::Hash.new
44
+ end
45
+
46
+ # Remove any expired locks from the object_locks
47
+ ################################
48
+ def remove_expired_object_locks
49
+ now = Time.now
50
+
51
+ # first delete any expired version locks
52
+ object_locks.each_value do |locks|
53
+ locks[:versions] ||= {}
54
+
55
+ locks[:versions].delete_if do |vers, exp|
56
+ if exp < now
57
+ log_debug "Removing expired lock on version #{vers} of title #{title}"
58
+ true
59
+ else
60
+ false
61
+ end
62
+ end
63
+ end
64
+
65
+ # now delete any expired title locks that have no versions locked
66
+ object_locks.delete_if do |_title, locks|
67
+ # keep it if there are versions locked
68
+ if !locks[:versions].empty?
69
+ false
70
+
71
+ # if there's a title lock expiration time, check it
72
+ elsif locks[:expires]
73
+ if locks[:expires] < now
74
+ log_debug "Removing expired lock on title #{title}"
75
+ true
76
+ else
77
+ false
78
+ end
79
+
80
+ # no title-lock expiration time
81
+ else
82
+ true
83
+ end
84
+ end
85
+ end
86
+
87
+ # Testing Concurrent::ReentrantReadWriteLock for titles and versions
88
+ # to be acquired and released in the route blocks
89
+ #
90
+ def rw_lock(title, version = nil)
91
+ @rw_locks ||= Concurrent::Hash.new
92
+ @rw_locks[title] ||= { lock: Concurrent::ReentrantReadWriteLock.new }
93
+ return @rw_locks[title] unless version
94
+
95
+ @rw_locks[title][version] ||= Concurrent::ReentrantReadWriteLock.new
96
+ end
97
+
98
+ end # ObjectLocks
99
+
100
+ end # Server
101
+
102
+ end # module Xolo
@@ -0,0 +1,89 @@
1
+ # Copyright 2025 Pixar
2
+ #
3
+ # Licensed under the terms set forth in the LICENSE.txt file available at
4
+ # at the root of this project.
5
+ #
6
+
7
+ # frozen_string_literal: true
8
+
9
+ # main module
10
+ module Xolo
11
+
12
+ # Server Module
13
+ module Server
14
+
15
+ module Routes
16
+
17
+ module Auth
18
+
19
+ # This is how we 'mix in' modules to Sinatra servers:
20
+ # We make them extentions here with
21
+ # extend Sinatra::Extension (from sinatra-contrib)
22
+ # and then 'register' them in the server with
23
+ # register Xolo::Server::<Module>
24
+ # Doing it this way allows us to split the code into a logical
25
+ # file structure, without re-opening the Sinatra::Base server app,
26
+ extend Sinatra::Extension
27
+
28
+ # when this module is included
29
+ def self.included(includer)
30
+ Xolo.verbose_include includer, self
31
+ end
32
+
33
+ # Auth a Xolo Admin via Jamf API login
34
+ # Must be a member of the Jamf Admin group
35
+ # named in Xolo::Server.config.admin_jamf_group
36
+ # before
37
+ ###################
38
+ post '/auth/login' do
39
+ request.body.rewind
40
+ payload = parse_json request.body.read
41
+ admin = payload[:admin]
42
+ pw = payload[:password]
43
+
44
+ err =
45
+ if !member_of_admin_jamf_group?(admin)
46
+ "'#{admin}' is not allowed to use the Xolo server"
47
+ elsif !authenticated_via_jamf?(admin, pw)
48
+ "Incorrect xolo admin username or password for '#{admin}'"
49
+ end
50
+
51
+ if err
52
+ log_info "Authentication failed: #{err}"
53
+ halt 401, { status: 401, error: err }
54
+ end
55
+
56
+ # Set the session values
57
+ session[:xolo_id] = "#{Time.now.to_i}#{SecureRandom.alphanumeric 3}"
58
+ session[:admin] = payload[:proxy_admin] ? "#{payload[:proxy_admin]}-via-#{admin}" : admin
59
+ session[:authenticated] = true
60
+ log_info "Authenticated admin '#{session[:admin]}' from #{request.ip}"
61
+
62
+ body({ admin: admin, authenticated: true, proxy_admin: payload[:proxy_admin] })
63
+ end
64
+
65
+ # Log out the current admin
66
+ ###################
67
+ post '/auth/logout' do
68
+ log_info "Logging out admin '#{session[:admin]}' from #{request.ip}"
69
+ session.clear
70
+ body({ authenticated: false })
71
+ end
72
+
73
+ # check if the current admin is allowed to set a title's release groups to 'all'
74
+ ###################
75
+ get '/auth/release_to_all_allowed' do
76
+ data = {
77
+ allowed: allowed_to_release_to_all?(session[:admin]),
78
+ msg: "Please contact #{Xolo::Server.config.release_to_all_contact} to set release groups to '#{Xolo::TARGET_ALL}'. Let us know why you think the title should be deployed to all computers. Until then you can leave the release groups empty, or use actual Jamf groups."
79
+ }
80
+ body data
81
+ end
82
+
83
+ end # module auth
84
+
85
+ end # Routes
86
+
87
+ end # Server
88
+
89
+ end # module Xolo
@@ -0,0 +1,89 @@
1
+ # Copyright 2025 Pixar
2
+ #
3
+ # Licensed under the terms set forth in the LICENSE.txt file available at
4
+ # at the root of this project.
5
+ #
6
+
7
+ # frozen_string_literal: true
8
+
9
+ # main module
10
+ module Xolo
11
+
12
+ # Server Module
13
+ module Server
14
+
15
+ module Routes
16
+
17
+ module JamfPro
18
+
19
+ # This is how we 'mix in' modules to Sinatra servers
20
+ # for route definitions and similar things
21
+ #
22
+ # (things to be 'included' for use in route and view processing
23
+ # are mixed in by delcaring them to be helpers)
24
+ #
25
+ # We make them extentions here with
26
+ # extend Sinatra::Extension (from sinatra-contrib)
27
+ # and then 'register' them in the server with
28
+ # register Xolo::Server::<Module>
29
+ # Doing it this way allows us to split the code into a logical
30
+ # file structure, without re-opening the Sinatra::Base server app,
31
+ # and let xeitwork do the requiring of those files
32
+ extend Sinatra::Extension
33
+
34
+ # Module methods
35
+ #
36
+ ##############################
37
+ ##############################
38
+
39
+ # when this module is included
40
+ def self.included(includer)
41
+ Xolo.verbose_include includer, self
42
+ end
43
+
44
+ # when this module is extended
45
+ def self.extended(extender)
46
+ Xolo.verbose_extend extender, self
47
+ end
48
+
49
+ # Routes
50
+ #
51
+ ##############################
52
+ ##############################
53
+
54
+ # We probably don't need this - just for testing for now.
55
+ ###############
56
+ get '/jamf/package-names' do
57
+ log_debug "Jamf: Fetching Jamf Package Names for #{session[:admin]}"
58
+ jamf_cnx
59
+ body Jamf::JPackage.all_names(:refresh, cnx: jamf_cnx).sort
60
+ ensure
61
+ jamf_cnx&.disconnect
62
+ end
63
+
64
+ # A list of all current computer groups
65
+ ###############
66
+ get '/jamf/computer-group-names' do
67
+ log_debug "Jamf: Fetching Jamf ComputerGroup Names for #{session[:admin]}"
68
+ body Jamf::ComputerGroup.all_names(:refresh, cnx: jamf_cnx).sort
69
+ ensure
70
+ jamf_cnx&.disconnect
71
+ end
72
+
73
+ # A list of all current categories
74
+ ###############
75
+ get '/jamf/category-names' do
76
+ log_debug "Jamf: Fetching Jamf Category Names for #{session[:admin]}"
77
+ jcnx = jamf_cnx
78
+ body Jamf::Category.all_names(:refresh, cnx: jcnx).sort
79
+ ensure
80
+ jcnx&.disconnect
81
+ end
82
+
83
+ end # Module
84
+
85
+ end # Routes
86
+
87
+ end # Server
88
+
89
+ end # module Xolo