xolo-server 2.0.3 → 2.2.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 +4 -4
- data/data/client/xolo +40 -3
- data/data/com.pixar.xoloserver.plist +9 -2
- data/lib/xolo/core/base_classes/title.rb +113 -13
- data/lib/xolo/core/base_classes/version.rb +5 -6
- data/lib/xolo/core/exceptions.rb +3 -1
- data/lib/xolo/core/version.rb +1 -1
- data/lib/xolo/server/app.rb +1 -1
- data/lib/xolo/server/configuration.rb +4 -4
- data/lib/xolo/server/helpers/auth.rb +3 -3
- data/lib/xolo/server/helpers/maintenance.rb +121 -60
- data/lib/xolo/server/helpers/progress_streaming.rb +43 -10
- data/lib/xolo/server/mixins/title_jamf_access.rb +268 -50
- data/lib/xolo/server/mixins/version_jamf_access.rb +83 -34
- data/lib/xolo/server/routes/maint.rb +15 -15
- data/lib/xolo/server/routes/titles.rb +3 -0
- data/lib/xolo/server/routes.rb +1 -1
- data/lib/xolo/server/title.rb +27 -4
- data/lib/xolo/server/version.rb +71 -28
- metadata +2 -2
|
@@ -12,7 +12,10 @@ module Xolo
|
|
|
12
12
|
|
|
13
13
|
module Helpers
|
|
14
14
|
|
|
15
|
-
# Nightly
|
|
15
|
+
# Nightly maintenance tasks
|
|
16
|
+
# - accept TEd EAs
|
|
17
|
+
# - autorelease versions
|
|
18
|
+
# - clean up old versions
|
|
16
19
|
#
|
|
17
20
|
# Also, alerts will be posted, and Emails will be sent to the
|
|
18
21
|
# admins who added versions that have been in pilot for more than
|
|
@@ -29,8 +32,11 @@ module Xolo
|
|
|
29
32
|
# Constants
|
|
30
33
|
#####################################
|
|
31
34
|
|
|
32
|
-
# At what hour should the nightly
|
|
33
|
-
|
|
35
|
+
# At what hour should the nightly maintenance run?
|
|
36
|
+
MAINT_HOUR = 2
|
|
37
|
+
|
|
38
|
+
# the route we POST to, to start the nightly process.
|
|
39
|
+
TIMED_MAINT_TRIGGER_ROUTE = '/maint/maint-internal'
|
|
34
40
|
|
|
35
41
|
# on which day of the month should we send the unreleased pilot notifications?
|
|
36
42
|
UNRELEASED_PILOTS_NOTIFICATION_DAY = 1
|
|
@@ -39,7 +45,7 @@ module Xolo
|
|
|
39
45
|
# be automatically deleted this many days later.
|
|
40
46
|
# If not set in the server config, this is
|
|
41
47
|
# the default value.
|
|
42
|
-
# use 0 or less to disable
|
|
48
|
+
# use 0 or less to disable maintenance of deprecated versions
|
|
43
49
|
DFT_DEPRECATED_LIFETIME_DAYS = 30
|
|
44
50
|
|
|
45
51
|
# If a pilot has not been released in this many
|
|
@@ -55,71 +61,69 @@ module Xolo
|
|
|
55
61
|
# Module Methods
|
|
56
62
|
#####################################
|
|
57
63
|
|
|
58
|
-
# A mutex for the
|
|
59
|
-
#
|
|
60
|
-
# TODO: use Concrrent Ruby instead of Mutex
|
|
64
|
+
# A mutex for the maint process
|
|
61
65
|
#
|
|
62
66
|
# @return [Mutex] the mutex
|
|
63
67
|
#####################
|
|
64
|
-
def self.
|
|
65
|
-
@
|
|
68
|
+
def self.maint_mutex
|
|
69
|
+
@maint_mutex ||= Mutex.new
|
|
66
70
|
end
|
|
67
71
|
|
|
68
|
-
# nightly
|
|
72
|
+
# nightly maint is done by a Concurrent::TimerTask, which checks every
|
|
69
73
|
# hour to see if it should do anything.
|
|
70
74
|
#
|
|
71
|
-
# It will only do the
|
|
72
|
-
# (02:00 - 02:59)
|
|
75
|
+
# It will only do the maint if the current time is in the MAINT_HOUR hour
|
|
76
|
+
# (02:00 - 02:59 if MAINT_HOUR = 2)
|
|
73
77
|
#
|
|
74
|
-
# We trigger the
|
|
78
|
+
# We trigger the maint by POSTing to TIMED_MAINT_TRIGGER_ROUTE, so that it runs
|
|
75
79
|
# in the context of a request, having access to Title and Version instantiation.
|
|
76
80
|
#
|
|
77
81
|
# @return [Concurrent::TimerTask] the timed task to do log rotation
|
|
78
82
|
######################################
|
|
79
|
-
def self.
|
|
80
|
-
return @
|
|
83
|
+
def self.maint_timer_task
|
|
84
|
+
return @maint_timer_task if @maint_timer_task
|
|
81
85
|
|
|
82
|
-
@
|
|
83
|
-
Concurrent::TimerTask.new(execution_interval: 3600) {
|
|
86
|
+
@maint_timer_task =
|
|
87
|
+
Concurrent::TimerTask.new(execution_interval: 3600) { post_to_start_maint }
|
|
84
88
|
|
|
85
|
-
Xolo::Server.logger.info 'Created Concurrent::TimerTask for nightly
|
|
86
|
-
@
|
|
89
|
+
Xolo::Server.logger.info 'Created Concurrent::TimerTask for nightly maintenance.'
|
|
90
|
+
@maint_timer_task
|
|
87
91
|
end
|
|
88
92
|
|
|
89
|
-
# When was our last
|
|
90
|
-
# @return [Time] the time of the last
|
|
93
|
+
# When was our last maint?
|
|
94
|
+
# @return [Time] the time of the last maint, or the epoch if never
|
|
91
95
|
######################################
|
|
92
|
-
def self.
|
|
93
|
-
@
|
|
96
|
+
def self.last_maint
|
|
97
|
+
@last_maint ||= Time.at(0)
|
|
94
98
|
end
|
|
95
99
|
|
|
96
|
-
# Set the time of the last
|
|
97
|
-
# @param time [Time] the time of the last
|
|
98
|
-
# @return [Time] the time of the last
|
|
100
|
+
# Set the time of the last maint
|
|
101
|
+
# @param time [Time] the time of the last maint
|
|
102
|
+
# @return [Time] the time of the last maint
|
|
99
103
|
######################################
|
|
100
|
-
def self.
|
|
101
|
-
@
|
|
104
|
+
def self.last_maint=(time)
|
|
105
|
+
@last_maint = time
|
|
102
106
|
end
|
|
103
107
|
|
|
104
|
-
# post to the server to start the
|
|
105
|
-
# This is done so that the
|
|
108
|
+
# post to the server to start the maint process
|
|
109
|
+
# This is done so that the maint can run in the context of a request,
|
|
106
110
|
# having access to Title and Version instantiation.
|
|
107
111
|
#
|
|
108
|
-
# @param force [Boolean] force the
|
|
112
|
+
# @param force [Boolean] force the maint to run now
|
|
109
113
|
# @return [void]
|
|
110
114
|
######################################
|
|
111
|
-
def self.
|
|
115
|
+
def self.post_to_start_maint(force: false)
|
|
112
116
|
if Xolo::Server.shutting_down?
|
|
113
|
-
Xolo::Server.logger.info 'Not starting
|
|
117
|
+
Xolo::Server.logger.info 'Not starting maintenance, server is shutting down'
|
|
114
118
|
return
|
|
115
119
|
end
|
|
116
120
|
|
|
117
|
-
# only run the
|
|
121
|
+
# only run the maintenance if it's between 2am and 3am
|
|
118
122
|
# and the last one was more than 23 hrs ago
|
|
119
|
-
|
|
120
|
-
return unless force || (Time.now.hour ==
|
|
123
|
+
last_maint_hrs_ago = (Time.now - last_maint) / 3600
|
|
124
|
+
return unless force || (Time.now.hour == MAINT_HOUR && last_maint_hrs_ago > 23)
|
|
121
125
|
|
|
122
|
-
uri = URI.parse "https://#{Xolo::Server::Helpers::Auth::IPV4_LOOPBACK}
|
|
126
|
+
uri = URI.parse "https://#{Xolo::Server::Helpers::Auth::IPV4_LOOPBACK}#{TIMED_MAINT_TRIGGER_ROUTE}"
|
|
123
127
|
https = Net::HTTP.new(uri.host, uri.port)
|
|
124
128
|
https.use_ssl = true
|
|
125
129
|
# The server cert may be self-signed and/or doesn't
|
|
@@ -130,32 +134,33 @@ module Xolo
|
|
|
130
134
|
request['Authorization'] = Xolo::Server::Helpers::Auth.internal_auth_token_header
|
|
131
135
|
|
|
132
136
|
response = https.request(request)
|
|
133
|
-
Xolo::Server.logger.info "
|
|
137
|
+
Xolo::Server.logger.info "Maintenance request response: #{response.code} #{response.body}"
|
|
134
138
|
end
|
|
135
139
|
|
|
136
|
-
#
|
|
140
|
+
# Perform maintenance tasks
|
|
137
141
|
# @return [void]
|
|
138
142
|
################################
|
|
139
|
-
def
|
|
143
|
+
def run_maint
|
|
140
144
|
if Xolo::Server.shutting_down?
|
|
141
|
-
log_info '
|
|
145
|
+
log_info 'Maint: Not starting maint, server is shutting down'
|
|
142
146
|
return
|
|
143
147
|
end
|
|
144
|
-
|
|
145
|
-
mutex = Xolo::Server::Helpers::Maintenance.
|
|
148
|
+
|
|
149
|
+
mutex = Xolo::Server::Helpers::Maintenance.maint_mutex
|
|
146
150
|
|
|
147
151
|
if mutex.locked?
|
|
148
|
-
log_warn '
|
|
152
|
+
log_warn 'Maint: already running, skipping this run'
|
|
149
153
|
return
|
|
150
154
|
end
|
|
151
155
|
mutex.lock
|
|
152
|
-
log_info '
|
|
156
|
+
log_info 'Maint: starting'
|
|
153
157
|
|
|
154
|
-
# add new
|
|
158
|
+
# add new maintenance tasks/methods here
|
|
155
159
|
accept_title_editor_eas
|
|
160
|
+
auto_release_versions
|
|
156
161
|
cleanup_versions
|
|
157
162
|
|
|
158
|
-
log_info '
|
|
163
|
+
log_info 'Maint: complete'
|
|
159
164
|
ensure
|
|
160
165
|
mutex&.unlock
|
|
161
166
|
end
|
|
@@ -166,11 +171,11 @@ module Xolo
|
|
|
166
171
|
######################################
|
|
167
172
|
def accept_title_editor_eas
|
|
168
173
|
unless Xolo::Server.config.jamf_auto_accept_xolo_eas
|
|
169
|
-
log_info '
|
|
174
|
+
log_info 'Maint: The xolo server is not configured to auto-accept Title Editor EAs'
|
|
170
175
|
return
|
|
171
176
|
end
|
|
172
177
|
|
|
173
|
-
log_info '
|
|
178
|
+
log_info 'Maint: Looking for Title Editor EAs to auto-accept'
|
|
174
179
|
|
|
175
180
|
# TODO: Be DRY with this stuff and similar in title_jamf_access.rb
|
|
176
181
|
Xolo::Server::Title.all_titles.each do |title|
|
|
@@ -178,20 +183,76 @@ module Xolo
|
|
|
178
183
|
next if title_obj.subscribed?
|
|
179
184
|
next unless title_obj.jamf_patch_ea_awaiting_acceptance?
|
|
180
185
|
|
|
181
|
-
log_info "
|
|
186
|
+
log_info "Maint: Auto-accepting Title Editor EA for title '#{title}'"
|
|
182
187
|
title_obj.accept_jamf_patch_ea_via_api
|
|
183
188
|
rescue => e
|
|
184
|
-
log_error "
|
|
189
|
+
log_error "Maint: Error auto-accepting Title Editor EA for title '#{title}': #{e}"
|
|
185
190
|
end # Xolo::Server::Title.all_titles.each
|
|
186
191
|
|
|
187
|
-
log_info '
|
|
192
|
+
log_info 'Maint: Done with Title Editor EAs to auto-accept'
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Do any pending auto-releases
|
|
196
|
+
# @return [void]
|
|
197
|
+
##############################
|
|
198
|
+
def auto_release_versions
|
|
199
|
+
log_info 'Maint: Auto-releasing appropriate versions'
|
|
200
|
+
|
|
201
|
+
today = Date.today
|
|
202
|
+
|
|
203
|
+
# Loop thru the titles
|
|
204
|
+
Xolo::Server::Title.all_titles.each do |title|
|
|
205
|
+
title_obj = instantiate_title title
|
|
206
|
+
|
|
207
|
+
# title must be subscribed and autopkg
|
|
208
|
+
# (enforced in xadm)
|
|
209
|
+
next unless title_obj.subscribed? && !title_obj.autopkg_recipe.pix_empty?
|
|
210
|
+
|
|
211
|
+
# title must have a non-negative integer for auto_release_delay,
|
|
212
|
+
# (number of days to wait before release)
|
|
213
|
+
# could be zero. (enforced in xadm)
|
|
214
|
+
# NOTE: it is stored as a string because it might be 'none'
|
|
215
|
+
# TODO: store none as nil, so we don't have to do the pix_integer check??
|
|
216
|
+
delay = title_obj.auto_release_delay.to_i
|
|
217
|
+
next unless title_obj.auto_release_delay.pix_integer? && !delay.negative?
|
|
218
|
+
|
|
219
|
+
# Any version with this creation date should be released now, skip if not
|
|
220
|
+
# So if the auto_release_delay is 7, its today - 7 days.
|
|
221
|
+
# if the auto_release_delay is 0, its today - 0 days, aka today
|
|
222
|
+
creation_date_to_be_released_today = today - delay
|
|
223
|
+
|
|
224
|
+
# Find the newest pilot version whose creation date is creation_date_to_be_released_today
|
|
225
|
+
# That one should be released now.
|
|
226
|
+
vobj_to_release = nil
|
|
227
|
+
title_obj.version_objects.each do |vobj|
|
|
228
|
+
next unless vobj.pilot?
|
|
229
|
+
next unless vobj.creation_date.to_date == creation_date_to_be_released_today
|
|
230
|
+
|
|
231
|
+
vobj_to_release = vobj
|
|
232
|
+
break
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# no versions to release today
|
|
236
|
+
next unless vobj_to_release
|
|
237
|
+
|
|
238
|
+
# release it
|
|
239
|
+
log_debug "Maint: About to auto-release version '#{vobj_to_release.version}' of '#{title}' which came out #{vobj_to_release.creation_date}"
|
|
240
|
+
|
|
241
|
+
# releasing a version is done via the title obj, since it affects the status
|
|
242
|
+
# of all versions.
|
|
243
|
+
title_obj.release vobj_to_release.version
|
|
244
|
+
|
|
245
|
+
log_info "Maint: Auto-released version '#{vobj_to_release.version}' of '#{title}' which came out #{vobj_to_release.creation_date}", alert: true
|
|
246
|
+
end # each title
|
|
247
|
+
|
|
248
|
+
log_debug 'Maint: Done auto-releasing'
|
|
188
249
|
end
|
|
189
250
|
|
|
190
251
|
# Cleanup versions.
|
|
191
252
|
# @return [void]
|
|
192
253
|
################################
|
|
193
254
|
def cleanup_versions
|
|
194
|
-
log_info '
|
|
255
|
+
log_info 'Maint: cleaning up deprecated and skipped versions'
|
|
195
256
|
|
|
196
257
|
Xolo::Server::Title.all_titles.each do |title|
|
|
197
258
|
title_obj = instantiate_title title
|
|
@@ -207,8 +268,8 @@ module Xolo
|
|
|
207
268
|
notify_admins_of_unreleased_pilots(title_obj)
|
|
208
269
|
end # each title
|
|
209
270
|
|
|
210
|
-
Xolo::Server::Helpers::Maintenance.
|
|
211
|
-
log_info '
|
|
271
|
+
Xolo::Server::Helpers::Maintenance.last_maint = Time.now
|
|
272
|
+
log_info 'Maint: versions cleanup complete'
|
|
212
273
|
end
|
|
213
274
|
|
|
214
275
|
# Cleanup a deprecated version.
|
|
@@ -291,7 +352,7 @@ module Xolo
|
|
|
291
352
|
|
|
292
353
|
progress "Server Shutdown by #{session[:admin]}", log: :info
|
|
293
354
|
|
|
294
|
-
|
|
355
|
+
stop_maint_timer_task
|
|
295
356
|
stop_log_rotation_timer_task
|
|
296
357
|
shutdown_pkg_deletion_pool
|
|
297
358
|
wait_for_object_locks
|
|
@@ -316,12 +377,12 @@ module Xolo
|
|
|
316
377
|
system "/bin/launchctl unload #{SERVER_LAUNCHD_PLIST}"
|
|
317
378
|
end
|
|
318
379
|
|
|
319
|
-
# Stop the
|
|
380
|
+
# Stop the maintenance timer task
|
|
320
381
|
# @return [void]
|
|
321
382
|
################################
|
|
322
|
-
def
|
|
323
|
-
progress 'Stopping the
|
|
324
|
-
Xolo::Server::Helpers::Maintenance.
|
|
383
|
+
def stop_maint_timer_task
|
|
384
|
+
progress 'Stopping the maint timer task', log: :info
|
|
385
|
+
Xolo::Server::Helpers::Maintenance.maint_timer_task.shutdown
|
|
325
386
|
end
|
|
326
387
|
|
|
327
388
|
# Stop the log rotation timer task
|
|
@@ -174,16 +174,49 @@ module Xolo
|
|
|
174
174
|
def stream_progress(stream_file:, stream:)
|
|
175
175
|
log_debug "About to tail: usr/bin/tail -f -c +1 #{Shellwords.escape stream_file.to_s}"
|
|
176
176
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
177
|
+
begin
|
|
178
|
+
File.open(stream_file, 'r') do |file|
|
|
179
|
+
# Go straight to the end of the file if you only want new lines
|
|
180
|
+
file.seek(0, IO::SEEK_END)
|
|
181
|
+
|
|
182
|
+
heartbeat_counter = 0
|
|
183
|
+
loop do
|
|
184
|
+
# Check if the stream was closed by the user
|
|
185
|
+
break if stream.closed?
|
|
186
|
+
|
|
187
|
+
# Try to read the next line
|
|
188
|
+
# This won't block but will return nil if no line is ready
|
|
189
|
+
if line = file.gets
|
|
190
|
+
break if line.chomp == Xolo::Server::PROGRESS_COMPLETE
|
|
191
|
+
|
|
192
|
+
# Send the line to the browser immediately
|
|
193
|
+
stream << line
|
|
194
|
+
else
|
|
195
|
+
# If no new line, sleep for a fraction of a second
|
|
196
|
+
# This saves your computer's CPU from working too hard
|
|
197
|
+
sleep 0.1
|
|
198
|
+
heartbeat_counter += 1
|
|
199
|
+
if heartbeat_counter == 100 # every 10 secs
|
|
200
|
+
stream << ':heartbeat'
|
|
201
|
+
heartbeat_counter = 0
|
|
202
|
+
end
|
|
203
|
+
end # if
|
|
204
|
+
end # loop
|
|
205
|
+
end # file open
|
|
206
|
+
rescue IOError, Errno::ECONNRESET
|
|
207
|
+
nil
|
|
208
|
+
end # begin
|
|
209
|
+
|
|
210
|
+
# stdin, stdouterr, wait_thr = Open3.popen2e("/usr/bin/tail -f -c +1 #{Shellwords.escape stream_file.to_s}")
|
|
211
|
+
# stdin.close
|
|
212
|
+
|
|
213
|
+
# while line = stdouterr.gets
|
|
214
|
+
# break if line.chomp == Xolo::Server::PROGRESS_COMPLETE
|
|
215
|
+
|
|
216
|
+
# stream << line
|
|
217
|
+
# end
|
|
218
|
+
# stdouterr.close
|
|
219
|
+
# wait_thr.exit
|
|
187
220
|
# TODO: deal with wait_thr.value.exitstatus > 0 ?
|
|
188
221
|
end
|
|
189
222
|
|