lib/jss/utility.rb in ruby-jss-1.5.1 vs lib/jss/utility.rb in ruby-jss-1.5.2
- old
+ new
@@ -21,63 +21,161 @@
# KIND, either express or implied. See the Apache License for the specific
# language governing permissions and limitations under the Apache License.
#
#
-#
module JSS
# A collection of useful utility methods. Mostly for
# converting values between formats, parsing data, and
# user interaction.
- # Converts an OS Version into an Array of higher OS versions.
+ # Hash of 'minor' => 'maint'
+ # The maximum maint release for macOS 10.minor.maint
+ # e.g the highest release of 10.6 was 10.6.8, the highest release of
+ # 10.15 was 10.15.7
#
- # It's unlikely that this library will still be in use as-is by the release of OS X 10.30.
- # Hopefully well before then JAMF will implement a "minimum OS" in the JSS itself.
+ # 12 is the default for the current OS and higher
+ # (and hoping apple doesn't release 10.16.13)
+ OS_TEN_MAXS = {
+ 2 => 8,
+ 3 => 9,
+ 4 => 11,
+ 5 => 8,
+ 6 => 8,
+ 7 => 5,
+ 8 => 5,
+ 9 => 5,
+ 10 => 5,
+ 11 => 6,
+ 12 => 6,
+ 13 => 6,
+ 14 => 6,
+ 15 => 7,
+ 16 => 12
+ }
+
+ # Hash of 'major' => 'minor'
+ # The maximum minor release for macOS major.minor
+ # e.g. the highest release of 11 is 11.12
#
- # @param min_os [String] the mimimum OS version to expand, e.g. ">=10.6.7" or "10.6.7"
+ # 12 is the default for the current OS and higher
+ # (and hoping apple doesn't release 11.13)
+ MAC_OS_MAXS = {
+ 11 => 12,
+ 12 => 12,
+ 13 => 12,
+ 14 => 12,
+ 15 => 12,
+ 16 => 12,
+ 17 => 12,
+ 18 => 12,
+ 19 => 12,
+ 20 => 12
+ }
+
+ # Converts an OS Version into an Array of equal or higher OS versions, up to
+ # some non-existant max, hopefully far in the future, currently 20.12
#
- # @return [Array] Nearly all potential OS versions from the minimum to 10.19.x.
+ # This array can then be joined with commas and used as the value of the
+ # os_requirements for Packages and Scripts.
#
+ # It's unlikely that this method, as written, will still be in use by
+ # the release of macOS 20.12, but currently thats the upper limit.
+ #
+ # Hopefully well before then JAMF will implement a "minimum OS" in Jamf Pro
+ # itself, then we could avoid the inherant limitations in using a method like
+ # this.
+ #
+ # When the highest maint. release of an OS version is not known, because its
+ # the currently released OS version or higher, then this method assumes '12'
+ # e.g. '10.16.12', '11.12', '12.12', etc.
+ #
+ # Apple has never released more than 11 updates to a version of macOS
+ # (that being 10.4), so hopefully 12 is enough
+ #
+ # Since Big Sur might report itself as either '10.16.x' or '11.x', this method
+ # will allow for both possibilities, and the array will contain whatever
+ # iterations needed for both version numbers
+ #
+ # @param min_os [String] the mimimum OS version to expand, e.g. ">=10.9.4" or "11.1"
+ #
+ # @return [Array] Nearly all potential OS versions from the minimum to 20.12
+ #
# @example
- # JSS.expand_min_os ">=10.6.7" # => returns this array
- # # ["10.6.7",
- # # "10.6.8",
+ # JSS.expand_min_os ">=10.9.4" # => returns this array
+ # # ["10.9.4",
+ # # "10.9.5",
+ # # "10.10.x"
# # ...
- # # "10.6.25",
- # # "10.7.x",
- # # "10.8.x",
+ # # "10.16.x",
+ # # "11.x",
+ # # "12.x",
# # ...
- # # "10.30.x"]
+ # # "20.x"]
#
#
def self.expand_min_os(min_os)
min_os = min_os.delete '>='
# split the version into major, minor and maintenance release numbers
- (maj, min, maint) = min_os.split('.')
+ major, minor, maint = min_os.split('.')
maint = 'x' if maint.nil? || maint == '0'
- # if the maint release number is an "x" just start the list of OK OS's with it
- if maint == 'x'
- ok_oses = [maj + '.' + min.to_s + '.x']
+ ok_oses = []
- # otherwise, start with it and explicitly add all maint releases up to 15
- # (and hope apple doesn't do more than 15 maint releases for an OS)
+ # if major == 11 e.g. big sur, e.g. 10.16,
+ # reset major & min so that we get coverage for both
+ # 10.16 and 11, since clients may report it as either
+ if major == '11'
+ major = '10'
+ maint = minor
+ minor = '16'
+ end
+
+ # Deal with 10.x.x up to 10.16
+ if major == '10'
+ # if the maint release number is an "x" just start the list of OK OS's with it
+ if maint == 'x'
+ ok_oses << min_os
+
+ # otherwise, start with it and explicitly add all maint releases for
+ # that maint version. if unknown because its a current or future OS,
+ # go as h
+ else
+ max_maint_for_minor = OS_TEN_MAXS[minor.to_i]
+ (maint.to_i..max_maint_for_minor).each do |m|
+ ok_oses << "#{major}.#{minor}.#{m}"
+ end # each m
+ end
+
+ # now account for all macOS versions starting with 10.
+ # up to 10.16.x
+ ((minor.to_i + 1)..16).each do |v|
+ ok_oses << "#{major}.#{v}.x"
+ end # each v
+
+ # now reset major to 11, so we can continue with BigSur and up
+ major = '11'
+ minor = minor == '16' ? maint : 'x'
+ end # if major == 10
+
+ # Now deal with Big Sur and higher, macOS 10.16/11
+ if minor == 'x'
+ ok_oses << "#{major}.#{minor}"
else
- ok_oses = []
- (maint.to_i..25).each do |m|
- ok_oses << maj + '.' + min + '.' + m.to_s
+ max_minor_for_major = MAC_OS_MAXS[major.to_i]
+ (minor.to_i..max_minor_for_major).each do |m|
+ ok_oses << "#{major}.#{m}"
end # each m
- end
+ end # if minor == x
- # now account for all OS X versions starting with 10.
- # up to 10.30.x
- ((min.to_i + 1)..30).each do |v|
- ok_oses << maj + '.' + v.to_s + '.x'
+ # now account for all macOS versions 11 and up
+ ((major.to_i + 1)..MAC_OS_MAXS.keys.max).each do |v|
+ ok_oses << "#{v}.x"
end # each v
+
ok_oses
end
# Scripts and packages can have processor limitations.
# This method tests a given processor, against a requirement
@@ -93,10 +191,11 @@
# @return [Boolean] can this pkg be installed with the processor
# given?
#
def self.processor_ok?(requirement, processor = nil)
return true if requirement.to_s.empty? || requirement =~ /none/i
+
processor ||= `/usr/bin/uname -p`
requirement == (processor.to_s.include?('86') ? 'x86' : 'ppc')
end
# Scripts and packages can have OS limitations.
@@ -113,10 +212,11 @@
# given?
#
def self.os_ok?(requirement, os_to_check = nil)
return true if requirement.to_s =~ /none/i
return true if requirement.to_s == 'n'
+
requirement = JSS.to_s_and_a(requirement)[:arrayform]
return true if requirement.empty?
os_to_check ||= `/usr/bin/sw_vers -productVersion`.chomp
@@ -185,10 +285,11 @@
def self.parse_plist(plist)
# did we get a string of xml, or a string pathname?
case plist
when String
return Plist.parse_xml plist if plist.include? '</plist>'
+
plist = Pathname.new plist
when Pathname
true
else
raise ArgumentError, 'Argument must be a path (as a Pathname or String) or a String of XML'
@@ -221,13 +322,11 @@
# Convert them to an integer of microseconds
usec = (the_dt.sec_fraction * 60 * 60 * 24 * (10**6)).to_i
# if the UTC offset of the datetime is zero, make a new one with the correct local offset
# (which might also be zero if we happen to be in GMT)
- if the_dt.offset.zero?
- the_dt = DateTime.new(the_dt.year, the_dt.month, the_dt.day, the_dt.hour, the_dt.min, the_dt.sec, JSS::TIME_ZONE_OFFSET)
- end
+ the_dt = DateTime.new(the_dt.year, the_dt.month, the_dt.day, the_dt.hour, the_dt.min, the_dt.sec, JSS::TIME_ZONE_OFFSET) if the_dt.offset.zero?
# now convert it to a Time and return it
Time.at the_dt.strftime('%s').to_i, usec
end # parse_time
# Deprecated - to be eventually removed in favor of
@@ -245,10 +344,11 @@
#
# @return [Time, nil] nil is returned if epoch is nil, 0 or an empty String.
#
def self.epoch_to_time(epoch)
return nil if NIL_DATES.include? epoch
+
Time.at(epoch.to_i / 1000.0)
end # parse_date
# Given a name, singular or plural, of a JSS::APIObject subclass as a String
# or Symbol (e.g. :computer/'computers'), return the class itself
@@ -264,10 +364,11 @@
# @return [Class] The class
#
def self.api_object_class(name)
klass = api_object_names[name.downcase.to_sym]
raise JSS::InvalidDataError, "Unknown API Object Class: #{name}" unless klass
+
klass
end
# APIObject subclasses have singular names, and are, of course
# capitalized, e.g. 'Computer'
@@ -281,15 +382,17 @@
#
# @return [Hash] APIObject subclass names to Classes
#
def self.api_object_names
return @api_object_names if @api_object_names
+
@api_object_names ||= {}
JSS.constants.each do |const|
klass = JSS.const_get const
next unless klass.is_a? Class
next unless klass.ancestors.include? JSS::APIObject
+
@api_object_names[klass.const_get(:RSRC_LIST_KEY).to_sym] = klass if klass.constants.include? :RSRC_LIST_KEY
@api_object_names[klass.const_get(:RSRC_OBJECT_KEY).to_sym] = klass if klass.constants.include? :RSRC_OBJECT_KEY
end
@api_object_names
end
@@ -322,10 +425,11 @@
#
# @return [Array<REXML::Element>]
#
def self.array_to_rexml_array(element, list)
raise JSS::InvalidDataError, 'Arg. must be an Array.' unless list.is_a? Array
+
element = element.to_s
list.map do |v|
e = REXML::Element.new(element)
e.text = v
e
@@ -348,10 +452,11 @@
#
# @return [Array<REXML::Element>] the Array of REXML elements.
#
def self.hash_to_rexml_array(hash)
raise InvalidDataError, 'Arg. must be a Hash.' unless hash.is_a? Hash
+
ary = []
hash.each_pair do |k, v|
el = REXML::Element.new k.to_s
el.text = v
ary << el
@@ -426,15 +531,15 @@
minor ||= 0
revision ||= 0
{
major: major.to_i,
- minor: minor.to_i,
- revision: revision.to_i,
- maint: revision.to_i,
- patch: revision.to_i,
- build: build,
+ minor: minor.to_i,
+ revision: revision.to_i,
+ maint: revision.to_i,
+ patch: revision.to_i,
+ build: build,
version: Gem::Version.new("#{major}.#{minor}.#{revision}")
}
end
# @return [Boolean] is this code running as root?
@@ -455,10 +560,11 @@
#
def self.stdin(line = 0)
@stdin_lines ||= ($stdin.tty? ? [] : $stdin.read.lines.map { |l| l.chomp("\n") })
return @stdin_lines.join("\n") if line <= 0
+
idx = line - 1
@stdin_lines[idx]
end
# Prompt for a password in a terminal.
@@ -487,12 +593,11 @@
# @param [Symbol] Set devmode :on or :off
#
# @return [Boolean] The new state of devmode
#
def self.devmode(setting)
- @devmode = setting == :on ? true : false
+ @devmode = setting == :on
end
-
# is devmode currently on?
#
# @return [Boolean]
#