xolo-admin 1.0.0

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.
@@ -0,0 +1,1139 @@
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
+ # frozen_string_literal: true
8
+
9
+ # we need these to be loaded before we load
10
+ require 'xolo/admin/title'
11
+ require 'xolo/admin/version'
12
+
13
+ module Xolo
14
+
15
+ module Admin
16
+
17
+ # A module for defining the CLI and Interactive options for xadm.
18
+ #
19
+ # The general structure of xadm commands is:
20
+ # xadm [global options] command [command options] [command arguments]
21
+ # in that order.
22
+ #
23
+ # The global options are things that affect the overall behavior of xadm,
24
+ # like --walkthru, --quiet, --json, and --debug.
25
+ #
26
+ # The command is the action to be taken, like 'add-title', 'edit-version',
27
+ # 'list-titles', etc.
28
+ #
29
+ # The command options are specific to the command, and are defined in the
30
+ # COMMANDS hash below.
31
+ #
32
+ # The command arguments are the things the command operates on, like the
33
+ # title name, or the title name and version.
34
+ #
35
+ module Options
36
+
37
+ # Constants
38
+ #########################
39
+ #########################
40
+
41
+ # These options affect the overall behavior of xadm. They must come
42
+ # before the command.
43
+ #
44
+ # NOTE: Optimist automatically provides --version -v and --help -h
45
+ GLOBAL_OPTIONS = {
46
+ walkthru: {
47
+ label: 'Run Interactively',
48
+ walkthru: false,
49
+ cli: :w,
50
+ desc: <<~ENDDESC
51
+ Run xadm in interactive mode when adding or editing titles or versions.
52
+ This causes xadm to present an interactive, menu-and-
53
+ prompt-driven interface. All command-options given on the
54
+ command line are ignored, and will be gathered
55
+ interactively.
56
+ The 'config' command is always interactive, even without --walkthru.
57
+ ENDDESC
58
+ },
59
+
60
+ auto_confirm: {
61
+ label: 'Auto Approve',
62
+ cli: :a,
63
+ walkthru: false,
64
+ desc: <<~ENDDESC
65
+ Do not ask for confirmation before making changes or using server-
66
+ admin commands.
67
+ This is mostly used for automating xadm.
68
+ Ignored if using --walkthru: if you're interactive you must confirm
69
+ your changes.
70
+ WARNING: Be careful that all values are correct.
71
+ ENDDESC
72
+ },
73
+
74
+ proxy_admin: {
75
+ label: 'Proxy Admin',
76
+ cli: :p,
77
+ walkthru: false,
78
+ type: :string,
79
+ validate: false,
80
+ desc: <<~ENDDESC
81
+ Used for automated workflows that connect to the xolo server using a
82
+ service account, not a user account. Most such automations have a way to
83
+ ascertain the identity of the user triggering the automation. They can use
84
+ that name here, and then it will be used on the server combined with the
85
+ actually authenticated service acct name for use in logging and status.
86
+
87
+ For example, if you have a CI/CD job that runs xadm commands, it will
88
+ connect to the xolo server using a service account such as 'xolo-cicd-runner'.
89
+ That job can get the name of the user triggering the job from an environment
90
+ variable, (or elsewhere) and pass that as the value of this option, like so:
91
+
92
+ xadm --proxy-admin $CICD_USER_NAME add-version my-title 1.2.3 [options...]
93
+
94
+ On the xolo server, the user will be recorded as
95
+
96
+ cicduser-via-xolo-cicd-runner
97
+
98
+ which will be used as the "added_by" value for the new version, and will show up
99
+ in the various logs.
100
+ ENDDESC
101
+ },
102
+
103
+ quiet: {
104
+ label: 'Quiet',
105
+ cli: :q,
106
+ walkthru: false,
107
+ desc: <<~ENDDESC
108
+ Run xadm in quiet mode
109
+ When used with add-, edit-, delete-, and release-
110
+ commands, nothing will be printed to standard output.
111
+
112
+ Ignored for other commands, the purpose of which is to print
113
+ something to standard output.
114
+ Also ignored if --debug is given
115
+
116
+ WARNING: For long-running processes, you may not see server errors!
117
+ ENDDESC
118
+ },
119
+
120
+ json: {
121
+ label: 'JSON',
122
+ cli: :j,
123
+ walkthru: false,
124
+ desc: <<~ENDDESC
125
+ For commands that output lists or info about titles and versions,
126
+ such as 'list-titles' or 'info <title> <version>',
127
+ return the data as raw JSON.
128
+ ENDDESC
129
+ },
130
+
131
+ debug: {
132
+ label: 'Debug',
133
+ cli: :d,
134
+ walkthru: false,
135
+ desc: <<~ENDDESC
136
+ Run xadm in debug mode
137
+ This causes more verbose output and full backtraces
138
+ to be printed on errors. Overrides --quiet
139
+ ENDDESC
140
+ }
141
+ }.freeze
142
+
143
+ # The xadm commands
144
+ #############################
145
+
146
+ # TODO: add commands for:
147
+ # - upload a manifest for a version
148
+ # - get the status of an MDM command given a uuid
149
+ # - get the code of a version script
150
+ # - get the code of an uninstall script
151
+ # - output the contents of a previous progress stream file, if it still exists
152
+
153
+ LIST_TITLES_CMD = 'list-titles'
154
+ ADD_TITLE_CMD = 'add-title'
155
+ EDIT_TITLE_CMD = 'edit-title'
156
+ DELETE_TITLE_CMD = 'delete-title'
157
+ FREEZE_TITLE_CMD = 'freeze'
158
+ THAW_TITLE_CMD = 'thaw'
159
+ LIST_FROZEN_CMD = 'list-frozen'
160
+
161
+ LIST_VERSIONS_CMD = 'list-versions'
162
+ ADD_VERSION_CMD = 'add-version'
163
+ EDIT_VERSION_CMD = 'edit-version'
164
+ RELEASE_VERSION_CMD = 'release'
165
+ DELETE_VERSION_CMD = 'delete-version'
166
+ DEPLOY_VERSION_CMD = 'deploy'
167
+
168
+ INFO_CMD = 'info' # info on a title or version
169
+ SEARCH_CMD = 'search' # search for titles
170
+ CHANGELOG_CMD = 'changelog' # show the changelog for a title
171
+ REPORT_CMD = 'report' # report on installations
172
+ REPAIR_CMD = 'repair' # run repair on titles or versions
173
+ CONFIG_CMD = 'config' # configure xadm
174
+
175
+ LIST_GROUPS_CMD = 'list-groups'
176
+ LIST_CATEGORIES_CMD = 'list-categories'
177
+ SAVE_CLIENT_CODE_CMD = 'save-client'
178
+
179
+ SERVER_STATUS_CMD = 'server-status'
180
+ HELP_CMD = 'help'
181
+
182
+ # server-admin commands
183
+ SERVER_CLEANUP_CMD = 'run-server-cleanup'
184
+ UPDATE_CLIENT_DATA_CMD = 'update-client-data'
185
+ ROTATE_SERVER_LOGS_CMD = 'rotate-server-logs'
186
+ SET_SERVER_LOG_LEVEL_CMD = 'set-server-log-level'
187
+ SHUTDOWN_SERVER_CMD = 'shutdown-server'
188
+
189
+ # various strings for the commands and opts
190
+
191
+ HELP_OPT = '--help'
192
+
193
+ DFT_CMD_TITLE_ARG_BANNER = " title: The unique name of a title in Xolo, e.g. 'google-chrome'"
194
+ DFT_CMD_VERSION_ARG_BANNER = " version: The version of the title you are working with. e.g. '12.34.5'"
195
+
196
+ TARGET_TITLE_PLACEHOLDER = Xolo::Admin::Title::TARGET_TITLE_PLACEHOLDER
197
+ TARGET_VERSION_PLACEHOLDER = 'TARGET_VERSION_PH'
198
+
199
+ PATCH_REPORT_OPTIONS = {
200
+ summary: {
201
+ label: 'Summary Only',
202
+ cli: :S,
203
+ type: :boolean,
204
+ validate: :validate_boolean,
205
+ default: false,
206
+ desc: <<~ENDDESC
207
+ Show a summary only: how many installs of each version, and how many in total.
208
+ ENDDESC
209
+ },
210
+
211
+ os: {
212
+ label: 'Show Operating System Version',
213
+ cli: :o,
214
+ type: :boolean,
215
+ validate: :validate_boolean,
216
+ default: false,
217
+ desc: <<~ENDDESC
218
+ Report the Operating System Version of the computer.
219
+ ENDDESC
220
+ },
221
+
222
+ building: {
223
+ label: 'Show Building',
224
+ cli: :b,
225
+ type: :boolean,
226
+ validate: :validate_boolean,
227
+ default: false,
228
+ desc: <<~ENDDESC
229
+ Report the Building of the computer.
230
+ ENDDESC
231
+ },
232
+
233
+ dept: {
234
+ label: 'Show Department',
235
+ cli: :d,
236
+ type: :boolean,
237
+ validate: :validate_boolean,
238
+ default: false,
239
+ desc: <<~ENDDESC
240
+ Report the Department of the computer.
241
+ ENDDESC
242
+ },
243
+
244
+ site: {
245
+ label: 'Show Site',
246
+ cli: :s,
247
+ type: :boolean,
248
+ validate: :validate_boolean,
249
+ default: false,
250
+ desc: <<~ENDDESC
251
+ Report the Site of the computer.
252
+ ENDDESC
253
+ },
254
+
255
+ frozen: {
256
+ label: 'Show Frozen Status',
257
+ cli: :f,
258
+ type: :boolean,
259
+ validate: :validate_boolean,
260
+ default: false,
261
+ desc: <<~ENDDESC
262
+ Report whether or not the computer is frozen for the title.
263
+ ENDDESC
264
+ },
265
+
266
+ id: {
267
+ label: 'Show Jamf ID',
268
+ cli: :i,
269
+ type: :boolean,
270
+ validate: :validate_boolean,
271
+ default: false,
272
+ desc: <<~ENDDESC
273
+ Report the Jamf ID of the computer.
274
+ ENDDESC
275
+ }
276
+ }.freeze
277
+
278
+ FREEZE_THAW_OPTIONS = {
279
+ users: {
280
+ label: 'Targets are usernames not computers',
281
+ cli: :u,
282
+ type: :boolean,
283
+ validate: :validate_boolean,
284
+ default: false,
285
+ desc: <<~ENDDESC
286
+ The targets of the command are usernames, not computers. All computers assigned to the user will be affected.
287
+ Default is false, targets are computer names.
288
+ ENDDESC
289
+ }
290
+ }.freeze
291
+
292
+ DEPLOY_VERSION_OPTIONS = {
293
+ groups: {
294
+ label: 'Computer group whose computers will be targeted',
295
+ cli: :g,
296
+ validate: :validate_deploy_groups,
297
+ type: :string,
298
+ multi: true,
299
+ readline_prompt: 'Group Name',
300
+ readline: :jamf_computer_group_names,
301
+ desc: <<~ENDDESC
302
+ One or more Jamf Computer Group names or ids whose members will receive the MDM deployment.
303
+
304
+ 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
+ ENDDESC
306
+ }
307
+ }.freeze
308
+
309
+ REPAIR_OPTIONS = {
310
+ versions: {
311
+ label: 'Repair all versions',
312
+ cli: :v,
313
+ type: :boolean,
314
+ validate: :validate_boolean,
315
+ default: false,
316
+ desc: <<~ENDDESC
317
+ When repairing a title, also repair all of its versions.
318
+ ENDDESC
319
+ }
320
+ }.freeze
321
+
322
+ SERVER_STATUS_OPTIONS = {
323
+ extended: {
324
+ label: 'Show Extended Status',
325
+ cli: :e,
326
+ type: :boolean,
327
+ validate: :validate_boolean,
328
+ default: false,
329
+ desc: <<~ENDDESC
330
+ Include more status information about the server, including the current
331
+ GEM_PATH, $LOAD_PATH, current object locks, and existing threads.
332
+ ENDDESC
333
+ }
334
+ }.freeze
335
+
336
+ SEARCH_TITLES_OPTIONS = {
337
+ summary: {
338
+ label: 'Show less detail, one line per matching title',
339
+ cli: :s,
340
+ type: :boolean,
341
+ validate: :validate_boolean,
342
+ default: false,
343
+ desc: <<~ENDDESC
344
+ Show only one line per matching title, with just the title name, display name,
345
+ publisher, contact email, and versions.
346
+ ENDDESC
347
+ }
348
+ }.freeze
349
+
350
+ # The commands that xadm understands
351
+ # For each command there is a hash of details, with these possible keys:
352
+ #
353
+ # - desc: [String] a one-line description of the command
354
+ #
355
+ # - display: [String] The command with any arguments, but no options.
356
+ # If no more specific usage: is defined, this is used in the help output
357
+ # to build the 'usage' line: "#{executable_file} #{cmd_display} [options]"
358
+ #
359
+ # - usage: [String] If the usage line generated with the display: value is inaccurate,
360
+ # you can define one explicitly here to override it.
361
+ #
362
+ # - arg_banner: [String] a line of help text defining the arguments taken by the command.
363
+ # Defaults to Xolo::Admin::Options::DFT_CMD_TITLE_ARG_BANNER
364
+ # Will automatically append Xolo::Admin::Options::DFT_CMD_VERSION_ARG_BANNER if
365
+ # the command is a version command. If ths command takes no args, set this to :none
366
+ #
367
+ # - target: [Symbol] Most commands operate on either titles, versions, or both. Set this
368
+ # to one of :title, :version, or :title_or_version to let xadm apply revelant state.
369
+ # Leave unset if the command doesn't take anything
370
+ #
371
+ # - confirmation: [Boolean] does this action require confirmation? (Are you sure?)
372
+ # Confirmation can be overriden with the --auto-confirm global opt, as is needed
373
+ # for using xadm in an automated workflow
374
+ #
375
+ # - opts: [Hash] The keys and details about all the options that can be given to this
376
+ # command. It is usually a constant from some other part of Xolo::Admin.
377
+ # See {Xolo::Core::BaseClasses::Title::ATTRIBUTES} for info about the contents
378
+ # of this Hash. If the command takes no options, set this to an empty hash '{}'
379
+ #
380
+ # - walkthru_header: A string displayed above the main walkthru menu describing what
381
+ # is happening. E.g. 'Editing Title foobar'.
382
+ # Use TARGET_TITLE_PLACEHOLDER and TARGET_VERSION_PLACEHOLDER as needed to sub in the
383
+ # names of the targets.
384
+ #
385
+ # - no_login: [Boolean] By default, xadm will connect to the xolo server. If that isn't
386
+ # needed for this command, set this to true.
387
+ #
388
+ # - process_method: [Symbol] The name of a method defined in Xolo::Admin::Processing.
389
+ # This method will be called to do the actual work of the command, after processing all
390
+ # the arguments and options.
391
+ #
392
+ # - streamed_response: [Boolean] Used for long-running server processes, like delete_title
393
+ # or delete_version. When true, we don't expect a final JSON response from the server, but
394
+ # instead a link to a stream of status messages, which we just print out as they arrive.
395
+ #
396
+ ###### Arguments vs options:
397
+ #
398
+ # We're using a more formal definition of these terms here.
399
+ #
400
+ # An argument is a value taken by a command - the thing the command will
401
+ # operate upon. This is usually a title, or a title and a version.
402
+ #
403
+ # An option is something that defines or modifies what the command will do
404
+ # with the argument(s). Options always begin with '-' or '--'
405
+ #
406
+ COMMANDS = {
407
+
408
+ LIST_TITLES_CMD => {
409
+ desc: 'List all software titles.',
410
+ display: LIST_TITLES_CMD,
411
+ opts: {},
412
+ arg_banner: :none,
413
+ process_method: :list_titles,
414
+ target: :none
415
+ },
416
+
417
+ ADD_TITLE_CMD => {
418
+ desc: 'Add a new software title',
419
+ display: "#{ADD_TITLE_CMD} title",
420
+ opts: Xolo::Admin::Title.cli_opts,
421
+ walkthru_header: "Adding Xolo Title '#{TARGET_TITLE_PLACEHOLDER}'",
422
+ target: :title,
423
+ process_method: :add_title,
424
+ streamed_response: true,
425
+ confirmation: true
426
+ },
427
+
428
+ EDIT_TITLE_CMD => {
429
+ desc: 'Edit an exising software title',
430
+ display: "#{EDIT_TITLE_CMD} title",
431
+ opts: Xolo::Admin::Title.cli_opts,
432
+ walkthru_header: "Editing Xolo Title '#{TARGET_TITLE_PLACEHOLDER}'",
433
+ process_method: :edit_title,
434
+ target: :title,
435
+ streamed_response: true,
436
+ confirmation: true
437
+ },
438
+
439
+ DELETE_TITLE_CMD => {
440
+ desc: 'Delete a software title, and all of its versions',
441
+ display: "#{DELETE_TITLE_CMD} title",
442
+ opts: {},
443
+ process_method: :delete_title,
444
+ target: :title,
445
+ streamed_response: true,
446
+ confirmation: true
447
+ },
448
+
449
+ FREEZE_TITLE_CMD => {
450
+ desc: 'Prevent computers from updating the currently installed version of a title.',
451
+ long_desc: <<~ENDLONG,
452
+ The target computers are added to a static group that is excluded from
453
+ all policies and patch policies related to this title and its versions.
454
+ If a computer doesn't have any version of the title, this will prevent
455
+ it from being installed via xolo (it will be 'frozen' in that state).
456
+ ENDLONG
457
+ display: "#{FREEZE_TITLE_CMD} title [--users] target [target ...] ",
458
+ opts: FREEZE_THAW_OPTIONS,
459
+ process_method: :freeze,
460
+ target: :title,
461
+ confirmation: true
462
+ },
463
+
464
+ THAW_TITLE_CMD => {
465
+ desc: 'Un-freeze computers, allowing them to resume updates of a title.',
466
+ ong_desc: <<~ENDLONG,
467
+ The target computers are removed from the static group that is excluded from
468
+ all policies and patch policies related to this title and its versions.
469
+ This will allow installation and updates to resume.
470
+ ENDLONG
471
+ display: "#{THAW_TITLE_CMD} title [--users] target [target ...]",
472
+ opts: FREEZE_THAW_OPTIONS,
473
+ process_method: :thaw,
474
+ target: :title,
475
+ confirmation: true
476
+ },
477
+
478
+ LIST_FROZEN_CMD => {
479
+ desc: 'List all computers that are frozen for a title.',
480
+ display: "#{LIST_FROZEN_CMD} title",
481
+ opts: {},
482
+ target: :title,
483
+ process_method: :list_frozen
484
+ },
485
+
486
+ LIST_VERSIONS_CMD => {
487
+ desc: 'List all versions of a title.',
488
+ display: "#{LIST_VERSIONS_CMD} title",
489
+ opts: {},
490
+ target: :title,
491
+ process_method: :list_versions
492
+ },
493
+
494
+ ADD_VERSION_CMD => {
495
+ desc: 'Add a new version to a title, making it available for piloting',
496
+ display: "#{ADD_VERSION_CMD} title version",
497
+ long_desc: <<~ENDLONG,
498
+ The version will be automatically installed or updated on any computers
499
+ in the 'pilot-groups' defined for the version. To manually install it
500
+ on other computers, you must use 'xolo install <title> <version>'.
501
+ ENDLONG
502
+ opts: Xolo::Admin::Version.cli_opts,
503
+ walkthru_header: "Adding Version '#{TARGET_VERSION_PLACEHOLDER}' to Xolo Title '#{TARGET_TITLE_PLACEHOLDER}'",
504
+ process_method: :add_version,
505
+ target: :version,
506
+ streamed_response: true,
507
+ confirmation: true
508
+ },
509
+
510
+ EDIT_VERSION_CMD => {
511
+ desc: 'Edit a version of a title',
512
+ display: "#{EDIT_VERSION_CMD} title version",
513
+ opts: Xolo::Admin::Version.cli_opts,
514
+ walkthru_header: "Editing Version '#{TARGET_VERSION_PLACEHOLDER}' of Xolo Title '#{TARGET_TITLE_PLACEHOLDER}'",
515
+ target: :version,
516
+ process_method: :edit_version,
517
+ streamed_response: true,
518
+ confirmation: true
519
+ },
520
+
521
+ RELEASE_VERSION_CMD => {
522
+ desc: "Take a version out of pilot and make it 'live'.",
523
+ long_desc: <<~ENDLONG,
524
+ Once released, the version will be updated on all computers
525
+ where it is installed. It will also be the version installed
526
+ manually on a computer when running 'xolo install <title>'.
527
+ ENDLONG
528
+ display: "#{RELEASE_VERSION_CMD} title version",
529
+ opts: {},
530
+ target: :version,
531
+ process_method: :release_version,
532
+ streamed_response: true,
533
+ confirmation: true
534
+ },
535
+
536
+ DELETE_VERSION_CMD => {
537
+ desc: 'Delete a version from a title.',
538
+ display: "#{DELETE_VERSION_CMD} title version",
539
+ opts: {},
540
+ target: :version,
541
+ process_method: :delete_version,
542
+ streamed_response: true,
543
+ confirmation: true
544
+ },
545
+
546
+ # TODO: allow uploading of manifests for xolo versions, to be used
547
+ # instead of the one generated by the server.
548
+ DEPLOY_VERSION_CMD => {
549
+ desc: 'Use MDM to install a version on one or more computers or computer groups.',
550
+ long_desc: <<~ENDLONG,
551
+ An MDM 'InstallEnterpriseApplication' command will be sent to the target computers to
552
+ install the version. If the version is already installed, it will be updated immediately.
553
+ Computers in any excluded-groups for the target will be removed from the list of targets
554
+ before the MDM command is sent.
555
+
556
+ Computers can be specified by name, serial number, or Jamf ID. Groups can be specified by
557
+ name or ID.
558
+
559
+ NOTE: Any automated installs (via Pilot or Release groups) will happen eventually anyway.
560
+ All Macs with the title already installed will also get new versions automatically.
561
+ Use of this command is only needed if you want to install a version manually and immediately
562
+ without connecting to the target computers.
563
+
564
+ The package for the version must be a signed 'Distribution Package', like those built with
565
+ 'productbuild', not a 'Component Package', as is generated by 'pkgbuild'.
566
+ When you upload the .pkg to Xolo, it will automatically get a basic manifest needed for
567
+ the MDM command.
568
+ ENDLONG
569
+ display: "#{DEPLOY_VERSION_CMD} title version [computer ...]",
570
+ opts: DEPLOY_VERSION_OPTIONS,
571
+ target: :version,
572
+ process_method: :deploy_version,
573
+ confirmation: true
574
+ },
575
+
576
+ SEARCH_CMD => {
577
+ desc: 'Search for titles.',
578
+ long_desc: <<~ENDLONG,
579
+ Matches text in title, display name, publisher, app name, bundle ID, or description. Displays all those values, plus current versions, for each matching title.
580
+ ENDLONG
581
+ display: "#{SEARCH_CMD} title",
582
+ opts: SEARCH_TITLES_OPTIONS,
583
+ target: :title,
584
+ process_method: :search_titles
585
+ },
586
+
587
+ INFO_CMD => {
588
+ desc: 'Show details about a title, or a version of a title',
589
+ display: "#{INFO_CMD} title [version]",
590
+ opts: {},
591
+ target: :title_or_version,
592
+ process_method: :show_info
593
+ },
594
+
595
+ REPORT_CMD => {
596
+ desc: 'Show a patch-report for a title, or a version of a title',
597
+ long_desc: <<~ENDDESC,
598
+ Patch reports list which computers have a title, or a version of the title, installed.
599
+ They always show the computer name, username and last contact date. If reporting all
600
+ versions, the version on each computer will also be listed.
601
+
602
+ Commandline options can be used to add more data to the report, such as operating system,
603
+ department, site, and so on.
604
+
605
+ To see machines with an unknown version of a title, use '#{Xolo::UNKNOWN}' as the version.
606
+
607
+ NOTE: When using --json, all options are included in the data.
608
+ ENDDESC
609
+ display: "#{REPORT_CMD} title [version]",
610
+ opts: PATCH_REPORT_OPTIONS,
611
+ target: :title_or_version,
612
+ process_method: :patch_report
613
+ },
614
+
615
+ REPAIR_CMD => {
616
+ desc: 'Ensure all Title Editor and Jamf Objects exist and are correctly configured',
617
+ long_desc: <<~ENDDESC,
618
+ For each title and version in Xolo, there are many objects in the Title Editor and Jamf Pro,
619
+ including patch definitions, criteria, policies, patch policies, computer groups, extension attributes and so on.
620
+
621
+ Normally these are created, updated, and deleted as needed by the Xolo server. However,
622
+ sometimes things can get out-of-whack.
623
+
624
+ This process will ensure the necessary Title Editor and Jamf Pro objects exist and have the
625
+ correct settings for the given title or version.
626
+
627
+ When specifying only a title, just the objects related to the title are repaired, not its versions.
628
+ If you want to repair all the versions as well, use the --versions option.
629
+
630
+ When specifying a title and a version, only that version is repaired.
631
+ ENDDESC
632
+ display: "#{REPAIR_CMD} title [version]",
633
+ opts: REPAIR_OPTIONS,
634
+ target: :title_or_version,
635
+ process_method: :repair,
636
+ confirmation: true
637
+ },
638
+
639
+ CHANGELOG_CMD => {
640
+ desc: 'Show the changelog for a title',
641
+ long_desc: <<~ENDDESC,
642
+ The changelog is a list of all the changes made to the title and its versions.
643
+ ENDDESC
644
+ display: "#{CHANGELOG_CMD} title",
645
+ opts: {},
646
+ target: :title,
647
+ process_method: :show_changelog
648
+ },
649
+
650
+ CONFIG_CMD => {
651
+ desc: 'Configure xadm. Always interactive, implies --walkthru',
652
+ long_desc: <<~ENDLONG,
653
+ This will display and allow you to set the configuration for xadm.
654
+ The command is always interactive, as if you provided --walkthru.
655
+ Values you set will be saved in the xadm config file,
656
+ located at ~/Library/Preferences/com.pixar.xolo.admin.config.yaml
657
+ Your password will be stored in your login keychain.
658
+
659
+ The configuration values are:
660
+
661
+ #{Xolo::Admin::Configuration.help_desc_text}
662
+
663
+ ENDLONG
664
+ display: CONFIG_CMD,
665
+ usage: "#{Xolo::Admin::EXECUTABLE_FILENAME} #{CONFIG_CMD}",
666
+ opts: Xolo::Admin::Configuration.cli_opts,
667
+ walkthru_header: 'Editing xadm configuration',
668
+ no_login: true,
669
+ arg_banner: :none,
670
+ process_method: :update_config
671
+ },
672
+
673
+ LIST_GROUPS_CMD => {
674
+ desc: 'List all computer groups in Jamf pro',
675
+ display: LIST_GROUPS_CMD,
676
+ usage: "#{Xolo::Admin::EXECUTABLE_FILENAME} [global-options] #{LIST_GROUPS_CMD}",
677
+ opts: {},
678
+ arg_banner: :none,
679
+ process_method: :list_groups
680
+ },
681
+
682
+ LIST_CATEGORIES_CMD => {
683
+ desc: 'List all categories in Jamf pro',
684
+ display: LIST_CATEGORIES_CMD,
685
+ usage: "#{Xolo::Admin::EXECUTABLE_FILENAME} [global-options] #{LIST_CATEGORIES_CMD}",
686
+ opts: {},
687
+ arg_banner: :none,
688
+ process_method: :list_categories
689
+ },
690
+
691
+ SAVE_CLIENT_CODE_CMD => {
692
+ desc: 'Save the xolo client tool to a directory for packaging and deployment.',
693
+ long_desc: <<~ENDLONG,
694
+ Saves the 'xolo' client tool to the specified directory. If no
695
+ directory is given, saves to /tmp/. The version saved is the same
696
+ version as the xadm tool you are running.
697
+
698
+ Once saved, you can package and deploy the client tool to your
699
+ managed Macs in whatever way is appropriate for your environment.
700
+
701
+ 'xolo' is a command-line tool for installing and otherwise working
702
+ with xolo-managed software on managed Macs. It is a single-file zsh script.
703
+ Fundamentally, it is a wrapper around the 'jamf policy' command, but it
704
+ also provides additional functionality.
705
+ See 'xolo help' for details.
706
+ ENDLONG
707
+ display: SAVE_CLIENT_CODE_CMD,
708
+ usage: "#{Xolo::Admin::EXECUTABLE_FILENAME} #{SAVE_CLIENT_CODE_CMD} [/path/to/dir]",
709
+ opts: {},
710
+ arg_banner: :none,
711
+ process_method: :save_client_code
712
+ },
713
+
714
+ HELP_CMD => {
715
+ desc: 'Get help for a specific command',
716
+ display: 'help command',
717
+ opts: {},
718
+ no_login: true
719
+ },
720
+
721
+ SERVER_STATUS_CMD => {
722
+ desc: '[Server Admins Only] Show status of Xolo server.',
723
+ long_desc: <<~ENDLONG,
724
+ Requires server-admin privileges.
725
+ Displays the current status of the server, including uptime, log level,
726
+ versions of various libraries, configuration, more.
727
+ ENDLONG
728
+ display: SERVER_STATUS_CMD,
729
+ opts: SERVER_STATUS_OPTIONS,
730
+ arg_banner: :none,
731
+ process_method: :server_status
732
+ },
733
+
734
+ SERVER_CLEANUP_CMD => {
735
+ desc: "[Server Admins Only] Run the server's cleanup process now.",
736
+ long_desc: <<~ENDLONG,
737
+ Requires server-admin privileges.
738
+ Once a version of a title is released, the preveiously released
739
+ version is marked as 'deprecated', and any older unreleased versions
740
+ are marked as 'skipped'. A nightly task will then delete all skipped
741
+ versions from xolo, as well as deprecated versions that have been than
742
+ deprecated more than some number of days, as configured configured on
743
+ the server. Running this command will do that cleanup now.
744
+ ENDLONG
745
+ display: SERVER_CLEANUP_CMD,
746
+ opts: {},
747
+ arg_banner: :none,
748
+ process_method: :server_cleanup,
749
+ confirmation: true
750
+ },
751
+
752
+ UPDATE_CLIENT_DATA_CMD => {
753
+ desc: '[Server Admins Only] Make the server update the client-data package now.',
754
+ long_desc: <<~ENDLONG,
755
+ Requires server-admin privileges.
756
+ Every time a change is made to a title or version, the server updates
757
+ a JSON file with the current state of all titles and versions. This
758
+ file is then packaged and deployed to all managed clients. Running
759
+ this command will force the server to update that file now.
760
+ ENDLONG
761
+ display: UPDATE_CLIENT_DATA_CMD,
762
+ opts: {},
763
+ arg_banner: :none,
764
+ process_method: :update_client_data,
765
+ confirmation: true
766
+ },
767
+
768
+ ROTATE_SERVER_LOGS_CMD => {
769
+ desc: '[Server Admins Only] Rotate the logs on the server now.',
770
+ long_desc: <<~ENDLONG,
771
+ Requires server-admin privileges.
772
+ Server log rotation is normally done nightly. Running this command
773
+ will rotate the logs now.
774
+ ENDLONG
775
+ display: ROTATE_SERVER_LOGS_CMD,
776
+ opts: {},
777
+ arg_banner: :none,
778
+ process_method: :rotate_server_logs,
779
+ confirmation: true
780
+ },
781
+
782
+ SET_SERVER_LOG_LEVEL_CMD => {
783
+ desc: '[Server Admins Only] Set the log level of the server logger.',
784
+ long_desc: <<~ENDLONG,
785
+ Requires server-admin privileges.
786
+ Sets the log level of the server logger to one of 'debug', 'info',
787
+ 'warn', 'error', or 'fatal'. This will affect the amount of output
788
+ written to the server log file.
789
+ ENDLONG
790
+ display: SET_SERVER_LOG_LEVEL_CMD,
791
+ usage: "#{Xolo::Admin::EXECUTABLE_FILENAME} #{SET_SERVER_LOG_LEVEL_CMD} level [options]",
792
+ opts: {},
793
+ arg_banner: ' level: The log level to set, one of "debug", "info", "warn", "error", "fatal"',
794
+ process_method: :set_server_log_level,
795
+ confirmation: true
796
+ },
797
+
798
+ SHUTDOWN_SERVER_CMD => {
799
+ desc: '[Server Admins Only] Shutdown or restart the server gracefully.',
800
+ long_desc: <<~ENDLONG,
801
+ Requires server-admin privileges.
802
+ Gracefully stop the xolo server process, optionally restarting it automatically.
803
+ This will attempt to finish any in-progress operations before shutting down.
804
+ If you don't use --restart, you must reload the launchd plist, or reboot the server machine to restart the server process.
805
+ ENDLONG
806
+ display: "#{SHUTDOWN_SERVER_CMD} [--restart]",
807
+ opts: {
808
+ restart: {
809
+ label: 'Restart after shutdown',
810
+ cli: :r,
811
+ type: :boolean,
812
+ validate: :validate_boolean,
813
+ default: false,
814
+ desc: <<~ENDDESC
815
+ Restart the server automatically after shutdown. If not given, you must reload the launchd plist, or reboot the server machine to restart the server process.
816
+ ENDDESC
817
+ }
818
+ },
819
+ arg_banner: :none,
820
+ process_method: :shutdown_server,
821
+ confirmation: true
822
+ }
823
+
824
+ }.freeze
825
+
826
+ # The commands that add something to xolo - how their options are processed and validated
827
+ # differs from those commands that just edit or report things.
828
+ ADD_COMMANDS = [ADD_TITLE_CMD, ADD_VERSION_CMD].freeze
829
+
830
+ EDIT_COMMANDS = [EDIT_TITLE_CMD, EDIT_VERSION_CMD].freeze
831
+
832
+ DELETE_COMMANDS = [DELETE_TITLE_CMD, DELETE_VERSION_CMD].freeze
833
+
834
+ # For these commands, the title or version must exist
835
+ MUST_EXIST_COMMANDS = [
836
+ EDIT_TITLE_CMD, EDIT_VERSION_CMD,
837
+ DELETE_TITLE_CMD, DELETE_VERSION_CMD, RELEASE_VERSION_CMD,
838
+ FREEZE_TITLE_CMD, THAW_TITLE_CMD, LIST_FROZEN_CMD, CHANGELOG_CMD, REPAIR_CMD
839
+ ].freeze
840
+
841
+ # we get the default min os from the server
842
+ DEFAULT_MIN_OS_ROUTE = '/default_min_os'
843
+
844
+ # Module methods
845
+ ##############################
846
+ ##############################
847
+
848
+ # when this module is included
849
+ def self.included(includer)
850
+ Xolo.verbose_include includer, self
851
+ end
852
+
853
+ # Get the default min_os from the server.
854
+ # If it's been customized in the server config, you
855
+ # will get that value, otherwise the Xolo default.
856
+ # The server route is not protected, so use a one-off faraday
857
+ # connection to get the value.
858
+ #
859
+ # @return [String] the default min_os for versions
860
+ ####################
861
+ def self.default_min_os
862
+ return @default_min_os if @default_min_os
863
+
864
+ url = URI.parse("https://#{Xolo::Admin.config.hostname}#{DEFAULT_MIN_OS_ROUTE}")
865
+ val_from_server = Faraday.new(url).get.body
866
+ @default_min_os = JSON.parse(val_from_server, symbolize_names: true)[:min_os]
867
+ rescue StandardError
868
+ @default_min_os = Xolo::Core::BaseClasses::Version::DEFAULT_MIN_OS
869
+ end
870
+
871
+ # Instance Methods
872
+ ##########################
873
+ ##########################
874
+
875
+ # the command definition details from Xolo::Admin::Options::COMMANDS
876
+ #
877
+ # @param cmd [Symbol] A key from Xolo::Admin::Options::COMMANDS
878
+ # defaults to the current cli_cmd.commadn
879
+ #
880
+ # @return [Hash] The value for the key
881
+ #######################
882
+ def cmd_details(cmd = nil)
883
+ cmd ||= cli_cmd.command
884
+ Xolo::Admin::Options::COMMANDS[cmd]
885
+ end
886
+
887
+ # Are we running in interactive mode?
888
+ # --walkthru was givin in the global opts
889
+ # @return [Boolean]
890
+ #######################
891
+ def walkthru?
892
+ global_opts.walkthru
893
+ end
894
+
895
+ # are we auto-confirming?
896
+ # --auto-confirm was given in the global opts
897
+ # and we are not using --walkthru
898
+ # @return [Boolean]
899
+ #######################
900
+ def auto_confirm?
901
+ global_opts.auto_confirm && !global_opts.walkthru
902
+ end
903
+
904
+ # do we need to ask for confirmation?
905
+ # the command we are running wants confirmation
906
+ # and auto_confirm? is false
907
+ # @return [Boolean]
908
+ #######################
909
+ def need_confirmation?
910
+ cmd_details[:confirmation] && !auto_confirm?
911
+ end
912
+
913
+ # are we outputing JSON?
914
+ # @return [Boolean]
915
+ #######################
916
+ def json?
917
+ global_opts.json
918
+ end
919
+
920
+ # are we showing debug output?
921
+ # @return [Boolean]
922
+ #######################
923
+ def debug?
924
+ global_opts.debug
925
+ end
926
+
927
+ # are we being quiet?
928
+ # --quiet was given, but --debug was not
929
+ # @return [Boolean]
930
+ #######################
931
+ def quiet?
932
+ global_opts.quiet && !debug? && !json?
933
+ end
934
+
935
+ # Global Opts
936
+ #
937
+ # The CLI options from xadm that come before the
938
+ # xadm command
939
+ #
940
+ # These are always set by Optimist.
941
+ #
942
+ # See Xolo::Admin::Options.cli_cmd_opts below for
943
+ # a short discussion about the optimist hash.
944
+ #
945
+ # @return [OpenStruct]
946
+ ############################
947
+ def global_opts
948
+ @global_opts ||= OpenStruct.new
949
+ end
950
+
951
+ # This will hold 2 or 3 items:
952
+ # :command - the xadm command we are processing
953
+ # :title - the title arg for the xadm command
954
+ # :version - the version arg, if the command processes a version
955
+ #
956
+ # e.g. running `xadm edit-title foobar`
957
+ # - Xolo::Admin::Options.cli_cmd.command => 'edit-title'
958
+ # - Xolo::Admin::Options.cli_cmd.title => 'foobar'
959
+ # - Xolo::Admin::Options.cli_cmd.version => nil
960
+ #
961
+ # e.g. running `xadm edit-version foobar 1.2.34`
962
+ # - Xolo::Admin::Options.cli_cmd.command => 'edit-version'
963
+ # - Xolo::Admin::Options.cli_cmd.title => 'foobar'
964
+ # - Xolo::Admin::Options.cli_cmd.version => '1.2.34'
965
+ #
966
+ # @return [OpenStruct]
967
+ ############################
968
+ def cli_cmd
969
+ @cli_cmd ||= OpenStruct.new
970
+ end
971
+
972
+ # CLI Command Opts - the options given on the command line
973
+ # for processing an xadm command.
974
+ #
975
+ # Will be set by Optimist in command_line.rb
976
+ #
977
+ # The options gathered by a walkthru are available in
978
+ # Xolo::Admin::Options.walkthru_cmd_opts
979
+ #
980
+ # The optimist data will contain a key matching every
981
+ # key from the option definitions hash, even if the key
982
+ # wasn't given on the commandline.
983
+ #
984
+ # So if there's a ':foo_bar' option defined, but --foo-bar
985
+ # wasn't given on the commandline,
986
+ # Xolo::Admin::Options.cli_cmd_opts[:foo_bar] will be set, but will
987
+ # be nil.
988
+ #
989
+ # More importantly, for each option that IS given on the commandline
990
+ # the optimist hash will contain a ':opt_name_given' key set to true.
991
+ # so for validation, we can only care about the values for which there
992
+ # is a *_given key, e.g. :foo_bar_given in the example above.
993
+ # See also Xolo::Admin::Validate.cli_cmd_opts.
994
+ #
995
+ # After validating the individual options provided, the values from
996
+ # current_values will be added to cli_cmd_opts for any options not
997
+ # given on the command-line. After that, the whole will be validated
998
+ # for internal consistency.
999
+ #
1000
+ # @return [OpenStruct]
1001
+ ############################
1002
+ def cli_cmd_opts
1003
+ @cli_cmd_opts ||= OpenStruct.new
1004
+ end
1005
+
1006
+ # Walkthru Command Opts - the options given via walkthrough
1007
+ # for processing an xadm command.
1008
+ #
1009
+ # This is intially set with the default, inherited, or existing
1010
+ # values for the object being created or edited.
1011
+ #
1012
+ # Before the walk through starts, its duped and the dup
1013
+ # used as the current_opt_values (see below)
1014
+ #
1015
+ # In walkthru, the current_opt_values are used to generate the menu
1016
+ # items showing the changes being made.
1017
+ #
1018
+ # e.g. if option :foo (label: 'Foo') starts with value 'bar'
1019
+ # at first the menu item will look like:
1020
+ #
1021
+ # 12) Foo: bar
1022
+ #
1023
+ # but if the walkthru user changes the value to 'baz', it'll look like this
1024
+ #
1025
+ # 12) Foo: bar => baz
1026
+ #
1027
+ # The changes themselves are reflected here in walkthru_cmd_opts, and it will be
1028
+ # used for validation of individual options, as well as overall internal
1029
+ # consistency, before being applied to the object at hand.
1030
+ #
1031
+ # @return [OpenStruct]
1032
+ ############################
1033
+ def walkthru_cmd_opts
1034
+ @walkthru_cmd_opts ||= OpenStruct.new
1035
+ end
1036
+
1037
+ # If the command we are running manipulates a title or version (the target), then
1038
+ # before we process the options given on the commandline or show the walkthru menu,
1039
+ # we need to know the 'current' values.
1040
+ #
1041
+ # The current values are:
1042
+ #
1043
+ # For titles:
1044
+ # - the default values for new titles, if it doesn't exist and we are adding it.
1045
+ # NB: at the moment there are no default values for new titles.
1046
+ # - the current values for the title, if it exists and we are editing it
1047
+ #
1048
+ # For versions:
1049
+ # - The default values for new versions, if we are adding the first one in a title
1050
+ # - The values of the most recent version, if we are adding a subsequent one for the title
1051
+ # - The values of this version, if we are editing an existing one
1052
+ #
1053
+ # For xadm configuration:
1054
+ # - The values from the config file and/or credentials from the keychain
1055
+ # - keychain values are not displayed in walkthru, but are shown to be
1056
+ # already set, or needed.
1057
+ #
1058
+ # @return [OpenStruct]
1059
+ #####################
1060
+ def current_opt_values
1061
+ return @current_opt_values if @current_opt_values
1062
+
1063
+ @current_opt_values = OpenStruct.new
1064
+
1065
+ opts_defs = Xolo::Admin::Options::COMMANDS[cli_cmd.command][:opts]
1066
+
1067
+ # config?
1068
+ if cli_cmd.command == CONFIG_CMD
1069
+ # Xolo::Admin::Configuration::KEYS.each_key { |key| @current_opt_values[key] = config.send(key) }
1070
+ opts_defs.each_key { |key| @current_opt_values[key] = config.send(key) }
1071
+
1072
+ # titles
1073
+ elsif title_command?
1074
+
1075
+ # adding a new one? just use defaults, if there are any
1076
+ if add_command?
1077
+ # defaults
1078
+ opts_defs.each do |key, deets|
1079
+ next if deets[:default].nil?
1080
+
1081
+ @current_opt_values[key] = deets[:default].is_a?(Proc) ? deets[:default].call : deets[:default]
1082
+ end
1083
+
1084
+ # editing? just use the current values
1085
+ elsif edit_command?
1086
+ current_title = Xolo::Admin::Title.fetch cli_cmd.title, server_cnx
1087
+ opts_defs.each_key { |key| @current_opt_values[key] = current_title.send(key) }
1088
+ end
1089
+
1090
+ # versions
1091
+ elsif version_command?
1092
+
1093
+ # adding a new one?
1094
+ if add_command?
1095
+ prev_version = Xolo::Admin::Title.latest_version cli_cmd.title, server_cnx
1096
+
1097
+ # Use the most recent version if we have one
1098
+ if prev_version
1099
+ pvers = Xolo::Admin::Version.fetch cli_cmd.title, prev_version, server_cnx
1100
+ opts_defs.each do |key, deets|
1101
+ next if deets[:do_not_inherit]
1102
+
1103
+ @current_opt_values[key] = pvers.send key
1104
+ end
1105
+
1106
+ # publish date is always today to start with
1107
+ @current_opt_values[:publish_date] = Date.today.to_s
1108
+
1109
+ # no prev version, so use the defaults
1110
+ else
1111
+ opts_defs.each do |key, deets|
1112
+ next if deets[:default].nil?
1113
+
1114
+ @current_opt_values[key] = deets[:default].is_a?(Proc) ? deets[:default].call : deets[:default]
1115
+ end
1116
+ end
1117
+
1118
+ # editing? just use the current values
1119
+ elsif edit_command?
1120
+ # do stuff here to fetch current values from the server.
1121
+ current_vers = Xolo::Admin::Version.fetch cli_cmd.title, cli_cmd.version, server_cnx
1122
+ opts_defs.each_key { |key| @current_opt_values[key] = current_vers.send(key) }
1123
+ end
1124
+ end
1125
+
1126
+ @current_opt_values
1127
+ end
1128
+
1129
+ # The options for the running command that are marked as :required
1130
+ ###########################
1131
+ def required_values
1132
+ @required_values ||= Xolo::Admin::Options::COMMANDS[cli_cmd.command][:opts].select { |_k, v| v[:required] }
1133
+ end
1134
+
1135
+ end # module Options
1136
+
1137
+ end # module Admin
1138
+
1139
+ end # module Xolo