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
|
@@ -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.
|
|
@@ -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,10 +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)
|
|
518
587
|
# if we're doing release groups, make sure the list includes 'all',
|
|
519
|
-
convert
|
|
588
|
+
if !convert.include?(Xolo::TARGET_ALL) && deets[:label] == Xolo::Admin::Title::ATTRIBUTES[:release_groups][:label]
|
|
589
|
+
convert << Xolo::TARGET_ALL
|
|
590
|
+
end
|
|
520
591
|
validate = nil
|
|
521
592
|
end
|
|
522
593
|
true
|
|
@@ -722,9 +793,67 @@ module Xolo
|
|
|
722
793
|
|
|
723
794
|
missing_values << deets[:label]
|
|
724
795
|
end
|
|
796
|
+
|
|
797
|
+
missing_values += title_missing_values if title_command?
|
|
798
|
+
|
|
725
799
|
missing_values
|
|
726
800
|
end
|
|
727
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
|
+
|
|
728
857
|
# Prompt for an editor to use from those in MULTILINE_EDITORS
|
|
729
858
|
# @return [String] the path to an editor to use for multiline values.
|
|
730
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
|
data/lib/xolo/admin/options.rb
CHANGED
|
@@ -152,6 +152,7 @@ module Xolo
|
|
|
152
152
|
|
|
153
153
|
LIST_TITLES_CMD = 'list-titles'
|
|
154
154
|
ADD_TITLE_CMD = 'add-title'
|
|
155
|
+
SUBSCRIBE_CMD = 'subscribe'
|
|
155
156
|
EDIT_TITLE_CMD = 'edit-title'
|
|
156
157
|
DELETE_TITLE_CMD = 'delete-title'
|
|
157
158
|
FREEZE_TITLE_CMD = 'freeze'
|
|
@@ -174,6 +175,7 @@ module Xolo
|
|
|
174
175
|
|
|
175
176
|
LIST_GROUPS_CMD = 'list-groups'
|
|
176
177
|
LIST_CATEGORIES_CMD = 'list-categories'
|
|
178
|
+
LIST_AVAILABLE_CMD = 'list-available'
|
|
177
179
|
SAVE_CLIENT_CODE_CMD = 'save-client'
|
|
178
180
|
|
|
179
181
|
SERVER_STATUS_CMD = 'server-status'
|
|
@@ -190,7 +192,7 @@ module Xolo
|
|
|
190
192
|
|
|
191
193
|
HELP_OPT = '--help'
|
|
192
194
|
|
|
193
|
-
DFT_CMD_TITLE_ARG_BANNER = " title: The unique name of a title in Xolo, e.g. 'google-chrome'"
|
|
195
|
+
DFT_CMD_TITLE_ARG_BANNER = " title: The unique name of a title in Xolo, e.g. 'google-chrome'\n Must be lowercase alphanumeric and dashes only."
|
|
194
196
|
DFT_CMD_VERSION_ARG_BANNER = " version: The version of the title you are working with. e.g. '12.34.5'"
|
|
195
197
|
|
|
196
198
|
TARGET_TITLE_PLACEHOLDER = Xolo::Admin::Title::TARGET_TITLE_PLACEHOLDER
|
|
@@ -299,7 +301,7 @@ module Xolo
|
|
|
299
301
|
readline_prompt: 'Group Name',
|
|
300
302
|
readline: :jamf_computer_group_names,
|
|
301
303
|
desc: <<~ENDDESC
|
|
302
|
-
One or more Jamf Computer Group names
|
|
304
|
+
One or more Jamf Computer Group names whose members will receive the MDM deployment.
|
|
303
305
|
|
|
304
306
|
When using the --groups CLI option, you can specify more than one group by using the option more than once, or by providing a single option value with the groups separated by commas.
|
|
305
307
|
ENDDESC
|
|
@@ -425,6 +427,17 @@ module Xolo
|
|
|
425
427
|
confirmation: true
|
|
426
428
|
},
|
|
427
429
|
|
|
430
|
+
# SUBSCRIBE_CMD => {
|
|
431
|
+
# desc: 'Subscribe to a software title from a Patch Source defined in Jamf Pro',
|
|
432
|
+
# display: "#{SUBSCRIBE_CMD} title",
|
|
433
|
+
# opts: Xolo::Admin::Title.subscribe_cli_opts,
|
|
434
|
+
# walkthru_header: "Subscribing to Xolo Title '#{TARGET_TITLE_PLACEHOLDER}'",
|
|
435
|
+
# target: :title,
|
|
436
|
+
# process_method: :subscribe_title,
|
|
437
|
+
# streamed_response: true,
|
|
438
|
+
# confirmation: true
|
|
439
|
+
# },
|
|
440
|
+
|
|
428
441
|
EDIT_TITLE_CMD => {
|
|
429
442
|
desc: 'Edit an exising software title',
|
|
430
443
|
display: "#{EDIT_TITLE_CMD} title",
|
|
@@ -554,7 +567,7 @@ module Xolo
|
|
|
554
567
|
before the MDM command is sent.
|
|
555
568
|
|
|
556
569
|
Computers can be specified by name, serial number, or Jamf ID. Groups can be specified by
|
|
557
|
-
name
|
|
570
|
+
name.
|
|
558
571
|
|
|
559
572
|
NOTE: Any automated installs (via Pilot or Release groups) will happen eventually anyway.
|
|
560
573
|
All Macs with the title already installed will also get new versions automatically.
|
|
@@ -688,6 +701,21 @@ module Xolo
|
|
|
688
701
|
process_method: :list_categories
|
|
689
702
|
},
|
|
690
703
|
|
|
704
|
+
LIST_AVAILABLE_CMD => {
|
|
705
|
+
desc: 'List all titles available for subscription, and their Patch Sources and Title IDs.',
|
|
706
|
+
long_desc: <<~ENDLONG,
|
|
707
|
+
When adding a subscribed title to Xolo, you need to know the Patch Source and Title ID.
|
|
708
|
+
This command lists all available titles from all defined Patch Sources on the Jamf Pro server,
|
|
709
|
+
and the unique identifiers for each. You can use those values with the --patch-source and --title-id
|
|
710
|
+
options of the 'xadm add-title' command.
|
|
711
|
+
ENDLONG
|
|
712
|
+
display: LIST_AVAILABLE_CMD,
|
|
713
|
+
usage: "#{Xolo::Admin::EXECUTABLE_FILENAME} [global-options] #{LIST_AVAILABLE_CMD}",
|
|
714
|
+
opts: {},
|
|
715
|
+
arg_banner: :none,
|
|
716
|
+
process_method: :list_available_titles
|
|
717
|
+
},
|
|
718
|
+
|
|
691
719
|
SAVE_CLIENT_CODE_CMD => {
|
|
692
720
|
desc: 'Save the xolo client tool to a directory for packaging and deployment.',
|
|
693
721
|
long_desc: <<~ENDLONG,
|
|
@@ -1090,6 +1118,12 @@ module Xolo
|
|
|
1090
1118
|
# versions
|
|
1091
1119
|
elsif version_command?
|
|
1092
1120
|
|
|
1121
|
+
# make note of the title being subscribed or managed
|
|
1122
|
+
title_obj = Xolo::Admin::Title.fetch(cli_cmd.title, server_cnx)
|
|
1123
|
+
|
|
1124
|
+
@current_opt_values[:subscribed] = title_obj.subscribed?
|
|
1125
|
+
@current_opt_values[:display_name] = title_obj.display_name
|
|
1126
|
+
|
|
1093
1127
|
# adding a new one?
|
|
1094
1128
|
if add_command?
|
|
1095
1129
|
prev_version = Xolo::Admin::Title.latest_version cli_cmd.title, server_cnx
|