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.
@@ -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
- required = deets[:required] && add_command
376
-
377
- desc = deets[:desc]
378
- desc = "#{desc}REQUIRED" if required
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
- raise Xolo::AuthenticationError, 'Invalid username or password'
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] = 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 = '"Xolo Admin Password"'
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
- output = Xolo::BLANK
146
- errs = Xolo::BLANK
147
-
148
- Open3.popen3("#{SEC_COMMAND} -i") do |stdin, stdout, stderr, wait_thr|
149
- # pid = wait_thr.pid # pid of the started process.
150
- stdin.puts cmd
151
- stdin.close
152
-
153
- output = stdout.read
154
- errs = stderr.read
155
-
156
- @security_exit_status = wait_thr.value # Process::Status object returned.
157
- end
158
- # exit 44 is 'The specified item could not be found in the keychain'
159
- return output.chomp if @security_exit_status.success?
160
-
161
- case @security_exit_status.exitstatus
162
- when SEC_STATUS_AUTH_ERROR
163
- raise Xolo::KeychainError, 'Problem accessing login keychain. Is it locked?'
164
-
165
- when SEC_STATUS_NOT_FOUND_ERROR
166
- raise Xolo::NoSuchItemError, "No xolo admin password. Please run 'xadm config'"
167
-
168
- else
169
- errs.chomp!
170
- errs =~ /: returned\s+(-?\d+)$/
171
- errnum = Regexp.last_match(1)
172
- desc = errnum ? security_error_desc(errnum) : errs
173
- desc ||= errs
174
- raise Xolo::KeychainError, "#{desc.gsub("\n", '; ')}; exit status #{@security_exit_status.exitstatus}"
175
- end # case
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
- desc = `#{SEC_COMMAND} error #{num}`
182
- return if desc.include?('unknown error')
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
- desc.chomp.split(num).last
185
- rescue StandardError
186
- nil
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
- # first escape backslashes
198
- str = str.to_s.gsub '\\', '\\\\\\'
240
+ # def security_escape(str)
241
+ # # first escape backslashes
242
+ # str = str.to_s.gsub '\\', '\\\\\\'
199
243
 
200
- # then single quotes
201
- str.gsub! "'", "\\\\'"
244
+ # # then single quotes
245
+ # str.gsub! "'", "\\\\'"
202
246
 
203
- # if other things need escaping, add them here
247
+ # # if other things need escaping, add them here
204
248
 
205
- "'#{str}'"
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
- # Get one line from terminal using default #gets method.
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
- new_val = walkthru_cmd_opts[key]
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
- return unless walkthru_cmd_opts[:app_name] || walkthru_cmd_opts[:app_bundle_id]
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 when using Version Script'
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 Target Group is '#{Xolo::TARGET_ALL}'" if tgt_all
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 => New 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 so they will be accepted for exiting and
507
- # clearing.
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
  ##################
@@ -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