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.
- checksums.yaml +7 -0
- data/LICENSE.txt +177 -0
- data/README.md +7 -0
- data/bin/xoloserver +106 -0
- data/data/com.pixar.xoloserver.plist +29 -0
- data/data/uninstall-pkgs-by-id.zsh +103 -0
- data/lib/xolo/server/app.rb +133 -0
- data/lib/xolo/server/command_line.rb +216 -0
- data/lib/xolo/server/configuration.rb +739 -0
- data/lib/xolo/server/constants.rb +70 -0
- data/lib/xolo/server/helpers/auth.rb +257 -0
- data/lib/xolo/server/helpers/client_data.rb +415 -0
- data/lib/xolo/server/helpers/file_transfers.rb +265 -0
- data/lib/xolo/server/helpers/jamf_pro.rb +156 -0
- data/lib/xolo/server/helpers/log.rb +97 -0
- data/lib/xolo/server/helpers/maintenance.rb +401 -0
- data/lib/xolo/server/helpers/notification.rb +145 -0
- data/lib/xolo/server/helpers/pkg_signing.rb +141 -0
- data/lib/xolo/server/helpers/progress_streaming.rb +252 -0
- data/lib/xolo/server/helpers/title_editor.rb +92 -0
- data/lib/xolo/server/helpers/titles.rb +145 -0
- data/lib/xolo/server/helpers/versions.rb +160 -0
- data/lib/xolo/server/log.rb +286 -0
- data/lib/xolo/server/mixins/changelog.rb +315 -0
- data/lib/xolo/server/mixins/title_jamf_access.rb +1668 -0
- data/lib/xolo/server/mixins/title_ted_access.rb +519 -0
- data/lib/xolo/server/mixins/version_jamf_access.rb +1541 -0
- data/lib/xolo/server/mixins/version_ted_access.rb +373 -0
- data/lib/xolo/server/object_locks.rb +102 -0
- data/lib/xolo/server/routes/auth.rb +89 -0
- data/lib/xolo/server/routes/jamf_pro.rb +89 -0
- data/lib/xolo/server/routes/maint.rb +174 -0
- data/lib/xolo/server/routes/title_editor.rb +71 -0
- data/lib/xolo/server/routes/titles.rb +285 -0
- data/lib/xolo/server/routes/uploads.rb +93 -0
- data/lib/xolo/server/routes/versions.rb +261 -0
- data/lib/xolo/server/routes.rb +168 -0
- data/lib/xolo/server/title.rb +1143 -0
- data/lib/xolo/server/version.rb +902 -0
- data/lib/xolo/server.rb +205 -0
- data/lib/xolo-server.rb +8 -0
- metadata +243 -0
|
@@ -0,0 +1,1541 @@
|
|
|
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-related access to the Jamf Pro server
|
|
19
|
+
#
|
|
20
|
+
module VersionJamfAccess
|
|
21
|
+
|
|
22
|
+
# Constants
|
|
23
|
+
#
|
|
24
|
+
##############################
|
|
25
|
+
##############################
|
|
26
|
+
|
|
27
|
+
# The group of macs with this version installed
|
|
28
|
+
# is named the full prefix plus this suffix.
|
|
29
|
+
JAMF_SMART_GROUP_NAME_INSTALLED_SFX = '-installed'
|
|
30
|
+
|
|
31
|
+
# The policy that does initial installs on-demand
|
|
32
|
+
# (via 'xolo install <title> <version') is named the full
|
|
33
|
+
# prefix plus this suffix.
|
|
34
|
+
JAMF_POLICY_NAME_MANUAL_INSTALL_SFX = '-manual-install'
|
|
35
|
+
|
|
36
|
+
# The policy that does auto-installs is named the full
|
|
37
|
+
# prefix plus this suffix.
|
|
38
|
+
# The scope is changed as needed when a version's status
|
|
39
|
+
# changes
|
|
40
|
+
JAMF_POLICY_NAME_AUTO_INSTALL_SFX = '-auto-install'
|
|
41
|
+
|
|
42
|
+
# The policy that does auto-re-installs is named the full
|
|
43
|
+
# prefix plus this suffix.
|
|
44
|
+
# The scope is changed as needed when a version's status
|
|
45
|
+
# changes
|
|
46
|
+
JAMF_POLICY_NAME_AUTO_REINSTALL_SFX = '-auto-reinstall'
|
|
47
|
+
|
|
48
|
+
# How long to wait after a pkg re-upload before creating/enabling/flushing
|
|
49
|
+
# the auto-reinstall policy
|
|
50
|
+
# See TODO in #wait_to_enable_reinstall_policy
|
|
51
|
+
JAMF_AUTO_REINSTALL_WAIT_SECS = 15 * 60
|
|
52
|
+
|
|
53
|
+
# POLICIES, PATCH POLICIES, SCOPING
|
|
54
|
+
#############################
|
|
55
|
+
#
|
|
56
|
+
# SMART GROUPS
|
|
57
|
+
# For each title there will be a smart group containing all macs that have any version
|
|
58
|
+
# of the title installed. The smart group will be named 'xolo-<title>-installed'
|
|
59
|
+
#
|
|
60
|
+
# It will be used as an exclusion for the auto-initial-installation policy for each version
|
|
61
|
+
# since if the title is installed at all, any installation is not 'initial' but an update, and
|
|
62
|
+
# will be handled by the Patch Policy.
|
|
63
|
+
#
|
|
64
|
+
# Since there is one per title, it's name is stored in the title object's #jamf_installed_group_name
|
|
65
|
+
# attribute, and the title object has has the method for creating it.
|
|
66
|
+
# It will be created when the first version is added to the title.
|
|
67
|
+
#
|
|
68
|
+
# POLICIES
|
|
69
|
+
# Each version gets two policies for initial installation
|
|
70
|
+
#
|
|
71
|
+
# - one for auto-installs called 'xolo-<title>-<version>-auto-install'
|
|
72
|
+
# - xolo server maintains the scope as needed
|
|
73
|
+
# - Targeted to pilot-groups first, then release-groups when released
|
|
74
|
+
# - Excluded for excluded groups and frozen-groups
|
|
75
|
+
# - never in self service
|
|
76
|
+
#
|
|
77
|
+
# - one for manual installs called 'xolo-<title>-<version>-manual-install'
|
|
78
|
+
# and self-service installs
|
|
79
|
+
# - xolo server maintains the scope as needed
|
|
80
|
+
# - Targeted to all with this trigger xolo-install-<target>-<version>
|
|
81
|
+
# - Excluded for excluded groups and frozen-groups
|
|
82
|
+
# - the xolo client will determine which is released when
|
|
83
|
+
# running 'xolo install <title>'
|
|
84
|
+
#
|
|
85
|
+
# NOTE: Other install policies can be created manually for other purposes, just
|
|
86
|
+
# don't name them with xolo-ish names
|
|
87
|
+
#
|
|
88
|
+
# PATCH POLICIES
|
|
89
|
+
# Each version gets one patch policy
|
|
90
|
+
#
|
|
91
|
+
# The patch policy is first scoped targeted to pilot groups.
|
|
92
|
+
# Excluded for excluded groups and frozen-groups
|
|
93
|
+
#
|
|
94
|
+
# When the version is released, the scope is changed to All
|
|
95
|
+
#
|
|
96
|
+
# NOTE: remember that patch polices are pre-limited to only 'eligible'
|
|
97
|
+
# machines - those that have a lower version installed and meet other
|
|
98
|
+
# conditions.
|
|
99
|
+
#
|
|
100
|
+
# But.... questions...
|
|
101
|
+
#
|
|
102
|
+
# Should it act like d3, and auto-install updates always?
|
|
103
|
+
# def. for auto-install groups... but how about for the general
|
|
104
|
+
# populace, like those who installed initially via SSvc?? Should it
|
|
105
|
+
# auto-update, or notify them to do it in SSvc?
|
|
106
|
+
#
|
|
107
|
+
# If d3-like behaviour:
|
|
108
|
+
# - it auto-installs for anyone who has any version installed
|
|
109
|
+
# - at first, scoped to any pilot-groups, they'll get the latest version
|
|
110
|
+
# - when released, re-scoped to 'all' (see note below)
|
|
111
|
+
#
|
|
112
|
+
#
|
|
113
|
+
# NOTE: Other patch policies can be created manually for other purposes, just
|
|
114
|
+
# don't name them with xolo-ish names
|
|
115
|
+
#
|
|
116
|
+
#####################
|
|
117
|
+
#
|
|
118
|
+
# install live
|
|
119
|
+
# => xolo install title
|
|
120
|
+
#
|
|
121
|
+
# runs 'jamf policy -trigger xolo-install-current-<title>'
|
|
122
|
+
# the xolo server maintains the trigger
|
|
123
|
+
#################
|
|
124
|
+
#
|
|
125
|
+
# install pilot
|
|
126
|
+
# => xolo install title version
|
|
127
|
+
#
|
|
128
|
+
# runs 'jamf policy -trigger xolo-install-<title>-<version>'
|
|
129
|
+
# the xolo server maintains the trigger
|
|
130
|
+
##################
|
|
131
|
+
#
|
|
132
|
+
# auto-install on pilot groups or target groups
|
|
133
|
+
# => xolo sync
|
|
134
|
+
#
|
|
135
|
+
# runs 'jamf policy'
|
|
136
|
+
# the xolo server maintains the scopes for the policies
|
|
137
|
+
# patch policies will be run as needed
|
|
138
|
+
##################
|
|
139
|
+
#
|
|
140
|
+
# get the lates JSON data about titles and versions
|
|
141
|
+
# => xolo update
|
|
142
|
+
#
|
|
143
|
+
# runs 'jamf policy -trigger xolo-update'
|
|
144
|
+
# the xolo server maintains a package that deploys the JSON file
|
|
145
|
+
##################
|
|
146
|
+
#
|
|
147
|
+
# list available titles or versions
|
|
148
|
+
# => xolo list-titles
|
|
149
|
+
#
|
|
150
|
+
# reads from a local JSON file of title & version data
|
|
151
|
+
# maintained by the xolo server and pushed out via
|
|
152
|
+
# a checkin policy
|
|
153
|
+
##################
|
|
154
|
+
|
|
155
|
+
# Module methods
|
|
156
|
+
#
|
|
157
|
+
# These are available as module methods but not as 'helper'
|
|
158
|
+
# methods in sinatra routes & views.
|
|
159
|
+
#
|
|
160
|
+
##############################
|
|
161
|
+
##############################
|
|
162
|
+
|
|
163
|
+
# when this module is included
|
|
164
|
+
##############################
|
|
165
|
+
def self.included(includer)
|
|
166
|
+
Xolo.verbose_include includer, self
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# when this module is extended
|
|
170
|
+
def self.extended(extender)
|
|
171
|
+
Xolo.verbose_extend extender, self
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Instance methods
|
|
175
|
+
#
|
|
176
|
+
# These are available directly in sinatra routes and views
|
|
177
|
+
#
|
|
178
|
+
##############################
|
|
179
|
+
|
|
180
|
+
####### The Xolo Version itself
|
|
181
|
+
###########################################
|
|
182
|
+
###########################################
|
|
183
|
+
|
|
184
|
+
# Create everything we need in Jamf
|
|
185
|
+
############################
|
|
186
|
+
def create_in_jamf
|
|
187
|
+
# this will create the JPackage object
|
|
188
|
+
jamf_package
|
|
189
|
+
|
|
190
|
+
# The
|
|
191
|
+
# - jamf_installed_group
|
|
192
|
+
# - jamf_auto_reinstall_policy
|
|
193
|
+
# aren't needed until there's a pkg re-upload
|
|
194
|
+
# and they will be created then if they doesn't already exist
|
|
195
|
+
|
|
196
|
+
# these will create the policies
|
|
197
|
+
jamf_auto_install_policy
|
|
198
|
+
jamf_manual_install_policy
|
|
199
|
+
|
|
200
|
+
activate_patch_version_in_jamf
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Apply edits to the Xolo version to Jamf as needed
|
|
204
|
+
# This includes scope changes in policies, changes to pkg 'reboot' setting
|
|
205
|
+
# and changes to pkg 'os_requirements'
|
|
206
|
+
# Uploading a new .pkg installer happen separately
|
|
207
|
+
#########################################
|
|
208
|
+
def update_version_in_jamf
|
|
209
|
+
update_pilot_groups if changes_for_update&.key? :pilot_groups
|
|
210
|
+
update_release_groups(ttl_obj: title_object) if changes_for_update&.key? :release_groups
|
|
211
|
+
update_excluded_groups(ttl_obj: title_object) if changes_for_update&.key? :excluded_groups
|
|
212
|
+
|
|
213
|
+
update_jamf_pkg_reboot if changes_for_update&.key? :reboot
|
|
214
|
+
update_jamf_pkg_min_os if changes_for_update&.key? :min_os
|
|
215
|
+
|
|
216
|
+
# TODO: Update the critera for the jamf_installed_group IF
|
|
217
|
+
#
|
|
218
|
+
# - the group exists AND
|
|
219
|
+
# - the title has changed how it determines installed versions, e.g. by adding or
|
|
220
|
+
# changing a version_script or app_bundle_id
|
|
221
|
+
# Changing those is very rare, and ill-advised, so we will implement
|
|
222
|
+
# this later.
|
|
223
|
+
#
|
|
224
|
+
# if jamf_installed_group_exist?
|
|
225
|
+
# end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Validate and fix any Jamf::JPackage objects that
|
|
229
|
+
# related to this version:
|
|
230
|
+
# - the package object
|
|
231
|
+
# - the installed-group
|
|
232
|
+
# - the auto-install policy
|
|
233
|
+
# - the manual-install policy
|
|
234
|
+
# - the auto-reinstall policy
|
|
235
|
+
# - the patch policy
|
|
236
|
+
#########################################
|
|
237
|
+
def repair_jamf_version_objects
|
|
238
|
+
repair_jamf_package
|
|
239
|
+
repair_jamf_installed_group # wont happen if no reupload_date
|
|
240
|
+
repair_jamf_auto_install_policy
|
|
241
|
+
repair_jamf_manual_install_policy
|
|
242
|
+
repair_jamf_auto_reinstall_policy # wont happen if no reupload_date
|
|
243
|
+
repair_jamf_patch_policy
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Delete an entire version from Jamf Pro
|
|
247
|
+
# This includes the package, the manual install policy, the auto install policy,
|
|
248
|
+
# and the patch policy.
|
|
249
|
+
#
|
|
250
|
+
# @return [void]
|
|
251
|
+
#
|
|
252
|
+
#########################
|
|
253
|
+
def delete_version_from_jamf
|
|
254
|
+
log_debug "Deleting Version '#{version}' from Jamf"
|
|
255
|
+
|
|
256
|
+
# The Policies
|
|
257
|
+
pols = [
|
|
258
|
+
jamf_manual_install_policy,
|
|
259
|
+
jamf_auto_install_policy,
|
|
260
|
+
jamf_auto_reinstall_policy,
|
|
261
|
+
jamf_patch_policy
|
|
262
|
+
]
|
|
263
|
+
pols.each do |pol|
|
|
264
|
+
next unless pol
|
|
265
|
+
|
|
266
|
+
progress "Jamf: Deleting #{pol.class} '#{pol.name}'", log: :info
|
|
267
|
+
pol.delete
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# The Installed Group
|
|
271
|
+
if jamf_installed_group
|
|
272
|
+
progress "Jamf: Deleting #{jamf_installed_group.class} '#{jamf_installed_group.name}'", log: :info
|
|
273
|
+
jamf_installed_group.delete
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Delete package object
|
|
277
|
+
# This is slow and it blocks, so do it in a thread and update progress every
|
|
278
|
+
# 15 secs
|
|
279
|
+
return unless Jamf::JPackage.valid_id packageName: jamf_pkg_name, cnx: jamf_cnx
|
|
280
|
+
|
|
281
|
+
delete_jamf_package
|
|
282
|
+
|
|
283
|
+
# The code below is used when we want real-time progress updates to xadm
|
|
284
|
+
# while deletion is happening. It's slow, so we're not using it now.
|
|
285
|
+
|
|
286
|
+
# msg = "Jamf: Starting deletion of Package '#{jamf_pkg_name}' id #{jamf_pkg_id} at #{Time.now.strftime '%F %T'}..."
|
|
287
|
+
# progress msg, log: :debug
|
|
288
|
+
|
|
289
|
+
# # do this in another thread, so we can report the progress while its happening
|
|
290
|
+
# pkg_del_thr = Thread.new { Jamf::JPackage.fetch(packageName: jamf_pkg_name, cnx: jamf_cnx).delete }
|
|
291
|
+
# pkg_del_thr.name = "package-deletion-thread-#{session[:xolo_id]}"
|
|
292
|
+
# sleep 15
|
|
293
|
+
# while pkg_del_thr.alive?
|
|
294
|
+
# progress "... #{Time.now.strftime '%F %T'} still deleting, this is slow, sorry."
|
|
295
|
+
# sleep 15
|
|
296
|
+
# end
|
|
297
|
+
|
|
298
|
+
# msg = "Jamf: Deleted Package '#{jamf_pkg_name}' id #{jamf_pkg_id} at #{Time.now.strftime '%F %T'}"
|
|
299
|
+
# progress msg, log: :debug
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# @return [String] The start of the Jamf Pro URL for GUI/WebApp access
|
|
303
|
+
################
|
|
304
|
+
def jamf_gui_url
|
|
305
|
+
server_app_instance.jamf_gui_url
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
####### The Jamf Package Object
|
|
309
|
+
###########################################
|
|
310
|
+
###########################################
|
|
311
|
+
|
|
312
|
+
# Create or fetch the Jamf::JPackage object for this version
|
|
313
|
+
# Returns nil if the package doesn't exist and we're deleting
|
|
314
|
+
#
|
|
315
|
+
# @return [Jamf::JPackage] the Package object associated with this version
|
|
316
|
+
######################
|
|
317
|
+
def jamf_package
|
|
318
|
+
return @jamf_package if @jamf_package
|
|
319
|
+
|
|
320
|
+
id = jamf_pkg_id || Jamf::JPackage.valid_id(name: jamf_pkg_name, cnx: jamf_cnx)
|
|
321
|
+
@jamf_package =
|
|
322
|
+
if id
|
|
323
|
+
log_debug "Jamf: Fetching Jamf::JPackage '#{id}'"
|
|
324
|
+
Jamf::JPackage.fetch id: id, cnx: jamf_cnx
|
|
325
|
+
else
|
|
326
|
+
return if deleting?
|
|
327
|
+
|
|
328
|
+
create_jamf_package
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# @return [Jamf::JPackage] Create the Jamf::JPackage object for this version and return it
|
|
333
|
+
#########################
|
|
334
|
+
def create_jamf_package
|
|
335
|
+
progress "Jamf: Creating Package object '#{jamf_pkg_name}'", log: :info
|
|
336
|
+
|
|
337
|
+
# The filename is temporary, and will be replaced when the file is uploaded
|
|
338
|
+
pkg = Jamf::JPackage.create(
|
|
339
|
+
cnx: jamf_cnx,
|
|
340
|
+
packageName: jamf_pkg_name,
|
|
341
|
+
fileName: "#{jamf_pkg_name}.pkg",
|
|
342
|
+
rebootRequired: reboot,
|
|
343
|
+
notes: jamf_package_notes,
|
|
344
|
+
categoryId: jamf_xolo_category_id,
|
|
345
|
+
osRequirements: ">=#{min_os}"
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
# TODO: Implement max_os, either here, or by maintaining a smart group?
|
|
349
|
+
# I really wish jamf would improve how package objects handle
|
|
350
|
+
# OS requirements, building in the concept of min/max
|
|
351
|
+
|
|
352
|
+
self.jamf_pkg_id = pkg.save
|
|
353
|
+
# save the data now so the pkg_id is available for immeadiate use, e.g. by pkg upload
|
|
354
|
+
save_local_data
|
|
355
|
+
pkg
|
|
356
|
+
rescue => e
|
|
357
|
+
msg = "Jamf: Failed to create Jamf::JPackage '#{jamf_pkg_name}': #{e.class}: #{e}"
|
|
358
|
+
log_error msg
|
|
359
|
+
raise Xolo::ServerError, msg
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
# @return [String] the 'notes' text for the Jamf::JPackage object for this version
|
|
363
|
+
#############################
|
|
364
|
+
def jamf_package_notes(ttl_obj: nil)
|
|
365
|
+
ttl_obj ||= title_object
|
|
366
|
+
pkg_notes = Xolo::Server::Version::JAMF_PKG_NOTES_PREFIX.sub(
|
|
367
|
+
Xolo::Server::Version::JAMF_PKG_NOTES_VERS_PH,
|
|
368
|
+
version
|
|
369
|
+
)
|
|
370
|
+
pkg_notes.sub!(
|
|
371
|
+
Xolo::Server::Version::JAMF_PKG_NOTES_TITLE_PH,
|
|
372
|
+
title
|
|
373
|
+
)
|
|
374
|
+
desc = ttl_obj.changes_for_update.dig(:description, :new) || ttl_obj.description
|
|
375
|
+
pkg_notes << desc
|
|
376
|
+
pkg_notes
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
# update the description for the Jamf::JPackage
|
|
380
|
+
# @return [void]
|
|
381
|
+
def update_jamf_package_notes(ttl_obj: nil)
|
|
382
|
+
ttl_obj ||= title_object
|
|
383
|
+
progress "Jamf: Updating notes for Jamf::JPackage '#{jamf_pkg_name}'", log: :debug
|
|
384
|
+
jamf_package.notes = jamf_package_notes(ttl_obj: ttl_obj)
|
|
385
|
+
jamf_package.save
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
# update the reboot setting for the Jamf::JPackage
|
|
389
|
+
# @return [void]
|
|
390
|
+
##########################
|
|
391
|
+
def update_jamf_pkg_reboot
|
|
392
|
+
new_reboot = changes_for_update&.key?(:reboot) ? changes_for_update[:reboot][:new] : reboot
|
|
393
|
+
progress "Jamf: Updating reboot setting for Jamf::JPackage '#{jamf_pkg_name}' to '#{new_reboot}'", log: :debug
|
|
394
|
+
jamf_package.rebootRequired = new_reboot
|
|
395
|
+
jamf_package.save
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
# update the min_os setting for the Jamf::JPackage
|
|
399
|
+
# @return [void]
|
|
400
|
+
##########################
|
|
401
|
+
def update_jamf_pkg_min_os
|
|
402
|
+
new_min = changes_for_update&.key?(:min_os) ? changes_for_update[:min_os][:new] : min_os
|
|
403
|
+
progress "Jamf: Updating os_requirement for Jamf::JPackage '#{jamf_pkg_name}' to '#{new_min}'",
|
|
404
|
+
log: :debug
|
|
405
|
+
jamf_package.osRequirements = ">=#{new_min}"
|
|
406
|
+
jamf_package.save
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
# repair the package object only
|
|
410
|
+
#############################
|
|
411
|
+
def repair_jamf_package
|
|
412
|
+
# If these values are all correct, nothing will be saved
|
|
413
|
+
progress "Jamf: Repairing Package '#{jamf_pkg_name}'", log: :info
|
|
414
|
+
jamf_package.packageName = jamf_pkg_name
|
|
415
|
+
jamf_package.fileName = "#{jamf_pkg_name}.pkg"
|
|
416
|
+
jamf_package.rebootRequired = reboot
|
|
417
|
+
jamf_package.notes = jamf_package_notes
|
|
418
|
+
jamf_package.categoryId = jamf_xolo_category_id
|
|
419
|
+
jamf_package.osRequirements = ">=#{min_os}"
|
|
420
|
+
jamf_package.save
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
# @return [String] the URL for the Package that installs this version in Jamf Pro
|
|
424
|
+
######################
|
|
425
|
+
def jamf_package_url
|
|
426
|
+
return @jamf_package_url if @jamf_package_url
|
|
427
|
+
return unless jamf_pkg_id
|
|
428
|
+
|
|
429
|
+
# the old url
|
|
430
|
+
# @jamf_package_url = "#{jamf_gui_url}/packages.html?id=#{jamf_pkg_id}&o=r"
|
|
431
|
+
|
|
432
|
+
@jamf_package_url = "#{jamf_gui_url}/view/settings/computer-management/packages/#{jamf_pkg_id}?tab=general"
|
|
433
|
+
# https://casper.pixar.com:8443/view/settings/computer-management/packages/12042?tab=general
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
# Delete the package for this version from Jamf Pro.
|
|
437
|
+
# Package deletion takes a long time, so we do it in a threadpool
|
|
438
|
+
# and tell the admin to check the Alert Tool for completion
|
|
439
|
+
# (if we have an alert tool in place) or to wait at least 5 min before
|
|
440
|
+
# re-adding the same version.
|
|
441
|
+
#
|
|
442
|
+
# @return [void]
|
|
443
|
+
#########################
|
|
444
|
+
def delete_jamf_package
|
|
445
|
+
pkg_id = Jamf::JPackage.valid_id packageName: jamf_pkg_name, cnx: jamf_cnx
|
|
446
|
+
return unless pkg_id
|
|
447
|
+
|
|
448
|
+
msg = "Jamf: Starting deletion of Package '#{jamf_pkg_name}' id #{jamf_pkg_id} at #{Time.now.strftime '%F %T'}"
|
|
449
|
+
progress msg, log: :info
|
|
450
|
+
|
|
451
|
+
warning = +"IMPORTANT: Package deletion is slow. If you plan to re-add this version, '#{version}', please\n "
|
|
452
|
+
warning <<
|
|
453
|
+
if Xolo::Server.config.alert_tool
|
|
454
|
+
'check your Xolo alerts for completion, which can take up to 5 minutes,'
|
|
455
|
+
else
|
|
456
|
+
'wait at least 5 minutes'
|
|
457
|
+
end
|
|
458
|
+
warning << ' before re-adding this version.'
|
|
459
|
+
|
|
460
|
+
progress warning, log: nil
|
|
461
|
+
|
|
462
|
+
self.class.pkg_deletion_pool.post do
|
|
463
|
+
start = Time.now
|
|
464
|
+
log_info "Jamf: Started threadpool deletion of Package '#{jamf_pkg_name}' id #{jamf_pkg_id} at #{start}"
|
|
465
|
+
jamf_cnx.timeout = 3600
|
|
466
|
+
Jamf::JPackage.delete pkg_id, cnx: jamf_cnx
|
|
467
|
+
finish = Time.now
|
|
468
|
+
duration = (finish - start).to_i.pix_humanize_secs
|
|
469
|
+
log_info "Jamf: Deleted Package '#{jamf_pkg_name}' id #{jamf_pkg_id} in #{duration}", alert: true
|
|
470
|
+
rescue => e
|
|
471
|
+
log_error "Package Deletion thread: #{e.class}: #{e}"
|
|
472
|
+
e.backtrace.each { |l| log_error "..#{l}" }
|
|
473
|
+
end
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
####### The Jamf Auto Install Policy
|
|
477
|
+
###########################################
|
|
478
|
+
###########################################
|
|
479
|
+
|
|
480
|
+
# @return [Boolean] does the jamf_auto_install_policy exist?
|
|
481
|
+
###########################
|
|
482
|
+
def jamf_auto_install_policy_exist?
|
|
483
|
+
Jamf::Policy.all_names(cnx: jamf_cnx).include? jamf_auto_install_policy_name
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
# Create or fetch the auto install policy for this version
|
|
487
|
+
# If we are deleting and it doesn't exist, return nil.
|
|
488
|
+
#
|
|
489
|
+
# @return [Jamf::Policy] The auto-install-policy for this version, if it exists
|
|
490
|
+
##########################
|
|
491
|
+
def jamf_auto_install_policy
|
|
492
|
+
@jamf_auto_install_policy ||=
|
|
493
|
+
if jamf_auto_install_policy_exist?
|
|
494
|
+
Jamf::Policy.fetch(name: jamf_auto_install_policy_name, cnx: jamf_cnx)
|
|
495
|
+
else
|
|
496
|
+
return if deleting?
|
|
497
|
+
|
|
498
|
+
create_jamf_auto_install_policy
|
|
499
|
+
end
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
# The auto install policy is triggered by checkin
|
|
503
|
+
# but may have narrow scope targets, or may be
|
|
504
|
+
# targeted to 'all' (after release)
|
|
505
|
+
# Before release, the targets are those defined in #pilot_groups_to_use
|
|
506
|
+
#
|
|
507
|
+
# After release, the targets are changed to those
|
|
508
|
+
# in title_object#target_group
|
|
509
|
+
#
|
|
510
|
+
# This policy is never in self service
|
|
511
|
+
# @return [Jamf::Policy] the auto install policy for this version
|
|
512
|
+
#########################
|
|
513
|
+
def create_jamf_auto_install_policy
|
|
514
|
+
progress "Jamf: Creating Auto Install Policy: #{jamf_auto_install_policy_name}", log: :debug
|
|
515
|
+
pol = Jamf::Policy.create name: jamf_auto_install_policy_name, cnx: jamf_cnx
|
|
516
|
+
configure_jamf_auto_install_policy(pol)
|
|
517
|
+
pol.save
|
|
518
|
+
pol
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
# repair the auto-install policy only
|
|
522
|
+
#############################
|
|
523
|
+
def repair_jamf_auto_install_policy
|
|
524
|
+
progress "Jamf: Repairing Auto Install Policy '#{jamf_auto_install_policy_name}'", log: :info
|
|
525
|
+
pol = jamf_auto_install_policy
|
|
526
|
+
configure_jamf_auto_install_policy(pol)
|
|
527
|
+
pol.save
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
# Configure the given policy as the auto-install policy for this version
|
|
531
|
+
# @param pol [Jamf::Policy] the policy to configure
|
|
532
|
+
################################
|
|
533
|
+
def configure_jamf_auto_install_policy(pol)
|
|
534
|
+
pol.category = Xolo::Server::JAMF_XOLO_CATEGORY
|
|
535
|
+
pol.set_trigger_event :checkin, true
|
|
536
|
+
pol.set_trigger_event :custom, Xolo::BLANK
|
|
537
|
+
pol.frequency = :once_per_computer
|
|
538
|
+
pol.retry_event = :checkin
|
|
539
|
+
pol.retry_attempts = 5
|
|
540
|
+
pol.recon = false
|
|
541
|
+
|
|
542
|
+
pol.package_names.each { |pkg_name| pol.remove_package pkg_name }
|
|
543
|
+
pol.add_package jamf_pkg_name
|
|
544
|
+
|
|
545
|
+
# exclusions are for always
|
|
546
|
+
set_policy_exclusions pol
|
|
547
|
+
|
|
548
|
+
# set the scope targets based on status
|
|
549
|
+
if pilot?
|
|
550
|
+
set_policy_pilot_groups pol
|
|
551
|
+
else
|
|
552
|
+
set_policy_release_groups pol
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
# enable or disable based on status
|
|
556
|
+
if pilot? || released?
|
|
557
|
+
pol.enable
|
|
558
|
+
else
|
|
559
|
+
pol.disable
|
|
560
|
+
end
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
# @return [String] the URL for the Jamf Pro Policy that does auto-installs of this version
|
|
564
|
+
######################
|
|
565
|
+
def jamf_auto_install_policy_url
|
|
566
|
+
return @jamf_auto_install_policy_url if @jamf_auto_install_policy_url
|
|
567
|
+
|
|
568
|
+
pol_id = Jamf::Policy.valid_id jamf_auto_install_policy_name, cnx: jamf_cnx
|
|
569
|
+
return unless pol_id
|
|
570
|
+
|
|
571
|
+
@jamf_auto_install_policy_url = "#{jamf_gui_url}/policies.html?id=#{pol_id}&o=r"
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
####### The Jamf Manual Install Policy
|
|
575
|
+
###########################################
|
|
576
|
+
###########################################
|
|
577
|
+
|
|
578
|
+
# @return [Boolean] does the jamf_manual_install_policy exist?
|
|
579
|
+
###########################
|
|
580
|
+
def jamf_manual_install_policy_exist?
|
|
581
|
+
Jamf::Policy.all_names(cnx: jamf_cnx).include? jamf_manual_install_policy_name
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
# Create or fetch the manual install policy for this version
|
|
585
|
+
# If we are deleting and it doesn't exist, return nil.
|
|
586
|
+
#
|
|
587
|
+
# @return [Jamf::Policy] The manual-install-policy for this version, if it exists
|
|
588
|
+
##########################
|
|
589
|
+
def jamf_manual_install_policy
|
|
590
|
+
@jamf_manual_install_policy ||=
|
|
591
|
+
if jamf_manual_install_policy_exist?
|
|
592
|
+
Jamf::Policy.fetch(name: jamf_manual_install_policy_name, cnx: jamf_cnx)
|
|
593
|
+
else
|
|
594
|
+
return if deleting?
|
|
595
|
+
|
|
596
|
+
create_jamf_manual_install_policy
|
|
597
|
+
end
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
# The manual install policy is always scoped to all computers, with
|
|
601
|
+
# exclusions
|
|
602
|
+
#
|
|
603
|
+
# The policy has a custom trigger, or can be installed via self service
|
|
604
|
+
#
|
|
605
|
+
#########################
|
|
606
|
+
def create_jamf_manual_install_policy
|
|
607
|
+
progress "Jamf: Creating Manual Install Policy: #{jamf_manual_install_policy_name}", log: :info
|
|
608
|
+
|
|
609
|
+
pol = Jamf::Policy.create name: jamf_manual_install_policy_name, cnx: jamf_cnx
|
|
610
|
+
configure_jamf_manual_install_policy(pol)
|
|
611
|
+
pol.save
|
|
612
|
+
pol
|
|
613
|
+
end
|
|
614
|
+
|
|
615
|
+
# repair the manual-install policy only
|
|
616
|
+
#############################
|
|
617
|
+
def repair_jamf_manual_install_policy
|
|
618
|
+
pol = jamf_manual_install_policy
|
|
619
|
+
progress "Jamf: Repairing Manual Install Policy '#{jamf_manual_install_policy_name}'", log: :info
|
|
620
|
+
configure_jamf_manual_install_policy(pol)
|
|
621
|
+
pol.save
|
|
622
|
+
end
|
|
623
|
+
|
|
624
|
+
# Configure the given policy as the manual-install policy for this version
|
|
625
|
+
# @param pol [Jamf::Policy] the policy to configure
|
|
626
|
+
##########################
|
|
627
|
+
def configure_jamf_manual_install_policy(pol)
|
|
628
|
+
pol.category = Xolo::Server::JAMF_XOLO_CATEGORY
|
|
629
|
+
pol.set_trigger_event :checkin, false
|
|
630
|
+
pol.set_trigger_event :custom, jamf_manual_install_trigger
|
|
631
|
+
pol.frequency = :ongoing
|
|
632
|
+
pol.recon = false
|
|
633
|
+
|
|
634
|
+
pol.package_names.each { |pkg_name| pol.remove_package pkg_name }
|
|
635
|
+
pol.add_package jamf_pkg_name
|
|
636
|
+
|
|
637
|
+
set_policy_to_all_targets pol
|
|
638
|
+
set_policy_exclusions pol
|
|
639
|
+
|
|
640
|
+
# These policies shouldn't be in ssvc
|
|
641
|
+
# only the title's jamf_manual_install_released_policy is
|
|
642
|
+
pol.remove_from_self_service if pol.in_self_service?
|
|
643
|
+
pol.enable
|
|
644
|
+
end
|
|
645
|
+
|
|
646
|
+
# @return [String] the URL for the Jamf Pro Policy that does manual installs of this version
|
|
647
|
+
######################
|
|
648
|
+
def jamf_manual_install_policy_url
|
|
649
|
+
return @jamf_manual_install_policy_url if @jamf_manual_install_policy_url
|
|
650
|
+
|
|
651
|
+
pol_id = Jamf::Policy.valid_id jamf_manual_install_policy_name, cnx: jamf_cnx
|
|
652
|
+
return unless pol_id
|
|
653
|
+
|
|
654
|
+
@jamf_manual_install_policy_url = "#{jamf_gui_url}/policies.html?id=#{pol_id}&o=r"
|
|
655
|
+
end
|
|
656
|
+
|
|
657
|
+
####### The Jamf Installed Group
|
|
658
|
+
###########################################
|
|
659
|
+
###########################################
|
|
660
|
+
|
|
661
|
+
# @return [Boolean] does the jamf_installed_group exist?
|
|
662
|
+
#########################
|
|
663
|
+
def jamf_installed_group_exist?
|
|
664
|
+
Jamf::ComputerGroup.all_names(cnx: jamf_cnx).include? jamf_installed_group_name
|
|
665
|
+
end
|
|
666
|
+
|
|
667
|
+
# Create or fetch the smart group of macs with this version installed
|
|
668
|
+
# If we are deleting and it doesn't exist, return nil
|
|
669
|
+
#
|
|
670
|
+
# @return [Jamf::ComputerGroup] the smart group.
|
|
671
|
+
#########################
|
|
672
|
+
def jamf_installed_group
|
|
673
|
+
return @jamf_installed_group if @jamf_installed_group
|
|
674
|
+
|
|
675
|
+
if jamf_installed_group_exist?
|
|
676
|
+
@jamf_installed_group = Jamf::ComputerGroup.fetch(
|
|
677
|
+
name: jamf_installed_group_name,
|
|
678
|
+
cnx: jamf_cnx
|
|
679
|
+
)
|
|
680
|
+
else
|
|
681
|
+
return if deleting?
|
|
682
|
+
# don't create unless there's been a re-upload of the pkg
|
|
683
|
+
return unless reupload_date
|
|
684
|
+
|
|
685
|
+
create_jamf_installed_group
|
|
686
|
+
end
|
|
687
|
+
@jamf_installed_group
|
|
688
|
+
end
|
|
689
|
+
|
|
690
|
+
# Create the smart group of macs with this version installed
|
|
691
|
+
#
|
|
692
|
+
# @return [Jamf::ComputerGroup] the smart group.
|
|
693
|
+
#########################
|
|
694
|
+
def create_jamf_installed_group
|
|
695
|
+
progress "Jamf: Creating smart group '#{jamf_installed_group_name}'", log: :info
|
|
696
|
+
|
|
697
|
+
@jamf_installed_group = Jamf::ComputerGroup.create(
|
|
698
|
+
name: jamf_installed_group_name,
|
|
699
|
+
type: :smart,
|
|
700
|
+
cnx: jamf_cnx
|
|
701
|
+
)
|
|
702
|
+
configure_jamf_installed_group @jamf_installed_group
|
|
703
|
+
@jamf_installed_group.save
|
|
704
|
+
@jamf_installed_group
|
|
705
|
+
end
|
|
706
|
+
|
|
707
|
+
# Reset the configuration of the jamf_installed_group
|
|
708
|
+
# but only if there's been a re-upload of the pkg
|
|
709
|
+
#########################
|
|
710
|
+
def repair_jamf_installed_group
|
|
711
|
+
return unless reupload_date
|
|
712
|
+
|
|
713
|
+
progress "Jamf: Repairing smart group '#{jamf_installed_group_name}'", log: :info
|
|
714
|
+
|
|
715
|
+
configure_jamf_installed_group jamf_installed_group
|
|
716
|
+
jamf_installed_group.save
|
|
717
|
+
end
|
|
718
|
+
|
|
719
|
+
# Set the configuration of the given smart group
|
|
720
|
+
# as needed for the jamf_installed_group
|
|
721
|
+
# @param grp [Jamf::ComputerGroup] the group to configure
|
|
722
|
+
#
|
|
723
|
+
# @return [void]
|
|
724
|
+
#########################
|
|
725
|
+
def configure_jamf_installed_group(grp)
|
|
726
|
+
progress "Jamf: Setting criteria for smart group '#{grp.name}'", log: :info
|
|
727
|
+
grp.criteria = Jamf::Criteriable::Criteria.new(jamf_installed_group_criteria)
|
|
728
|
+
end
|
|
729
|
+
|
|
730
|
+
# The criteria for the smart group in Jamf that contains all Macs
|
|
731
|
+
# with this version of this title installed
|
|
732
|
+
#
|
|
733
|
+
# If we have, or are about to update to, a version_script (EA) then use it,
|
|
734
|
+
# otherwise use the app_name and app_bundle_id.
|
|
735
|
+
#
|
|
736
|
+
# @return [Array<Jamf::Criteriable::Criterion>]
|
|
737
|
+
###################################
|
|
738
|
+
def jamf_installed_group_criteria
|
|
739
|
+
# does this title use an app bundle?
|
|
740
|
+
if title_object.app_name
|
|
741
|
+
[
|
|
742
|
+
Jamf::Criteriable::Criterion.new(
|
|
743
|
+
and_or: :and,
|
|
744
|
+
name: 'Application Title',
|
|
745
|
+
search_type: 'is',
|
|
746
|
+
value: title_object.app_name
|
|
747
|
+
),
|
|
748
|
+
|
|
749
|
+
Jamf::Criteriable::Criterion.new(
|
|
750
|
+
and_or: :and,
|
|
751
|
+
name: 'Application Bundle ID',
|
|
752
|
+
search_type: 'is',
|
|
753
|
+
value: title_object.app_bundle_id
|
|
754
|
+
),
|
|
755
|
+
|
|
756
|
+
Jamf::Criteriable::Criterion.new(
|
|
757
|
+
and_or: :and,
|
|
758
|
+
name: 'Application Version',
|
|
759
|
+
search_type: 'is',
|
|
760
|
+
value: version
|
|
761
|
+
)
|
|
762
|
+
]
|
|
763
|
+
|
|
764
|
+
# if not, it must have a version script
|
|
765
|
+
elsif title_object.version_script
|
|
766
|
+
[
|
|
767
|
+
Jamf::Criteriable::Criterion.new(
|
|
768
|
+
and_or: :and,
|
|
769
|
+
name: title_object.jamf_normal_ea_name,
|
|
770
|
+
search_type: 'is',
|
|
771
|
+
value: version
|
|
772
|
+
)
|
|
773
|
+
]
|
|
774
|
+
|
|
775
|
+
else
|
|
776
|
+
raise Xolo::Core::Exceptions::InvalidDataError, "Title #{title} has neither a version_script nor a defined app bundle."
|
|
777
|
+
end
|
|
778
|
+
end
|
|
779
|
+
|
|
780
|
+
#########################
|
|
781
|
+
def jamf_installed_group_url
|
|
782
|
+
return @jamf_installed_group_url if @jamf_installed_group_url
|
|
783
|
+
|
|
784
|
+
gr_id = Jamf::ComputerGroup.valid_id jamf_installed_group_name, cnx: jamf_cnx
|
|
785
|
+
return unless gr_id
|
|
786
|
+
|
|
787
|
+
@jamf_installed_group_url = "#{jamf_gui_url}/smartComputerGroups.html?id=#{gr_id}&o=r"
|
|
788
|
+
end
|
|
789
|
+
|
|
790
|
+
####### The Jamf Auto Re-Install Policy
|
|
791
|
+
###########################################
|
|
792
|
+
###########################################
|
|
793
|
+
|
|
794
|
+
# @return [Boolean] does the jamf_auto_reinstall_policy exist?
|
|
795
|
+
##########################
|
|
796
|
+
def jamf_auto_reinstall_policy_exist?
|
|
797
|
+
Jamf::Policy.all_names(cnx: jamf_cnx).include? jamf_auto_reinstall_policy_name
|
|
798
|
+
end
|
|
799
|
+
|
|
800
|
+
# Create or fetch the auto re-install policy for this version
|
|
801
|
+
# If we are deleting and it doesn't exist, return nil.
|
|
802
|
+
#
|
|
803
|
+
# @return [Jamf::Policy] The auto-install-policy for this version, if it exists
|
|
804
|
+
##########################
|
|
805
|
+
def jamf_auto_reinstall_policy
|
|
806
|
+
@jamf_auto_reinstall_policy ||=
|
|
807
|
+
if jamf_auto_reinstall_policy_exist?
|
|
808
|
+
Jamf::Policy.fetch(name: jamf_auto_reinstall_policy_name, cnx: jamf_cnx)
|
|
809
|
+
else
|
|
810
|
+
return if deleting?
|
|
811
|
+
# don't create unless there's been a re-upload of the pkg
|
|
812
|
+
return unless reupload_date
|
|
813
|
+
|
|
814
|
+
create_jamf_auto_reinstall_policy
|
|
815
|
+
end
|
|
816
|
+
end
|
|
817
|
+
|
|
818
|
+
# The auto rionstall policy, for when a pkg is re-uploaded for this version.
|
|
819
|
+
# @return [Jamf::Policy] the auto install policy for this version
|
|
820
|
+
#########################
|
|
821
|
+
def create_jamf_auto_reinstall_policy
|
|
822
|
+
progress "Jamf: Creating Auto Re-Install Policy: #{jamf_auto_reinstall_policy_name}", log: :debug
|
|
823
|
+
pol = Jamf::Policy.create name: jamf_auto_reinstall_policy_name, cnx: jamf_cnx
|
|
824
|
+
configure_jamf_auto_reinstall_policy(pol)
|
|
825
|
+
pol.save
|
|
826
|
+
pol
|
|
827
|
+
end
|
|
828
|
+
|
|
829
|
+
# Reset the configuration of the jamf_installed_group
|
|
830
|
+
#########################
|
|
831
|
+
def repair_jamf_auto_reinstall_policy
|
|
832
|
+
return unless reupload_date
|
|
833
|
+
|
|
834
|
+
progress "Jamf: Repairing Auto Re-Install Policy: #{jamf_auto_reinstall_policy_name}", log: :debug
|
|
835
|
+
configure_jamf_auto_reinstall_policy jamf_auto_reinstall_policy
|
|
836
|
+
end
|
|
837
|
+
|
|
838
|
+
# Set the proper config for the auto reinstall policy
|
|
839
|
+
# @param pol [Jamf::Policy] the policy to configure
|
|
840
|
+
######################
|
|
841
|
+
def configure_jamf_auto_reinstall_policy(pol)
|
|
842
|
+
pol.category = Xolo::Server::JAMF_XOLO_CATEGORY
|
|
843
|
+
pol.set_trigger_event :checkin, true
|
|
844
|
+
pol.set_trigger_event :custom, Xolo::BLANK
|
|
845
|
+
pol.frequency = :once_per_computer
|
|
846
|
+
pol.recon = false
|
|
847
|
+
pol.retry_event = :checkin
|
|
848
|
+
pol.retry_attempts = 5
|
|
849
|
+
pol.scope.set_targets :computer_groups, [jamf_installed_group_name]
|
|
850
|
+
|
|
851
|
+
# exclusions are for always
|
|
852
|
+
set_policy_exclusions pol
|
|
853
|
+
|
|
854
|
+
pol.package_names.each { |pkg_name| pol.remove_package pkg_name }
|
|
855
|
+
pol.add_package jamf_pkg_name
|
|
856
|
+
|
|
857
|
+
# NOTE: this policy is not enabled by default - it will be enabled
|
|
858
|
+
# if/when the pkg for the policy is re-uploaded
|
|
859
|
+
pol.enable if reupload_date.is_a? Time
|
|
860
|
+
end
|
|
861
|
+
|
|
862
|
+
# @return [String] the URL for the Jamf Pro Policy that does auto reinstalls of this version
|
|
863
|
+
######################
|
|
864
|
+
def jamf_auto_reinstall_policy_url
|
|
865
|
+
return @jamf_auto_reinstall_policy_url if @jamf_auto_reinstall_policy_url
|
|
866
|
+
|
|
867
|
+
pol_id = Jamf::Policy.valid_id jamf_auto_reinstall_policy_name, cnx: jamf_cnx
|
|
868
|
+
return unless pol_id
|
|
869
|
+
|
|
870
|
+
@jamf_auto_reinstall_policy_url = "#{jamf_gui_url}/policies.html?id=#{pol_id}&o=r"
|
|
871
|
+
end
|
|
872
|
+
|
|
873
|
+
# This will start a thread
|
|
874
|
+
# that will wait some period of time (to allow for pkg uploads
|
|
875
|
+
# to complete) before enabling and flushing the logs for the reinstall policy.
|
|
876
|
+
# This will make all macs with this version installed get it re-installed.
|
|
877
|
+
# @return [void]
|
|
878
|
+
def wait_to_enable_reinstall_policy
|
|
879
|
+
return if @enable_reinstall_policy_thread&.alive?
|
|
880
|
+
return unless reupload_date
|
|
881
|
+
|
|
882
|
+
# TODO: some setting to determine how long to wait?
|
|
883
|
+
# - If uploading via the Jamf API, we need to give it time
|
|
884
|
+
# to then upload the file to the cloud distribution point
|
|
885
|
+
# - If uploading via a custom tool, we need to give that
|
|
886
|
+
# tool time to re-upload to wherever it uploads to
|
|
887
|
+
# - May need to wait for other non-jamf/non-xolo processes
|
|
888
|
+
# to sync the package to other distribution points. This
|
|
889
|
+
# might be very site-specific.
|
|
890
|
+
|
|
891
|
+
# For now, we wait 15 minutes.
|
|
892
|
+
wait_time = JAMF_AUTO_REINSTALL_WAIT_SECS
|
|
893
|
+
|
|
894
|
+
@enable_reinstall_policy_thread = Thread.new do
|
|
895
|
+
log_debug "Jamf: Starting enable_reinstall_policy_thread: waiting #{wait_time} seconds before enabling reinstall policy for version #{version} of title #{title}"
|
|
896
|
+
sleep wait_time
|
|
897
|
+
|
|
898
|
+
log_debug "Jamf: enable_reinstall_policy_thread: enabling and flushing logs for reinstall policy for version #{version} of title #{title}"
|
|
899
|
+
|
|
900
|
+
pol = jamf_auto_reinstall_policy
|
|
901
|
+
pol.enable
|
|
902
|
+
pol.flush_logs
|
|
903
|
+
pol.save
|
|
904
|
+
end
|
|
905
|
+
@enable_reinstall_policy_thread.name = "enable_reinstall_policy_thread-#{title}-#{version}"
|
|
906
|
+
end
|
|
907
|
+
|
|
908
|
+
####### The Jamf Patch Policy
|
|
909
|
+
###########################################
|
|
910
|
+
###########################################
|
|
911
|
+
|
|
912
|
+
# @return [Boolean] does the jamf_patch_policy exist?
|
|
913
|
+
###########################
|
|
914
|
+
def jamf_patch_policy_exist?
|
|
915
|
+
Jamf::PatchPolicy.all_names(:refresh, cnx: jamf_cnx).include? jamf_patch_policy_name
|
|
916
|
+
end
|
|
917
|
+
|
|
918
|
+
# Fetch or create the patch policy for this version
|
|
919
|
+
# If we are deleting and it doesn't exist, return nil.
|
|
920
|
+
# @return [Jamf::PatchPolicy, nil] The patch policy for this version, if it exists
|
|
921
|
+
##########################
|
|
922
|
+
def jamf_patch_policy
|
|
923
|
+
@jamf_patch_policy ||=
|
|
924
|
+
if jamf_patch_policy_exist?
|
|
925
|
+
Jamf::PatchPolicy.fetch(name: jamf_patch_policy_name, cnx: jamf_cnx)
|
|
926
|
+
else
|
|
927
|
+
return if deleting?
|
|
928
|
+
|
|
929
|
+
create_jamf_patch_policy
|
|
930
|
+
end
|
|
931
|
+
end
|
|
932
|
+
|
|
933
|
+
# @return [Jamf::PatchPolicy] The xolo patch policy for this version
|
|
934
|
+
#########################
|
|
935
|
+
def create_jamf_patch_policy
|
|
936
|
+
progress "Jamf: Creating Patch Policy for Version '#{version}' of Title '#{title}'.", log: :info
|
|
937
|
+
|
|
938
|
+
# TODO: decide how many patch policies - see comments at top
|
|
939
|
+
# Probably one: for pilots initially and then rescoped to all for release
|
|
940
|
+
#
|
|
941
|
+
# TODO: How to set these, and should they be settable
|
|
942
|
+
# at the Xolo::Title or Xolo::Version level?
|
|
943
|
+
#
|
|
944
|
+
# allow downgrade? No, to start with.
|
|
945
|
+
# When a version is released, IF we are rolling back, then this will be set.
|
|
946
|
+
# This is to be set only on the current release and only when it was a rollback.
|
|
947
|
+
#
|
|
948
|
+
# patch_unknown_versions... yes?
|
|
949
|
+
#
|
|
950
|
+
# if not in ssvc:
|
|
951
|
+
# - grace period?
|
|
952
|
+
# - update warning Message and Subject
|
|
953
|
+
#
|
|
954
|
+
# if in ssvc:
|
|
955
|
+
# - any way to use existing icon?
|
|
956
|
+
# - use title desc... do we want a version desc??
|
|
957
|
+
# - notifications? Message and Subject? SSvc only, Notif Ctr?
|
|
958
|
+
# - deadline and grace period message and subbject
|
|
959
|
+
|
|
960
|
+
ppol = Jamf::PatchPolicy.create(
|
|
961
|
+
cnx: jamf_cnx,
|
|
962
|
+
name: jamf_patch_policy_name,
|
|
963
|
+
patch_title: title_object.jamf_patch_title.id,
|
|
964
|
+
target_version: version
|
|
965
|
+
)
|
|
966
|
+
|
|
967
|
+
# when first creating a patch policy, its status is always
|
|
968
|
+
# 'pilot' so the scope targets are the pilot groups, if any.
|
|
969
|
+
# When the version is released, the patch policy will be
|
|
970
|
+
# rescoped to all targets (limited by eligibility)
|
|
971
|
+
set_policy_pilot_groups ppol
|
|
972
|
+
|
|
973
|
+
# exclusions are for always
|
|
974
|
+
set_policy_exclusions ppol
|
|
975
|
+
|
|
976
|
+
# This will be set to true as needed if
|
|
977
|
+
# a rollback is being done
|
|
978
|
+
ppol.allow_downgrade = false
|
|
979
|
+
|
|
980
|
+
# This should always be false, so that
|
|
981
|
+
# we don't accidentally downgrade non-xolo test installs,
|
|
982
|
+
# or server-pushed updates (like with commvault or cisco VPN)
|
|
983
|
+
ppol.patch_unknown = false
|
|
984
|
+
|
|
985
|
+
ppol.enable
|
|
986
|
+
|
|
987
|
+
ppol.save
|
|
988
|
+
|
|
989
|
+
# refetch it rather than using the one we just created
|
|
990
|
+
Jamf::PatchPolicy.fetch(name: jamf_patch_policy_name, cnx: jamf_cnx)
|
|
991
|
+
end
|
|
992
|
+
|
|
993
|
+
# repair the patch policy only
|
|
994
|
+
#############################
|
|
995
|
+
def repair_jamf_patch_policy
|
|
996
|
+
progress "Jamf: Repairing Patch Policy '#{jamf_patch_policy_name}'", log: :info
|
|
997
|
+
assign_pkg_to_patch_in_jamf
|
|
998
|
+
|
|
999
|
+
ppol = jamf_patch_policy
|
|
1000
|
+
ppol.name = jamf_patch_policy_name
|
|
1001
|
+
ppol.target_version = version
|
|
1002
|
+
|
|
1003
|
+
# This should always be false, so that
|
|
1004
|
+
# we don't accidentally downgrade non-xolo test installs,
|
|
1005
|
+
# or server-pushed updates (like with commvault or cisco VPN)
|
|
1006
|
+
ppol.patch_unknown = false
|
|
1007
|
+
|
|
1008
|
+
if pilot?
|
|
1009
|
+
set_policy_pilot_groups ppol
|
|
1010
|
+
else
|
|
1011
|
+
ppol.scope.set_all_targets
|
|
1012
|
+
end
|
|
1013
|
+
|
|
1014
|
+
# exclusions are for always
|
|
1015
|
+
set_policy_exclusions ppol
|
|
1016
|
+
|
|
1017
|
+
if pilot? || released?
|
|
1018
|
+
ppol.enable
|
|
1019
|
+
else
|
|
1020
|
+
ppol.disable
|
|
1021
|
+
end
|
|
1022
|
+
|
|
1023
|
+
ppol.save
|
|
1024
|
+
end
|
|
1025
|
+
|
|
1026
|
+
# @return [String] the URL for the Jamf Pro Patch Policy that updates to this version
|
|
1027
|
+
######################
|
|
1028
|
+
def jamf_patch_policy_url
|
|
1029
|
+
return @jamf_patch_policy_url if @jamf_patch_policy_url
|
|
1030
|
+
|
|
1031
|
+
title_id = title_object.jamf_patch_title_id
|
|
1032
|
+
|
|
1033
|
+
pol_id = Jamf::PatchPolicy.valid_id jamf_patch_policy_name, cnx: jamf_cnx
|
|
1034
|
+
return unless pol_id
|
|
1035
|
+
|
|
1036
|
+
@jamf_manual_install_policy_url = "#{jamf_gui_url}/patchDeployment.html?softwareTitleId=#{title_id}&id=#{pol_id}&o=r"
|
|
1037
|
+
end
|
|
1038
|
+
|
|
1039
|
+
####### General Policy Handling
|
|
1040
|
+
###########################################
|
|
1041
|
+
###########################################
|
|
1042
|
+
|
|
1043
|
+
# set target groups in a pilot [patch] policy object's scope
|
|
1044
|
+
# REMEMBER TO SAVE THE POLICY LATER
|
|
1045
|
+
#
|
|
1046
|
+
# @param pol [Jamf::Policy, Jamf::PatchPolicy]
|
|
1047
|
+
# @return [void]
|
|
1048
|
+
############################
|
|
1049
|
+
def set_policy_pilot_groups(pol)
|
|
1050
|
+
pilots = pilot_groups_to_use
|
|
1051
|
+
pilots ||= []
|
|
1052
|
+
log_debug "Jamf: setting pilot scope targets for #{pol.class} '#{pol.name}' to: #{pilots.join ', '}"
|
|
1053
|
+
|
|
1054
|
+
pol.scope.set_targets :computer_groups, pilots
|
|
1055
|
+
end
|
|
1056
|
+
|
|
1057
|
+
# Set a policy to be scoped to all targets
|
|
1058
|
+
# REMEMBER TO SAVE THE POLICY LATER
|
|
1059
|
+
#
|
|
1060
|
+
# @param pol [Jamf::Policy, Jamf::PatchPolicy]
|
|
1061
|
+
# @return [void]
|
|
1062
|
+
############################
|
|
1063
|
+
def set_policy_to_all_targets(pol)
|
|
1064
|
+
log_debug "Jamf: setting scope target for #{pol.class} '#{pol.name}' to all computers"
|
|
1065
|
+
pol.scope.set_all_targets
|
|
1066
|
+
end
|
|
1067
|
+
|
|
1068
|
+
# set target groups in a non=pilot [patch] policy object's scope
|
|
1069
|
+
# REMEMBER TO SAVE THE POLICY LATER
|
|
1070
|
+
#
|
|
1071
|
+
# @param pol [Jamf::Policy, Jamf::PatchPolicy]
|
|
1072
|
+
# @param ttl_obj [Xolo::Server::Title] The pre-instantiated title for ths version.
|
|
1073
|
+
# if nil, we'll instantiate it now
|
|
1074
|
+
# @return [void]
|
|
1075
|
+
############################
|
|
1076
|
+
def set_policy_release_groups(pol, ttl_obj: nil)
|
|
1077
|
+
ttl_obj ||= title_object
|
|
1078
|
+
targets = release_groups_to_use(ttl_obj: ttl_obj) || []
|
|
1079
|
+
|
|
1080
|
+
log_debug "Jamf: setting release scope targets for #{pol.class} '#{pol.name}' to: #{targets.join ', '}"
|
|
1081
|
+
|
|
1082
|
+
if targets.include? Xolo::TARGET_ALL
|
|
1083
|
+
pol.scope.set_all_targets
|
|
1084
|
+
else
|
|
1085
|
+
pol.scope.set_targets :computer_groups, targets
|
|
1086
|
+
end
|
|
1087
|
+
end
|
|
1088
|
+
|
|
1089
|
+
# set excluded groups in a [patch] policy object's scope
|
|
1090
|
+
# REMEMBER TO SAVE THE POLICY LATER
|
|
1091
|
+
#
|
|
1092
|
+
# This applies more nuance to the 'excluded_groups_to_use' depending on
|
|
1093
|
+
# the policy in question. E.g. manual-install policy should not
|
|
1094
|
+
# have the installed-group excluded, to allow re-installs
|
|
1095
|
+
#
|
|
1096
|
+
# @param pol [Jamf::Policy, Jamf::PatchPolicy]
|
|
1097
|
+
# @param ttl_obj [Xolo::Server::Title] The pre-instantiated title for ths version.
|
|
1098
|
+
# if nil, we'll instantiate it now
|
|
1099
|
+
############################
|
|
1100
|
+
def set_policy_exclusions(pol, ttl_obj: nil)
|
|
1101
|
+
ttl_obj ||= title_object
|
|
1102
|
+
# dup, so when we add the installed group below, we don't
|
|
1103
|
+
# keep that for future calls to this method.
|
|
1104
|
+
exclusions = excluded_groups_to_use(ttl_obj: ttl_obj).dup
|
|
1105
|
+
exclusions ||= []
|
|
1106
|
+
|
|
1107
|
+
# the initial auto-install policies must also exclude any mac with the title
|
|
1108
|
+
# already installed
|
|
1109
|
+
#
|
|
1110
|
+
# But the manual-install policy and the auto-reinstall should never exclude it - so that
|
|
1111
|
+
# it can be manually installed or automatically re-installed whenever needed
|
|
1112
|
+
if pol.is_a?(Jamf::Policy) && pol.name == jamf_auto_install_policy_name
|
|
1113
|
+
# calling ttl_obj.jamf_installed_group will create the group if needed
|
|
1114
|
+
exclusions << ttl_obj.jamf_installed_group_name
|
|
1115
|
+
end
|
|
1116
|
+
|
|
1117
|
+
log_debug "Jamf: updating exclusions for #{pol.class} '#{pol.name}' to: #{exclusions.join ', '}"
|
|
1118
|
+
|
|
1119
|
+
exclusions.uniq!
|
|
1120
|
+
pol.scope.set_exclusions :computer_groups, exclusions
|
|
1121
|
+
end
|
|
1122
|
+
|
|
1123
|
+
# Disable the auto-install and patch policies for this version when it
|
|
1124
|
+
# is deprecated or skipped
|
|
1125
|
+
#
|
|
1126
|
+
# Leave the manual install policy active, but remove it from self-service
|
|
1127
|
+
#
|
|
1128
|
+
# @param reason [Symbol] :deprecated or :skipped
|
|
1129
|
+
#
|
|
1130
|
+
# @return [void]
|
|
1131
|
+
#########################
|
|
1132
|
+
def disable_policies_for_deprecation_or_skipping(reason)
|
|
1133
|
+
progress "Jamf: Disabling auto-install policy for #{reason} version '#{version}'"
|
|
1134
|
+
pol = jamf_auto_install_policy
|
|
1135
|
+
pol.disable
|
|
1136
|
+
pol.save
|
|
1137
|
+
|
|
1138
|
+
# don't disable the auto-reinstall policy - it may be needed
|
|
1139
|
+
# for any re-uploads of the pkg for this version, even if deprecated/skipped
|
|
1140
|
+
|
|
1141
|
+
progress "Jamf: Disabling patch policy for #{reason} version '#{version}'"
|
|
1142
|
+
ppol = jamf_patch_policy
|
|
1143
|
+
ppol.disable
|
|
1144
|
+
# ensure patch policy is NOT set to 'allow downgrade'
|
|
1145
|
+
ppol.allow_downgrade = false
|
|
1146
|
+
ppol.save
|
|
1147
|
+
end
|
|
1148
|
+
|
|
1149
|
+
# reset all the policies for this version to pilot
|
|
1150
|
+
#
|
|
1151
|
+
# @return [void]
|
|
1152
|
+
######################
|
|
1153
|
+
def reset_policies_to_pilot
|
|
1154
|
+
# set scope targets of auto-install policy to pilot-groups and re-enable
|
|
1155
|
+
msg = "Jamf: Version '#{version}': Setting scope targets of auto-install policy to pilot_groups: #{pilot_groups_to_use.join(', ')}"
|
|
1156
|
+
progress msg, log: :info
|
|
1157
|
+
|
|
1158
|
+
jamf_auto_install_policy.scope.set_targets :computer_groups, pilot_groups_to_use
|
|
1159
|
+
jamf_auto_install_policy.enable
|
|
1160
|
+
jamf_auto_install_policy.save
|
|
1161
|
+
|
|
1162
|
+
msg = "Jamf: Version '#{version}': Setting scope targets of patch policy to pilot_groups"
|
|
1163
|
+
progress msg, log: :info
|
|
1164
|
+
|
|
1165
|
+
# set scope targets of patch policy to pilot-groups and re-enable
|
|
1166
|
+
jamf_patch_policy.scope.set_targets :computer_groups, pilot_groups_to_use
|
|
1167
|
+
# ensure patch policy is NOT set to 'allow downgrade'
|
|
1168
|
+
jamf_patch_policy.allow_downgrade = false
|
|
1169
|
+
jamf_patch_policy.enable
|
|
1170
|
+
jamf_patch_policy.save
|
|
1171
|
+
end
|
|
1172
|
+
|
|
1173
|
+
# Update all the pilot_groups policy scopes for this version when
|
|
1174
|
+
# either the title or version has changed them
|
|
1175
|
+
#
|
|
1176
|
+
# Nothing to do if the version isn't currently in :pilot status
|
|
1177
|
+
#
|
|
1178
|
+
# @param ttl_obj [Xolo::Server::Title] The pre-instantiated title for ths version.
|
|
1179
|
+
# if nil, we'll instantiate it now
|
|
1180
|
+
#########################
|
|
1181
|
+
def update_pilot_groups
|
|
1182
|
+
# nothing unless we're in pilot
|
|
1183
|
+
return unless status == Xolo::Server::Version::STATUS_PILOT
|
|
1184
|
+
|
|
1185
|
+
# - no changes to the manual install policy: scope-target is all
|
|
1186
|
+
|
|
1187
|
+
# - update the auto install policy
|
|
1188
|
+
progress "Jamf: Updating pilot groups for Auto Install Policy '#{jamf_auto_install_policy_name}'."
|
|
1189
|
+
|
|
1190
|
+
pol = jamf_auto_install_policy
|
|
1191
|
+
|
|
1192
|
+
set_policy_pilot_groups(pol)
|
|
1193
|
+
pol.save
|
|
1194
|
+
|
|
1195
|
+
# - update the patch policy
|
|
1196
|
+
progress "Jamf: Updating pilot groups for Patch Policy '#{jamf_patch_policy_name}'."
|
|
1197
|
+
|
|
1198
|
+
pol = jamf_patch_policy
|
|
1199
|
+
|
|
1200
|
+
set_policy_pilot_groups(pol)
|
|
1201
|
+
pol.save
|
|
1202
|
+
end
|
|
1203
|
+
|
|
1204
|
+
# Update all the release_groups policy scopes for this version when
|
|
1205
|
+
# either the title or version has changed them
|
|
1206
|
+
#
|
|
1207
|
+
# Nothing to do if the version is currently in pending or pilot status
|
|
1208
|
+
#
|
|
1209
|
+
# @param ttl_obj [Xolo::Server::Title] The pre-instantiated title for ths version.
|
|
1210
|
+
# if nil, we'll instantiate it now
|
|
1211
|
+
#########################
|
|
1212
|
+
def update_release_groups(ttl_obj: nil)
|
|
1213
|
+
return unless status == Xolo::Server::Version::STATUS_RELEASED
|
|
1214
|
+
|
|
1215
|
+
# - no changes to the manual install policy: scope-target is all
|
|
1216
|
+
|
|
1217
|
+
# - update the auto-install policy
|
|
1218
|
+
pol = jamf_auto_install_policy
|
|
1219
|
+
return unless pol
|
|
1220
|
+
|
|
1221
|
+
set_policy_release_groups(pol, ttl_obj: ttl_obj)
|
|
1222
|
+
pol.save
|
|
1223
|
+
progress "Jamf: updated release groups for Auto Install Policy '#{jamf_auto_install_policy_name}'.",
|
|
1224
|
+
log: :info
|
|
1225
|
+
|
|
1226
|
+
# - no changes to the patch policy: scope-target is all once released
|
|
1227
|
+
end
|
|
1228
|
+
|
|
1229
|
+
# Update all the excluded_groups policy scopes for this version when
|
|
1230
|
+
# either the title or version has changed them
|
|
1231
|
+
#
|
|
1232
|
+
# Applies regardless of status
|
|
1233
|
+
#
|
|
1234
|
+
# @param ttl_obj [Xolo::Server::Title] The pre-instantiated title for ths version.
|
|
1235
|
+
# if nil, we'll instantiate it now
|
|
1236
|
+
#########################
|
|
1237
|
+
def update_excluded_groups(ttl_obj: nil)
|
|
1238
|
+
log_debug "Updating Excluded Groups for Version '#{version}' of Title '#{title}'"
|
|
1239
|
+
|
|
1240
|
+
# - update the manual install policy
|
|
1241
|
+
pol = jamf_manual_install_policy
|
|
1242
|
+
if pol
|
|
1243
|
+
progress "Jamf: Updating excluded groups for Manual Install Policy '#{jamf_auto_install_policy_name}'."
|
|
1244
|
+
set_policy_exclusions(pol, ttl_obj: ttl_obj)
|
|
1245
|
+
pol.save
|
|
1246
|
+
end
|
|
1247
|
+
|
|
1248
|
+
# - update the auto install policy
|
|
1249
|
+
pol = jamf_auto_install_policy
|
|
1250
|
+
if pol
|
|
1251
|
+
progress "Jamf: Updating excluded groups for Auto Install Policy '#{jamf_auto_install_policy_name}'."
|
|
1252
|
+
set_policy_exclusions(pol, ttl_obj: ttl_obj)
|
|
1253
|
+
pol.save
|
|
1254
|
+
end
|
|
1255
|
+
|
|
1256
|
+
# - update the auto reinstall policy, but only if it exists
|
|
1257
|
+
if jamf_auto_reinstall_policy_exist?
|
|
1258
|
+
pol = jamf_auto_reinstall_policy
|
|
1259
|
+
if pol
|
|
1260
|
+
progress "Jamf: Updating excluded groups for Auto ReInstall Policy '#{jamf_auto_reinstall_policy_name}'."
|
|
1261
|
+
set_policy_exclusions(pol, ttl_obj: ttl_obj)
|
|
1262
|
+
pol.save
|
|
1263
|
+
end
|
|
1264
|
+
end
|
|
1265
|
+
|
|
1266
|
+
# - update the patch policy
|
|
1267
|
+
pol = jamf_patch_policy
|
|
1268
|
+
return unless pol
|
|
1269
|
+
|
|
1270
|
+
progress "Jamf: Updating exccluded groups for Patch Policy '#{jamf_patch_policy_name}'."
|
|
1271
|
+
set_policy_exclusions(pol, ttl_obj: ttl_obj)
|
|
1272
|
+
pol.save
|
|
1273
|
+
end
|
|
1274
|
+
|
|
1275
|
+
####### General Jamf Patch Handling
|
|
1276
|
+
###########################################
|
|
1277
|
+
###########################################
|
|
1278
|
+
|
|
1279
|
+
# @return [Jamf::PatchTitle::Version] The Jamf::PatchTitle::Version for this
|
|
1280
|
+
# Xolo version
|
|
1281
|
+
#####################
|
|
1282
|
+
def jamf_patch_version
|
|
1283
|
+
return @jamf_patch_version if @jamf_patch_version
|
|
1284
|
+
|
|
1285
|
+
# NOTE: in the line below, use the title_object's call to #jamf_patch_title
|
|
1286
|
+
# because that will cache the Jamf::PatchTitle instance, and we need to
|
|
1287
|
+
# use it to save changes to its Versions.
|
|
1288
|
+
# Using the class method won't cache the instance we will need in the
|
|
1289
|
+
# future.
|
|
1290
|
+
@jamf_patch_version = title_object.jamf_patch_title.versions[version]
|
|
1291
|
+
return @jamf_patch_version if @jamf_patch_version
|
|
1292
|
+
|
|
1293
|
+
# TODO: wait for it to appear when adding?
|
|
1294
|
+
msg = "Jamf: Version '#{version}' of Title '#{title}' is not visible in Jamf. Is the Patch enabled in the Title Editor?"
|
|
1295
|
+
log_error msg
|
|
1296
|
+
raise Xolo::NoSuchItemError, msg
|
|
1297
|
+
end
|
|
1298
|
+
|
|
1299
|
+
# Wait until the version is visible from the title editor
|
|
1300
|
+
# then assign the pkg to it in Jamf Patch,
|
|
1301
|
+
# and create the patch policy.
|
|
1302
|
+
#
|
|
1303
|
+
# Do this in a thread so the xadm user doesn't wait up to ?? minutes.
|
|
1304
|
+
#
|
|
1305
|
+
# @return [void]
|
|
1306
|
+
#########################
|
|
1307
|
+
def activate_patch_version_in_jamf
|
|
1308
|
+
# don't do this if there's already one running for this instance
|
|
1309
|
+
if @activate_patch_version_thread&.alive?
|
|
1310
|
+
log_debug "Jamf: activate_patch_version_thread already running. Caller: #{caller_locations.first}"
|
|
1311
|
+
return
|
|
1312
|
+
end
|
|
1313
|
+
|
|
1314
|
+
msg = "Jamf: Will assign Jamf pkg '#{jamf_pkg_name}' and create the patch policy when this version becomes visible to Jamf Pro from the Title Editor."
|
|
1315
|
+
progress msg, log: :debug
|
|
1316
|
+
|
|
1317
|
+
@activate_patch_version_thread = Thread.new do
|
|
1318
|
+
log_debug "Jamf: Starting activate_patch_version_thread waiting for version #{version} of title #{title} to become visible from the title editor"
|
|
1319
|
+
|
|
1320
|
+
start_time = Time.now
|
|
1321
|
+
max_time = start_time + Xolo::Server::MAX_JAMF_WAIT_FOR_TITLE_EDITOR
|
|
1322
|
+
start_time = start_time.strftime '%F %T'
|
|
1323
|
+
|
|
1324
|
+
did_it = false
|
|
1325
|
+
|
|
1326
|
+
while Time.now < max_time
|
|
1327
|
+
sleep 15
|
|
1328
|
+
log_debug "Jamf: checking for version #{version} of title #{title} to become visible from the title editor since #{start_time}"
|
|
1329
|
+
|
|
1330
|
+
# check for the existence of the jamf_patch_title every time, since it might have gone away
|
|
1331
|
+
# if the title was deleted while this was happening.
|
|
1332
|
+
next unless title_object.jamf_patch_title(refresh: true) && title_object.jamf_patch_title.versions.key?(version)
|
|
1333
|
+
|
|
1334
|
+
did_it = true
|
|
1335
|
+
break
|
|
1336
|
+
end
|
|
1337
|
+
|
|
1338
|
+
if did_it
|
|
1339
|
+
assign_pkg_to_patch_in_jamf
|
|
1340
|
+
# give jamf a moment to catch up and refresh the patch title
|
|
1341
|
+
# so we see the pkg has been assigned
|
|
1342
|
+
sleep 2
|
|
1343
|
+
title_object.jamf_patch_title(refresh: true)
|
|
1344
|
+
|
|
1345
|
+
create_jamf_patch_policy
|
|
1346
|
+
msg = "Jamf: Version '#{version}' of title '#{title}' is now visible in Jamf Pro. Package assigned and Patch policy created."
|
|
1347
|
+
log_info msg, alert: true
|
|
1348
|
+
else
|
|
1349
|
+
msg = "Jamf: ERROR: Version '#{version}' of title '#{title}' has not become visible from the Title Editor in over #{Xolo::Server::MAX_JAMF_WAIT_FOR_TITLE_EDITOR} seconds. The package has not been assigned, and no patch policy was created."
|
|
1350
|
+
log_error msg, alert: true
|
|
1351
|
+
end
|
|
1352
|
+
end # thread
|
|
1353
|
+
@activate_patch_version_thread.name = "activate_patch_version_thread-#{title}-#{version}"
|
|
1354
|
+
end
|
|
1355
|
+
|
|
1356
|
+
# Assign the Package to the Jamf::PatchTitle::Version for this Xolo version.
|
|
1357
|
+
# This 'activates' the version in Jamf Patch, and must happen before
|
|
1358
|
+
# patch policies can be created
|
|
1359
|
+
#
|
|
1360
|
+
# Jamf::PatchTitle::Version objects are contained in the matching
|
|
1361
|
+
# Jamf::PatchTitle, and to make or save changes, we have to fetch the title,
|
|
1362
|
+
# update the version, and save the title.
|
|
1363
|
+
#
|
|
1364
|
+
# NOTE: This can't happen until Jamf see's the version in the title editor
|
|
1365
|
+
# otherwise you'll get an error. The methods that call this should ensure
|
|
1366
|
+
# the version is visible.
|
|
1367
|
+
#
|
|
1368
|
+
# @return [void]
|
|
1369
|
+
########################################
|
|
1370
|
+
def assign_pkg_to_patch_in_jamf
|
|
1371
|
+
log_info "Jamf: Assigning package '#{jamf_pkg_name}' to patch version '#{version}' of title '#{title}'"
|
|
1372
|
+
|
|
1373
|
+
jamf_patch_version.package = jamf_pkg_name
|
|
1374
|
+
title_object.jamf_patch_title.save
|
|
1375
|
+
end
|
|
1376
|
+
|
|
1377
|
+
# Get the patch report for this version
|
|
1378
|
+
# @return [Arrah<Hash>] Data for each computer with this version of this title installed
|
|
1379
|
+
######################
|
|
1380
|
+
def patch_report
|
|
1381
|
+
title_object.patch_report vers: version
|
|
1382
|
+
end
|
|
1383
|
+
|
|
1384
|
+
####### MDM Deployment
|
|
1385
|
+
###########################################
|
|
1386
|
+
###########################################
|
|
1387
|
+
|
|
1388
|
+
# Install this version on a one or more computers via MDM.
|
|
1389
|
+
#
|
|
1390
|
+
# @param targets [Hash ] With the following keys
|
|
1391
|
+
# - computers: [Array<String, Integer>] The computer identifiers to install on.
|
|
1392
|
+
# Identifiers are either serial numbers, names, or Jamf IDs.
|
|
1393
|
+
# - groups: [Array<String, Integer>] The names or ids of computer groups to install on.
|
|
1394
|
+
#
|
|
1395
|
+
# @return [Hash] The results of the install with the following keys
|
|
1396
|
+
# - removals: [Array<Hash>] { device: <String>, group: <InteStringger>, reason: <String> }
|
|
1397
|
+
# - queuedCommands: [Array<Hash>] { device: <String>, commandUuid: <String> }
|
|
1398
|
+
# - errors: [Array<Hash>] { device: <String>, group: <Integer>, reason: <String> }
|
|
1399
|
+
#
|
|
1400
|
+
def deploy_via_mdm(targets)
|
|
1401
|
+
unless dist_pkg
|
|
1402
|
+
raise Xolo::UnsupportedError,
|
|
1403
|
+
'MDM deployment is not supported for this version, it is not a Distribution Package.'
|
|
1404
|
+
end
|
|
1405
|
+
|
|
1406
|
+
all_targets = targets[:computers] || []
|
|
1407
|
+
removals = []
|
|
1408
|
+
|
|
1409
|
+
# expand groups into computers,
|
|
1410
|
+
all_targets += expand_groups_for_deploy(targets[:groups], removals) if targets[:groups]
|
|
1411
|
+
|
|
1412
|
+
# remove duplicates
|
|
1413
|
+
all_targets.uniq!
|
|
1414
|
+
|
|
1415
|
+
# remove invalid computers, after this all_targets will be valid computer ids
|
|
1416
|
+
remove_invalid_computers_for_deploy(all_targets, removals)
|
|
1417
|
+
|
|
1418
|
+
# remove members of excluded groups from the list of targets
|
|
1419
|
+
remove_exclusions_from_deploy(all_targets, removals)
|
|
1420
|
+
|
|
1421
|
+
if all_targets.empty?
|
|
1422
|
+
log_info "Jamf: No valid computers to deploy to for version '#{version}' of title '#{title}'."
|
|
1423
|
+
queued_cmds = []
|
|
1424
|
+
deploy_errs = []
|
|
1425
|
+
|
|
1426
|
+
else
|
|
1427
|
+
# deploy the package to the computers
|
|
1428
|
+
jamf_package.deploy_via_mdm computer_ids: all_targets
|
|
1429
|
+
# convert ids to names for the response
|
|
1430
|
+
comp_ids_to_names = Jamf::Computer.map_all(:id, to: :name, cnx: jamf_cnx)
|
|
1431
|
+
|
|
1432
|
+
queued_cmds = jamf_package.deploy_response[:queuedCommands].map do |qc|
|
|
1433
|
+
{ device: comp_ids_to_names[qc[:device]], commandUuid: qc[:commandUuid] }
|
|
1434
|
+
end
|
|
1435
|
+
|
|
1436
|
+
deploy_errs = jamf_package.deploy_response[:errors].map do |err|
|
|
1437
|
+
{ device: comp_ids_to_names[err[:device]], reason: err[:reason] }
|
|
1438
|
+
end
|
|
1439
|
+
|
|
1440
|
+
log_info "Jamf: Deployed version '#{version}' of title '#{title}' to #{all_targets.size} computers via MDM"
|
|
1441
|
+
|
|
1442
|
+
end
|
|
1443
|
+
|
|
1444
|
+
removals.each { |r| log_info "Jamf: Removal #{r}" }
|
|
1445
|
+
queued_cmds.each { |qc| log_info "Jamf: Queued Command #{qc}" }
|
|
1446
|
+
deploy_errs.each { |err| log_info "Jamf: Error #{err}" }
|
|
1447
|
+
|
|
1448
|
+
{
|
|
1449
|
+
removals: removals,
|
|
1450
|
+
queuedCommands: queued_cmds,
|
|
1451
|
+
errors: deploy_errs
|
|
1452
|
+
}
|
|
1453
|
+
end
|
|
1454
|
+
|
|
1455
|
+
# expand computer groups given for deploy_via_mdm
|
|
1456
|
+
#
|
|
1457
|
+
# @param groups [Array<String, Integer>] The names or ids of computer groups to install on.
|
|
1458
|
+
# @param removals [Array<Hash>] The groups that are not valid, for reporting back to the caller
|
|
1459
|
+
#
|
|
1460
|
+
# @return [Array<Integer>] The ids of the computers in the groups
|
|
1461
|
+
#########################
|
|
1462
|
+
def expand_groups_for_deploy(groups, removals)
|
|
1463
|
+
log_debug "Expanding group targets for MDM deployment of title '#{title}', version '#{version}'"
|
|
1464
|
+
|
|
1465
|
+
computers = []
|
|
1466
|
+
groups.each do |g|
|
|
1467
|
+
gid = Jamf::ComputerGroup.valid_id g, cnx: jamf_cnx
|
|
1468
|
+
if gid
|
|
1469
|
+
jgroup = Jamf::ComputerGroup.fetch id: gid, cnx: jamf_cnx
|
|
1470
|
+
|
|
1471
|
+
if excluded_groups_to_use.include? jgroup.name
|
|
1472
|
+
log_debug "Jamf: Group '#{jgroup.name}' is in the excluded groups list. Removing."
|
|
1473
|
+
removals << { device: nil, group: g, reason: "Group '#{jgroup.name}' is in the excluded groups list" }
|
|
1474
|
+
next
|
|
1475
|
+
end
|
|
1476
|
+
log_debug "Jamf: Adding computers from group '#{jgroup.name}' to deployment targets"
|
|
1477
|
+
computers += jgroup.member_ids
|
|
1478
|
+
else
|
|
1479
|
+
log_debug "Jamf: Group '#{g}' not found in Jamf Pro. Removing."
|
|
1480
|
+
removals << { device: nil, group: g, reason: 'Group not found in Jamf Pro' }
|
|
1481
|
+
end
|
|
1482
|
+
end
|
|
1483
|
+
computers
|
|
1484
|
+
end
|
|
1485
|
+
|
|
1486
|
+
# remove invalid computers from the list of targets for deploy_via_mdm
|
|
1487
|
+
#
|
|
1488
|
+
# @param targets [Array<String, Integer>] The names or ids of computers to install on.
|
|
1489
|
+
# @param removals [Array<Hash>] The computers that are not valid, for reporting back to the caller
|
|
1490
|
+
#
|
|
1491
|
+
# @return [void]
|
|
1492
|
+
#########################
|
|
1493
|
+
def remove_invalid_computers_for_deploy(targets, removals)
|
|
1494
|
+
log_debug "Removing invalid computer targets for MDM deployment of title '#{title}', version '#{version}'"
|
|
1495
|
+
|
|
1496
|
+
targets.map! do |c|
|
|
1497
|
+
id = Jamf::Computer.valid_id c, cnx: jamf_cnx
|
|
1498
|
+
if id
|
|
1499
|
+
id
|
|
1500
|
+
else
|
|
1501
|
+
removals << { device: c, group: nil, reason: 'Computer not found in Jamf Pro' }
|
|
1502
|
+
nil
|
|
1503
|
+
end
|
|
1504
|
+
end.compact!
|
|
1505
|
+
end
|
|
1506
|
+
|
|
1507
|
+
# Remove exclusions from the list of targets for deploy_via_mdm
|
|
1508
|
+
#
|
|
1509
|
+
# @param targets [Array<Integer>] The ids of computers to install on.
|
|
1510
|
+
# @param removals [Array<Hash>] The computers that are not valid, for reporting back to the caller
|
|
1511
|
+
#
|
|
1512
|
+
# @return [void]
|
|
1513
|
+
#########################
|
|
1514
|
+
def remove_exclusions_from_deploy(targets, removals)
|
|
1515
|
+
log_debug "Removing excluded computer targets for MDM deployment of title '#{title}', version '#{version}'"
|
|
1516
|
+
|
|
1517
|
+
excluded_groups_to_use.each do |group|
|
|
1518
|
+
gid = Jamf::ComputerGroup.valid_id group, cnx: jamf_cnx
|
|
1519
|
+
unless gid
|
|
1520
|
+
log_error "Jamf: Excluded group '#{group}' not found in Jamf Pro. Skipping."
|
|
1521
|
+
next
|
|
1522
|
+
end # unless gid
|
|
1523
|
+
|
|
1524
|
+
jgroup = Jamf::ComputerGroup.fetch id: gid, cnx: jamf_cnx
|
|
1525
|
+
jgroup.members.each do |member|
|
|
1526
|
+
next unless targets.include? member[:id]
|
|
1527
|
+
|
|
1528
|
+
log_debug "Jamf: Removing computer '#{member[:name]}' (#{member[:id]}) from deployment targets because it is in excluded group '#{group}'"
|
|
1529
|
+
targets.delete member[:id]
|
|
1530
|
+
removals << { device: member[:name], group: nil, reason: "In excluded group '#{group}'" }
|
|
1531
|
+
end
|
|
1532
|
+
end # excluded_groups_to_use.each
|
|
1533
|
+
end
|
|
1534
|
+
|
|
1535
|
+
end # VersionJamfAccess
|
|
1536
|
+
|
|
1537
|
+
end # Mixins
|
|
1538
|
+
|
|
1539
|
+
end # Server
|
|
1540
|
+
|
|
1541
|
+
end # module Xolo
|