xolo-admin 1.0.0 → 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/README.md +40 -3
- data/bin/xadm +4 -1
- data/data/client/xolo +1233 -0
- data/lib/optimist_with_insert_blanks.rb +1216 -0
- data/lib/xolo/admin/command_line.rb +18 -5
- data/lib/xolo/admin/configuration.rb +16 -0
- data/lib/xolo/admin/connection.rb +18 -5
- data/lib/xolo/admin/credentials.rb +94 -50
- data/lib/xolo/admin/highline_terminal.rb +14 -47
- data/lib/xolo/admin/interactive.rb +146 -15
- data/lib/xolo/admin/jamf_pro.rb +14 -0
- data/lib/xolo/admin/options.rb +37 -3
- data/lib/xolo/admin/processing.rb +78 -13
- data/lib/xolo/admin/progress_history.rb +1 -1
- data/lib/xolo/admin/title.rb +33 -24
- data/lib/xolo/admin/validate.rb +222 -44
- data/lib/xolo/admin/version.rb +55 -18
- data/lib/xolo/core/base_classes/configuration.rb +238 -0
- data/lib/xolo/core/base_classes/server_object.rb +112 -0
- data/lib/xolo/core/base_classes/title.rb +884 -0
- data/lib/xolo/core/base_classes/version.rb +641 -0
- data/lib/xolo/core/constants.rb +85 -0
- data/lib/xolo/core/exceptions.rb +52 -0
- data/lib/xolo/core/json_wrappers.rb +43 -0
- data/lib/xolo/core/loading.rb +59 -0
- data/lib/xolo/core/output.rb +292 -0
- data/lib/xolo/core/security_cmd.rb +128 -0
- data/lib/xolo/core/version.rb +21 -0
- data/lib/xolo/core.rb +47 -0
- metadata +17 -11
|
@@ -333,7 +333,7 @@ module Xolo
|
|
|
333
333
|
vers_cmd = version_command?
|
|
334
334
|
title_or_vers_command = title_or_version_command?
|
|
335
335
|
add_command = add_command?
|
|
336
|
-
edit_command?
|
|
336
|
+
edit_command = edit_command?
|
|
337
337
|
arg_banner = Xolo::Admin::Options::COMMANDS.dig(cmd, :arg_banner)
|
|
338
338
|
arg_banner ||= Xolo::Admin::Options::DFT_CMD_TITLE_ARG_BANNER
|
|
339
339
|
|
|
@@ -370,12 +370,25 @@ module Xolo
|
|
|
370
370
|
cmd_opts.each do |opt_key, deets|
|
|
371
371
|
next unless deets[:cli]
|
|
372
372
|
|
|
373
|
+
desc = deets[:desc]
|
|
374
|
+
|
|
373
375
|
# Required opts are only required when adding.
|
|
374
376
|
# when editing, they should already exist
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
377
|
+
if add_command
|
|
378
|
+
if deets[:edit_only]
|
|
379
|
+
next
|
|
380
|
+
elsif deets[:required]
|
|
381
|
+
desc = "#{desc}REQUIRED"
|
|
382
|
+
required = true
|
|
383
|
+
elsif deets[:title_type]
|
|
384
|
+
desc = "#{desc}Only used with #{deets[:title_type]} titles."
|
|
385
|
+
required = deets[:required]
|
|
386
|
+
else
|
|
387
|
+
required = false
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
next if edit_command && deets[:add_only]
|
|
379
392
|
|
|
380
393
|
# booleans are CLI flags defaulting to false
|
|
381
394
|
# everything else is a string that we will convert as we validate later
|
|
@@ -94,6 +94,22 @@ module Xolo
|
|
|
94
94
|
ENDDESC
|
|
95
95
|
},
|
|
96
96
|
|
|
97
|
+
# @!attribute pw
|
|
98
|
+
# @return [String]
|
|
99
|
+
ssl_verify: {
|
|
100
|
+
label: 'Verify SSL Cert',
|
|
101
|
+
type: :boolean,
|
|
102
|
+
invalid_msg: '',
|
|
103
|
+
validate: :validate_boolean,
|
|
104
|
+
default: true,
|
|
105
|
+
desc: <<~ENDDESC
|
|
106
|
+
If your Xolo server is using a self-signed SSL certificate,
|
|
107
|
+
set this to false, to disable SSL verification.
|
|
108
|
+
|
|
109
|
+
Defaults to true, which is recommended for production servers.
|
|
110
|
+
ENDDESC
|
|
111
|
+
},
|
|
112
|
+
|
|
97
113
|
# @!attribute pw
|
|
98
114
|
# @return [String]
|
|
99
115
|
no_gui: {
|
|
@@ -20,6 +20,7 @@ module Xolo
|
|
|
20
20
|
##############################
|
|
21
21
|
|
|
22
22
|
TIMEOUT = 300
|
|
23
|
+
UPLOAD_TIMEOUT = 1800
|
|
23
24
|
OPEN_TIMEOUT = 10
|
|
24
25
|
|
|
25
26
|
PING_ROUTE = '/ping'
|
|
@@ -76,7 +77,19 @@ module Xolo
|
|
|
76
77
|
raise Xolo::ServerError, "#{resp.status}: #{resp.body}"
|
|
77
78
|
end
|
|
78
79
|
rescue Faraday::UnauthorizedError
|
|
79
|
-
|
|
80
|
+
msg = "Invalid username or password. If you recently changed your Jamf Pro password, please update it using 'xadm config'."
|
|
81
|
+
raise Xolo::AuthenticationError, msg
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# @return [Hash] the SSL options for Faraday connections
|
|
85
|
+
##################
|
|
86
|
+
def ssl_opts
|
|
87
|
+
# true if nil or true, false if false
|
|
88
|
+
verify = config.ssl_verify.nil? || config.ssl_verify
|
|
89
|
+
|
|
90
|
+
{
|
|
91
|
+
verify: verify
|
|
92
|
+
}
|
|
80
93
|
end
|
|
81
94
|
|
|
82
95
|
# @pararm host [String] an alternate hostname to use, defaults to config.hostname
|
|
@@ -107,7 +120,7 @@ module Xolo
|
|
|
107
120
|
@server_cnx = nil if host
|
|
108
121
|
return @server_cnx if @server_cnx
|
|
109
122
|
|
|
110
|
-
@server_cnx = Faraday.new(server_url(host: host)) do |cnx|
|
|
123
|
+
@server_cnx = Faraday.new(server_url(host: host), ssl: ssl_opts) do |cnx|
|
|
111
124
|
cnx.options[:timeout] = TIMEOUT
|
|
112
125
|
cnx.options[:open_timeout] = OPEN_TIMEOUT
|
|
113
126
|
|
|
@@ -145,7 +158,7 @@ module Xolo
|
|
|
145
158
|
|
|
146
159
|
req_opts = { on_data: streaming_proc }
|
|
147
160
|
|
|
148
|
-
@streaming_cnx = Faraday.new(server_url(host: host), request: req_opts) do |cnx|
|
|
161
|
+
@streaming_cnx = Faraday.new(server_url(host: host), request: req_opts, ssl: ssl_opts) do |cnx|
|
|
149
162
|
cnx.options[:timeout] = TIMEOUT
|
|
150
163
|
cnx.options[:open_timeout] = OPEN_TIMEOUT
|
|
151
164
|
cnx.use Xolo::Admin::CookieJar
|
|
@@ -168,8 +181,8 @@ module Xolo
|
|
|
168
181
|
@upload_cnx = nil if host
|
|
169
182
|
return @upload_cnx if @upload_cnx
|
|
170
183
|
|
|
171
|
-
@upload_cnx = Faraday.new(server_url(host: host)) do |cnx|
|
|
172
|
-
cnx.options[:timeout] =
|
|
184
|
+
@upload_cnx = Faraday.new(server_url(host: host), ssl: ssl_opts) do |cnx|
|
|
185
|
+
cnx.options[:timeout] = UPLOAD_TIMEOUT
|
|
173
186
|
cnx.options[:open_timeout] = OPEN_TIMEOUT
|
|
174
187
|
|
|
175
188
|
cnx.request :multipart
|
|
@@ -39,7 +39,13 @@ module Xolo
|
|
|
39
39
|
XOLO_CREDS_SVC = 'com.pixar.xolo.password'
|
|
40
40
|
|
|
41
41
|
# the Label for the generic 'Xolo::Admin::Credentials' keychain entry
|
|
42
|
-
XOLO_CREDS_LBL = '
|
|
42
|
+
XOLO_CREDS_LBL = 'Xolo Admin Password'
|
|
43
|
+
|
|
44
|
+
# Pre v2.0.0, the label was different. We need to check for the old one when fetching the password
|
|
45
|
+
# and if it's there, update it to the new one.
|
|
46
|
+
# TODO: Remove this in 2.1.0 or other appropriate future version,
|
|
47
|
+
# once we can be sure most users have updated to 2.0.0 or later.
|
|
48
|
+
XOLO_CREDS_LBL_OLD = '"Xolo Admin Password"'
|
|
43
49
|
|
|
44
50
|
# Module methods
|
|
45
51
|
##############################
|
|
@@ -70,10 +76,16 @@ module Xolo
|
|
|
70
76
|
cmd << '-w'
|
|
71
77
|
run_security(cmd.map { |i| security_escape i }.join(' '))
|
|
72
78
|
|
|
79
|
+
# TEMPORARY - check for the old label if we didn't find the password with the new label, and if we find it with the old label, update it to the new label
|
|
80
|
+
# TODO: Remove this in 2.1.0 or other appropriate future version,
|
|
81
|
+
# once we can be sure most users have updated to 2.0.0 or later.
|
|
82
|
+
rescue Xolo::NoSuchItemError
|
|
83
|
+
fetch_and_update_pw_if_necessary
|
|
84
|
+
|
|
73
85
|
# If we can't access the keychain, prompt for the password. This is usually
|
|
74
86
|
# when we're running in a non-GUI session, e.g. via ssh.
|
|
75
87
|
rescue Xolo::KeychainError
|
|
76
|
-
raise unless @security_exit_status.exitstatus == SEC_STATUS_NO_GUI_ERROR
|
|
88
|
+
raise unless @security_exit_status.exitstatus == Xolo::Core::SecurityCmd::SEC_STATUS_NO_GUI_ERROR
|
|
77
89
|
raise unless STDOUT.isatty
|
|
78
90
|
|
|
79
91
|
question = "Keychain not accessible.\nPlease enter the xolo admin password for #{config.admin}: "
|
|
@@ -82,6 +94,38 @@ module Xolo
|
|
|
82
94
|
end
|
|
83
95
|
end
|
|
84
96
|
|
|
97
|
+
# TEMPORARY - update the label of the existing keychain item if it has the old label but not the new one,
|
|
98
|
+
# to avoid prompting users to re-enter their password when we change the label in 2.0.0
|
|
99
|
+
# TODO: Remove this in 2.1.0 or other appropriate future version,
|
|
100
|
+
# once we can be sure most users have updated to 2.0.0 or later.
|
|
101
|
+
#####################
|
|
102
|
+
def fetch_and_update_pw_if_necessary
|
|
103
|
+
cmd = ['find-generic-password']
|
|
104
|
+
cmd << '-s'
|
|
105
|
+
cmd << XOLO_CREDS_SVC
|
|
106
|
+
cmd << '-l'
|
|
107
|
+
cmd << XOLO_CREDS_LBL_OLD
|
|
108
|
+
cmd << '-w'
|
|
109
|
+
pw = run_security(cmd.map { |i| security_escape i }.join(' '))
|
|
110
|
+
|
|
111
|
+
if @security_exit_status.exitstatus == Xolo::Core::SecurityCmd::SEC_STATUS_NOT_FOUND_ERROR
|
|
112
|
+
raise Xolo::NoSuchItemError, "No xolo admin password. Please run 'xadm config'"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# then delete the old item with the old label
|
|
116
|
+
cmd = ['delete-generic-password']
|
|
117
|
+
cmd << '-s'
|
|
118
|
+
cmd << XOLO_CREDS_SVC
|
|
119
|
+
cmd << '-l'
|
|
120
|
+
cmd << XOLO_CREDS_LBL_OLD
|
|
121
|
+
run_security(cmd.map { |i| security_escape i }.join(' '))
|
|
122
|
+
|
|
123
|
+
# if we found the password with the old label, update it to the new label
|
|
124
|
+
store_pw(ENV['USER'], pw)
|
|
125
|
+
|
|
126
|
+
pw
|
|
127
|
+
end
|
|
128
|
+
|
|
85
129
|
# Store an item in the default keychain
|
|
86
130
|
#
|
|
87
131
|
# @param acct [String] The username for the password.
|
|
@@ -141,50 +185,50 @@ module Xolo
|
|
|
141
185
|
# @return [String] the stdout of the 'security' command.
|
|
142
186
|
#
|
|
143
187
|
######
|
|
144
|
-
def run_security(cmd)
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
end # run_security
|
|
188
|
+
# def run_security(cmd)
|
|
189
|
+
# output = Xolo::BLANK
|
|
190
|
+
# errs = Xolo::BLANK
|
|
191
|
+
|
|
192
|
+
# Open3.popen3("#{Xolo::Core::SecurityCmd::SEC_COMMAND} -i") do |stdin, stdout, stderr, wait_thr|
|
|
193
|
+
# # pid = wait_thr.pid # pid of the started process.
|
|
194
|
+
# stdin.puts cmd
|
|
195
|
+
# stdin.close
|
|
196
|
+
|
|
197
|
+
# output = stdout.read
|
|
198
|
+
# errs = stderr.read
|
|
199
|
+
|
|
200
|
+
# @security_exit_status = wait_thr.value # Process::Status object returned.
|
|
201
|
+
# end
|
|
202
|
+
# # exit 44 is 'The specified item could not be found in the keychain'
|
|
203
|
+
# return output.chomp if @security_exit_status.success?
|
|
204
|
+
|
|
205
|
+
# case @security_exit_status.exitstatus
|
|
206
|
+
# when Xolo::Core::SecurityCmd::SEC_STATUS_AUTH_ERROR
|
|
207
|
+
# raise Xolo::KeychainError, 'Problem accessing login keychain. Is it locked?'
|
|
208
|
+
|
|
209
|
+
# when Xolo::Core::SecurityCmd::SEC_STATUS_NOT_FOUND_ERROR
|
|
210
|
+
# raise Xolo::NoSuchItemError, "No xolo admin password. Please run 'xadm config'"
|
|
211
|
+
|
|
212
|
+
# else
|
|
213
|
+
# errs.chomp!
|
|
214
|
+
# errs =~ /: returned\s+(-?\d+)$/
|
|
215
|
+
# errnum = Regexp.last_match(1)
|
|
216
|
+
# desc = errnum ? security_error_desc(errnum) : errs
|
|
217
|
+
# desc ||= errs
|
|
218
|
+
# raise Xolo::KeychainError, "#{desc.gsub("\n", '; ')}; exit status #{@security_exit_status.exitstatus}"
|
|
219
|
+
# end # case
|
|
220
|
+
# end # run_security
|
|
177
221
|
|
|
178
222
|
# use `security error` to get a description of an error number
|
|
179
223
|
##############
|
|
180
|
-
def security_error_desc(num)
|
|
181
|
-
|
|
182
|
-
|
|
224
|
+
# def security_error_desc(num)
|
|
225
|
+
# desc = `#{Xolo::Core::SecurityCmd::SEC_COMMAND} error #{num}`
|
|
226
|
+
# return if desc.include?('unknown error')
|
|
183
227
|
|
|
184
|
-
|
|
185
|
-
rescue StandardError
|
|
186
|
-
|
|
187
|
-
end
|
|
228
|
+
# desc.chomp.split(num).last
|
|
229
|
+
# rescue StandardError
|
|
230
|
+
# nil
|
|
231
|
+
# end
|
|
188
232
|
|
|
189
233
|
# given a string, wrap it in single quotes and escape internal single quotes
|
|
190
234
|
# and backslashes so it can be used in the interactive 'security' command
|
|
@@ -193,17 +237,17 @@ module Xolo
|
|
|
193
237
|
#
|
|
194
238
|
# @return [String] the escaped string
|
|
195
239
|
###################
|
|
196
|
-
def security_escape(str)
|
|
197
|
-
|
|
198
|
-
|
|
240
|
+
# def security_escape(str)
|
|
241
|
+
# # first escape backslashes
|
|
242
|
+
# str = str.to_s.gsub '\\', '\\\\\\'
|
|
199
243
|
|
|
200
|
-
|
|
201
|
-
|
|
244
|
+
# # then single quotes
|
|
245
|
+
# str.gsub! "'", "\\\\'"
|
|
202
246
|
|
|
203
|
-
|
|
247
|
+
# # if other things need escaping, add them here
|
|
204
248
|
|
|
205
|
-
|
|
206
|
-
end # security_escape
|
|
249
|
+
# "'#{str}'"
|
|
250
|
+
# end # security_escape
|
|
207
251
|
|
|
208
252
|
end # module Prefs
|
|
209
253
|
|
|
@@ -1,30 +1,22 @@
|
|
|
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
1
|
# frozen_string_literal: true
|
|
9
2
|
|
|
10
|
-
# MonkeyPatch HighLine::Terminal#readline_read so that Readline
|
|
11
|
-
# lines can be case-insensitive, and have a prompt.
|
|
12
|
-
#
|
|
13
|
-
# To use a prompt, put it in the environtment variable 'XADM_HIGHLINE_READLINE_PROMPT'
|
|
14
|
-
#
|
|
15
|
-
# To make the readline completion case-insensitive, set the environment
|
|
16
|
-
# variable XADM_HIGHLINE_READLINE_CASE_INSENSITIVE to anything.
|
|
17
|
-
#
|
|
18
|
-
# This really only modifies the Regexp used for the completion_proc to make it
|
|
19
|
-
# case insensitive if desired (adding an 'i' to the end)
|
|
20
|
-
# and sets the prompt when calling Readline.readline
|
|
21
|
-
#
|
|
22
|
-
# TODO: Do this 'smartly' as with the monkeypatches in pixar-ruby-extensions
|
|
23
|
-
#
|
|
24
3
|
class HighLine
|
|
25
4
|
|
|
26
5
|
class Terminal
|
|
27
6
|
|
|
7
|
+
# MonkeyPatch HighLine::Terminal#readline_read so that Readline
|
|
8
|
+
# lines can be case-insensitive, and have a prompt.
|
|
9
|
+
#
|
|
10
|
+
# To use a prompt, put it in the environtment variable 'XADM_HIGHLINE_READLINE_PROMPT'
|
|
11
|
+
#
|
|
12
|
+
# To make the readline completion case-insensitive, set the environment
|
|
13
|
+
# variable XADM_HIGHLINE_READLINE_CASE_INSENSITIVE to anything.
|
|
14
|
+
#
|
|
15
|
+
# This really only modifies the Regexp used for the completion_proc to make it
|
|
16
|
+
# case insensitive if desired (adding an 'i' to the end)
|
|
17
|
+
# and sets the prompt when calling Readline.readline
|
|
18
|
+
#################################
|
|
19
|
+
#
|
|
28
20
|
# Use readline to read one line
|
|
29
21
|
# @param question [HighLine::Question] question from where to get
|
|
30
22
|
# autocomplete candidate strings
|
|
@@ -51,31 +43,6 @@ class HighLine
|
|
|
51
43
|
raw_answer
|
|
52
44
|
end
|
|
53
45
|
|
|
54
|
-
|
|
55
|
-
##############################
|
|
56
|
-
# def get_line_default(highline)
|
|
57
|
-
# raise EOFError, 'The input stream is exhausted.' if highline.track_eof? && highline.input.eof?
|
|
58
|
-
|
|
59
|
-
# highline.output.print "#{ENV['XADM_HIGHLINE_LINE_PROMPT']}" if ENV['XADM_HIGHLINE_LINE_PROMPT']
|
|
60
|
-
|
|
61
|
-
# highline.input.gets
|
|
62
|
-
# end
|
|
63
|
-
|
|
64
|
-
end # terminal
|
|
65
|
-
|
|
66
|
-
# # Deals with the task of "asking" a question
|
|
67
|
-
# class QuestionAsker
|
|
68
|
-
|
|
69
|
-
# alias ask_once_real ask_once
|
|
70
|
-
|
|
71
|
-
# # Gets just one answer, as opposed to #gather_answers
|
|
72
|
-
# #
|
|
73
|
-
# # @return [String] answer
|
|
74
|
-
# def ask_once
|
|
75
|
-
# @highline.output.print "#{ENV['XADM_HIGHLINE_LINE_PROMPT']}" if ENV['XADM_HIGHLINE_LINE_PROMPT']
|
|
76
|
-
# ask_once_real
|
|
77
|
-
# end
|
|
78
|
-
|
|
79
|
-
# end # question asker
|
|
46
|
+
end # class Terminal
|
|
80
47
|
|
|
81
48
|
end
|
|
@@ -98,17 +98,34 @@ module Xolo
|
|
|
98
98
|
|
|
99
99
|
# The menu items for setting values
|
|
100
100
|
cmd_details(cmd)[:opts].each do |key, deets|
|
|
101
|
+
# dont displau items for the other kind of action
|
|
102
|
+
next if deets[:add_only] && edit_command?
|
|
103
|
+
next if deets[:edit_only] && add_command?
|
|
104
|
+
|
|
105
|
+
# only show items for the current title type, if applicable
|
|
106
|
+
if deets[:title_type]
|
|
107
|
+
if walkthru_cmd_opts[:subscribed]
|
|
108
|
+
next if deets[:title_type] == Xolo::MANAGED
|
|
109
|
+
elsif deets[:title_type] == Xolo::SUBSCRIBED
|
|
110
|
+
next
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
101
114
|
curr_val = current_opt_values[key]
|
|
102
|
-
|
|
115
|
+
|
|
103
116
|
not_avail = send(deets[:walkthru_na]) if deets[:walkthru_na]
|
|
117
|
+
|
|
118
|
+
# if a value is not available, remove any previously set value
|
|
119
|
+
walkthru_cmd_opts.delete_field(key) if not_avail && walkthru_cmd_opts.to_h.key?(key)
|
|
120
|
+
|
|
121
|
+
new_val = walkthru_cmd_opts[key]
|
|
122
|
+
|
|
104
123
|
menu_item = menu_item_text(deets[:label], oldval: curr_val, newval: new_val, not_avail: not_avail)
|
|
105
124
|
|
|
106
125
|
# no processing if item not available
|
|
107
126
|
if not_avail
|
|
108
|
-
# menu.choice(nil, nil, menu_item) {}
|
|
109
127
|
menu.choice(menu_item) {}
|
|
110
128
|
else
|
|
111
|
-
# menu.choice(nil, nil, menu_item) { prompt_for_walkthru_value key, deets, curr_val }
|
|
112
129
|
menu.choice(menu_item) { prompt_for_walkthru_value key, deets, curr_val }
|
|
113
130
|
end
|
|
114
131
|
end
|
|
@@ -139,22 +156,63 @@ module Xolo
|
|
|
139
156
|
end # until done with menu
|
|
140
157
|
end # def self.display_title_menu(_title)
|
|
141
158
|
|
|
159
|
+
# @return [String, nil] If a string, a reason why the given menu item is not available now.
|
|
160
|
+
# If nil, the menu item is displayed normally.
|
|
161
|
+
##############################
|
|
162
|
+
def display_name_na
|
|
163
|
+
return unless walkthru_cmd_opts[:subscribed]
|
|
164
|
+
|
|
165
|
+
'N/A when subcribing via a Patch Source'
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# @return [String, nil] If a string, a reason why the given menu item is not available now.
|
|
169
|
+
# If nil, the menu item is displayed normally.
|
|
170
|
+
##############################
|
|
171
|
+
def publisher_na
|
|
172
|
+
return unless walkthru_cmd_opts[:subscribed]
|
|
173
|
+
|
|
174
|
+
'N/A when subcribing via a Patch Source'
|
|
175
|
+
end
|
|
176
|
+
|
|
142
177
|
# @return [String, nil] If a string, a reason why the given menu item is not available now.
|
|
143
178
|
# If nil, the menu item is displayed normally.
|
|
144
179
|
##############################
|
|
145
180
|
def version_script_na
|
|
146
|
-
|
|
181
|
+
unless walkthru_cmd_opts[:app_name] || walkthru_cmd_opts[:app_bundle_id] || walkthru_cmd_opts[:subscribed]
|
|
182
|
+
return
|
|
183
|
+
end
|
|
147
184
|
|
|
148
|
-
'N/A when using App Name/BundleID'
|
|
185
|
+
'N/A when using App Name/BundleID, or subcribing via a Patch Source'
|
|
149
186
|
end
|
|
150
187
|
|
|
151
188
|
# @return [String, nil] If a string, a reason why the given menu item is not available now.
|
|
152
189
|
# If nil, the menu item is displayed normally.
|
|
153
190
|
##############################
|
|
154
191
|
def app_name_bundleid_na
|
|
155
|
-
return unless walkthru_cmd_opts[:version_script]
|
|
192
|
+
return unless walkthru_cmd_opts[:version_script] || walkthru_cmd_opts[:subscribed]
|
|
193
|
+
|
|
194
|
+
'N/A when using Version Script, or subcribing via a Patch Source'
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# @return [String, nil] If a string, a reason why the given menu item is not available now.
|
|
198
|
+
# If nil, the menu item is displayed normally.
|
|
199
|
+
##############################
|
|
200
|
+
def patch_source_na
|
|
201
|
+
# if walkthru_cmd_opts[:version_script] || walkthru_cmd_opts[:app_name] || walkthru_cmd_opts[:app_bundle_id]
|
|
202
|
+
# 'N/A when using Version Script or App Name/BundleID'
|
|
203
|
+
# elsif walkthru_cmd_opts[:publisher] || walkthru_cmd_opts[:display_name]
|
|
204
|
+
# 'N/A when using Display Name or Publisher'
|
|
205
|
+
# end
|
|
206
|
+
nil unless walkthru_cmd_opts[:subscribed]
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# @return [String, nil] If a string, a reason why the given menu item is not available now.
|
|
210
|
+
# If nil, the menu item is displayed normally.
|
|
211
|
+
##############################
|
|
212
|
+
def title_id_na
|
|
213
|
+
return if walkthru_cmd_opts[:patch_source]
|
|
156
214
|
|
|
157
|
-
'N/A
|
|
215
|
+
'N/A until Patch Source is set'
|
|
158
216
|
end
|
|
159
217
|
|
|
160
218
|
# @return [String, nil] If a string, a reason why the given menu item is not available now.
|
|
@@ -163,7 +221,7 @@ module Xolo
|
|
|
163
221
|
def ssvc_na
|
|
164
222
|
tgt_all = walkthru_cmd_opts[:release_groups]&.include?(Xolo::TARGET_ALL)
|
|
165
223
|
|
|
166
|
-
"N/A if
|
|
224
|
+
"N/A if Release Group is '#{Xolo::TARGET_ALL}'" if tgt_all
|
|
167
225
|
end
|
|
168
226
|
|
|
169
227
|
# @return [String, nil] If a string, a reason why the given menu item is not available now.
|
|
@@ -223,7 +281,14 @@ module Xolo
|
|
|
223
281
|
|
|
224
282
|
header_text.sub! Xolo::Admin::Options::TARGET_TITLE_PLACEHOLDER, cli_cmd.title if cli_cmd.title
|
|
225
283
|
header_text.sub! Xolo::Admin::Options::TARGET_VERSION_PLACEHOLDER, cli_cmd.version if cli_cmd.version
|
|
226
|
-
|
|
284
|
+
if edit_command?
|
|
285
|
+
header_text <<
|
|
286
|
+
if current_opt_values[:subscribed]
|
|
287
|
+
" (subscribed as '#{current_opt_values[:display_name]}')"
|
|
288
|
+
else
|
|
289
|
+
' (managed)'
|
|
290
|
+
end
|
|
291
|
+
end
|
|
227
292
|
header_sep_line = Xolo::DASH * header_text.length
|
|
228
293
|
|
|
229
294
|
system 'clear'
|
|
@@ -231,7 +296,7 @@ module Xolo
|
|
|
231
296
|
#{header_sep_line}
|
|
232
297
|
#{header_text}
|
|
233
298
|
#{header_sep_line}
|
|
234
|
-
Current Settings
|
|
299
|
+
Current Settings -> New Settings
|
|
235
300
|
|
|
236
301
|
ENDPUTS
|
|
237
302
|
end
|
|
@@ -455,7 +520,11 @@ module Xolo
|
|
|
455
520
|
validated_new_val = deets[:multi] ? [] : nil
|
|
456
521
|
all_done = false
|
|
457
522
|
until all_done
|
|
458
|
-
latest_input = Readline.readline(prompt, true)
|
|
523
|
+
latest_input = Readline.readline(prompt, true).strip
|
|
524
|
+
# dragging in items from the finder will esacpe spaces in the path with \'s
|
|
525
|
+
# in the shell this is good, but ruby is interpreting the \'s, so lets remove them.
|
|
526
|
+
latest_input.gsub!(/\\ /, ' ')
|
|
527
|
+
|
|
459
528
|
break if latest_input == Xolo::X
|
|
460
529
|
return Xolo::NONE if !deets[:required] && (latest_input == Xolo::NONE)
|
|
461
530
|
|
|
@@ -503,8 +572,8 @@ module Xolo
|
|
|
503
572
|
# if deets[:readline] is a symbol, its an xadm method that returns an array
|
|
504
573
|
# of the possible values for readline completion and validation;
|
|
505
574
|
# only things in the array are allowed, so no need for other validation or conversion
|
|
506
|
-
# We add 'x' and 'none' to the list
|
|
507
|
-
#
|
|
575
|
+
# We add 'x' and 'none' to the list for clearing it, and if a multi-value we add 'x'
|
|
576
|
+
# for exiting out of the prompt
|
|
508
577
|
#
|
|
509
578
|
# if its just truthy then we use readline without a pre-set list of values
|
|
510
579
|
# (e.g. paths, which might not exist locally) and may have a separate validate
|
|
@@ -513,8 +582,12 @@ module Xolo
|
|
|
513
582
|
if deets[:readline]
|
|
514
583
|
if deets[:readline].is_a? Symbol
|
|
515
584
|
convert = send deets[:readline]
|
|
516
|
-
convert << Xolo::NONE unless deets[:required]
|
|
517
|
-
convert << Xolo::X
|
|
585
|
+
convert << Xolo::NONE unless convert.include?(Xolo::NONE) || deets[:required]
|
|
586
|
+
convert << Xolo::X if deets[:multi] && !convert.include?(Xolo::X)
|
|
587
|
+
# if we're doing release groups, make sure the list includes 'all',
|
|
588
|
+
if !convert.include?(Xolo::TARGET_ALL) && deets[:label] == Xolo::Admin::Title::ATTRIBUTES[:release_groups][:label]
|
|
589
|
+
convert << Xolo::TARGET_ALL
|
|
590
|
+
end
|
|
518
591
|
validate = nil
|
|
519
592
|
end
|
|
520
593
|
true
|
|
@@ -720,9 +793,67 @@ module Xolo
|
|
|
720
793
|
|
|
721
794
|
missing_values << deets[:label]
|
|
722
795
|
end
|
|
796
|
+
|
|
797
|
+
missing_values += title_missing_values if title_command?
|
|
798
|
+
|
|
723
799
|
missing_values
|
|
724
800
|
end
|
|
725
801
|
|
|
802
|
+
# Process missing valus for titles
|
|
803
|
+
# @return [Array<String>] The title-specific missing values
|
|
804
|
+
##################################
|
|
805
|
+
def title_missing_values
|
|
806
|
+
title_missing_values = []
|
|
807
|
+
|
|
808
|
+
# if subscribing, need patch source and title id
|
|
809
|
+
# if walkthru_cmd_opts[:type] == Xolo::SUBSCRIBED
|
|
810
|
+
if walkthru_cmd_opts[:subscribed]
|
|
811
|
+
unless walkthru_cmd_opts[:patch_source]
|
|
812
|
+
title_missing_values << Xolo::Admin::Title::ATTRIBUTES[:patch_source][:label]
|
|
813
|
+
end
|
|
814
|
+
title_missing_values << Xolo::Admin::Title::ATTRIBUTES[:title_id][:label] unless walkthru_cmd_opts[:title_id]
|
|
815
|
+
|
|
816
|
+
# if managed, need display name, publisher and version script or app name/bundle id,
|
|
817
|
+
else
|
|
818
|
+
unless walkthru_cmd_opts[:publisher]
|
|
819
|
+
title_missing_values << Xolo::Admin::Title::ATTRIBUTES[:publisher][:label]
|
|
820
|
+
end
|
|
821
|
+
unless walkthru_cmd_opts[:display_name]
|
|
822
|
+
title_missing_values << Xolo::Admin::Title::ATTRIBUTES[:display_name][:label]
|
|
823
|
+
end
|
|
824
|
+
|
|
825
|
+
# version script or app name/bundle id
|
|
826
|
+
if walkthru_cmd_opts[:version_script] || walkthru_cmd_opts[:app_name] || walkthru_cmd_opts[:app_bundle_id]
|
|
827
|
+
# need both app name and bundle id if using either
|
|
828
|
+
if walkthru_cmd_opts[:app_name] && !walkthru_cmd_opts[:app_bundle_id]
|
|
829
|
+
title_missing_values << Xolo::Admin::Title::ATTRIBUTES[:app_bundle_id][:label]
|
|
830
|
+
elsif walkthru_cmd_opts[:app_bundle_id] && !walkthru_cmd_opts[:app_name]
|
|
831
|
+
title_missing_values << Xolo::Admin::Title::ATTRIBUTES[:app_name][:label]
|
|
832
|
+
end
|
|
833
|
+
else
|
|
834
|
+
title_missing_values << "#{Xolo::Admin::Title::ATTRIBUTES[:version_script][:label]} OR #{Xolo::Admin::Title::ATTRIBUTES[:app_name][:label]} & #{Xolo::Admin::Title::ATTRIBUTES[:app_bundle_id][:label]}"
|
|
835
|
+
end
|
|
836
|
+
|
|
837
|
+
end
|
|
838
|
+
|
|
839
|
+
# if expiring, need expire path
|
|
840
|
+
if walkthru_cmd_opts[:expiration].to_i.positive? && !walkthru_cmd_opts[:expiration_paths].pix_empty?
|
|
841
|
+
title_missing_values << Xolo::Admin::Title::ATTRIBUTES[:expiration_paths][:label]
|
|
842
|
+
end
|
|
843
|
+
|
|
844
|
+
# if in ssvc, need category and icon
|
|
845
|
+
return title_missing_values unless walkthru_cmd_opts[:self_service]
|
|
846
|
+
|
|
847
|
+
unless walkthru_cmd_opts[:self_service_category]
|
|
848
|
+
title_missing_values << Xolo::Admin::Title::ATTRIBUTES[:self_service_category][:label]
|
|
849
|
+
end
|
|
850
|
+
return title_missing_values if walkthru_cmd_opts[:self_service_icon]
|
|
851
|
+
|
|
852
|
+
title_missing_values << Xolo::Admin::Title::ATTRIBUTES[:self_service_icon][:label]
|
|
853
|
+
|
|
854
|
+
title_missing_values
|
|
855
|
+
end # def title_missing_values
|
|
856
|
+
|
|
726
857
|
# Prompt for an editor to use from those in MULTILINE_EDITORS
|
|
727
858
|
# @return [String] the path to an editor to use for multiline values.
|
|
728
859
|
##################
|
data/lib/xolo/admin/jamf_pro.rb
CHANGED
|
@@ -32,6 +32,9 @@ module Xolo
|
|
|
32
32
|
# Xolo server route to the list available categories
|
|
33
33
|
CATEGORY_NAME_ROUTE = "#{JAMF_ROUTE_BASE}/category-names"
|
|
34
34
|
|
|
35
|
+
# Xolo server route to the list of available titles for subscription
|
|
36
|
+
AVAILABLE_TITLES_ROUTE = "#{JAMF_ROUTE_BASE}/available-titles-for-subscription"
|
|
37
|
+
|
|
35
38
|
# Module Methods
|
|
36
39
|
##########################
|
|
37
40
|
##########################
|
|
@@ -68,6 +71,17 @@ module Xolo
|
|
|
68
71
|
@jamf_category_names ||= server_cnx.get(CATEGORY_NAME_ROUTE).body
|
|
69
72
|
end
|
|
70
73
|
|
|
74
|
+
# @return [Array<String>] data about all titles available for subscription in Jamf Pro.
|
|
75
|
+
#######################
|
|
76
|
+
def jamf_available_titles
|
|
77
|
+
@jamf_available_titles ||= server_cnx.get(AVAILABLE_TITLES_ROUTE).body
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# @return [Array<String>] All Patch Sources with any available titles in Jamf Pro
|
|
81
|
+
def jamf_patch_sources_with_available_titles
|
|
82
|
+
@jamf_patch_sources_with_available_titles ||= jamf_available_titles.map { |t| t[:source_name] }.sort.uniq
|
|
83
|
+
end
|
|
84
|
+
|
|
71
85
|
end # module
|
|
72
86
|
|
|
73
87
|
end # module Admin
|