Class: Xolo::Server::Version
- Inherits:
-
Core::BaseClasses::Version
- Object
- Core::BaseClasses::ServerObject
- Core::BaseClasses::Version
- Xolo::Server::Version
- 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_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
Constants inherited from Core::BaseClasses::Version
Core::BaseClasses::Version::ATTRIBUTES, Core::BaseClasses::Version::DEFAULT_MIN_OS, Core::BaseClasses::Version::STATUS_DEPRECATED, Core::BaseClasses::Version::STATUS_PENDING, Core::BaseClasses::Version::STATUS_PILOT, Core::BaseClasses::Version::STATUS_RELEASED, Core::BaseClasses::Version::STATUS_SKIPPED, Core::BaseClasses::Version::USE_TITLE_FOR_KILLAPP
Instance Attribute Summary collapse
-
#changes_for_update ⇒ Hash
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.
-
#current_action ⇒ Symbol
The current action being taken on this title one of :creating, :updating, :deleting.
-
#jamf_auto_install_policy_name ⇒ Object
readonly
Jamf auto-install policies are named this.
-
#jamf_auto_reinstall_policy_name ⇒ Object
readonly
Jamf auto re-install policies are named this.
-
#jamf_installed_group_name ⇒ String
readonly
For each version there will be a smart group containing all macs that have that version of the title installed.
-
#jamf_manual_install_policy_name ⇒ Object
(also: #jamf_manual_install_trigger)
readonly
Jamf manual install policies are named this.
-
#jamf_obj_name_pfx ⇒ Object
readonly
Jamf object names start with this.
-
#jamf_patch_policy_name ⇒ Object
readonly
Jamf Patch Policy is named this.
-
#jamf_pkg_id ⇒ Object
readonly
The Jamf Package object has this jamf id.
-
#new_data_for_update ⇒ Object
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.
-
#pkg_is_from_autopkg ⇒ Boolean
Is the pkg being processed now from an autopkg recipe?.
-
#server_app_instance ⇒ Object
The instance of Xolo::Server::App that instantiated this version object.
-
#ted_id_number ⇒ Object
The Windoo::Patch#patchId.
-
#title_object(refresh: false) ⇒ Xolo::Server::Title
This might have been set already if we were instantiated via our title.
Attributes inherited from Core::BaseClasses::Version
#created_by, #creation_date, #deployed_by, #deprecated_by, #deprecation_date, #dist_pkg, #jamf_pkg, #jamf_pkg_file, #jamf_pkg_name, #modification_date, #modified_by, #release_date, #reupload_date, #reuploaded_by, #sha_512, #skipped_by, #skipped_date, #status, #upload_date, #uploaded_by
Class Method Summary collapse
-
.add_version_via_subscription(title_object:, new_version:) ⇒ void
add a new version in response to a patch title update webhook event.
-
.all_versions(title) ⇒ Array<String>
A list of all known versions for a title, just the basenames of all the version files with the extension removed.
-
.data_dir(title, version) ⇒ Pathname
The the local directory containing various files specific to the given version of a title.
-
.data_file(title, version) ⇒ Pathname
The the local JSON file containing the current values for the given version of a title.
-
.in_ted?(patch_id, cnx:) ⇒ Boolean
Does the given patch exist in the Title Editor?.
-
.load(title, version) ⇒ Xolo::Server::Title
Instantiate from the local JSON file containing the current values for the given version of a title.
-
.locked?(title, version) ⇒ Boolean
Is a version locked for updates?.
-
.manifest_file(title, version) ⇒ Pathname
The the local xml plist file containing the .pkg manifest for the given version of a title.
-
.pkg_deletion_pool ⇒ Queue
The package-deletion thread pool.
-
.pkg_deletion_pool_info ⇒ Hash
info about the current pkg deletion pool state, for the /state route.
-
.version_dir(title) ⇒ Pathname
The directory containing subdirectories for each version of a title.
-
.version_dirs(title) ⇒ Array<Pathname>
All version directories for a title.
Instance Method Summary collapse
-
#<=>(other) ⇒ Object
version comparison.
-
#admin ⇒ String
This can be manually set earlier in the request handling to use a non-standard admin username.
-
#create ⇒ void
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.
-
#creating? ⇒ Boolean
Are we creating this version?.
-
#data_dir ⇒ Pathname
The data directory for this version.
-
#data_file ⇒ Pathname
The JSON data file for this version.
-
#delete(update_title: true, deleting_title: false) ⇒ void
Delete the version.
-
#deleting? ⇒ Boolean
Are we deleting this version?.
-
#deprecate ⇒ void
deprecate this version.
-
#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.
-
#handle_autopkg_during_create ⇒ Object
Do autopkg stuff during creation.
-
#initialize(data_hash) ⇒ Version
constructor
NOTE: be sure to only instantiate these using the servers ‘instantiate_version’ method, or else they might not have all the correct innards.
-
#jamf_cnx(refresh: false) ⇒ Jamf::Connection
A single Jamf Pro API connection to use for the life of this instance.
-
#lock ⇒ Object
Lock this version for updates.
-
#locked? ⇒ Boolean
Is this version locked for updates?.
-
#managed? ⇒ Boolean
or a managed title?.
-
#manifest_file ⇒ Pathname
The manifest plist file for this version.
-
#order_index ⇒ Integer
The index of this version in the title’s reversed version_order array.
-
#pilot_groups_to_use ⇒ Array<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.
-
#progress(msg, log: :debug, alert: false) ⇒ void
Append a message to the progress stream file, optionally sending it also to the log.
-
#release(rollback:) ⇒ void
Release this version, possibly rolling back from a previously newer version This should only be called by the title.
-
#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.
-
#releasing? ⇒ Boolean
Are we releasing this version?.
-
#repair ⇒ Object
Repair this version.
-
#repairing? ⇒ Boolean
Are we repairing this version?.
-
#reset_to_pilot ⇒ void
Reset this version to ‘pilot’ status, since we are rolling back to a previous version.
-
#save_local_data ⇒ void
Save our current data out to our JSON data file This overwrites the existing data.
- #session ⇒ Hash
-
#skip ⇒ void
skip this version.
-
#subscribed? ⇒ Boolean
Is this version part of a subscribed title?.
-
#ted_cnx ⇒ Windoo::Connection
A single Title Editor connection to use for the life of this instance.
-
#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?.
-
#ted_title(refresh: false) ⇒ Windoo::SoftwareTitle
The Windoo::SoftwareTitle object that represents this version’s title in the title editor.
-
#to_h ⇒ Object
Add more data to our hash.
-
#unlock ⇒ Object
Unlock this version for updates.
-
#update(new_data) ⇒ void
Update a this version, updating to the local filesystem, Jamf Pro, and the Title Editor as needed.
-
#update_local_instance_values ⇒ void
Update our instance attributes with any new data before saving the changes back out to the file system.
-
#updating? ⇒ Boolean
Are we updating this version?.
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_managed_patch_version_in_jamf, #activate_patch_version_in_jamf, #activate_subscribed_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_exist?, #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
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
Methods included from Helpers::JamfPro
extended, included, #jamf_gui_url, #jamf_obj_name_pfx_base, #jamf_xolo_category_id, #valid_forced_exclusion_group_name
Methods inherited from Core::BaseClasses::Version
#deprecated?, #pending?, #pilot?, #released?, #skipped?
Methods inherited from Core::BaseClasses::ServerObject
Methods included from Core::JSONWrappers
extended, included, #parse_json
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
384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 |
# File 'lib/xolo/server/version.rb', line 384 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 = "#{jamf_obj_name_pfx_base}#{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_update ⇒ Hash (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’] } }
368 369 370 |
# File 'lib/xolo/server/version.rb', line 368 def changes_for_update @changes_for_update end |
#current_action ⇒ Symbol
Returns The current action being taken on this title one of :creating, :updating, :deleting.
372 373 374 |
# File 'lib/xolo/server/version.rb', line 372 def current_action @current_action end |
#jamf_auto_install_policy_name ⇒ Object (readonly)
Jamf auto-install policies are named this
333 334 335 |
# File 'lib/xolo/server/version.rb', line 333 def jamf_auto_install_policy_name @jamf_auto_install_policy_name end |
#jamf_auto_reinstall_policy_name ⇒ Object (readonly)
Jamf auto re-install policies are named this
339 340 341 |
# File 'lib/xolo/server/version.rb', line 339 def jamf_auto_reinstall_policy_name @jamf_auto_reinstall_policy_name end |
#jamf_installed_group_name ⇒ String (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
330 331 332 |
# File 'lib/xolo/server/version.rb', line 330 def jamf_installed_group_name @jamf_installed_group_name end |
#jamf_manual_install_policy_name ⇒ Object (readonly) Also known as: jamf_manual_install_trigger
Jamf manual install policies are named this
336 337 338 |
# File 'lib/xolo/server/version.rb', line 336 def jamf_manual_install_policy_name @jamf_manual_install_policy_name end |
#jamf_obj_name_pfx ⇒ Object (readonly)
Jamf object names start with this
321 322 323 |
# File 'lib/xolo/server/version.rb', line 321 def jamf_obj_name_pfx @jamf_obj_name_pfx end |
#jamf_patch_policy_name ⇒ Object (readonly)
Jamf Patch Policy is named this
345 346 347 |
# File 'lib/xolo/server/version.rb', line 345 def jamf_patch_policy_name @jamf_patch_policy_name end |
#jamf_pkg_id ⇒ Object (readonly)
The Jamf Package object has this jamf id
348 349 350 |
# File 'lib/xolo/server/version.rb', line 348 def jamf_pkg_id @jamf_pkg_id end |
#new_data_for_update ⇒ Object (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
354 355 356 |
# File 'lib/xolo/server/version.rb', line 354 def new_data_for_update @new_data_for_update end |
#pkg_is_from_autopkg ⇒ Boolean
Returns is the pkg being processed now from an autopkg recipe?.
375 376 377 |
# File 'lib/xolo/server/version.rb', line 375 def pkg_is_from_autopkg @pkg_is_from_autopkg end |
#server_app_instance ⇒ Object
The instance of Xolo::Server::App that instantiated this version 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.
308 309 310 |
# File 'lib/xolo/server/version.rb', line 308 def server_app_instance @server_app_instance end |
#ted_id_number ⇒ Object
The Windoo::Patch#patchId
318 319 320 |
# File 'lib/xolo/server/version.rb', line 318 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
571 572 573 574 |
# File 'lib/xolo/server/version.rb', line 571 def title_object(refresh: false) @title_object = nil if refresh @title_object ||= server_app_instance.instantiate_title title end |
Class Method Details
.add_version_via_subscription(title_object:, new_version:) ⇒ void
This method returns an undefined value.
add a new version in response to a patch title update webhook event. This doesn’t upload a pkg - it just creates the version in Xolo, and then someone can upload a pkg to it via xadm or autopkg will do it if configured.
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 |
# File 'lib/xolo/server/version.rb', line 234 def self.add_version_via_subscription(title_object:, new_version:) title_object.log_info "Adding new version '#{new_version}' for subscribed title '#{title_object.title}'" # get more details about this version from the JPAPI patch_version_data = title_object.patch_versions(version: new_version).first unless patch_version_data msg = "Could not get patch version data from JPAPI for version '#{new_version}' of subscribed title '#{title_object.title}'. Cannot create new version in Xolo without this data. Aborting." title_object.log_error msg, alert: true return end title_object.log_debug "Got patch version data from JPAPI for version '#{new_version}': #{patch_version_data}" # put the data into a hash for creating a new version object vobj_data = { publish_date: Time.parse(patch_version_data[:releaseDate]), standalone: patch_version_data[:standalone], min_os: patch_version_data[:minimumOperatingSystem], reboot: patch_version_data[:rebootRequired], killapps: [] } # Killapps for subscribed titles? The API only shows app names without the .app, e.g. # "killApps": [ # { # "appName": "ChrislTestHelper" # }, # { # "appName": "Chrisl Test" # } # ] # Since we don't manage them, we'll just record them in the data like this... unless patch_version_data[:killApps].pix_empty? patch_version_data[:killApps].each do |ka| vobj_data[:killapps] << "#{ka[:appName]}.app;unknown.from.subscription" end end # instantiate the version object title_object.log_debug "Instantiating version via subscription '#{new_version}' of title '#{title_object.title}' (#{title_object.class}) with data: #{vobj_data}" vobj = title_object.server_app_instance.instantiate_version( title: title_object, version: new_version, **vobj_data ) # create it in xolo vobj.create # tell someone msg = "ACTION REQUIRED: New pilot version '#{new_version}' for subscribed title '#{title_object.title}' has been created in Xolo via subscription." # if not autopkg enabled, we need to tell someone to upload a pkg for this new version unless title_object.autopkg_enabled? # update general alert msg msg = "#{msg}\nPlease upload a .pkg for it ASAP using this command:\n xadm edit-version #{title_object.title} #{new_version} --pkg-to-upload /path/to/installer.pkg" # email to title contact vobj.server_app_instance.send_email to: title_object.contact_email, subject: 'Need manual upload of xolo pkg', msg: msg end # if title_object.autopkg_enabled? # send general alert vobj.log_info msg, alert: true end |
.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.
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
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
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?.
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
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?
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
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_pool ⇒ Queue
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
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_info ⇒ Hash
info about the current pkg deletion pool state, for the /state route
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.
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.
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
418 419 420 421 422 423 |
# File 'lib/xolo/server/version.rb', line 418 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 |
#admin ⇒ String
This can be manually set earlier in the request handling to use a non-standard admin username
550 551 552 |
# File 'lib/xolo/server/version.rb', line 550 def admin @admin ||= session[:admin] end |
#create ⇒ void
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
637 638 639 640 641 642 643 644 645 646 647 648 649 650 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 677 678 679 680 681 682 683 684 685 686 687 688 689 690 |
# File 'lib/xolo/server/version.rb', line 637 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 unless subscribed? 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 # all done unless we need to get a pkg via autopkg # pkg upload from xadm will happen in a separate process, # so we don't want to do it here in the create method # do we have an uploaded pkg? if pkg_to_upload.to_s.start_with? '/' progress "Pkg will be uploaded to xolo via xadm shortly, from path '#{pkg_to_upload}'", log: :info # if we have an autopkg recipe and dir, get the .pkg and upload it to Jamf elsif title_object.autopkg_enabled? handle_autopkg_during_create # otherwise tell someone we need a .pkg else msg = "No --pkg-to-upload given for version '#{version}' of title #{title}, and no autopkg recipe enabled. Please upload a pkg via xadm or enable autopkg for this title." # no alert when subscribed because a better alert mesg is sent from add_version_via_subscription subscribed? ? progress(msg, log: :warn) : progress(msg, log: :warn, alert: true) end ensure unlock end |
#creating? ⇒ Boolean
Returns Are we creating this version?.
435 436 437 |
# File 'lib/xolo/server/version.rb', line 435 def creating? current_action == :creating end |
#data_dir ⇒ Pathname
The data directory for this version
593 594 595 |
# File 'lib/xolo/server/version.rb', line 593 def data_dir self.class.data_dir title, version end |
#data_file ⇒ Pathname
The JSON data file for this version
600 601 602 |
# File 'lib/xolo/server/version.rb', line 600 def data_file self.class.data_file title, version end |
#delete(update_title: true, deleting_title: false) ⇒ void
This method returns an undefined value.
Delete the version
980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 |
# File 'lib/xolo/server/version.rb', line 980 def delete(update_title: true, deleting_title: false) lock @current_action = :deleting delete_version_from_jamf # NOTE: we no longer delete the patch from the Title Editor # unless the whole title is being deleted, because # patches may be needed for reporting purposes. # When the title is deleted, the title's delete method # will delete all patches for all versions. # If other situations arise where we need to delete # ted patches individually, set deleting_title to true. delete_patch_from_ted if deleting_title && managed? # 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?.
453 454 455 |
# File 'lib/xolo/server/version.rb', line 453 def deleting? current_action == :deleting end |
#deprecate ⇒ void
This method returns an undefined value.
deprecate this version
874 875 876 877 878 879 880 881 882 883 884 885 886 |
# File 'lib/xolo/server/version.rb', line 874 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.
502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 |
# File 'lib/xolo/server/version.rb', line 502 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 |
#handle_autopkg_during_create ⇒ Object
Do autopkg stuff during creation
695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 |
# File 'lib/xolo/server/version.rb', line 695 def handle_autopkg_during_create return unless title_object.autopkg_enabled? pkg_src = title_object.run_autopkg_recipe if pkg_src.nil? msg = 'AutoPkg recipe is enabled for this title, but no pkg was found after running the recipe. Please check the AutoPkg recipe and the server log for details.' progress msg, log: :warn, alert: true return end oldest_allowed = Time.now - 1200 # 20 minutes ago if pkg_src.mtime < oldest_allowed msg = "AutoPkg recipe is enabled for this title, and a pkg was found after running the recipe, but it was last modified at #{pkg_src.mtime}, which is more than 20 minutes ago. To avoid accidentally uploading an old pkg, the server will not upload this pkg. Please check the AutoPkg recipe and the server log for details." progress msg, log: :warn, alert: true return end # this lets future code know that the pkg we're working with came from autopkg self.pkg_is_from_autopkg = true # Upload the pkg to Jamf, and associate it with this version server_app_instance.process_and_upload_autopkg_pkg(title, self, pkg_src) end |
#jamf_cnx(refresh: false) ⇒ Jamf::Connection
Returns a single Jamf Pro API connection to use for the life of this instance.
586 587 588 |
# File 'lib/xolo/server/version.rb', line 586 def jamf_cnx(refresh: false) server_app_instance.jamf_cnx refresh: refresh end |
#lock ⇒ Object
Lock this version for updates
1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 |
# File 'lib/xolo/server/version.rb', line 1017 def lock raise Xolo::ServerError, 'Server is shutting down' if Xolo::Server.shutting_down? while locked? if (Time.now.to_i % 5).zero? log_debug "Method #{caller_locations.first.label} is waiting for update lock on Version '#{version}' of title '#{title}'..." end 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}, by method #{caller_locations.first.label}" end |
#locked? ⇒ Boolean
Is this version locked for updates?
1011 1012 1013 |
# File 'lib/xolo/server/version.rb', line 1011 def locked? self.class.locked?(title, version) end |
#managed? ⇒ Boolean
or a managed title?
726 727 728 |
# File 'lib/xolo/server/version.rb', line 726 def managed? !subscribed? end |
#manifest_file ⇒ Pathname
The manifest plist file for this version
607 608 609 |
# File 'lib/xolo/server/version.rb', line 607 def manifest_file self.class.manifest_file title, version end |
#order_index ⇒ Integer
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.
429 430 431 |
# File 'lib/xolo/server/version.rb', line 429 def order_index title_object.version_order.reverse.index version end |
#pilot_groups_to_use ⇒ Array<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.
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 |
# File 'lib/xolo/server/version.rb', line 468 def pilot_groups_to_use return @pilot_groups_to_use if @pilot_groups_to_use # any defined in the version override any in the title @pilot_groups_to_use = changes_for_update&.key?(:pilot_groups) ? changes_for_update[:pilot_groups][:new] : pilot_groups return @pilot_groups_to_use unless @pilot_groups_to_use.empty? # if none defined in the version, look in the title @pilot_groups_to_use = if title_object.changes_for_update&.key?(:pilot_groups) title_object.changes_for_update[:pilot_groups][:new] else title_object.pilot_groups end end |
#progress(msg, log: :debug, alert: false) ⇒ void
This method returns an undefined value.
Append a message to the progress stream file, optionally sending it also to the log
564 565 566 |
# File 'lib/xolo/server/version.rb', line 564 def progress(msg, log: :debug, alert: false) server_app_instance.progress msg, log: log, alert: alert end |
#release(rollback:) ⇒ void
This method returns an undefined value.
Release this version, possibly rolling back from a previously newer version This should only be called by the title. The initial ‘release’ action starts in the title, and then calls this method on the version to do the version-specific release steps.
831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 |
# File 'lib/xolo/server/version.rb', line 831 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.enable 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.
532 533 534 535 536 537 |
# File 'lib/xolo/server/version.rb', line 532 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?.
459 460 461 |
# File 'lib/xolo/server/version.rb', line 459 def releasing? current_action == :releasing end |
#repair ⇒ Object
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
795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 |
# File 'lib/xolo/server/version.rb', line 795 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 end save_local_data ensure unlock end |
#repairing? ⇒ Boolean
Returns Are we repairing this version?.
447 448 449 |
# File 'lib/xolo/server/version.rb', line 447 def repairing? current_action == :repairing end |
#reset_to_pilot ⇒ void
This method returns an undefined value.
Reset this version to ‘pilot’ status, since we are rolling back to a previous version
910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 |
# File 'lib/xolo/server/version.rb', line 910 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_data ⇒ void
This method returns an undefined value.
Save our current data out to our JSON data file This overwrites the existing data.
958 959 960 961 962 963 964 965 966 967 968 |
# File 'lib/xolo/server/version.rb', line 958 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 |
#session ⇒ Hash
541 542 543 544 |
# File 'lib/xolo/server/version.rb', line 541 def session server_app_instance&.session || {} # @session ||= {} end |
#skip ⇒ void
This method returns an undefined value.
skip this version
892 893 894 895 896 897 898 899 900 901 902 903 |
# File 'lib/xolo/server/version.rb', line 892 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 |
#subscribed? ⇒ Boolean
Is this version part of a subscribed title?
721 722 723 |
# File 'lib/xolo/server/version.rb', line 721 def subscribed? title_object.subscribed? end |
#ted_cnx ⇒ Windoo::Connection
Returns a single Title Editor connection to use for the life of this instance.
579 580 581 |
# File 'lib/xolo/server/version.rb', line 579 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?
618 619 620 621 |
# File 'lib/xolo/server/version.rb', line 618 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.
626 627 628 629 |
# File 'lib/xolo/server/version.rb', line 626 def ted_title(refresh: false) @ted_title = nil if refresh @ted_title ||= Windoo::SoftwareTitle.fetch id: title, cnx: ted_cnx end |
#to_h ⇒ Object
Add more data to our hash
1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 |
# File 'lib/xolo/server/version.rb', line 1045 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 |
#unlock ⇒ Object
Unlock this version for updates
1035 1036 1037 1038 1039 1040 1041 |
# File 'lib/xolo/server/version.rb', line 1035 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
736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 |
# File 'lib/xolo/server/version.rb', line 736 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 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.}\nSee server log for details." # re-raise for proper error handling in the server app raise ensure unlock end |
#update_local_instance_values ⇒ void
This method returns an undefined value.
Update our instance attributes with any new data before saving the changes back out to the file system
931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 |
# File 'lib/xolo/server/version.rb', line 931 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?.
441 442 443 |
# File 'lib/xolo/server/version.rb', line 441 def updating? current_action == :updating end |