xolo-server 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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +42 -4
  3. data/bin/xoloserver +3 -0
  4. data/data/client/xolo +1233 -0
  5. data/lib/optimist_with_insert_blanks.rb +1216 -0
  6. data/lib/xolo/core/base_classes/configuration.rb +238 -0
  7. data/lib/xolo/core/base_classes/server_object.rb +112 -0
  8. data/lib/xolo/core/base_classes/title.rb +884 -0
  9. data/lib/xolo/core/base_classes/version.rb +641 -0
  10. data/lib/xolo/core/constants.rb +85 -0
  11. data/lib/xolo/core/exceptions.rb +52 -0
  12. data/lib/xolo/core/json_wrappers.rb +43 -0
  13. data/lib/xolo/core/loading.rb +59 -0
  14. data/lib/xolo/core/output.rb +292 -0
  15. data/lib/xolo/core/security_cmd.rb +128 -0
  16. data/lib/xolo/core/version.rb +21 -0
  17. data/lib/xolo/core.rb +47 -0
  18. data/lib/xolo/server/app.rb +7 -0
  19. data/lib/xolo/server/configuration.rb +243 -38
  20. data/lib/xolo/server/constants.rb +10 -0
  21. data/lib/xolo/server/helpers/auth.rb +19 -2
  22. data/lib/xolo/server/helpers/autopkg.rb +157 -0
  23. data/lib/xolo/server/helpers/client_data.rb +90 -60
  24. data/lib/xolo/server/helpers/file_transfers.rb +412 -82
  25. data/lib/xolo/server/helpers/jamf_pro.rb +31 -7
  26. data/lib/xolo/server/helpers/log.rb +2 -0
  27. data/lib/xolo/server/helpers/maintenance.rb +1 -0
  28. data/lib/xolo/server/helpers/notification.rb +4 -3
  29. data/lib/xolo/server/helpers/pkg_signing.rb +16 -12
  30. data/lib/xolo/server/helpers/progress_streaming.rb +9 -12
  31. data/lib/xolo/server/helpers/subscriptions.rb +119 -0
  32. data/lib/xolo/server/helpers/titles.rb +27 -3
  33. data/lib/xolo/server/helpers/versions.rb +23 -11
  34. data/lib/xolo/server/mixins/changelog.rb +9 -16
  35. data/lib/xolo/server/mixins/title_jamf_access.rb +375 -390
  36. data/lib/xolo/server/mixins/title_ted_access.rb +50 -8
  37. data/lib/xolo/server/mixins/version_jamf_access.rb +118 -129
  38. data/lib/xolo/server/mixins/version_ted_access.rb +34 -4
  39. data/lib/xolo/server/object_locks.rb +2 -1
  40. data/lib/xolo/server/routes/auth.rb +2 -2
  41. data/lib/xolo/server/routes/jamf_pro.rb +11 -1
  42. data/lib/xolo/server/routes/maint.rb +2 -1
  43. data/lib/xolo/server/routes/subscriptions.rb +126 -0
  44. data/lib/xolo/server/routes/title_editor.rb +1 -1
  45. data/lib/xolo/server/routes/titles.rb +26 -11
  46. data/lib/xolo/server/routes/uploads.rb +0 -14
  47. data/lib/xolo/server/routes/versions.rb +14 -13
  48. data/lib/xolo/server/routes.rb +15 -23
  49. data/lib/xolo/server/title.rb +100 -77
  50. data/lib/xolo/server/version.rb +178 -18
  51. data/lib/xolo/server.rb +8 -0
  52. metadata +20 -11
