xolo-admin 1.0.1 → 2.0.2
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 +152 -79
- 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 +76 -7
- data/lib/xolo/admin/progress_history.rb +1 -1
- data/lib/xolo/admin/title.rb +31 -24
- data/lib/xolo/admin/validate.rb +219 -41
- data/lib/xolo/admin/version.rb +53 -18
- data/lib/xolo/core/base_classes/title.rb +254 -18
- data/lib/xolo/core/base_classes/version.rb +47 -7
- 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) }
|
|
@@ -941,7 +981,7 @@ module Xolo
|
|
|
941
981
|
def server_cleanup
|
|
942
982
|
return unless confirmed? 'Run the Xolo Server cleanup process'
|
|
943
983
|
|
|
944
|
-
result = server_cnx.post(
|
|
984
|
+
result = server_cnx.post(SERVER_CLEANUP_ROUTE).body
|
|
945
985
|
puts result[:result]
|
|
946
986
|
rescue StandardError => e
|
|
947
987
|
handle_processing_error e
|
|
@@ -1036,6 +1076,35 @@ module Xolo
|
|
|
1036
1076
|
list_in_cols header, jamf_computer_group_names.sort_by(&:downcase)
|
|
1037
1077
|
end
|
|
1038
1078
|
|
|
1079
|
+
# List Available titles for subscription
|
|
1080
|
+
#
|
|
1081
|
+
# @return [void]
|
|
1082
|
+
#############################
|
|
1083
|
+
def list_available_titles
|
|
1084
|
+
data = jamf_available_titles.sort_by { |t| t[:app_name].downcase }
|
|
1085
|
+
data.map! do |t|
|
|
1086
|
+
{
|
|
1087
|
+
display_name: t[:app_name],
|
|
1088
|
+
publisher: t[:publisher],
|
|
1089
|
+
patch_source: t[:source_name],
|
|
1090
|
+
title_id: t[:name_id],
|
|
1091
|
+
current_version: t[:current_version],
|
|
1092
|
+
last_modified: t[:last_modified]
|
|
1093
|
+
}
|
|
1094
|
+
end
|
|
1095
|
+
|
|
1096
|
+
if json?
|
|
1097
|
+
puts JSON.pretty_generate(data)
|
|
1098
|
+
return
|
|
1099
|
+
end
|
|
1100
|
+
|
|
1101
|
+
title = 'Titles Available for Subscription'
|
|
1102
|
+
header = %w[DisplayName Publisher PatchSource TitleID]
|
|
1103
|
+
lines = data.map { |t| [t[:display_name], t[:publisher], t[:patch_source], t[:title_id]] }
|
|
1104
|
+
|
|
1105
|
+
show_text generate_report(lines, header_row: header, title: title)
|
|
1106
|
+
end
|
|
1107
|
+
|
|
1039
1108
|
# List all the SSVC categories in jamf pro
|
|
1040
1109
|
#
|
|
1041
1110
|
# @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,7 +168,7 @@ 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:)
|
|
171
|
+
def release(cnx = self.cnx, version:)
|
|
162
172
|
resp = cnx.patch "#{SERVER_ROUTE}/#{title}/release/#{version}", {}
|
|
163
173
|
resp.body
|
|
164
174
|
end
|
|
@@ -168,7 +178,7 @@ module Xolo
|
|
|
168
178
|
# @param versions [Boolean] if true, repair all versions of this title
|
|
169
179
|
# @return [Hash] the response body from the server
|
|
170
180
|
####################
|
|
171
|
-
def repair(cnx, versions: false)
|
|
181
|
+
def repair(cnx = self.cnx, versions: false)
|
|
172
182
|
resp = cnx.post "#{SERVER_ROUTE}/#{title}/repair", { repair_versions: versions }
|
|
173
183
|
resp.body
|
|
174
184
|
end
|
|
@@ -177,7 +187,7 @@ module Xolo
|
|
|
177
187
|
# @param cnx [Faraday::Connection] The connection to use, must be logged in already
|
|
178
188
|
# @return [Hash] the response data
|
|
179
189
|
####################
|
|
180
|
-
def delete(cnx)
|
|
190
|
+
def delete(cnx = self.cnx)
|
|
181
191
|
self.class.delete title, cnx
|
|
182
192
|
end
|
|
183
193
|
|
|
@@ -186,7 +196,7 @@ module Xolo
|
|
|
186
196
|
# @param cnx [Faraday::Connection] The connection to use, must be logged in already
|
|
187
197
|
# @return [Hash] the response data
|
|
188
198
|
####################
|
|
189
|
-
def freeze(targets, users = false, cnx)
|
|
199
|
+
def freeze(targets, users = false, cnx = self.cnx)
|
|
190
200
|
data = { targets: targets, users: users }
|
|
191
201
|
resp = cnx.put "#{SERVER_ROUTE}/#{title}/freeze", data
|
|
192
202
|
resp.body
|
|
@@ -197,7 +207,7 @@ module Xolo
|
|
|
197
207
|
# @param cnx [Faraday::Connection] The connection to use, must be logged in already
|
|
198
208
|
# @return [Hash] the response data
|
|
199
209
|
####################
|
|
200
|
-
def thaw(targets, users = false, cnx)
|
|
210
|
+
def thaw(targets, users = false, cnx = self.cnx)
|
|
201
211
|
data = { targets: targets, users: users }
|
|
202
212
|
resp = cnx.put "#{SERVER_ROUTE}/#{title}/thaw", data
|
|
203
213
|
resp.body
|
|
@@ -207,7 +217,7 @@ module Xolo
|
|
|
207
217
|
# @param cnx [Faraday::Connection] The connection to use, must be logged in already
|
|
208
218
|
# @return [Hash{String => String}] computer name => user name
|
|
209
219
|
####################
|
|
210
|
-
def frozen(cnx)
|
|
220
|
+
def frozen(cnx = self.cnx)
|
|
211
221
|
resp = cnx.get "#{SERVER_ROUTE}/#{title}/frozen"
|
|
212
222
|
resp.body
|
|
213
223
|
end
|
|
@@ -216,7 +226,7 @@ module Xolo
|
|
|
216
226
|
# @param cnx [Faraday::Connection] The connection to use, must be logged in already
|
|
217
227
|
# @return [Hash{String => String}] page_name => url
|
|
218
228
|
####################
|
|
219
|
-
def gui_urls(cnx)
|
|
229
|
+
def gui_urls(cnx = self.cnx)
|
|
220
230
|
resp = cnx.get "#{SERVER_ROUTE}/#{title}/urls"
|
|
221
231
|
resp.body
|
|
222
232
|
end
|
|
@@ -227,7 +237,7 @@ module Xolo
|
|
|
227
237
|
# @param cnx [Faraday::Connection] The connection to use, must be logged in already
|
|
228
238
|
# @return [Array<Hash>] The change log for this title
|
|
229
239
|
####################
|
|
230
|
-
def changelog(cnx)
|
|
240
|
+
def changelog(cnx = self.cnx)
|
|
231
241
|
resp = cnx.get "#{SERVER_ROUTE}/#{title}/changelog"
|
|
232
242
|
resp.body
|
|
233
243
|
end
|
|
@@ -248,19 +258,16 @@ module Xolo
|
|
|
248
258
|
"Can't upload self service icon '#{self_service_icon}': file doesn't exist or is not readable"
|
|
249
259
|
end
|
|
250
260
|
|
|
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
261
|
mimetype = `/usr/bin/file --brief --mime-type #{Shellwords.escape self_service_icon.expand_path.to_s}`.chomp
|
|
258
262
|
upfile = Faraday::Multipart::FilePart.new(self_service_icon.expand_path.to_s, mimetype)
|
|
259
263
|
content = { file: upfile }
|
|
260
|
-
# route = "#{UPLOAD_ICON_ROUTE}/#{title}"
|
|
261
|
-
route = "#{SERVER_ROUTE}/#{title}/#{UPLOAD_ICON_ROUTE}"
|
|
262
264
|
|
|
265
|
+
route = "#{SERVER_ROUTE}/#{title}/#{UPLOAD_ICON_ROUTE}"
|
|
263
266
|
upload_cnx.post(route) { |req| req.body = content }
|
|
267
|
+
rescue Xolo::NoSuchItemError
|
|
268
|
+
raise
|
|
269
|
+
rescue StandardError => e
|
|
270
|
+
raise Xolo::ConnectionError, "#{e.class}: #{e.message}"
|
|
264
271
|
end
|
|
265
272
|
|
|
266
273
|
# Get the patch report data for this title
|
|
@@ -269,16 +276,16 @@ module Xolo
|
|
|
269
276
|
# @param cnx [Faraday::Connection] The connection to use, must be logged in already
|
|
270
277
|
# @return [Array<Hash>] Data for each computer with any version of this title installed
|
|
271
278
|
##################################
|
|
272
|
-
def patch_report_data(cnx)
|
|
279
|
+
def patch_report_data(cnx = self.cnx)
|
|
273
280
|
resp = cnx.get "#{SERVER_ROUTE}/#{title}/patch_report"
|
|
274
281
|
resp.body
|
|
275
282
|
end
|
|
276
283
|
|
|
277
284
|
# Add more data to our hash
|
|
278
285
|
###########################
|
|
279
|
-
def to_h
|
|
280
|
-
|
|
281
|
-
end
|
|
286
|
+
# def to_h
|
|
287
|
+
# super
|
|
288
|
+
# end
|
|
282
289
|
|
|
283
290
|
end # class Title
|
|
284
291
|
|