Class: Xolo::Server::Version

Inherits:
Core::BaseClasses::Version
  • Object
show all
Includes:
Comparable, Helpers::JamfPro, Helpers::Log, Helpers::Notification, Helpers::TitleEditor, Mixins::Changelog, Mixins::VersionJamfAccess, Mixins::VersionTedAccess
Defined in:
lib/xolo/server/version.rb

Overview

Xolo Version/Patch as used on the Xolo Server

The code in this file mostly deals with the data on the Xolo server itself, and general methods for manipulating the version.

Code for interacting with the Title Editor and Jamf Pro are in the helpers and mixins.

NOTE be sure to only instantiate these using the server’s ‘instantiate_version’ method, or else they might not have all the correct innards

Constant Summary collapse

VERSIONS_DIRNAME =

On the server, xolo versions are represented by JSON files in the ‘versions’ directory of the title directory

So a title ‘foobar’ would have a directory

(Xolo::Server::DATA_DIR)/titles/foobar/

In there will be a ‘versions’ dir containing json files for each version of the title.

'versions'
JAMF_PKG_NOTES_VERS_PH =
'XOLO-VERSION-HERE'
JAMF_PKG_NOTES_TITLE_PH =
'XOLO-TITLE-HERE'
JAMF_PKG_NOTES_PREFIX =

The ‘Notes’ of a jamf pkg are the Xolo Title Description, with this prepended

<<~ENDNOTES
  This package is maintained by 'xolo', to install version '#{JAMF_PKG_NOTES_VERS_PH}' of title '#{JAMF_PKG_NOTES_TITLE_PH}'. The description in Xolo is:


ENDNOTES
MAX_PKG_DELETION_THREADS =
10
STUB_PATCH_VERSION =

STUB PATCH

We create a fake ‘stub’ patch with all ted titles so that we can activate the title before any real version is added and also accept any EA/version_script, either manually or automatically

This version should never be available to any mac, and needs no patch policies or packages.

It should also never be deleted until the title itself is deleted.

'0.0.0x0'
STUB_PATCH_CAPABILITY_CRITERION_NAME =

machines that can install this version

'Operating System Version'
STUB_PATCH_CAPABILITY_CRITERION_OPERATOR =
'less than or equal'
STUB_PATCH_CAPABILITY_CRITERION_VALUE =
'10.0'
STUB_PATCH_COMPONENT_NAME =

machines that have this version installed

'Xolo Stub'
STUB_PATCH_COMPONENT_CRITERION_NAME =
'Application Title'
STUB_PATCH_COMPONENT_CRITERION_OPERATOR =
'is'
STUB_PATCH_COMPONENT_CRITERION_VALUE =
'XoloStub-DoesNotExist.app'

Constants included from Mixins::VersionJamfAccess

Mixins::VersionJamfAccess::JAMF_AUTO_REINSTALL_WAIT_SECS, Mixins::VersionJamfAccess::JAMF_POLICY_NAME_AUTO_INSTALL_SFX, Mixins::VersionJamfAccess::JAMF_POLICY_NAME_AUTO_REINSTALL_SFX, Mixins::VersionJamfAccess::JAMF_POLICY_NAME_MANUAL_INSTALL_SFX, Mixins::VersionJamfAccess::JAMF_SMART_GROUP_NAME_INSTALLED_SFX

Constants included from Mixins::Changelog

Mixins::Changelog::TITLE_CHANGELOG_FILENAME

Constants included from Helpers::Notification

Helpers::Notification::ALERT_TOOL_EMAIL_PREFIX, Helpers::Notification::DFT_EMAIL_FROM

Constants included from Helpers::JamfPro

Helpers::JamfPro::PATCH_REPORT_JPAPI_PAGE_SIZE, Helpers::JamfPro::PATCH_REPORT_UNKNOWN_VERSION

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Mixins::VersionTedAccess

#create_patch_in_ted, #delete_patch_from_ted, #enable_ted_patch, #get_patch_component_criteria_params, included, #repair_ted_patch, #set_app_component, #set_ea_component, #set_patch_capabilites, #set_patch_killapps, #set_ted_patch_component_criteria, #ted_patch_url, #update_patch_in_ted

Methods included from Mixins::VersionJamfAccess