@@ -0,0 +1,884 @@
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
+ # frozen_string_literal: true
9
+
10
+ # main module
11
+ module Xolo
12
+
13
+ module Core
14
+
15
+ module BaseClasses
16
+
17
+ # The base class for dealing with Titles in the
18
+ # Xolo Server, Admin, and Client modules.
19
+ #
20
+ # This class holds the common aspects of Xolo Titles as used
21
+ # on the Xolo server, in the Xolo Admin CLI app 'xadm', and the
22
+ # client app 'xolo' - most importately it defines which data they
23
+ # exchange.
24
+ #
25
+ ############################
26
+ class Title < Xolo::Core::BaseClasses::ServerObject
27
+
28
+ # Mixins
29
+ #############################
30
+ #############################
31
+
32
+ # Constants
33
+ #############################
34
+ #############################
35
+
36
+ MIN_TITLE_DESC_LENGTH = 25
37
+
38
+ # Attributes
39
+ ######################
40
+ ######################
41
+
42
+ # Attributes of Titles.
43
+ #
44
+ # This hash defines the common attributes of all Xolo titles - which should
45
+ # be available in all subclasses, both on the server, in xadm, and via the
46
+ # client.
47
+ #
48
+ # Each of those subclasses might define (and store or calculate) other
49
+ # attributes as needed.
50
+ #
51
+ # Importantly, the attributes defined here are those that are transfered
52
+ # between xadm and the server as JSON data, and then used to instantiate the
53
+ # title in the local context. They are also stored in the JSON file deployed
54
+ # to clients containing all currently known titles and versions.
55
+ #
56
+ # In this hash, each key is the name of an attribute, and the values are
57
+ # hashes defining the details of that attribute.
58
+ #
59
+ # Since they are used by all subclasses, the details may include info used
60
+ # by one subclass but not another. For example, these attributes are
61
+ # used to create/define CLI & walkthru options for xadm, the settings for
62
+ # which are ignored on the server.
63
+ #
64
+ # The info below also applies to {Xolo:Core::BaseClasses::Version::ATTRIBUTES}, q.v.
65
+ #
66
+ # Title attributes have these details:
67
+ #
68
+ # - label: [String] to be displayed in admin interaction, walkthru, help msgs
69
+ # or error messages. e.g. 'Display Name' for the attribute display_name
70
+ #
71
+ # - required: [Boolean] Must be provided when making a new title, cannot be
72
+ # deleted from existing titles. (but can be changed if not immutable)
73
+ #
74
+ # - immutable: [Boolean] This value can only be set when creatimg a new title.
75
+ # When editing an existing title, it cannot be changed.
76
+ #
77
+ # - title_type: [Symbol, nil] If set to :managed or :subscribed, this option
78
+ # is required, and only available, when the title is of that type. If nil or not set,
79
+ # the option is available for all title types.
80
+ #
81
+ # - cli: [Symbol, false] What is the 'short' option flag for this option?
82
+ # The long option flag is the key, preceded with '--' and with underscores
83
+ # changed to '-', so display_name is set using the cli option '--display-name'.
84
+ # If this is set to :n then the short version is '-n'
85
+ #
86
+ # If this attribute can't be set via walkthru or a CLI option, set this to false.
87
+ #
88
+ # - multi: [Boolean] If true, this option takes multiple values and is stored as
89
+ # an Array. On the commandline, it can be given multiple times and all the values
90
+ # will be in an array in the options hash.
91
+ # E.g. if foo: { multi: true } and these options are given on the commandline
92
+ # --foo val1 --foo val2 --foo 'val 3'
93
+ # then the options hash will contain: :foo => ['val1', 'val2', 'val 3']
94
+ # It can also be given as a single comma-separated string, e.g.
95
+ # --foo 'val1, val2, val 3'
96
+ #
97
+ # In --walkthru the user will be asked to keep entering values, and to
98
+ # end input with an 'x' by itself.
99
+ #
100
+ # - default: [String, Numeric, Boolean, Proc] the default value if nothing is
101
+ # provided or inherited. Note that titles never inherit values, only versions do.
102
+ #
103
+ # - validate: [Boolean, Symbol] how to validate & convert values for this attribute.
104
+ #
105
+ # - If true (not just truthy) call method named 'validate_<key>' in {Xolo::Admin::Validate}
106
+ # passing in the value to validate.
107
+ # e.g. Xolo::Admin::Validate.validate_display_name(new_display_name)
108
+ #
109
+ # - If a Symbol, it's an arbitrary method to call on the {Xolo::Admin::Validate} module
110
+ # e.g. :non_empty_array will validate the value using
111
+ # Xolo::Admin::Validate.non_empty_array(some_array_value)
112
+ #
113
+ # - Anything else: no validation, and the value will be a String
114
+ #
115
+ # - type: [Symbol] the data type of the value. One of: :boolean, :string, :integer,
116
+ # :time
117
+ #
118
+ # NOTE: We are not using Optimist's validation & auto-conversion of these types, they
119
+ # all come from the CLI as strings, and the matching method in {Xolo::Admin::Validate}
120
+ # is used to validate and convert the values.
121
+ # The YARD docs for each attribute indicate the Class of the value in the
122
+ # Title object after CLI processing.
123
+ #
124
+ # - invalid_msg: [String] Custom message to display when the value fails validation.
125
+ #
126
+ # - desc: [String] Helpful text explaining what the attribute is, and what its CLI option means.
127
+ # Displayed during walkthru, in help messages, and in some err messages.
128
+ #
129
+ # - walkthru_na: [Symbol] The name of a method to call (usually defined in
130
+ # {Xolo::Admin::Interactive}) when building the walk thru menu item for this option.
131
+ # If it returns a string, it is an explanation of wny this option
132
+ # is not available at the moment, and the item is not selectable. If it returns nil, the
133
+ # item is displayed and handled as normal.
134
+ #
135
+ # - multiline [Boolean] If true, the value for this option can be many lines lione, and in
136
+ # walkthru, will be presented to the user in an editor like vim. See also multi: above
137
+ #
138
+ # - ted_attribute: [Symbol] If this attribute has a matching one on the related
139
+ # Title Editor Title, this is the name of that attribute in the Windoo::SoftwareTitle
140
+ #
141
+ # - readline: [Symbol] If set, use readline to get the value from the user during walkthru.
142
+ # If the symbol is :get_files, a custom method is called that uses readline with shell-style
143
+ # auto-complete to get one or more file paths.
144
+ # Any other symbol is the name of a method to call, which will return an array of possible
145
+ # values for the option, which will be used for readline-based auto-completion.
146
+ #
147
+ # - readline_prompt: When using readline to gather mulit: values, this prompt is shown at the
148
+ # start of each line of input for the next item.
149
+ #
150
+ # - read_only: [Boolean] defaults to false. When true, the server maintains this value, and
151
+ # its only readable via xadm.
152
+ #
153
+ # - add_only: [Boolean] This option is only used when creating a new title, it is not
154
+ # available when editing an existing title.
155
+ #
156
+ # - edit_only: [Boolean] This option is only used when editing an existing title, it is not
157
+ # available when creating a new title.
158
+ #
159
+ # - hide_from_info: [Boolean] when true, do not show this attribute in the 'info' xadm output
160
+ # NOTE: it will still be available when --json is given with the info command.
161
+ #
162
+ # - changelog: [Boolean] When true, changes to this attribute is included in the changelog for the title.
163
+ #
164
+ ATTRIBUTES = {
165
+
166
+ # @!attribute title
167
+ # @return [String] The unique title-string for this title.
168
+ title: {
169
+ label: 'Title',
170
+ ted_attribute: :id,
171
+ required: true,
172
+ immutable: true,
173
+ cli: false,
174
+ type: :string,
175
+ hide_from_info: true,
176
+ validate: true,
177
+ invalid_msg: 'Not a valid title: must be lowercase alphanumeric and dashes only',
178
+ desc: <<~ENDDESC
179
+ A unique string identifying this Title, e.g. 'folio' or 'google-chrome'.
180
+ The same as a 'basename' in d3.
181
+ Must contain only lowercase letters, numbers, and dashes.
182
+ ENDDESC
183
+ },
184
+
185
+ # @!attribute subscribed
186
+ # @return [Boolean] is this title subscribed via a Jamf Patch Source?
187
+ subscribed: {
188
+ label: 'Subscribed?',
189
+ cli: :B,
190
+ type: :boolean,
191
+ immutable: true,
192
+ add_only: true,
193
+ validate: :validate_boolean,
194
+ default: false,
195
+ changelog: true,
196
+ desc: <<~ENDDESC
197
+ Is this title subscribed via a Jamf Patch Source? If not, it is managed by Xolo. By default titles are managed. Once created, titles cannot be changed between managed and subscribed.
198
+
199
+ Managed titles have their entire definition, versions and updates managed by Xolo; xadm is used to provide basic details about the title, such as a the display name, publisher, the mechanism by which Jamf Pro knows which version is installed on a Mac. Versions of managed titles are also added and maintained via xadm, setting values such as killapps, OS restrictions, and so on.
200
+
201
+ Subscribed titles and their version definitions are maintained by a Patch Source configrued in Jamf Pro. The source defines some aspects of the title and its versions, including the display name, publisher, and mechanism for determining installed versions. New versions are also managed by the Patch Source, including killapps, OS restrictions, and other settings. When they appear, Xolo will at least inform someone (notify the contact email) that a new version is available and needs a .pkg, but can also use autopkg to automatically fetch .pkgs and add new versions for piloting.
202
+
203
+ When a title is subscribed, these options are required:
204
+ --patch-source
205
+ --title-id
206
+
207
+ and these are not available, since the patch source manages them:
208
+ --display-name
209
+ --publisher
210
+ --app-name
211
+ --app-bundle-id
212
+ --version-script
213
+
214
+ When a subscribed title is added, a Xolo Version is automatically added for the most recent version available from the Patch Source.
215
+
216
+ Once created as subscribed or managed, a title cannot be changed to the other type. If you need to change the type, you must delete and re-create the title. Also once created, the patch source and title id for a subscribed title cannot be changed.
217
+
218
+ In all cases, a .pkg must be provided for each version known to Xolo, either by uploading it via xadm or configuring the server and the title to use AutoPkg to acquire it.
219
+ ENDDESC
220
+ },
221
+
222
+ # @!attribute description
223
+ # @return [String] A description of what this title installs
224
+ description: {
225
+ label: 'Description',
226
+ required: true,
227
+ cli: :d,
228
+ type: :string,
229
+ validate: :validate_title_desc,
230
+ changelog: true,
231
+ multiline: true,
232
+ invalid_msg: <<~ENDINV,
233
+ Not a valid description, must be at least #{MIN_TITLE_DESC_LENGTH} characters.
234
+
235
+ Provide a useful dscription of what the software does, why it's in Xolo, URLs, developer names, etc.
236
+
237
+ DO NOT USE, e.g. 'Installs Some App', because we know that already and it isn't helpful.
238
+ ENDINV
239
+ desc: <<~ENDDESC
240
+ A useful dscription of what the software installed by this title does or why it's in Xolo. You can also include URLs, developer names, support info, etc.
241
+
242
+ DO NOT use, e.g. 'Installs Some App', because we know that already and it isn't helpful.
243
+
244
+ IMPORTANT: If this title appears in Self Service, the description will be visible to users.
245
+
246
+ Must be at least #{MIN_TITLE_DESC_LENGTH} Characters.
247
+ ENDDESC
248
+ },
249
+
250
+ # @!attribute display_name
251
+ # @return [String] The display-name for this title
252
+ display_name: {
253
+ label: 'Display Name',
254
+ ted_attribute: :name,
255
+ title_type: Xolo::MANAGED,
256
+ # required: true, # only required if not subscribed
257
+ cli: :n,
258
+ type: :string,
259
+ validate: :validate_title_display_name,
260
+ walkthru_na: :display_name_na,
261
+ invalid_msg: 'Not a valid display name, must be at least three characters long.',
262
+ changelog: true,
263
+ desc: <<~ENDDESC
264
+ A human-friendly name for the Software Title, e.g. 'Google Chrome', or 'NFS Menubar'.
265
+ Must be at least three characters long.
266
+
267
+ Cannot be used for subscribed titles, since the Patch Source defines the display name for those.
268
+ ENDDESC
269
+ },
270
+
271
+ # @!attribute publisher
272
+ # @return [String] The entity that publishes this title
273
+ publisher: {
274
+ label: 'Publisher',
275
+ ted_attribute: :publisher,
276
+ # required: true, # only required if not subscribed
277
+ title_type: Xolo::MANAGED,
278
+ cli: :P,
279
+ type: :string,
280
+ walkthru_na: :publisher_na,
281
+ validate: true,
282
+ changelog: true,
283
+ invalid_msg: '"Not a valid Publisher, must be at least three characters.',
284
+ desc: <<~ENDDESC
285
+ The company or entity that publishes this title, e.g. 'Apple, Inc.' or 'Pixar Animation Studios'.
286
+
287
+ Cannot be used for subscribed titles, since the Patch Source defines the publisher for those.
288
+ ENDDESC
289
+ },
290
+
291
+ # @!attribute app_name
292
+ # @return [String] The name of the .app installed by this title. Nil if no .app is installed
293
+ app_name: {
294
+ label: 'App Name',
295
+ ted_attribute: :appName,
296
+ title_type: Xolo::MANAGED,
297
+ cli: :a,
298
+ validate: true,
299
+ type: :string,
300
+ changelog: true,
301
+ walkthru_na: :app_name_bundleid_na,
302
+ invalid_msg: "Not a valid App name, must end with '.app'",
303
+ desc: <<~ENDDESC
304
+ If this title installs a .app bundle, the app's name must be provided. This the name of the bundle itself on disk, e.g. 'Google Chrome.app'.
305
+
306
+ Jamf Patch Management uses this, plus the app's bundle id, to determine if the title is installed on a computer, and if so, which version.
307
+
308
+ If the title does not install a .app bundle, leave this blank, and provide a --version-script.
309
+
310
+ Cannot be used for subscribed titles, since the Patch Source defines the app name for those.
311
+
312
+ REQUIRED if --app-bundle-id is used.
313
+ ENDDESC
314
+ },
315
+
316
+ # @!attribute app_bundle_id
317
+ # @return [String] The bundle ID of the .app installed by this title. Nil if no .app is installed
318
+ app_bundle_id: {
319
+ label: 'App Bundle ID',
320
+ ted_attribute: :bundleId,
321
+ title_type: Xolo::MANAGED,
322
+ cli: :b,
323
+ validate: true,
324
+ type: :string,
325
+ changelog: true,
326
+ walkthru_na: :app_name_bundleid_na,
327
+ invalid_msg: '"Not a valid bundle-id, must include at least one dot.',
328
+ desc: <<~ENDDESC
329
+ If this title installs a .app bundle, the app's bundle-id must be provided. This is found in the CFBundleIdentifier key of the app's Info.plist, e.g. 'com.google.chrome'
330
+
331
+ Jamf Patch Management uses this, plus the app's name, to determine if the title is installed on a computer, and if so, which version.
332
+
333
+ If the title does not install a .app bundle, or if the .app doesn't provide its version via the bundle id (e.g. Firefox) leave this blank, and provide a --version-script.
334
+
335
+ Cannot be used for subscribed titles, since the Patch Source defines the bundle ID for those.
336
+
337
+ REQUIRED if --app-name is used.
338
+ ENDDESC
339
+ },
340
+
341
+ # @!attribute version_script
342
+ # @return [String] A script that will return the currently installed version of this
343
+ # title on a client mac
344
+ version_script: {
345
+ label: 'Version Script',
346
+ title_type: Xolo::MANAGED,
347
+ cli: :v,
348
+ # while the script is stored in the Title Editor as the extension attribute
349
+ # its handled differently, so we don't specify a ted_attribute here.
350
+ validate: true,
351
+ type: :string,
352
+ readline: :get_files,
353
+ changelog: true,
354
+ walkthru_na: :version_script_na,
355
+ invalid_msg: "Invalid Script Path. Local File must exist and start with '#!'.",
356
+ desc: <<~ENDDESC
357
+ If this title does NOT install a .app bundle, enter the local path to a script which will run on managed computers and output a <result> tag with the currently installed version of this title.
358
+
359
+ E.g. if version 1.2.3 is installed, the script should output the text:
360
+ <result>1.2.3</result>
361
+ and if no version of the title is installed, it should output:
362
+ <result></result>
363
+
364
+ Cannot be used for subscribed titles, since the Patch Source defines the version script for those.
365
+
366
+ REQUIRED unless --app-name and --app-bundle-id are used.
367
+ ENDDESC
368
+ },
369
+
370
+ # @!attribute patch_source
371
+ # @return [String] The name of the Jamf Patch Source that provides this title via subscription
372
+ patch_source: {
373
+ label: 'Subscription Patch Source',
374
+ title_type: Xolo::SUBSCRIBED,
375
+ cli: :S,
376
+ type: :string,
377
+ immutable: true,
378
+ validate: true,
379
+ add_only: true,
380
+ walkthru_na: :patch_source_na,
381
+ readline: :jamf_patch_sources_with_available_titles,
382
+ invalid_msg: 'Invalid Patch Source. Unknown Patch Source Name, or it has no available titles.',
383
+ desc: <<~ENDDESC
384
+ The name of the Jamf Patch Source that provides this title via subscription.
385
+
386
+ This value and --title-id are required for 'subscribed' titles.
387
+
388
+ To see a list of titles available for subscription, with their Patch Sources and IDs, use `xadm list-available`.
389
+
390
+ Can only be used when adding a new title, not when editing an existing one.
391
+ ENDDESC
392
+
393
+ },
394
+
395
+ # @!attribute title_id
396
+ # @return [String] The TitleID of the title on the specified Patch Source
397
+ title_id: {
398
+ label: 'Subscription Title ID',
399
+ title_type: Xolo::SUBSCRIBED,
400
+ cli: :T,
401
+ type: :string,
402
+ immutable: true,
403
+ add_only: true,
404
+ walkthru_na: :title_id_na,
405
+ validate: true,
406
+ invalid_msg: 'Invalid Title ID. Not available in any Patch Source.',
407
+ desc: <<~ENDDESC
408
+ The TitleID of the title on the specified Patch Source.
409
+
410
+ This is the unique identifier for each title on a patch source, it is a sometimes-meaningless string of alphanumeric characters.
411
+
412
+ To find the TitleID of a title available for subscription, use `xadm list-available`
413
+
414
+ Can only be used when adding a new title, not when editing an existing one.
415
+ ENDDESC
416
+
417
+ },
418
+
419
+ # TODO: Add validation?
420
+ # @!attribute autopkg_recipe
421
+ # @return [String] The autopkg recipe to run when new versions are added to this title
422
+ autopkg_recipe: {
423
+ label: 'AutoPkg Recipe',
424
+ cli: :R,
425
+ validate: false,
426
+ type: :string,
427
+ changelog: true,
428
+ invalid_msg: "Unknown autopkg recipe. Use one that is configured on the server, or '#{Xolo::NONE}'.",
429
+ desc: <<~ENDDESC
430
+ The name of an autopkg recipe to run when a new version is added to this title. It is expected that the version being added is the latest available and will match the .pkg acquired by a run of the recipe.
431
+
432
+ AutoPkg, and the recipe, must be installed and configured on the xoloserver host.
433
+
434
+ When using this, --autopkg-dir must also be provided
435
+
436
+ To unset, use '#{Xolo::NONE}'
437
+ ENDDESC
438
+ },
439
+
440
+ # TODO: Add validation?
441
+ # @!attribute autopkg_dir
442
+ # @return [String] The path containg the .pkg acquired by the --autopkg-recipe
443
+ autopkg_dir: {
444
+ label: 'AutoPkg Output Directory',
445
+ cli: :D,
446
+ validate: false,
447
+ type: :string,
448
+ changelog: true,
449
+ invalid_msg: "Must be an absolute path starting with / and containing at least one more /, or '#{Xolo::NONE}'.",
450
+ desc: <<~ENDDESC
451
+ The path to the directory where the --autopkg-recipe will store the acquired .pkg.
452
+
453
+ After running the recipe, xoloserver will look for the newest .pkg file in this directory and upload it to the Jamf distribution servers, as with any other pkg.
454
+
455
+ Required when using --autopkg-recipe
456
+
457
+ To unset, use '#{Xolo::NONE}'
458
+ ENDDESC
459
+ },
460
+
461
+ # @!attribute pilot_groups
462
+ # @return [Array<String>] Jamf groups that will automatically get new versions installed or
463
+ # updated when added, for piloting
464
+ pilot_groups: {
465
+ label: 'Pilot Computer Groups',
466
+ cli: :p,
467
+ validate: true,
468
+ type: :string,
469
+ multi: true,
470
+ changelog: true,
471
+ readline_prompt: 'Group Name',
472
+ readline: :jamf_computer_group_names,
473
+ invalid_msg: "Invalid group. Must be an existing Jamf Computer Group, or '#{Xolo::NONE}'.",
474
+ desc: <<~ENDDESC
475
+ One or more Jamf Computer Groups whose members will automatically have new versions installed or updated for testing before it is released.
476
+
477
+ These computers will be used for testing not just the software, but the installation process itself. Exclusions win, so computers that are also in an excluded group for the title will not be used as pilots.
478
+
479
+ Pilot groups can also be defined per version, in which case these will be ignored. Defining them in the title is useful for subscribed titles, where new versions are created automatically.
480
+
481
+ When a version is released, the computers in the release_groups defined in the title will automatically have this version installed - and any non-frozen computers with an older version will have it updated.
482
+
483
+ When using the --pilot-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.
484
+
485
+ When adding a new version, the pilot groups from the previous version will be inherited if you don't specify any. To make the new version have no pilot groups, and fall back to these defined in the title, use '#{Xolo::NONE}'
486
+
487
+ NOTE: Any non-excluded computer can be used for piloting at any time by manually installing the yet-to-be-released version using `sudo xolo install` or `xadm deploy`. The members of the pilot groups are just the ones that will have it auto-installed.
488
+ ENDDESC
489
+ },
490
+
491
+ # Whenever one of the groups listed is Xolo::TARGET_ALL ('all') then all other groups are
492
+ # ignored or deleted and the array contains only 'all'
493
+ #
494
+ # @!attribute release_groups
495
+ # @return [Array<String>] Jamf groups that will automatically get this title installed or
496
+ # updated when released
497
+ release_groups: {
498
+ label: 'Release Computer Groups',
499
+ cli: :r,
500
+ validate: true,
501
+ type: :string,
502
+ multi: true,
503
+ readline_prompt: 'Group Name',
504
+ changelog: true,
505
+ readline: :jamf_computer_group_names,
506
+ invalid_msg: 'Invalid release group(s). Must exist in Jamf and not be excluded.',
507
+ desc: <<~ENDDESC
508
+ One or more Jamf Computer Groups whose members will automatically have this title installed when new versions are released.
509
+
510
+ If your Xolo administrators allow it, you can use '#{Xolo::TARGET_ALL}' to auto-install on all computers that aren't excluded. If not, you'll be told how to request setting release groups to '#{Xolo::TARGET_ALL}'.
511
+
512
+ NOTE: Titles can always be installed manually (via command line or Self Service) on non-excluded computers. It's OK to have no release groups.
513
+
514
+ When using the --release-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.
515
+
516
+ To remove all existing, use '#{Xolo::NONE}'.
517
+
518
+ NOTE: When a version is in 'pilot', before it is released, these groups are ignored, but instead a set of 'pilot' groups is defined for each version - and those groups will have that version auto-installed.
519
+ ENDDESC
520
+ },
521
+
522
+ # @!attribute excluded_groups
523
+ # @return [Array<String>] Jamf groups that are not allowed to install this title
524
+ excluded_groups: {
525
+ label: 'Excluded Computer Groups',
526
+ cli: :x,
527
+ validate: true,
528
+ type: :string,
529
+ multi: true,
530
+ changelog: true,
531
+ readline_prompt: 'Group Name',
532
+ readline: :jamf_computer_group_names,
533
+ invalid_msg: 'Invalid excluded computer group(s). Must exist in Jamf.',
534
+ desc: <<~ENDDESC
535
+ One or more Jamf Computer Groups whose members are not allowed to install this title.
536
+
537
+ When a computer is in one of these groups, the title is not available even if the computer is in a pilot or release group.
538
+
539
+ When using the --excluded-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.
540
+
541
+ To remove all existing, use '#{Xolo::NONE}'.
542
+
543
+ NOTE: Regardless of the excluded groups set here, if the server has defined a 'forced_exclusion' in its config, that group is always excluded from all xolo titles. Also, computers that are 'frozen' for a title are excluded.
544
+ ENDDESC
545
+ },
546
+
547
+ # @!attribute uninstall_script
548
+ # @return [String] The path to a script starting with '#!' that will uninstall any version of
549
+ # this title.
550
+ uninstall_script: {
551
+ label: 'Uninstall Script',
552
+ cli: :U,
553
+ validate: true,
554
+ type: :string,
555
+ readline: :get_files,
556
+ walkthru_na: :uninstall_script_na,
557
+ changelog: true,
558
+ invalid_msg: "Invalid Script Path. Local File must exist and start with '#!'.",
559
+ desc: <<~ENDDESC
560
+ By default, Xolo cannot un-install a title. To make it do so you must provide either --uninstall-script or --uninstall-ids.
561
+
562
+ When using --uninstall-script, provide a path to a local file containing a script (starting with '#!') that will uninstall any version of this title.
563
+
564
+ This is useful when the publisher provides a custom uninstaller, or to uninstall things that were not installed via .pkg files (e.g. drag-installs).
565
+
566
+ Either --uninstall-script or --uninstall-ids must be provided if you want to set --expiration.
567
+
568
+ Use '#{Xolo::NONE}' to unset this,
569
+ ENDDESC
570
+ },
571
+
572
+ # @!attribute uninstall_ids
573
+ # @return [String] One or more package identifiers recognized by pkgutil, that will uninstall the
574
+ # currently installed version of if this title.
575
+ uninstall_ids: {
576
+ label: 'Uninstall IDs',
577
+ cli: :u,
578
+ validate: true,
579
+ type: :string,
580
+ multi: true,
581
+ multi_prompt: 'Pkg ID',
582
+ walkthru_na: :uninstall_ids_na,
583
+ changelog: true,
584
+ invalid_msg: 'Invalid uninstall ids',
585
+ desc: <<~ENDDESC
586
+ By default, Xolo cannot un-install a title. To make it do so you must provide either --uninstall-ids or --uninstall-script.
587
+
588
+ When using --uninstall-ids, provide one or more package identifiers, as listed by `pkgutil --pkgs`.
589
+ Xolo will use them to create a Jamf Script that will delete all the files that were installed by the matching .pkg installers.
590
+
591
+ NOTE: Package IDs will not work if the item was installed without using an installer.pkg
592
+ (e.g. drag-installing)
593
+
594
+ Either --uninstall-script or --uninstall-ids must be provided if you want to set --expiration.
595
+
596
+ When using the --uninstall-ids CLI option, you can specify more than one ID by using the option more than once, or by providing a single option value with the IDs separated by commas.
597
+
598
+ Use '#{Xolo::NONE}' to unset this,
599
+ ENDDESC
600
+ },
601
+
602
+ # @!attribute expiration
603
+ # @return [Integer] Number of days of disuse before the title is uninstalled.
604
+ expiration: {
605
+ label: 'Expire Days',
606
+ cli: :e,
607
+ validate: true,
608
+ type: :integer,
609
+ walkthru_na: :expiration_na,
610
+ changelog: true,
611
+ invalid_msg: "Invalid expiration. Must be a non-negative integer, or zero or '#{Xolo::NONE}' for no expiration.",
612
+ desc: <<~ENDDESC
613
+ Automatically uninstall this title if none of the items listed as '--expire-paths' have been opened in this number of days.
614
+ This can be useful for reclaiming unused licenses, especially if users can re-install as needed via Self Service.
615
+
616
+ IMPORTANT:
617
+ - This title must have an '--uninstall-method' set, or it can't be uninstalled by xolo
618
+ - You must define one or more --expire-paths
619
+
620
+ Setting this to '#{Xolo::NONE}' or zero, means 'do not expire'.
621
+ ENDDESC
622
+ },
623
+
624
+ # @!attribute expire_paths
625
+ # @return [Array<String>] App names that are considered to be 'used' if they spend any time
626
+ # in the foreground.
627
+ expire_paths: {
628
+ label: 'Expiration Paths',
629
+ cli: :E,
630
+ validate: true,
631
+ type: :string,
632
+ multi: true,
633
+ walkthru_na: :expiration_paths_na,
634
+ changelog: true,
635
+ readline: :get_files,
636
+ readline_prompt: 'Path',
637
+ invalid_msg: 'Invalid expiration paths. Must be absolute paths starting with /',
638
+ desc: <<~ENDDESC
639
+ The full paths to one or items (e.g. '/Applications/Google Chrome.app') that must be opened within the --expiration period to prevent automatic uninstall of the title. The paths need not be .apps, but can be anything. If the item at the path has never been opened, its date-added is used. If it doesn't exist, it is considered to never have been opened.
640
+
641
+ If multiple paths are specified, any one of them being opened will count. This is useful for multi-app titles, such as Microsoft Office, or when different versions have different app names.
642
+
643
+ When using the --expiration-paths CLI option, you can specify more than one path by using the option more than once, or by providing a single option value with the paths separated by commas.
644
+ ENDDESC
645
+ },
646
+
647
+ # @!attribute self_service
648
+ # @return [Boolean] Does this title appear in Self Service?
649
+ self_service: {
650
+ label: 'In Self Service?',
651
+ cli: :s,
652
+ type: :boolean,
653
+ validate: :validate_boolean,
654
+ default: false,
655
+ changelog: true,
656
+ walkthru_na: :ssvc_na,
657
+ desc: <<~ENDDESC
658
+ Make this title available in Self Service. Only the currently released version will be available.
659
+
660
+ While in pilot, a version is installed via its auto-install policy,
661
+ or 'sudo xolo install <title> <version>' or updated via its Patch Policy.
662
+
663
+ It will never be available to excluded computers.
664
+
665
+ Self Service is not available for titles with the release_group 'all'.
666
+ ENDDESC
667
+ },
668
+
669
+ # @!attribute self_service_category
670
+ # @return [String] The main Self Service category in which to show this title
671
+ self_service_category: {
672
+ label: 'Self Service Category',
673
+ cli: :c,
674
+ validate: true,
675
+ type: :string,
676
+ walkthru_na: :ssvc_na,
677
+ changelog: true,
678
+ readline: :jamf_category_names,
679
+ invalid_msg: 'Invalid category. Must exist in Jamf Pro.',
680
+ desc: <<~ENDDESC
681
+ The Category in which to display this title in Self Service.
682
+ REQUIRED if self_service is true, ignored otherwise
683
+ ENDDESC
684
+ },
685
+
686
+ # NOTE: This isn't stored anywhere after its used. If it is provided via
687
+ # xadm add-title or edit-title, it will be uploaded and applied at that
688
+ # time, but then if you fetch the title again, this value will be nil.
689
+ #
690
+ # @!attribute self_service_icon
691
+ # @return [String] Path to a local image file to use as the Self Service icon for this title.
692
+ self_service_icon: {
693
+ label: 'Self Service Icon',
694
+ cli: :i,
695
+ validate: true,
696
+ type: :string,
697
+ readline: :get_files,
698
+ changelog: true,
699
+ walkthru_na: :ssvc_na,
700
+ invalid_msg: 'Invalid Icon. Must exist locally and be a PNG, JPG, or GIF file.',
701
+ desc: <<~ENDDESC
702
+ Path to a local image file to use as the icon for this title in Self Service.
703
+ The file must be a PNG, JPG, or GIF file. The recommended size is 512x512 pixels.
704
+ If one has already been set, this will replace it.
705
+ ENDDESC
706
+ },
707
+
708
+ # @!attribute contact_email
709
+ # @return [String] Email address for the person or team responsible for this title
710
+ contact_email: {
711
+ label: 'Contact Email Address',
712
+ cli: :m,
713
+ required: true,
714
+ validate: true,
715
+ type: :string,
716
+ changelog: true,
717
+ invalid_msg: 'Invalid Email address.',
718
+ desc: <<~ENDDESC
719
+ The email address of the team or person responsible for this title. Used for notifications, questions, etc.
720
+
721
+ A mailing list for a team is preferable to an individual's email address, since individuals may leave the team.
722
+ ENDDESC
723
+ },
724
+
725
+ # @!attribute created_by
726
+ # @return [String] The login of the admin who created this title
727
+ created_by: {
728
+ label: 'Created By',
729
+ type: :string,
730
+ cli: false,
731
+ read_only: true, # maintained by the server, not editable by xadm TODO: same as cli: false??
732
+ desc: <<~ENDDESC
733
+ The login of the admin who created this title.
734
+ ENDDESC
735
+ },
736
+
737
+ # @!attribute creation_date
738
+ # @return [Time] The date this title was created.
739
+ creation_date: {
740
+ label: 'Creation Date',
741
+ type: :time,
742
+ cli: false,
743
+ read_only: true, # maintained by the server, not editable by xadm
744
+ desc: <<~ENDDESC
745
+ The date this title was created.
746
+ ENDDESC
747
+ },
748
+
749
+ # @!attribute modified_by
750
+ # @return [String] The login of the admin who last modified this title
751
+ modified_by: {
752
+ label: 'Modified By',
753
+ type: :string,
754
+ cli: false,
755
+ read_only: true, # maintained by the server, not editable by xadm
756
+ desc: <<~ENDDESC
757
+ The login of the admin who last modified this title.
758
+ ENDDESC
759
+ },
760
+
761
+ # @!attribute modification_date
762
+ # @return [Time] The date this title was last modified.
763
+ modification_date: {
764
+ label: 'Modification Date',
765
+ type: :time,
766
+ cli: false,
767
+ read_only: true, # maintained by the server, not editable by xadm
768
+ desc: <<~ENDDESC
769
+ The date this title was last modified.
770
+ ENDDESC
771
+ },
772
+
773
+ # @!attribute version_order
774
+ # @return [Array<String>] The known versions, newest to oldest
775
+ version_order: {
776
+ label: 'Versions',
777
+ type: :string,
778
+ multi: true,
779
+ cli: false,
780
+ read_only: true, # maintained by the server, not editable by xadm
781
+ desc: <<~ENDDESC
782
+ The known versions of the title, newest to oldest
783
+ ENDDESC
784
+ },
785
+
786
+ # @!attribute released_version
787
+ # @return [String] The currently released version, if any
788
+ released_version: {
789
+ label: 'Released Version',
790
+ type: :string,
791
+ cli: false,
792
+ changelog: true,
793
+ read_only: true, # maintained by the server, not editable by xadm
794
+ desc: <<~ENDDESC
795
+ The currently released version
796
+ ENDDESC
797
+ },
798
+
799
+ # @!attribute ted_id_number
800
+ # @return [Integer] The Windoo::SoftwareTitle#softwareTitleId
801
+ ted_id_number: {
802
+ label: 'Title Editor ID Number',
803
+ type: :integer,
804
+ cli: false,
805
+ changelog: false,
806
+ read_only: true, # maintained by the server, not editable by xadm
807
+ desc: <<~ENDDESC
808
+ The id number of the matching Software Title in the Title Editor
809
+ ENDDESC
810
+ },
811
+
812
+ # @!attribute jamf_patch_title_id
813
+ # @return [Integer] The Windoo::SoftwareTitle#softwareTitleId
814
+ jamf_patch_title_id: {
815
+ label: 'The Jamf ID Number of this Patch Title',
816
+ type: :integer,
817
+ cli: false,
818
+ changelog: false,
819
+ read_only: true, # maintained by the server, not editable by xadm
820
+ desc: <<~ENDDESC
821
+ The id number of the matching Patch Title in the Jamf Pro
822
+ ENDDESC
823
+ }
824
+
825
+ }.freeze
826
+
827
+ ATTRIBUTES.each do |attr, deets|
828
+ attr_accessor attr
829
+
830
+ # boolean attrs get a ? method
831
+ alias_method "#{attr}?", attr if deets[:type] == :boolean
832
+
833
+ next unless deets[:multi]
834
+
835
+ # ensure that multi value attrs return an empty array if nil
836
+ define_method attr do
837
+ iv = "@#{attr}"
838
+ instance_variable_set(iv, []) if instance_variable_get(iv).nil?
839
+ instance_variable_get(iv)
840
+ end
841
+ end
842
+
843
+ # Constructor
844
+ ######################
845
+ ######################
846
+
847
+ def initialize(data_hash)
848
+ super
849
+ # zero means no expiration
850
+ @expiration = nil if @expiration.to_i.zero?
851
+ end
852
+
853
+ # Instance Methods
854
+ ######################
855
+ ######################
856
+
857
+ # the latest version of this title in Xolo
858
+ # @return [String]
859
+ ####################
860
+ def latest_version
861
+ version_order&.first
862
+ end
863
+
864
+ # Is this a managed title?
865
+ # @return [Boolean]
866
+ #######################
867
+ def managed?
868
+ !subscribed?
869
+ end
870
+
871
+ # Does this title get its pkgs from AutoPkg?
872
+ # @return [Boolean]
873
+ ############################
874
+ def autopkg_enabled?
875
+ autopkg_recipe && autopkg_dir
876
+ end
877
+
878
+ end # class Title
879
+
880
+ end # module BaseClasses
881
+
882
+ end # module Core
883
+
884
+ end # module Xolo