xolo-admin 1.0.1 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bin/xadm +3 -0
- data/data/client/xolo +182 -82
- data/lib/xolo/admin/command_line.rb +18 -5
- data/lib/xolo/admin/connection.rb +4 -2
- data/lib/xolo/admin/credentials.rb +94 -50
- data/lib/xolo/admin/highline_terminal.rb +14 -47
- data/lib/xolo/admin/interactive.rb +144 -15
- data/lib/xolo/admin/jamf_pro.rb +14 -0
- data/lib/xolo/admin/options.rb +37 -3
- data/lib/xolo/admin/processing.rb +79 -7
- data/lib/xolo/admin/progress_history.rb +1 -1
- data/lib/xolo/admin/title.rb +35 -25
- data/lib/xolo/admin/validate.rb +219 -41
- data/lib/xolo/admin/version.rb +56 -19
- data/lib/xolo/core/base_classes/title.rb +261 -20
- data/lib/xolo/core/base_classes/version.rb +53 -8
- data/lib/xolo/core/constants.rb +7 -3
- data/lib/xolo/core/security_cmd.rb +128 -0
- data/lib/xolo/core/version.rb +1 -1
- data/lib/xolo/core.rb +1 -0
- metadata +4 -9
|
@@ -26,7 +26,7 @@ module Xolo
|
|
|
26
26
|
# Routes for server admins
|
|
27
27
|
|
|
28
28
|
SERVER_STATUS_ROUTE = '/maint/state'
|
|
29
|
-
|
|
29
|
+
SERVER_CLEANUP_ROUTE = '/maint/cleanup'
|
|
30
30
|
SERVER_ROTATE_LOGS_ROUTE = '/maint/rotate-logs'
|
|
31
31
|
SERVER_UPDATE_CLIENT_DATA_ROUTE = '/maint/update-client-data'
|
|
32
32
|
SERVER_LOG_LEVEL_ROUTE = '/maint/set-log-level'
|
|
@@ -72,6 +72,14 @@ module Xolo
|
|
|
72
72
|
puts msg unless quiet?
|
|
73
73
|
end
|
|
74
74
|
|
|
75
|
+
# TODO: Use this everywhere in admin
|
|
76
|
+
##########################
|
|
77
|
+
def show_debug(msg)
|
|
78
|
+
return unless debug?
|
|
79
|
+
|
|
80
|
+
puts "DEBUG: #{msg}"
|
|
81
|
+
end
|
|
82
|
+
|
|
75
83
|
# Search for a title in Xolo
|
|
76
84
|
# Looks for the search string (case insensitive) in these attributes:
|
|
77
85
|
# - title
|
|
@@ -234,19 +242,30 @@ module Xolo
|
|
|
234
242
|
|
|
235
243
|
new_title = Xolo::Admin::Title.new opts_to_process
|
|
236
244
|
|
|
245
|
+
# TMP TESTING
|
|
246
|
+
# puts 'DEBUG: opts_to_process:'
|
|
247
|
+
# opts_to_process.to_h.each do |k, v|
|
|
248
|
+
# puts " #{k}: #{v}"
|
|
249
|
+
# end
|
|
250
|
+
# return
|
|
251
|
+
|
|
237
252
|
response_data = new_title.add(server_cnx)
|
|
238
253
|
|
|
239
254
|
if debug?
|
|
240
255
|
puts "DEBUG: response_data: #{response_data}"
|
|
241
256
|
puts
|
|
242
257
|
end
|
|
243
|
-
|
|
244
|
-
|
|
258
|
+
if response_data[:progress_stream_url_path]
|
|
259
|
+
display_progress response_data[:progress_stream_url_path]
|
|
260
|
+
else
|
|
261
|
+
puts '# No progress stream URL returned'
|
|
262
|
+
return
|
|
263
|
+
end
|
|
245
264
|
|
|
246
265
|
# Upload the ssvc icon, if any?
|
|
247
266
|
upload_ssvc_icon new_title
|
|
248
267
|
|
|
249
|
-
speak "Title '#{cli_cmd.title}' has been added to Xolo.\
|
|
268
|
+
speak "Title '#{cli_cmd.title}' has been added to Xolo.\nEnsure there's at least one version to enable piloting and deployment"
|
|
250
269
|
rescue StandardError => e
|
|
251
270
|
handle_processing_error e
|
|
252
271
|
end
|
|
@@ -307,9 +326,26 @@ module Xolo
|
|
|
307
326
|
def upload_ssvc_icon(title)
|
|
308
327
|
return unless title.self_service_icon.is_a? Pathname
|
|
309
328
|
|
|
329
|
+
speak 'Pausing to let jamf server catch up before uploading self-service icon...'
|
|
330
|
+
sleep 30
|
|
310
331
|
speak "Uploading self-service icon #{title.self_service_icon.basename}, #{title.self_service_icon.pix_humanize_size} to Xolo."
|
|
311
332
|
|
|
312
|
-
|
|
333
|
+
wait_for_title_timer = 0
|
|
334
|
+
begin
|
|
335
|
+
title.upload_self_service_icon(upload_cnx)
|
|
336
|
+
rescue Xolo::ConnectionError => e
|
|
337
|
+
speak "Error uploading self-service icon: #{e.message}, trying again in 5 secs"
|
|
338
|
+
# If we get a 404, it might be because the title isn't fully created yet.
|
|
339
|
+
# Wait a bit and try again, up to a certain number of seconds.
|
|
340
|
+
if wait_for_title_timer < 90
|
|
341
|
+
sleep 5
|
|
342
|
+
wait_for_title_timer += 5
|
|
343
|
+
retry
|
|
344
|
+
else
|
|
345
|
+
msg = "Failed to upload icon for Title '#{title}' after retrying for #{wait_for_title_timer} seconds."
|
|
346
|
+
raise Xolo::NoSuchItemError, msg
|
|
347
|
+
end # if
|
|
348
|
+
end # begin
|
|
313
349
|
|
|
314
350
|
speak 'Self-service icon uploaded. Will be added to Self Service policies as needed'
|
|
315
351
|
rescue StandardError => e
|
|
@@ -478,6 +514,7 @@ module Xolo
|
|
|
478
514
|
opts_to_process.title = cli_cmd.title
|
|
479
515
|
opts_to_process.version = cli_cmd.version
|
|
480
516
|
|
|
517
|
+
title_obj = Xolo::Admin::Title.fetch cli_cmd.title, server_cnx
|
|
481
518
|
new_vers = Xolo::Admin::Version.new opts_to_process
|
|
482
519
|
response_data = new_vers.add(server_cnx)
|
|
483
520
|
|
|
@@ -489,7 +526,8 @@ module Xolo
|
|
|
489
526
|
display_progress response_data[:progress_stream_url_path]
|
|
490
527
|
|
|
491
528
|
# Upload the pkg, if any?
|
|
492
|
-
upload_pkg(new_vers)
|
|
529
|
+
upload_pkg(new_vers) unless title_obj.autopkg_enabled?
|
|
530
|
+
|
|
493
531
|
speak 'It can take up to 15 minutes for the version to be available via Jamf and Self Service.'
|
|
494
532
|
rescue StandardError => e
|
|
495
533
|
handle_processing_error e
|
|
@@ -510,6 +548,8 @@ module Xolo
|
|
|
510
548
|
def upload_pkg(version)
|
|
511
549
|
return unless version.pkg_to_upload.is_a? Pathname
|
|
512
550
|
|
|
551
|
+
# TODO: deal with autopkg pkgs, should we be able to do this?
|
|
552
|
+
|
|
513
553
|
speak "Uploading #{version.pkg_to_upload.basename}, #{version.pkg_to_upload.pix_humanize_size} to Xolo"
|
|
514
554
|
# start the upload in a thread
|
|
515
555
|
upload_thr = Thread.new { version.upload_pkg(upload_cnx) }
|
|
@@ -710,6 +750,9 @@ module Xolo
|
|
|
710
750
|
# description is handled specially below
|
|
711
751
|
next if attr == :description
|
|
712
752
|
|
|
753
|
+
# only show patch_source and title_id if subscribed
|
|
754
|
+
next if %i[patch_source title_id].include?(attr) && !title.subscribed?
|
|
755
|
+
|
|
713
756
|
value = title.send attr
|
|
714
757
|
value = value.join(Xolo::COMMA_JOIN) if value.is_a? Array
|
|
715
758
|
puts "- #{deets[:label]}: #{value}".pix_word_wrap
|
|
@@ -941,7 +984,7 @@ module Xolo
|
|
|
941
984
|
def server_cleanup
|
|
942
985
|
return unless confirmed? 'Run the Xolo Server cleanup process'
|
|
943
986
|
|
|
944
|
-
result = server_cnx.post(
|
|
987
|
+
result = server_cnx.post(SERVER_CLEANUP_ROUTE).body
|
|
945
988
|
puts result[:result]
|
|
946
989
|
rescue StandardError => e
|
|
947
990
|
handle_processing_error e
|
|
@@ -1036,6 +1079,35 @@ module Xolo
|
|
|
1036
1079
|
list_in_cols header, jamf_computer_group_names.sort_by(&:downcase)
|
|
1037
1080
|
end
|
|
1038
1081
|
|
|
1082
|
+
# List Available titles for subscription
|
|
1083
|
+
#
|
|
1084
|
+
# @return [void]
|
|
1085
|
+
#############################
|
|
1086
|
+
def list_available_titles
|
|
1087
|
+
data = jamf_available_titles.sort_by { |t| t[:app_name].downcase }
|
|
1088
|
+
data.map! do |t|
|
|
1089
|
+
{
|
|
1090
|
+
display_name: t[:app_name],
|
|
1091
|
+
publisher: t[:publisher],
|
|
1092
|
+
patch_source: t[:source_name],
|
|
1093
|
+
title_id: t[:name_id],
|
|
1094
|
+
current_version: t[:current_version],
|
|
1095
|
+
last_modified: t[:last_modified]
|
|
1096
|
+
}
|
|
1097
|
+
end
|
|
1098
|
+
|
|
1099
|
+
if json?
|
|
1100
|
+
puts JSON.pretty_generate(data)
|
|
1101
|
+
return
|
|
1102
|
+
end
|
|
1103
|
+
|
|
1104
|
+
title = 'Titles Available for Subscription'
|
|
1105
|
+
header = %w[DisplayName Publisher PatchSource TitleID]
|
|
1106
|
+
lines = data.map { |t| [t[:display_name], t[:publisher], t[:patch_source], t[:title_id]] }
|
|
1107
|
+
|
|
1108
|
+
show_text generate_report(lines, header_row: header, title: title)
|
|
1109
|
+
end
|
|
1110
|
+
|
|
1039
1111
|
# List all the SSVC categories in jamf pro
|
|
1040
1112
|
#
|
|
1041
1113
|
# @return [void]
|
|
@@ -85,7 +85,7 @@ module Xolo
|
|
|
85
85
|
###################
|
|
86
86
|
def progress_history
|
|
87
87
|
progress_history_file.pix_touch
|
|
88
|
-
history = YAML.
|
|
88
|
+
history = YAML.safe_load progress_history_file.read, permitted_classes: [Symbol, Time]
|
|
89
89
|
history ||= {}
|
|
90
90
|
|
|
91
91
|
now = Time.now
|
data/lib/xolo/admin/title.rb
CHANGED
|
@@ -43,10 +43,17 @@ module Xolo
|
|
|
43
43
|
#############################
|
|
44
44
|
|
|
45
45
|
# @return [Hash{Symbol: Hash}] The ATTRIBUTES that are available as CLI & walkthru options
|
|
46
|
+
##############################
|
|
46
47
|
def self.cli_opts
|
|
47
48
|
@cli_opts ||= ATTRIBUTES.select { |_k, v| v[:cli] }
|
|
48
49
|
end
|
|
49
50
|
|
|
51
|
+
# @return [Hash{Symbol: Hash}] The ATTRIBUTES that are available as opts for the subscribe command
|
|
52
|
+
################################
|
|
53
|
+
# def self.subscribe_cli_opts
|
|
54
|
+
# @subscribe_cli_opts ||= ATTRIBUTES.select { |_k, v| v[:cli_subscribe] }
|
|
55
|
+
# end
|
|
56
|
+
|
|
50
57
|
# @return [Array<String>] The currently known titles names on the server
|
|
51
58
|
#############################
|
|
52
59
|
def self.all_titles(cnx)
|
|
@@ -77,8 +84,9 @@ module Xolo
|
|
|
77
84
|
####################
|
|
78
85
|
def self.fetch(title, cnx)
|
|
79
86
|
resp = cnx.get "#{SERVER_ROUTE}/#{title}"
|
|
80
|
-
|
|
81
|
-
|
|
87
|
+
title_obj = new resp.body
|
|
88
|
+
title_obj.cnx = cnx
|
|
89
|
+
title_obj
|
|
82
90
|
rescue Faraday::ResourceNotFound
|
|
83
91
|
raise Xolo::NoSuchItemError, "No such title '#{title}'"
|
|
84
92
|
end
|
|
@@ -115,6 +123,8 @@ module Xolo
|
|
|
115
123
|
# Attributes
|
|
116
124
|
######################
|
|
117
125
|
######################
|
|
126
|
+
# the server connection used to fetch this version
|
|
127
|
+
attr_accessor :cnx
|
|
118
128
|
|
|
119
129
|
# Constructor
|
|
120
130
|
######################
|
|
@@ -139,7 +149,7 @@ module Xolo
|
|
|
139
149
|
# @param cnx [Faraday::Connection] The connection to use, must be logged in already
|
|
140
150
|
# @return [Hash] the response body from the server
|
|
141
151
|
####################
|
|
142
|
-
def add(cnx)
|
|
152
|
+
def add(cnx = self.cnx)
|
|
143
153
|
resp = cnx.post SERVER_ROUTE, to_h
|
|
144
154
|
resp.body
|
|
145
155
|
end
|
|
@@ -148,7 +158,7 @@ module Xolo
|
|
|
148
158
|
# @param cnx [Faraday::Connection] The connection to use, must be logged in already
|
|
149
159
|
# @return [Hash] the response body from the server
|
|
150
160
|
####################
|
|
151
|
-
def update(cnx)
|
|
161
|
+
def update(cnx = self.cnx)
|
|
152
162
|
resp = cnx.put "#{SERVER_ROUTE}/#{title}", to_h
|
|
153
163
|
resp.body
|
|
154
164
|
end
|
|
@@ -158,8 +168,11 @@ module Xolo
|
|
|
158
168
|
# @param version [String] the version to release
|
|
159
169
|
# @return [Hash] the response body from the server
|
|
160
170
|
####################
|
|
161
|
-
def release(cnx, version:)
|
|
162
|
-
resp = cnx.patch "#{SERVER_ROUTE}/#{title}/release/#{version}", {}
|
|
171
|
+
def release(cnx = self.cnx, version:)
|
|
172
|
+
resp = cnx.patch "#{SERVER_ROUTE}/#{title}/release/#{URI::Parser.new.escape version}", {}
|
|
173
|
+
### the CGI escape one doesn't seem to work with, e.g. "7.1.5 (84650)"
|
|
174
|
+
# resp = cnx.patch "#{SERVER_ROUTE}/#{title}/release/#{CGI.escape version}", {}
|
|
175
|
+
|
|
163
176
|
resp.body
|
|
164
177
|
end
|
|
165
178
|
|
|
@@ -168,7 +181,7 @@ module Xolo
|
|
|
168
181
|
# @param versions [Boolean] if true, repair all versions of this title
|
|
169
182
|
# @return [Hash] the response body from the server
|
|
170
183
|
####################
|
|
171
|
-
def repair(cnx, versions: false)
|
|
184
|
+
def repair(cnx = self.cnx, versions: false)
|
|
172
185
|
resp = cnx.post "#{SERVER_ROUTE}/#{title}/repair", { repair_versions: versions }
|
|
173
186
|
resp.body
|
|
174
187
|
end
|
|
@@ -177,7 +190,7 @@ module Xolo
|
|
|
177
190
|
# @param cnx [Faraday::Connection] The connection to use, must be logged in already
|
|
178
191
|
# @return [Hash] the response data
|
|
179
192
|
####################
|
|
180
|
-
def delete(cnx)
|
|
193
|
+
def delete(cnx = self.cnx)
|
|
181
194
|
self.class.delete title, cnx
|
|
182
195
|
end
|
|
183
196
|
|
|
@@ -186,7 +199,7 @@ module Xolo
|
|
|
186
199
|
# @param cnx [Faraday::Connection] The connection to use, must be logged in already
|
|
187
200
|
# @return [Hash] the response data
|
|
188
201
|
####################
|
|
189
|
-
def freeze(targets, users = false, cnx)
|
|
202
|
+
def freeze(targets, users = false, cnx = self.cnx)
|
|
190
203
|
data = { targets: targets, users: users }
|
|
191
204
|
resp = cnx.put "#{SERVER_ROUTE}/#{title}/freeze", data
|
|
192
205
|
resp.body
|
|
@@ -197,7 +210,7 @@ module Xolo
|
|
|
197
210
|
# @param cnx [Faraday::Connection] The connection to use, must be logged in already
|
|
198
211
|
# @return [Hash] the response data
|
|
199
212
|
####################
|
|
200
|
-
def thaw(targets, users = false, cnx)
|
|
213
|
+
def thaw(targets, users = false, cnx = self.cnx)
|
|
201
214
|
data = { targets: targets, users: users }
|
|
202
215
|
resp = cnx.put "#{SERVER_ROUTE}/#{title}/thaw", data
|
|
203
216
|
resp.body
|
|
@@ -207,7 +220,7 @@ module Xolo
|
|
|
207
220
|
# @param cnx [Faraday::Connection] The connection to use, must be logged in already
|
|
208
221
|
# @return [Hash{String => String}] computer name => user name
|
|
209
222
|
####################
|
|
210
|
-
def frozen(cnx)
|
|
223
|
+
def frozen(cnx = self.cnx)
|
|
211
224
|
resp = cnx.get "#{SERVER_ROUTE}/#{title}/frozen"
|
|
212
225
|
resp.body
|
|
213
226
|
end
|
|
@@ -216,7 +229,7 @@ module Xolo
|
|
|
216
229
|
# @param cnx [Faraday::Connection] The connection to use, must be logged in already
|
|
217
230
|
# @return [Hash{String => String}] page_name => url
|
|
218
231
|
####################
|
|
219
|
-
def gui_urls(cnx)
|
|
232
|
+
def gui_urls(cnx = self.cnx)
|
|
220
233
|
resp = cnx.get "#{SERVER_ROUTE}/#{title}/urls"
|
|
221
234
|
resp.body
|
|
222
235
|
end
|
|
@@ -227,7 +240,7 @@ module Xolo
|
|
|
227
240
|
# @param cnx [Faraday::Connection] The connection to use, must be logged in already
|
|
228
241
|
# @return [Array<Hash>] The change log for this title
|
|
229
242
|
####################
|
|
230
|
-
def changelog(cnx)
|
|
243
|
+
def changelog(cnx = self.cnx)
|
|
231
244
|
resp = cnx.get "#{SERVER_ROUTE}/#{title}/changelog"
|
|
232
245
|
resp.body
|
|
233
246
|
end
|
|
@@ -248,19 +261,16 @@ module Xolo
|
|
|
248
261
|
"Can't upload self service icon '#{self_service_icon}': file doesn't exist or is not readable"
|
|
249
262
|
end
|
|
250
263
|
|
|
251
|
-
# upfile = Faraday::UploadIO.new(
|
|
252
|
-
# self_service_icon.to_s,
|
|
253
|
-
# 'application/octet-stream',
|
|
254
|
-
# self_service_icon.basename.to_s
|
|
255
|
-
# )
|
|
256
|
-
|
|
257
264
|
mimetype = `/usr/bin/file --brief --mime-type #{Shellwords.escape self_service_icon.expand_path.to_s}`.chomp
|
|
258
265
|
upfile = Faraday::Multipart::FilePart.new(self_service_icon.expand_path.to_s, mimetype)
|
|
259
266
|
content = { file: upfile }
|
|
260
|
-
# route = "#{UPLOAD_ICON_ROUTE}/#{title}"
|
|
261
|
-
route = "#{SERVER_ROUTE}/#{title}/#{UPLOAD_ICON_ROUTE}"
|
|
262
267
|
|
|
268
|
+
route = "#{SERVER_ROUTE}/#{title}/#{UPLOAD_ICON_ROUTE}"
|
|
263
269
|
upload_cnx.post(route) { |req| req.body = content }
|
|
270
|
+
rescue Xolo::NoSuchItemError
|
|
271
|
+
raise
|
|
272
|
+
rescue StandardError => e
|
|
273
|
+
raise Xolo::ConnectionError, "#{e.class}: #{e.message}"
|
|
264
274
|
end
|
|
265
275
|
|
|
266
276
|
# Get the patch report data for this title
|
|
@@ -269,16 +279,16 @@ module Xolo
|
|
|
269
279
|
# @param cnx [Faraday::Connection] The connection to use, must be logged in already
|
|
270
280
|
# @return [Array<Hash>] Data for each computer with any version of this title installed
|
|
271
281
|
##################################
|
|
272
|
-
def patch_report_data(cnx)
|
|
282
|
+
def patch_report_data(cnx = self.cnx)
|
|
273
283
|
resp = cnx.get "#{SERVER_ROUTE}/#{title}/patch_report"
|
|
274
284
|
resp.body
|
|
275
285
|
end
|
|
276
286
|
|
|
277
287
|
# Add more data to our hash
|
|
278
288
|
###########################
|
|
279
|
-
def to_h
|
|
280
|
-
|
|
281
|
-
end
|
|
289
|
+
# def to_h
|
|
290
|
+
# super
|
|
291
|
+
# end
|
|
282
292
|
|
|
283
293
|
end # class Title
|
|
284
294
|
|