#activate_patch_version_in_jamf, #assign_pkg_to_patch_in_jamf, #configure_jamf_auto_install_policy, #configure_jamf_auto_reinstall_policy, #configure_jamf_installed_group, #configure_jamf_manual_install_policy, #create_in_jamf, #create_jamf_auto_install_policy, #create_jamf_auto_reinstall_policy, #create_jamf_installed_group, #create_jamf_manual_install_policy, #create_jamf_package, #create_jamf_patch_policy, #delete_jamf_package, #delete_version_from_jamf, #deploy_via_mdm, #disable_policies_for_deprecation_or_skipping, #expand_groups_for_deploy, extended, included, #jamf_auto_install_policy, #jamf_auto_install_policy_exist?, #jamf_auto_install_policy_url, #jamf_auto_reinstall_policy, #jamf_auto_reinstall_policy_exist?, #jamf_auto_reinstall_policy_url, #jamf_gui_url, #jamf_installed_group, #jamf_installed_group_criteria, #jamf_installed_group_exist?, #jamf_installed_group_url, #jamf_manual_install_policy, #jamf_manual_install_policy_exist?, #jamf_manual_install_policy_url, #jamf_package, #jamf_package_notes, #jamf_package_url, #jamf_patch_policy, #jamf_patch_policy_exist?, #jamf_patch_policy_url, #jamf_patch_version, #patch_report, #remove_exclusions_from_deploy, #remove_invalid_computers_for_deploy, #repair_jamf_auto_install_policy, #repair_jamf_auto_reinstall_policy, #repair_jamf_installed_group, #repair_jamf_manual_install_policy, #repair_jamf_package, #repair_jamf_patch_policy, #repair_jamf_version_objects, #reset_policies_to_pilot, #set_policy_exclusions, #set_policy_pilot_groups, #set_policy_release_groups, #set_policy_to_all_targets, #update_excluded_groups, #update_jamf_package_notes, #update_jamf_pkg_min_os, #update_jamf_pkg_reboot, #update_pilot_groups, #update_release_groups, #update_version_in_jamf, #wait_to_enable_reinstall_policy

Methods included from Mixins::Changelog

#backup_changelog, backup_file_dir, #changelog, #changelog_backup_file, #changelog_file, #changelog_lock, changelog_locks, #delete_changelog, #hostname_from_ip, included, #log_change, #log_update_changes, #note_changes_for_update_and_log

Methods included from Helpers::Notification

#email_from, included, #send_alert, #send_email, #send_email_alert, #server_fqdn, #server_name

Methods included from Helpers::Log

included, #log_debug, #log_error, #log_fatal, #log_info, #log_unknown, #log_warn, #logger, #session_svr_obj_id

Methods included from Helpers::TitleEditor

included

Methods included from Helpers::JamfPro

extended, included, #jamf_gui_url, #jamf_xolo_category_id, #valid_forced_exclusion_group_name

Constructor Details

#initialize(data_hash) ⇒ Version

NOTE: be sure to only instantiate these using the servers ‘instantiate_version’ method, or else they might not have all the correct innards



307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
# File 'lib/xolo/server/version.rb', line 307

def initialize(data_hash)
  super

  # These attrs aren't defined in the ATTRIBUTES
  # and/or are not stored in the on-disk json file

  @ted_id_number ||= data_hash[:ted_id_number]
  @jamf_pkg_id ||= data_hash[:jamf_pkg_id]

  # and these can be generated now
  @jamf_obj_name_pfx = "#{Xolo::Server::JAMF_OBJECT_NAME_PFX}#{title}-#{version}"

  @jamf_pkg_name ||= @jamf_obj_name_pfx

  @jamf_installed_group_name = "#{jamf_obj_name_pfx}#{JAMF_SMART_GROUP_NAME_INSTALLED_SFX}"

  @jamf_auto_install_policy_name = "#{jamf_obj_name_pfx}#{JAMF_POLICY_NAME_AUTO_INSTALL_SFX}"
  @jamf_manual_install_policy_name = "#{jamf_obj_name_pfx}#{JAMF_POLICY_NAME_MANUAL_INSTALL_SFX}"
  @jamf_auto_reinstall_policy_name = "#{jamf_obj_name_pfx}#{JAMF_POLICY_NAME_AUTO_REINSTALL_SFX}"

  @jamf_patch_policy_name = @jamf_obj_name_pfx

  # we set @jamf_pkg_file when a pkg is uploaded
  # since we don't know until then if its a .pkg or .zip
  # It will be stored in the local data and reloaded as needed
end

Instance Attribute Details

#changes_for_updateHash (readonly)

Also when applying updates, this will hold the changes being made: the differences between tne current attributs and the new_data_for_update We’ll figure this out at the start of the update and can use it later to 1) avoid doing things we don’t need to 2) log the changes in the change log at the very end

This is a Hash with keys of the attribute names that have changed the values are Hashes with keys of :old and :new e.g. { pilot_groups: { old: [‘foo’], new: [‘bar’] } }

Returns:

  • (Hash)


294
295
296
# File 'lib/xolo/server/version.rb', line 294

def changes_for_update
  @changes_for_update
end

#current_actionSymbol

