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.
@@ -0,0 +1,641 @@
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 Versions/Patches in the
18
+ # Xolo Server, Admin, and Client modules.
19
+ #
20
+ # This class holds the common aspects of Xolo Versinos 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 Version < Xolo::Core::BaseClasses::ServerObject
27
+
28
+ # Mixins
29
+ #############################
30
+ #############################
31
+
32
+ # Constants
33
+ #############################
34
+ #############################
35
+
36
+ # The title editor requires a value for min os, so use this
37
+ # as the default if not specified in the server config
38
+ DEFAULT_MIN_OS = '10.9'
39
+
40
+ # when this is provided as a killapp, the killapp will
41
+ # be defined by the app_name and app_bundle_id used in the
42
+ # title.
43
+ USE_TITLE_FOR_KILLAPP = 'use-title'
44
+
45
+ # Has been created in Xolo, but not yet made available
46
+ # for installation
47
+ STATUS_PENDING = 'pending'
48
+
49
+ # Has been released from the Title Editor, but is only
50
+ # available for piloting in Xolo. Will be auto-installed
51
+ # on non-excluded members of the pilot groups
52
+ STATUS_PILOT = 'pilot'
53
+
54
+ # Has been fully released in Xolo, this is the currently
55
+ # 'live' version. Will be auto-installed
56
+ # on non-excluded members of the release groups
57
+ STATUS_RELEASED = 'released'
58
+
59
+ # Was pending or pilot, but was never released in Xolo,
60
+ # and now a newer version has been released
61
+ STATUS_SKIPPED = 'skipped'
62
+
63
+ # Was released in Xolo, but now a newer version has been
64
+ # released
65
+ STATUS_DEPRECATED = 'deprecated'
66
+
67
+ # Attributes
68
+ ######################
69
+ ######################
70
+
71
+ # Attributes of Versions
72
+ # See the definition for {Xolo::Core::BaseClasses::Title::ATTRIBUTES}
73
+ ATTRIBUTES = {
74
+
75
+ # @!attribute title
76
+ # @return [String] The title to which this version belongs
77
+ title: {
78
+ label: 'Title',
79
+ read_only: true,
80
+ immutable: true,
81
+ cli: false,
82
+ type: :string,
83
+ validate: true,
84
+ invalid_msg: 'Not a valid version! Cannot already exist in this title.',
85
+ desc: <<~ENDDESC
86
+ A unique version string identifying this version in this title, e.g. '12.34.5'.
87
+ ENDDESC
88
+ },
89
+
90
+ # @!attribute version
91
+ # @return [String] The version-string for this version.
92
+ version: {
93
+ label: 'Version',
94
+ required: true,
95
+ immutable: true,
96
+ do_not_inherit: true,
97
+ cli: false,
98
+ type: :string,
99
+ validate: true,
100
+ invalid_msg: 'Not a valid version! Cannot already exist in this title.',
101
+ ted_attribute: :version,
102
+ desc: <<~ENDDESC
103
+ A unique version string identifying this version in this title, e.g. '12.34.5'.
104
+ ENDDESC
105
+ },
106
+
107
+ # @!attribute publish_date
108
+ # @return [Time] When the publisher released this version. Defaults to today.
109
+ publish_date: {
110
+ label: 'Publish Date',
111
+ type: :time,
112
+ default: -> { Time.now.to_s },
113
+ title_type: Xolo::MANAGED,
114
+ do_not_inherit: true,
115
+ cli: :d,
116
+ validate: true,
117
+ changelog: true,
118
+ ted_attribute: :releaseDate,
119
+ invalid_msg: 'Not a valid date!',
120
+ desc: <<~ENDDESC
121
+ The date this version was released by the publisher.
122
+ Default is today.
123
+ ENDDESC
124
+ },
125
+
126
+ # @!attribute min_os = required to create the Title Editor Patch
127
+ # @return [String] The minimum OS version that this version can be installed on.
128
+ min_os: {
129
+ label: 'Minimum OS',
130
+ cli: :o,
131
+ type: :string,
132
+ title_type: Xolo::MANAGED,
133
+ required: false,
134
+ validate: true,
135
+ default: DEFAULT_MIN_OS,
136
+ changelog: true,
137
+ ted_attribute: :minimumOperatingSystem,
138
+ invalid_msg: "Not a valid OS version! Must be XX[.YY[.ZZ]] format, e.g. '10.9' or '11.0.1'.",
139
+ desc: <<~ENDDESC
140
+ The lowest version of macOS able to run this version of this title. Defaults to a value set in the server config, or #{DEFAULT_MIN_OS}.
141
+ ENDDESC
142
+ },
143
+
144
+ # @!attribute max_os
145
+ # @return [String] The maximum OS version that this version can be installed on.
146
+ max_os: {
147
+ label: 'Maximum OS',
148
+ cli: :O,
149
+ type: :string,
150
+ title_type: Xolo::MANAGED,
151
+ validate: true,
152
+ changelog: true,
153
+ # default: Xolo::NONE,
154
+ invalid_msg: "Not a valid OS version! Must be XX[.YY[.ZZ]] format, e.g. '10.9' or '11.0.1'.",
155
+ desc: <<~ENDDESC
156
+ The highest version of macOS able to run this version of this title.
157
+ ENDDESC
158
+ },
159
+
160
+ # @!attribute reboot
161
+ # @return [Boolean] Does this version need a reboot after installing?
162
+ reboot: {
163
+ label: 'Reboot',
164
+ cli: :r,
165
+ type: :boolean,
166
+ title_type: Xolo::MANAGED,
167
+ validate: :validate_boolean,
168
+ ted_attribute: :reboot,
169
+ default: false,
170
+ changelog: true,
171
+ desc: <<~ENDDESC
172
+ The installation of this version requires the computer to reboot. Users will be notified before installation.
173
+ ENDDESC
174
+ },
175
+
176
+ # @!attribute standalone
177
+ # @return [Boolean] Is this version a full installer? (if not, its an incremental patch)
178
+ standalone: {
179
+ label: 'Standalone',
180
+ cli: :s,
181
+ type: :boolean,
182
+ title_type: Xolo::MANAGED,
183
+ validate: :validate_boolean,
184
+ default: true,
185
+ ted_attribute: :standalone,
186
+ changelog: true,
187
+ desc: <<~ENDDESC
188
+ The installer for this version is a full installer, not an incremental patch that must be installed on top of an earlier version.
189
+ ENDDESC
190
+ },
191
+
192
+ # @!attribute killapps
193
+ # @return [Array<String>] The apps that cannot be running when this version is installed
194
+ killapps: {
195
+ label: 'KillApps',
196
+ cli: :k,
197
+ type: :string,
198
+ title_type: Xolo::MANAGED,
199
+ multi: true,
200
+ readline: true,
201
+ readline_prompt: 'KillApp',
202
+ validate: true,
203
+ changelog: true,
204
+ # default: Xolo::NONE,
205
+ invalid_msg: 'Not a valid killapp!',
206
+ desc: <<~ENDDESC
207
+ A killapp is an application that cannot be running while this version is installed.
208
+ If running, installation is delayed, and users are notified to quit.
209
+
210
+ Killapps are defined by an app name e.g. 'Google Chrome.app', and the app's Bundle ID
211
+ e.g. 'com.google.chrome'.
212
+
213
+ Specify them together separated by a semi-colon, e.g.
214
+ 'Google Chrome.app;com.google.chrome'
215
+
216
+ If the title for this version has a defined --app-name and --app-bundle-id, you can
217
+ use them as a killapp by specifying '#{USE_TITLE_FOR_KILLAPP}'
218
+
219
+ If not using --walkthru you can use --killapps multiple times
220
+ ENDDESC
221
+ },
222
+
223
+ # @!attribute patch_unknown
224
+ # @return [Boolean] Should 'unknown' versions of this title be updated to this version automatically?
225
+ patch_unknown: {
226
+ label: 'Patch Unknown Versions',
227
+ cli: :U,
228
+ type: :boolean,
229
+ validate: :validate_boolean,
230
+ default: false,
231
+ changelog: true,
232
+ desc: <<~ENDDESC
233
+ Should 'unknown' versions of this title be updated to this version automatically by Jamf Patch Management?
234
+
235
+ When Jamf Patch determines that a title is installed on a computer, but version reported is not among those known to Jamf Patch, it marks the version as 'unknown'. Setting this option to true will cause Jamf Patch to automatically install the pkg for this version on those Macs with 'unknown' versions.
236
+
237
+ This can cause problems if that unknown version is actually newer than this version, e.g. a beta or pre-release version, or when the app has a 'self-update' mechanism that installs newer versions outside of Jamf Patch before it is aware of them.
238
+
239
+ But sometimes it may be desirable to have all unknown versions updated to this version, e.g. when the title is a helper app that is not regularly updated, or when the title is being newly managed by Xolo/Jamf Patch and you want to get all existing installations onto this version.
240
+ ENDDESC
241
+ },
242
+
243
+ # @!attribute pilot_groups
244
+ # @return [Array<String>] Jamf groups that will automatically get this version installed or
245
+ # updated for piloting
246
+ pilot_groups: {
247
+ label: 'Pilot Computer Groups',
248
+ cli: :p,
249
+ validate: true,
250
+ type: :string,
251
+ multi: true,
252
+ changelog: true,
253
+ readline_prompt: 'Group Name',
254
+ readline: :jamf_computer_group_names,
255
+ invalid_msg: "Invalid group. Must be an existing Jamf Computer Group, or '#{Xolo::NONE}'.",
256
+ desc: <<~ENDDESC
257
+ One or more Jamf Computer Groups whose members will automatically have this version installed or updated for testing before it is released.
258
+
259
+ 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.
260
+
261
+ When this version is released, the computers in the release_groups defined in the title will automatically have this version installed - and any computers with an older version will have it updated.
262
+
263
+ 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.
264
+
265
+ 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 use '#{Xolo::NONE}'.
266
+
267
+ 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 <title> <version>`. The members of the pilot groups are just the ones that will have it auto-installed.
268
+ ENDDESC
269
+ },
270
+
271
+ # @!attribute jamf_pkg
272
+ # @return [String] The file name of the installer for the Jamf Package object that
273
+ # installs this version. 'xolo-<title>-<version>.pkg' (or .zip)
274
+ pkg_to_upload: {
275
+ label: 'Upload Package',
276
+ type: :string,
277
+ cli: :u,
278
+ validate: true,
279
+ readline: :get_files,
280
+ do_not_inherit: true,
281
+ hide_from_info: true,
282
+ invalid_msg: 'Invalid installer pkg. Must exist locally and be a flat .pkg file',
283
+ desc: <<~ENDDESC
284
+ The path to a local copy of the installer package for this version. Will be uploaded to Xolo and then Jamf Pro distribution point(s), replacing any previously uploaded.
285
+
286
+ Must be a flat .pkg file, or a .zip compressed old-style bundle package.
287
+
288
+ It will be renamed to 'xolo-<title>-<version>.pkg' (or .zip).
289
+ If your Xolo server is confiured to sign unsigned packages, it will do so along the way.
290
+
291
+ Required when creating a new version unless the title is configrued to use autopkg.
292
+ ENDDESC
293
+ },
294
+
295
+ # @!attribute status
296
+ # @return [String] One of: STATUS_PENDING, STATUS_PILOT, STATUS_RELEASED,
297
+ # STATUS_SKIPPED, or STATUS_DEPRECATED
298
+ status: {
299
+ label: 'Status',
300
+ type: :symbol,
301
+ do_not_inherit: true,
302
+ cli: false,
303
+ read_only: true, # maintained by the server, not editable by xadm TODO: same as cli: false??
304
+ desc: <<~ENDDESC
305
+ The status of this version in Xolo:
306
+ - pending: Not yet available for installation.
307
+ - pilot: Can be installed for piloting, will auto install on any pilot-groups.
308
+ - released: This is the current version, generally available, will auto-install on target groups.
309
+ - skipped: Was created, and maybe piloted, but never released.
310
+ - deprecated: Was released, but a newer version has since been released.
311
+ ENDDESC
312
+ },
313
+
314
+ # @!attribute created_by
315
+ # @return [String] The login of the admin who created this version.
316
+ created_by: {
317
+ label: 'Created By',
318
+ type: :string,
319
+ do_not_inherit: true,
320
+ cli: false,
321
+ read_only: true, # maintained by the server, not editable by xadm TODO: same as cli: false??
322
+ desc: <<~ENDDESC
323
+ The login of the admin who created this version.
324
+ ENDDESC
325
+ },
326
+
327
+ # @!attribute creation_date
328
+ # @return [Time] The date this version was created.
329
+ creation_date: {
330
+ label: 'Creation Date',
331
+ type: :time,
332
+ do_not_inherit: true,
333
+ cli: false,
334
+ read_only: true, # maintained by the server, not editable by xadm TODO: same as cli: false??
335
+ desc: <<~ENDDESC
336
+ When this version was created.
337
+ ENDDESC
338
+ },
339
+
340
+ # @!attribute modified_by
341
+ # @return [String] The login of the admin who last modified this version.
342
+ modified_by: {
343
+ label: 'Modified By',
344
+ type: :string,
345
+ cli: false,
346
+ do_not_inherit: true,
347
+ read_only: true, # maintained by the server, not editable by xadm TODO: same as cli: false??
348
+ desc: <<~ENDDESC
349
+ The login of the admin who last modified this version.
350
+ ENDDESC
351
+ },
352
+
353
+ # @!attribute modification_date
354
+ # @return [Time] The date this version was last modified.
355
+ modification_date: {
356
+ label: 'Modification Date',
357
+ type: :time,
358
+ cli: false,
359
+ do_not_inherit: true,
360
+ read_only: true, # maintained by the server, not editable by xadm TODO: same as cli: false??
361
+ desc: <<~ENDDESC
362
+ When this version was last modified.
363
+ ENDDESC
364
+ },
365
+
366
+ # @!attribute deployed_by
367
+ # @return [String] The login of the admin who released this version in Xolo.
368
+ # This is when the Xolo sets the status of this version to 'released', making it
369
+ # no longer 'in pilot' and the one to be installed or updated by default.
370
+ released_by: {
371
+ label: 'Released By',
372
+ type: :string,
373
+ cli: false,
374
+ do_not_inherit: true,
375
+ read_only: true, # maintained by the server, not editable by xadm TODO: same as cli: false??
376
+ desc: <<~ENDDESC
377
+ The login of the admin who released this version in Xolo.
378
+ This is when the Xolo sets the status of this version to 'released', making it
379
+ no longer 'in pilot' and the one to be installed or updated by default.
380
+ ENDDESC
381
+ },
382
+
383
+ # @!attribute release_date
384
+ # @return [Time] The timestamp this version was released in Xolo.
385
+ # This is when the Xolo sets the status of this version to 'released', making it
386
+ # no longer 'in pilot' and the one to be installed or updated by default.
387
+ release_date: {
388
+ label: 'Release Date',
389
+ type: :time,
390
+ cli: false,
391
+ do_not_inherit: true,
392
+ read_only: true, # maintained by the server, not editable by xadm TODO: same as cli: false??
393
+ desc: <<~ENDDESC
394
+ When this version was released in Xolo.
395
+ This is when the Xolo sets the status of this version to 'released', making it
396
+ no longer 'in pilot' and the one to be installed or updated by default.
397
+ ENDDESC
398
+ },
399
+
400
+ # @!attribute deprecated_by
401
+ # @return [String] The login of the admin who deprecated this version in Xolo by releasing
402
+ # a newer version.
403
+ deprecated_by: {
404
+ label: 'Deprecated By',
405
+ type: :string,
406
+ cli: false,
407
+ do_not_inherit: true,
408
+ read_only: true, # maintained by the server, not editable by xadm TODO: same as cli: false??
409
+ desc: <<~ENDDESC
410
+ The login of the admin who deprecated this version in Xolo by releasing a newer version.
411
+ ENDDESC
412
+ },
413
+
414
+ # @!attribute deprecation_date
415
+ # @return [Time] The timestamp this version was deprecated in Xolo.
416
+ # This is when the Xolo sets the status of this version to 'deprecated', meaning
417
+ # it was released, but a newer version has since been released.
418
+ deprecation_date: {
419
+ label: 'Deprecation Date',
420
+ type: :time,
421
+ cli: false,
422
+ do_not_inherit: true,
423
+ read_only: true, # maintained by the server, not editable by xadm TODO: same as cli: false??
424
+ desc: <<~ENDDESC
425
+ When this version was deprecated in Xolo.
426
+ This is when the Xolo sets the status of this version to 'deprecated', which is when a newer version has been released.
427
+ It will still be available for manual installation until it is deleted.
428
+ Deletion is automatic after a period of time, unless the server is configured otherwise.
429
+ ENDDESC
430
+ },
431
+
432
+ # @!attribute skipped_by
433
+ # @return [String] The login of the admin who skipped this version in Xolo by releasing
434
+ # a newer version.
435
+ skipped_by: {
436
+ label: 'Skipped By',
437
+ type: :string,
438
+ cli: false,
439
+ do_not_inherit: true,
440
+ read_only: true, # maintained by the server, not editable by xadm TODO: same as cli: false??
441
+ desc: <<~ENDDESC
442
+ The login of the admin who skipped this version in Xolo by releasing a newer version.
443
+ ENDDESC
444
+ },
445
+
446
+ # @!attribute skipped_date
447
+ # @return [Time] The timestamp this version was skipped in Xolo.
448
+ # This is when the Xolo sets the status of this version to 'skipped', meaning
449
+ # it was never released in Xolo, and now a newer version has been released.
450
+ skipped_date: {
451
+ label: 'Skipped Date',
452
+ type: :time,
453
+ cli: false,
454
+ do_not_inherit: true,
455
+ read_only: true, # maintained by the server, not editable by xadm TODO: same as cli: false??
456
+ desc: <<~ENDDESC
457
+ When this version was skipped in Xolo.
458
+ This is when the Xolo sets the status of this version to 'skipped', meaning it was never released in Xolo, and now a newer version has been released.
459
+ It will be automatically deleted at the next nightly cleanup, unless the server is configured otherwise.
460
+ ENDDESC
461
+ },
462
+
463
+ # @!attribute jamf_pkg_name
464
+ # @return [String] The display name of the Jamf Package object that installs this version.
465
+ # 'xolo-<title>-<version>'
466
+ jamf_pkg_name: {
467
+ label: 'Jamf Package Name',
468
+ type: :string,
469
+ do_not_inherit: true,
470
+ cli: false,
471
+ desc: <<~ENDDESC
472
+ The display name of the Jamf Package object that installs this version. 'xolo-<title>-<version>'
473
+ ENDDESC
474
+ },
475
+
476
+ # @!attribute jamf_pkg_id
477
+ # @return [String] The id of the Jamf Package object that installs this version.
478
+ # This is an integer in a string, as are all IDs in the Jamf Pro API.
479
+ # TODO: Stop using pkgID - or any jamf ID, use names for accessing all jamf objects
480
+ jamf_pkg_id: {
481
+ label: 'Jamf Package ID',
482
+ type: :string,
483
+ read_only: true,
484
+ do_not_inherit: true,
485
+ cli: false,
486
+ desc: <<~ENDDESC
487
+ The id of the Jamf Package object that installs this version. 'xolo-<title>-<version>'
488
+ ENDDESC
489
+ },
490
+
491
+ # @!attribute jamf_pkg_file
492
+ # @return [String] The file name of the installer.pkg file used by the Jamf Package object to
493
+ # installs this version. 'xolo-<title>-<version>.pkg'
494
+ jamf_pkg_file: {
495
+ label: 'Jamf Package File',
496
+ type: :string,
497
+ do_not_inherit: true,
498
+ cli: false,
499
+ desc: <<~ENDDESC
500
+ The installer filename of the Jamf Package object that installs this version: 'xolo-<title>-<version>.pkg' (or .zip).
501
+ ENDDESC
502
+ },
503
+
504
+ # @!attribute upload_date
505
+ # @return [Time] Timestamp of the original pkg upload
506
+ upload_date: {
507
+ label: 'Original Pkg Upload Date',
508
+ type: :time,
509
+ do_not_inherit: true,
510
+ cli: false,
511
+ read_only: true, # maintained by the server, not editable by xadm TODO: same as cli: false??
512
+ desc: <<~ENDDESC
513
+ The time the first .pkg for this version was uploaded.
514
+ ENDDESC
515
+ },
516
+
517
+ # @!attribute uploaded_by
518
+ # @return [String] The login of the admin who uploaded the original pkg
519
+ uploaded_by: {
520
+ label: 'Original Pkg Uploaded By',
521
+ type: :string,
522
+ cli: false,
523
+ do_not_inherit: true,
524
+ read_only: true, # maintained by the server, not editable by xadm TODO: same as cli: false??
525
+ desc: <<~ENDDESC
526
+ The login of the admin who last uploaded the first .pkg for this version.
527
+ ENDDESC
528
+ },
529
+
530
+ # @!attribute reupload_date
531
+ # @return [Time] Timestamp of the latest pkg reupload
532
+ reupload_date: {
533
+ label: 'Latest Pkg Re-Upload Date',
534
+ type: :time,
535
+ do_not_inherit: true,
536
+ cli: false,
537
+ read_only: true, # maintained by the server, not editable by xadm TODO: same as cli: false??
538
+ desc: <<~ENDDESC
539
+ The last time the .pkg for this version was re-uploaded, after the initial upload.
540
+ ENDDESC
541
+ },
542
+
543
+ # @!attribute reuploaded_by
544
+ # @return [String] The login of the admin who reuploaded the latest pkg
545
+ reuploaded_by: {
546
+ label: 'Latest Pkg Re-Uploaded By',
547
+ type: :string,
548
+ cli: false,
549
+ do_not_inherit: true,
550
+ read_only: true, # maintained by the server, not editable by xadm TODO: same as cli: false??
551
+ desc: <<~ENDDESC
552
+ The login of the admin who last re-uploaded the .pkg for this version, after the initial upload.
553
+ ENDDESC
554
+ },
555
+
556
+ # @!attribute dist_pkg
557
+ # @return [Boolean] Is the most recently uploaded package a Distribution package? If so it can be used
558
+ # for MDM deployment.
559
+ dist_pkg: {
560
+ label: 'Distribution Package',
561
+ type: :boolean,
562
+ do_not_inherit: true,
563
+ cli: false,
564
+ desc: <<~ENDDESC
565
+ If true, the most recently uploaded .pkg file is a flat Distribution package, and can be deployed via MDM using the 'xadm deploy' command. Nil if no pkg was ever uploaded via xolo. Uploading a different .pkg file via other means will not change this value, and may cause the pkg to fail to deploy via MDM.
566
+ This value is set by the server when the pkg is uploaded.
567
+ ENDDESC
568
+ },
569
+
570
+ # @!attribute sha_512
571
+ # @return [String] The SHA512 checksum of the most recently uploaded package
572
+ sha_512: {
573
+ label: 'Package Checksum (SHA512)',
574
+ type: :string,
575
+ do_not_inherit: true,
576
+ cli: false,
577
+ desc: <<~ENDDESC
578
+ The SHA512 checksum of the most recently uploaded package.
579
+ NOTE: The Jamf Server may use an MD5 checksum in the package object.
580
+ This value is set by the server when the pkg is uploaded.
581
+ ENDDESC
582
+ }
583
+
584
+ }.freeze
585
+
586
+ ATTRIBUTES.each do |attr, deets|
587
+ attr_accessor attr
588
+
589
+ # boolean attrs get a ? method
590
+ alias_method "#{attr}?", attr if deets[:type] == :boolean
591
+
592
+ next unless deets[:multi]
593
+
594
+ # ensure that multi value attrs return an empty array if nil
595
+ define_method attr do
596
+ ivar_name = "@#{attr}"
597
+ instance_variable_set(ivar_name, []) if instance_variable_get(ivar_name).nil?
598
+ instance_variable_get(ivar_name)
599
+ end
600
+ end
601
+
602
+ # Constructor
603
+ ######################
604
+ ######################
605
+
606
+ # Instance Methods
607
+ ######################
608
+ ######################
609
+
610
+ ######################
611
+ def pilot?
612
+ status == STATUS_PILOT || status.nil?
613
+ end
614
+
615
+ ######################
616
+ def released?
617
+ status == STATUS_RELEASED
618
+ end
619
+
620
+ ######################
621
+ def skipped?
622
+ status == STATUS_SKIPPED
623
+ end
624
+
625
+ ######################
626
+ def deprecated?
627
+ status == STATUS_DEPRECATED
628
+ end
629
+
630
+ ######################
631
+ def pending?
632
+ status == STATUS_PENDING
633
+ end
634
+
635
+ end # class Title
636
+
637
+ end # module BaseClasses
638
+
639
+ end # module Core
640
+
641
+ end # module Xolo