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.
@@ -12,7 +12,10 @@ module Xolo
12
12
 
13
13
  module Helpers
14
14
 
15
- # Nightly cleanup of deprecated and skipped packages.
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 cleanup run?
33
- CLEANUP_HOUR = 2
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 cleanup of deprecated versions
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 cleanup process
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.cleanup_mutex
65
- @cleanup_mutex ||= Mutex.new
68
+ def self.maint_mutex
69
+ @maint_mutex ||= Mutex.new
66
70
  end
67
71
 
68
- # nightly cleanup is done by a Concurrent::TimerTask, which checks every
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 cleanup if the current time is in the 2am hour
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 cleanup by POSTing to /cleanup, so that it runs
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.cleanup_timer_task
80
- return @cleanup_timer_task if @cleanup_timer_task
83
+ def self.maint_timer_task
84
+ return @maint_timer_task if @maint_timer_task
81
85
 
82
- @cleanup_timer_task =
83
- Concurrent::TimerTask.new(execution_interval: 3600) { post_to_start_cleanup }
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 cleanup.'
86
- @cleanup_timer_task
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 cleanup?
90
- # @return [Time] the time of the last cleanup, or the epoch if never
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.last_cleanup
93
- @last_cleanup ||= Time.at(0)
96
+ def self.last_maint
97
+ @last_maint ||= Time.at(0)
94
98
  end
95
99
 
96
- # Set the time of the last cleanup
97
- # @param time [Time] the time of the last cleanup
98
- # @return [Time] the time of the last cleanup
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.last_cleanup=(time)
101
- @last_cleanup = time
104
+ def self.last_maint=(time)
105
+ @last_maint = time
102
106
  end
103
107
 
104
- # post to the server to start the cleanup process
105
- # This is done so that the cleanup can run in the context of a request,
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 cleanup to run now
112
+ # @param force [Boolean] force the maint to run now
109
113
  # @return [void]
110
114
  ######################################
111
- def self.post_to_start_cleanup(force: false)
115
+ def self.post_to_start_maint(force: false)
112
116
  if Xolo::Server.shutting_down?
113
- Xolo::Server.logger.info 'Not starting cleanup, server is shutting down'
117
+ Xolo::Server.logger.info 'Not starting maintenance, server is shutting down'
114
118
  return
115
119
  end
116
120
 
117
- # only run the cleanup if it's between 2am and 3am
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
- last_cleanup_hrs_ago = (Time.now - last_cleanup) / 3600
120
- return unless force || (Time.now.hour == CLEANUP_HOUR && last_cleanup_hrs_ago > 23)
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}/maint/cleanup-internal"
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 "Cleanup request response: #{response.code} #{response.body}"
137
+ Xolo::Server.logger.info "Maintenance request response: #{response.code} #{response.body}"
134
138
  end
135
139
 
136
- # Cleanup things that need to be cleaned up
140
+ # Perform maintenance tasks
137
141
  # @return [void]
138
142
  ################################
139
- def run_cleanup
143
+ def run_maint
140
144
  if Xolo::Server.shutting_down?
141
- log_info 'Cleanup: Not starting cleanup, server is shutting down'
145
+ log_info 'Maint: Not starting maint, server is shutting down'
142
146
  return
143
147
  end
144
- # TODO: Use Concurrent ruby rather than this instance variable
145
- mutex = Xolo::Server::Helpers::Maintenance.cleanup_mutex
148
+
149
+ mutex = Xolo::Server::Helpers::Maintenance.maint_mutex
146
150
 
147
151
  if mutex.locked?
148
- log_warn 'Cleanup: already running, skipping this run'
152
+ log_warn 'Maint: already running, skipping this run'
149
153
  return
150
154
  end
151
155
  mutex.lock
152
- log_info 'Cleanup: starting'
156
+ log_info 'Maint: starting'
153
157
 
154
- # add new cleanup tasks/methods here
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 'Cleanup: complete'
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 'Cleanup: The xolo server is not configured to auto-accept Title Editor EAs'
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 'Cleanup: Looking for Title Editor EAs to auto-accept'
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 "Cleanup: Auto-accepting Title Editor EA for title '#{title}'"
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 "Cleanup: Error auto-accepting Title Editor EA for title '#{title}': #{e}"
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 'Cleanup: Done with Title Editor EAs to auto-accept'
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 'Cleanup: cleaning up deprecated and skipped versions'
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.last_cleanup = Time.now
211
- log_info 'Cleanup: versions cleanup complete'
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
- stop_cleanup_timer_task
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 cleanup timer task
380
+ # Stop the maintenance timer task
320
381
  # @return [void]
321
382
  ################################
322
- def stop_cleanup_timer_task
323
- progress 'Stopping the cleanup timer task', log: :info
324
- Xolo::Server::Helpers::Maintenance.cleanup_timer_task.shutdown
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
- stdin, stdouterr, wait_thr = Open3.popen2e("/usr/bin/tail -f -c +1 #{Shellwords.escape stream_file.to_s}")
178
- stdin.close
179
-
180
- while line = stdouterr.gets
181
- break if line.chomp == Xolo::Server::PROGRESS_COMPLETE
182
-
183
- stream << line
184
- end
185
- stdouterr.close
186
- wait_thr.exit
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