Returns The current action being taken on this title one of :creating, :updating, :deleting.

Returns:

  • (Symbol)

    The current action being taken on this title one of :creating, :updating, :deleting



298
299
300
# File 'lib/xolo/server/version.rb', line 298

def current_action
  @current_action
end

#jamf_auto_install_policy_nameObject (readonly)

Jamf auto-install policies are named this



259
260
261
# File 'lib/xolo/server/version.rb', line 259

def jamf_auto_install_policy_name
  @jamf_auto_install_policy_name
end

#jamf_auto_reinstall_policy_nameObject (readonly)

Jamf auto re-install policies are named this



265
266
267
# File 'lib/xolo/server/version.rb', line 265

def jamf_auto_reinstall_policy_name
  @jamf_auto_reinstall_policy_name
end

#jamf_installed_group_nameString (readonly)

For each version there will be a smart group containing all macs that have that version of the title installed. The smart group will be named ‘xolo-<title>-<version>-installed’

It will be used as the target for the auto-reinstall

Returns:

  • (String)

    the name of the smart group



256
257
258
# File 'lib/xolo/server/version.rb', line 256

def jamf_installed_group_name
  @jamf_installed_group_name
end

#jamf_manual_install_policy_nameObject (readonly) Also known as: jamf_manual_install_trigger

Jamf manual install policies are named this



262
263
264
# File 'lib/xolo/server/version.rb', line 262

def jamf_manual_install_policy_name
  @jamf_manual_install_policy_name
end

#jamf_obj_name_pfxObject (readonly)

Jamf object names start with this



247
248
249
# File 'lib/xolo/server/version.rb', line 247

def jamf_obj_name_pfx
  @jamf_obj_name_pfx
end

#jamf_patch_policy_nameObject (readonly)

Jamf Patch Policy is named this



271
272
273
# File 'lib/xolo/server/version.rb', line 271

def jamf_patch_policy_name
  @jamf_patch_policy_name
end

#jamf_pkg_idObject (readonly)

The Jamf Package object has this jamf id



274
275
276
# File 'lib/xolo/server/version.rb', line 274

def jamf_pkg_id
  @jamf_pkg_id
end

#new_data_for_updateObject (readonly)

when applying updates, the new data is stored here so it can be accessed by update-methods and compared to the current instanace values both for updating the title, and the versions



280
281
282
# File 'lib/xolo/server/version.rb', line 280

def new_data_for_update
  @new_data_for_update
end

#server_app_instanceObject

The instance of Xolo::Server::App that instantiated this title object. This is how we access things that are available in routes and helpers, like the single Jamf and TEd connections for this App instance.



234
235
236
# File 'lib/xolo/server/version.rb', line 234

def server_app_instance
  @server_app_instance
end

#ted_id_numberObject

The Windoo::Patch#patchId



244
245
246
# File 'lib/xolo/server/version.rb', line 244

def ted_id_number
  @ted_id_number
end

#title_object(refresh: false) ⇒ Xolo::Server::Title

This might have been set already if we were instantiated via our title

Returns:



482
483
484
485
# File 'lib/xolo/server/version.rb', line 482

def title_object(refresh: false)
  @title_object = nil if refresh
  @title_object ||= server_app_instance.instantiate_title title
end

Class Method Details

.all_versions(title) ⇒ Array<String>

Returns A list of all known versions for a title, just the basenames of all the version files with the extension removed.

Returns:

  • (Array<String>)

    A list of all known versions for a title, just the basenames of all the version files with the extension removed



117
118
119
# File 'lib/xolo/server/version.rb', line 117

def self.all_versions(title)
  version_dirs(title).map { |c| c.basename.to_s }
end

.data_dir(title, version) ⇒ Pathname

The the local directory containing various files specific to the given version of a title

Returns:

  • (Pathname)


130
131
132
# File 'lib/xolo/server/version.rb', line 130

def self.data_dir(title, version)
  version_dir(title) + version
end

.data_file(title, version) ⇒ Pathname

The the local JSON file containing the current values for the given version of a title

Returns:

  • (Pathname)


143
144
145
# File 'lib/xolo/server/version.rb', line 143

def self.data_file(title, version)
  data_dir(title, version) + "#{version}.json"
end

.in_ted?(patch_id, cnx:) ⇒ Boolean

Returns Does the given patch exist in the Title Editor?.

Parameters:

  • patch_id (String)

    the id number of the patch we are looking for

Returns:

  • (Boolean)

    Does the given patch exist in the Title Editor?



182
183
184
# File 'lib/xolo/server/version.rb', line 182

def self.in_ted?(patch_id, cnx:)
  Windoo::Patch.all_ids(cnx: cnx).include? patch_id
end

.load(title, version) ⇒ Xolo::Server::Title

Instantiate from the local JSON file containing the current values for the given version of a title

NOTE: All instantiation should happen using the #instantiate_version method in the server app instance. Please don’t call this method directly

Returns:



173
174
175
176
# File 'lib/xolo/server/version.rb', line 173

def self.load(title, version)
  Xolo::Server.logger.debug "Loading version '#{version}' of title '#{title}' from file"
  new parse_json(data_file(title, version).read)
end

.locked?(title, version) ⇒ Boolean

Is a version locked for updates?

Returns:

  • (Boolean)


188
189
190
191
# File 'lib/xolo/server/version.rb', line 188

def self.locked?(title, version)
  curr_lock = Xolo::Server.object_locks.dig title, :versions, version
  curr_lock && curr_lock > Time.now
end

.manifest_file(title, version) ⇒ Pathname

The the local xml plist file containing the .pkg manifest for the given version of a title

Returns:

  • (Pathname)


156
157
158
# File 'lib/xolo/server/version.rb', line 156

def self.manifest_file(title, version)
  data_dir(title, version) + "#{version}.manifest.plist"
end

.pkg_deletion_poolQueue

The package-deletion thread pool

the auto_terminate is false to prevents the threads from being daemonized, and running after the main thread exits. This is important because launchd jobs should never do that.

See ruby-concurrency.github.io/concurrent-ruby/master/file.thread_pools.html

Returns:

  • (Queue)

    The package-deletion thread pool



202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/xolo/server/version.rb', line 202

def self.pkg_deletion_pool
  @pkg_deletion_pool ||= Concurrent::ThreadPoolExecutor.new(
    name: 'package-deletion',
    min_threads: 1, # start with 1 thread
    max_threads: MAX_PKG_DELETION_THREADS, # create at most 10 threads
    max_queue: 0, # no limit
    auto_terminate: false, # see method comments above
    idletime: 60 # seconds thread can remain idle before it is reclaimed, default is 60
    # fallback_policy: :abort # the default is :abort, which will raise a
    #   Concurrent::RejectedExecutionError exception and discard the task
  )
end

.pkg_deletion_pool_infoHash

info about the current pkg deletion pool state, for the /state route

Returns:

  • (Hash)


219
220
221
222
223
224
# File 'lib/xolo/server/version.rb', line 219

def self.pkg_deletion_pool_info
  {
    threads: pkg_deletion_pool.length,
    queued_tasks: pkg_deletion_pool.queue_length
  }
end

.version_dir(title) ⇒ Pathname

Returns The directory containing subdirectories for each version of a title. They contain JSON and other files for the versions.

Returns:

  • (Pathname)

    The directory containing subdirectories for each version of a title. They contain JSON and other files for the versions.



101
102
103
# File 'lib/xolo/server/version.rb', line 101

def self.version_dir(title)
  Xolo::Server::Title.title_dir(title) + VERSIONS_DIRNAME
end

.version_dirs(title) ⇒ Array<Pathname>

Returns All version directories for a title.

Returns:

  • (Array<Pathname>)

    All version directories for a title



108
109
110
111
# File 'lib/xolo/server/version.rb', line 108

def self.version_dirs(title)
  vdir = version_dir(title)
  vdir.directory? ? vdir.children : []
end

Instance Method Details

#<=>(other) ⇒ Object

version comparison

Raises:

  • (Xolo::InvalidDataError)

See Also:

  • Comparable


341
342
343
344
345
346
# File 'lib/xolo/server/version.rb', line 341

def <=>(other)
  raise Xolo::InvalidDataError, 'Cannot compare with other classes' unless other.is_a? Xolo::Server::Version
  raise Xolo::InvalidDataError, 'Cannot compare versions of different titles' unless other.title == title

  order_index <=> other.order_index
end

#adminString

Returns:

  • (String)


461
462
463
# File 'lib/xolo/server/version.rb', line 461

def admin
  session[:admin]
end

#createvoid

This method returns an undefined value.

Save a new version, adding to the local filesystem, Jamf Pro, and the Title Editor as needed This should be running in the context of #with_streaming



548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
# File 'lib/xolo/server/version.rb', line 548

def create
  lock
  @current_action = :creating

  self.creation_date = Time.now
  self.created_by = admin
  self.status = STATUS_PENDING
  log_debug "creation_date: #{creation_date}, created_by: #{created_by}"

  # save to file here so that we have something to delete if
  # the next couple steps fail
  progress 'Saving version data to Xolo server'
  save_local_data

  create_patch_in_ted
  enable_ted_patch
  title_object.enable_ted_title

  create_in_jamf

  self.status = STATUS_PILOT

  # save to file again now, because saving to TitleEd and Jamf will
  # add some data
  save_local_data

  # prepend our version to the version_order array of the title
  progress "Updating title version_order, prepending '#{version}'", log: :info
  title_object.prepend_version(version)

  log_change msg: 'Version Created'

  progress "Version '#{version}' of Title '#{title}' has been created in Xolo.", log: :info
ensure
  unlock
end

#creating?Boolean

Returns Are we creating this version?.

Returns:

  • (Boolean)

    Are we creating this version?



358
359
360
# File 'lib/xolo/server/version.rb', line 358

def creating?
  current_action == :creating
end

#data_dirPathname

The data directory for this version

Returns:

  • (Pathname)


504
505
506
# File 'lib/xolo/server/version.rb', line 504

def data_dir
  self.class.data_dir title, version
end

#data_filePathname

The JSON data file for this version

Returns:

  • (Pathname)


511
512
513
# File 'lib/xolo/server/version.rb', line 511

def data_file
  self.class.data_file title, version
end

#delete(update_title: true) ⇒ void

This method returns an undefined value.

Delete the version

Parameters:

  • update_title (Boolean) (defaults to: true)

    Update the title for this version to know the version is gone. Set this to false when the title itself is being deleted and calling this method.



830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
# File 'lib/xolo/server/version.rb', line 830

def delete(update_title: true)
  lock
  @current_action = :deleting

  delete_patch_from_ted
  delete_version_from_jamf

  # remove from the title's list of versions
  progress 'Deleting version from title data on the Xolo server', log: :debug
  title_object.remove_version(version) if update_title

  # delete the local data
  progress 'Deleting version data from the Xolo server', log: :info
  data_dir.rmtree
  log_change msg: 'Version Deleted'

  progress "Version '#{version}' of Title '#{title}' has been deleted from Xolo.", log: :info
ensure
  unlock
end

#deleting?Boolean

Returns Are we deleting this version?.

Returns:

  • (Boolean)

    Are we deleting this version?



376
377
378
# File 'lib/xolo/server/version.rb', line 376

def deleting?
  current_action == :deleting
end

#deprecatevoid

This method returns an undefined value.

deprecate this version



726
727
728
729
730
731
732
733
734
735
736
737
738
# File 'lib/xolo/server/version.rb', line 726

def deprecate
  lock
  progress "Deprecating older released version '#{version}'"
  disable_policies_for_deprecation_or_skipping :deprecated
  self.status = STATUS_DEPRECATED
  self.deprecation_date = Time.now
  self.deprecated_by = admin
  log_change msg: 'Version Deprecated'

  save_local_data
ensure
  unlock
end

#excluded_groups_to_use(ttl_obj: nil) ⇒ Array<String>

The scope excluded groups to use in policies and patch policies for all versions of this title.

Excluded groups are defined in the title, applying to all versions, and may be augmented by:

  • Xolo::Server.config.forced_exclusion, a group excluded from ALL of xolo, defined in the server config.

  • The title’s jamf_frozen_group_name, if it exists, containing computers that have been ‘frozen’ to a single version.

For initial install policies, the smart group of macs with any version installed (jamf_installed_group_name) “xolo-<title>-installed” is also excluded, because otherwise the initial-install policies would stomp on the patch policies.

Parameters:

  • ttl_obj (Xolo::Server::Title) (defaults to: nil)

    The pre-instantiated title for ths version. if nil, we’ll instantiate it now

Returns:

  • (Array<String>)

    the excluded groups to use



415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
# File 'lib/xolo/server/version.rb', line 415

def excluded_groups_to_use(ttl_obj: nil)
  return @excluded_groups_to_use if @excluded_groups_to_use

  ttl_obj ||= title_object
  # get the excluded groups from the title
  # Use .dup so we don't modify the original
  @excluded_groups_to_use = ttl_obj.changes_for_update&.key?(:excluded_groups) ? ttl_obj.changes_for_update[:excluded_groups][:new].dup : ttl_obj.excluded_groups.dup

  # always exclude the frozen static group
  # calling ttl_obj.jamf_frozen_group will create the group if needed
  @excluded_groups_to_use << ttl_obj.jamf_frozen_group.name
  log_debug "Appended '#{ttl_obj.jamf_frozen_group_name}' to @excluded_groups_to_use"

  # always exclude Xolo::Server.config.forced_exclusion if defined
  @excluded_groups_to_use << valid_forced_exclusion_group_name if valid_forced_exclusion_group_name

  @excluded_groups_to_use.uniq!
  log_debug "Excluded groups to use: #{@excluded_groups_to_use.join ', '}"

  @excluded_groups_to_use
end

#jamf_cnx(refresh: false) ⇒ Jamf::Connection

Returns a single Jamf Pro API connection to use for the life of this instance.

Returns:

  • (Jamf::Connection)

    a single Jamf Pro API connection to use for the life of this instance



497
498
499
# File 'lib/xolo/server/version.rb', line 497

def jamf_cnx(refresh: false)
  server_app_instance.jamf_cnx refresh: refresh
end

#lockObject

Lock this version for updates

Raises:

  • (Xolo::ServerError)


859
860
861
862
863
864
865
866
867
868
869
870
871
# File 'lib/xolo/server/version.rb', line 859

def lock
  raise Xolo::ServerError, 'Server is shutting down' if Xolo::Server.shutting_down?

  while locked?
    log_debug "Waiting for update lock on Version '#{version}' of title '#{title}'..." if (Time.now.to_i % 5).zero?
    sleep 0.33
  end
  Xolo::Server.object_locks[title] ||= { versions: {} }

  exp = Time.now + Xolo::Server::ObjectLocks::OBJECT_LOCK_LIMIT
  Xolo::Server.object_locks[title][:versions][version] = exp
  log_debug "Locked version '#{version}' of title '#{title}' for updates until #{exp}"
end

#locked?Boolean

Is this version locked for updates?

Returns:

  • (Boolean)


853
854
855
# File 'lib/xolo/server/version.rb', line 853

def locked?
  self.class.locked?(title, version)
end

#manifest_filePathname

The manifest plist file for this version

Returns:

  • (Pathname)


518
519
520
# File 'lib/xolo/server/version.rb', line 518

def manifest_file
  self.class.manifest_file title, version
end

#order_indexInteger

Returns The index of this version in the title’s reversed version_order array. We reverse it because the version_order array holds the newest versions first, so the index of the newest version is 0, the next newest is 1, etc - we need the opposite of that.

Returns:

  • (Integer)

    The index of this version in the title’s reversed version_order array. We reverse it because the version_order array holds the newest versions first, so the index of the newest version is 0, the next newest is 1, etc - we need the opposite of that.



352
353
354
# File 'lib/xolo/server/version.rb', line 352

def order_index
  title_object.version_order.reverse.index version
end

#pilot_groups_to_useArray<String>

The scope target groups to use in policies and patch policies during pilot This is defined in each version, and inherited when new versions are created.

Returns:

  • (Array<String>)

    the pilot groups to use



391
392
393
394
395
# File 'lib/xolo/server/version.rb', line 391

def pilot_groups_to_use
  return @pilot_groups_to_use if @pilot_groups_to_use

  @pilot_groups_to_use = changes_for_update&.key?(:pilot_groups) ? changes_for_update[:pilot_groups][:new] : pilot_groups
end

#progress(msg, log: :debug) ⇒ void

This method returns an undefined value.

Append a message to the progress stream file, optionally sending it also to the log

Parameters:

  • message (String)

    the message to append

  • log (Symbol) (defaults to: :debug)

    the level at which to log the message one of :debug, :info, :warn, :error, :fatal, or :unknown. Default is nil, which doesn’t log the message at all.



475
476
477
# File 'lib/xolo/server/version.rb', line 475

def progress(msg, log: :debug)
  server_app_instance.progress msg, log: log
end

#release(rollback:) ⇒ void

This method returns an undefined value.

Release this version, possibly rolling back from a previously newer version

Parameters:

  • rollback (Boolean)

    If true, this version is being released as a rollback



684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
# File 'lib/xolo/server/version.rb', line 684

def release(rollback:)
  lock
  @current_action = :releasing
  # set scope targets of auto-install policy to release-groups
  msg = "Jamf: Version '#{version}': Setting scope targets of auto-install policy to release_groups: #{release_groups_to_use.join(', ')}"
  progress msg, log: :info
  pol = jamf_auto_install_policy
  set_policy_release_groups pol
  pol.save

  # set scope targets of patch policy to all (in patch pols, 'all' means 'all eligible')
  msg = "Jamf: Version '#{version}': Setting scope targets of patch policy to all eligible computers"
  progress msg, log: :info
  ppol = jamf_patch_policy
  ppol.scope.set_all_targets

  # if rollback, make sure the patch policy is set to 'allow downgrade'
  if rollback
    msg = "Jamf: Version '#{version}': Setting patch policy to allow downgrade"
    progress msg, log: :info
    ppol.allow_downgrade = true
  else
    ppol.allow_downgrade = false
  end
  ppol.save

  # change status to 'released'
  self.status = STATUS_RELEASED
  self.release_date = Time.now
  self.released_by = admin
  chg_msg = rollback ? 'Version Released - Rolled Back' : 'Version Released'
  log_change msg: chg_msg

  save_local_data
ensure
  unlock
end

#release_groups_to_use(ttl_obj: nil) ⇒ Array<String>

The scope target groups to use in policies and patch policies when the version is released This is defined in the title and applies to all versions.

Parameters:

  • ttl_obj (Xolo::Server::Title) (defaults to: nil)

    The pre-instantiated title for ths version. if nil, we’ll instantiate it now

Returns:

  • (Array<String>)

    the target groups to use



445
446
447
448
449
450
# File 'lib/xolo/server/version.rb', line 445

def release_groups_to_use(ttl_obj: nil)
  return @release_groups_to_use if @release_groups_to_use

  ttl_obj ||= title_object
  @release_groups_to_use = ttl_obj.changes_for_update&.key?(:release_groups) ? ttl_obj.changes_for_update[:release_groups][:new] : ttl_obj.release_groups
end

#releasing?Boolean

Returns Are we releasing this version?.

Returns:

  • (Boolean)

    Are we releasing this version?



382
383
384
# File 'lib/xolo/server/version.rb', line 382

def releasing?
  current_action == :releasing
end

#repairObject

Repair this version. Look at the Title Editor patch object, and ensure it’s correct based on the local data file.

- version order
- min os
- max os
- standalone
- reboot
- release date
- killapps
- component criteria
  - component name '<title>'
- capability criteria
- enabled

Then look at the various Jamf objects pertaining to this version, and ensure they are correct

- package object 'xolo-<title>-<version>'
  - filename 'xolo-<title>-<version>.pkg'
  - description
  - os limitations
- auto install policy 'xolo-<title>-<version>-auto-install'
- manual install policy  'xolo-<title>-<version>-manual-install'
- patch policy 'xolo-<title>-<version>'

Then look at the xolo metadata, and fix whatever is needed



651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
# File 'lib/xolo/server/version.rb', line 651

def repair
  lock
  @current_action = :repairing
  log_change msg: "Repairing version '#{version}'"
  progress "Starting repair of version '#{version}' of title '#{title}'", log: :debug

  repair_ted_patch
  repair_jamf_version_objects

  # If there's a reupload, but no original, make the orig the same as the re
  unless upload_date
    if reupload_date && reuploaded_by
      new_date = reupload_date
      new_by = reuploaded_by
    else
      new_date = Time.parse '2025-02-15'
      new_by = 'buzzlightyear'
    end
    progress "Fixing original upload date: #{new_date}, by: #{new_by}", log: :debug
    self.upload_date = new_date
    self.uploaded_by = new_by
    save_local_data
  end
ensure
  unlock
end

#repairing?Boolean

Returns Are we repairing this version?.

Returns:

  • (Boolean)

    Are we repairing this version?



370
371
372
# File 'lib/xolo/server/version.rb', line 370

def repairing?
  current_action == :repairing
end

#reset_to_pilotvoid

This method returns an undefined value.

Reset this version to ‘pilot’ status, since we are rolling back to a previous version



762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
# File 'lib/xolo/server/version.rb', line 762

def reset_to_pilot
  return if status == STATUS_PILOT

  lock
  progress "Resetting version '#{version}' to pilot status due to rollback of an older version"
  reset_policies_to_pilot
  self.status = STATUS_PILOT
  self.skipped_date = nil
  self.skipped_by = nil
  self.deprecation_date = nil
  self.deprecated_by = nil
  log_change msg: 'Version Reset to Pilot'
  save_local_data
ensure
  unlock
end

#save_local_datavoid

This method returns an undefined value.

Save our current data out to our JSON data file This overwrites the existing data.



810
811
812
813
814
815
816
817
818
819
820
# File 'lib/xolo/server/version.rb', line 810

def save_local_data
  data_dir.mkpath

  self.modification_date = Time.now
  self.modified_by = admin
  log_debug "Version '#{version}' of Title '#{title}' noting modification by #{modified_by}"

  file = data_file
  log_debug "Saving local version data to: #{file}"
  file.pix_atomic_write to_json
end

#sessionHash

Returns:

  • (Hash)


454
455
456
457
# File 'lib/xolo/server/version.rb', line 454

def session
  server_app_instance&.session || {}
  # @session ||= {}
end

#skipvoid

This method returns an undefined value.

skip this version



744
745
746
747
748
749
750
751
752
753
754
755
# File 'lib/xolo/server/version.rb', line 744

def skip
  lock
  progress "Skipping unreleased version '#{version}'"
  disable_policies_for_deprecation_or_skipping :skipped
  self.status = STATUS_SKIPPED
  self.skipped_date = Time.now
  self.skipped_by = admin
  log_change msg: 'Version Skipped'
  save_local_data
ensure
  unlock
end

#ted_cnxWindoo::Connection

Returns a single Title Editor connection to use for the life of this instance.

Returns:

  • (Windoo::Connection)

    a single Title Editor connection to use for the life of this instance



490
491
492
# File 'lib/xolo/server/version.rb', line 490

def ted_cnx
  server_app_instance.ted_cnx
end

#ted_patch(refresh: false) ⇒ Windoo::Patch

TODO: maybe pass in an appropriate Windoo::SoftwareTitle, so we don’t have to use refresh all the time to re-fetch, if we just re-fetched from elsewhere?

Returns:

  • (Windoo::Patch)

    The Windoo::Patch object that represents this version in the title editor



529
530
531
532
# File 'lib/xolo/server/version.rb', line 529

def ted_patch(refresh: false)
  @ted_patch = nil if refresh
  @ted_patch ||= ted_title(refresh: refresh).patches.patch(version)
end

#ted_title(refresh: false) ⇒ Windoo::SoftwareTitle

Returns The Windoo::SoftwareTitle object that represents this version’s title in the title editor.

Returns:

  • (Windoo::SoftwareTitle)

    The Windoo::SoftwareTitle object that represents this version’s title in the title editor



537
538
539
540
# File 'lib/xolo/server/version.rb', line 537

def ted_title(refresh: false)
  @ted_title = nil if refresh
  @ted_title ||= Windoo::SoftwareTitle.fetch id: title, cnx: ted_cnx
end

#to_hObject

Add more data to our hash



885
886
887
888
889
890
891
892
893
894
895
896
# File 'lib/xolo/server/version.rb', line 885

def to_h
  hash = super

  # These attrs aren't defined in the ATTRIBUTES
  # but we want them in the hash and/or JSON
  hash[:jamf_pkg_id] = jamf_pkg_id
  hash[:ted_id_number] = ted_id_number
  hash[:pilot_groups_to_use] = pilot_groups_to_use
  hash[:release_groups_to_use] = release_groups_to_use

  hash.sort.to_h
end

#unlockObject

Unlock this version for updates



875
876
877
878
879
880
881
# File 'lib/xolo/server/version.rb', line 875

def unlock
  curr_lock = Xolo::Server.object_locks.dig title, :versions, version
  return unless curr_lock

  Xolo::Server.object_locks[title][:versions].delete version
  log_debug "Unlocked version '#{version}' of title '#{title}' for updates"
end

#update(new_data) ⇒ void

This method returns an undefined value.

Update a this version, updating to the local filesystem, Jamf Pro, and the Title Editor as needed

Parameters:

  • new_data (Hash)

    The new data sent from xadm



591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
# File 'lib/xolo/server/version.rb', line 591

def update(new_data)
  lock
  @current_action = :updating
  @new_data_for_update = new_data
  @changes_for_update = note_changes_for_update_and_log
  if @changes_for_update.pix_empty?
    progress 'No changes to make', log: :info
    return
  end

  log_info "Updating version '#{version}' of title '#{title}' for admin '#{admin}'"

  # changelog - log the changes now, and
  # if there is an error, we'll log that too
  # saying the above changes were not completed and to
  # look at the server log for details.
  log_update_changes

  # update ted before jamf
  update_patch_in_ted
  enable_ted_patch
  update_version_in_jamf
  update_local_instance_values
  save_local_data

  # new pkg uploads happen in a separate process
rescue => e
  log_change msg: "ERROR: The update failed and the changes didn't all go through!\n#{e.class}: #{e.message}\nSee server log for details."

  # re-raise for proper error handling in the server app
  raise
ensure
  unlock
end

#update_local_instance_valuesvoid

This method returns an undefined value.

Update our instance attributes with any new data before saving the changes back out to the file system



783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
# File 'lib/xolo/server/version.rb', line 783

def update_local_instance_values
  # update instance data with new data before writing out to the filesystem.
  # Do this last so that the instance values can be compared to
  # new_data_for_update in the steps above.
  # Also, those steps might have updated some server-specific attributes
  # which will be saved to the file system as well.
  ATTRIBUTES.each do |attr, deets|
    # make sure these are updated elsewhere if needed,
    # e.g. modification data.
    next if deets[:read_only]
    next unless deets[:cli]

    new_val = new_data_for_update[attr]
    old_val = send(attr)
    next if new_val == old_val

    log_debug "Updating Xolo Version attribute '#{attr}': '#{old_val}' -> '#{new_val}'"
    send "#{attr}=", new_val
  end
  # update any other server-specific attributes here...
end

#updating?Boolean

Returns Are we updating this version?.

Returns:

  • (Boolean)

    Are we updating this version?



364
365
366
# File 'lib/xolo/server/version.rb', line 364

def updating?
  current_action == :updating
end