Version comparisons - munki/munki GitHub Wiki
When finding the latest version of a software title in a catalog, or when comparing the version of software currently installed with a version Munki has in its repo, Munki must compare "version strings" to decide which item is newer (or has a higher version).
A standard, valid version string is a series of integers, separated by periods. In other words, a standard version string consists only of digits and periods. There is no limit to the number of version "parts". Here are some examples of valid standard version strings:
1
2.0
3.1.0
4.10.1
5.1.3.4356
6.1.1.1.1
0.1.2.3.4.5
If both version strings to be compared are in this format, Munki can compare them in a way that most humans would expect.
1.0 > 2.0
2.0 == 2.0.0
2.10 > 2.2
Unfortunately, some software vendors provide version strings that contain additional characters besides digits and periods. Some examples:
v1.0
1.0b2
2.0d1
3.0rev4
8.0 (build 6300)
4 . 0 . 1
9518b3bc2
These may or may not compare as you'd expect. Worse, these might compare differently in Munki 6 and earlier than they do in Munki 7 and later (Munki 7 was a rewrite of the core Munki tools from Python to Swift).
For example, in Munki 6:
8.0 (build 6300) > 8.0.1 (build 6301)
But in Munki 7:
8.0 (build 6300) < 8.0.1 (build 6301)
The key point here is that non-standard version strings may not compare the way you expect, and if they happen to do so in Munki prior to version 7, they may not in Munki 7+.
Here are some examples of comparing non-standard version strings.
Note that the results of some of these comparisons are probably not what the developer intended. "1.0b1" compares as newer than "1.0", for example.
The results of the same comparisons in Munki 7 differ in some cases. In some specific cases, the result is arguably more "correct" (or rather, what a person might want the result to be). But this is not always guaranteed for comparisons of non-standard version strings.
| Comparison | Presumed Intent | Munki 6 | Munki 7 |
|---|---|---|---|
| "1.0b1" < "1.0" | True | False | False |
| "1.0b1" < "1.0b2" | True | True | True |
| "1.0a1" < "1.0b1" | True | True | True |
| "1.0d1" < "1.0b1" | True | False | False |
| "v1.0.0" < "v1.0.1" | True | True | True |
| "1.0" < "v1.0v1" | True | True | True |
| "v1.0" < "1.0.1" | True | False | False |
| "1.0.0rc6" < "1.0.0" | True | False | False |
| "1.0.0-rc6" < "1.0.0" | True | False | True |
| "8.0 (build 6300)" < "8.0.1 (build 6301)" | True | False | True |
If the vendor is using non-standard version strings, you will likely need to make sure Munki can compare two versions of the software correctly. This often means making changes to the pkginfo for the item(s) to ensure the version comparisons are as expected.
You, as the admin, are in control of the value of the version key in pkginfos. You can, and usually should, normalize/clean up those versions so that Munki can accurately find the latest version of an item in the catalog.
Instead of using a non-standard version string that comes from an application's Info.plist:
<key>version</key>
<string>8.0 (build 6300)</string>you can and should "normalize" that:
<key>version</key>
<string>8.0</string>If the pkginfo relies on comparing receipts with non-standard version strings, consider adding an installs array comparing an application or framework bundle with a more standard version string if available. Alternately, an installcheck_script or version_script might be helpful.
Consider this installs array containing a non-standard version string in CFBundleShortVersionString:
<key>installs</key>
<array>
<dict>
<key>CFBundleIdentifier</key>
<string>net.tunnelblick.tunnelblick</string>
<key>CFBundleName</key>
<string>Tunnelblick</string>
<key>CFBundleShortVersionString</key>
<string>8.0.1 (build 6301)</string>
<key>CFBundleVersion</key>
<string>6301</string>
<key>minosversion</key>
<string>13.0</string>
<key>path</key>
<string>/Applications/Tunnelblick.app</string>
<key>type</key>
<string>application</string>
<key>version_comparison_key</key>
<string>CFBundleShortVersionString</string>
</dict>
</array>The previous version looked like this:
<key>installs</key>
<array>
<dict>
<key>CFBundleIdentifier</key>
<string>net.tunnelblick.tunnelblick</string>
<key>CFBundleName</key>
<string>Tunnelblick</string>
<key>CFBundleShortVersionString</key>
<string>8.0 (build 6300)</string>
<key>CFBundleVersion</key>
<string>6300</string>
<key>minosversion</key>
<string>13.0</string>
<key>path</key>
<string>/Applications/Tunnelblick.app</string>
<key>type</key>
<string>application</string>
<key>version_comparison_key</key>
<string>CFBundleShortVersionString</string>
</dict>
</array>
Munki 6 and earlier would not compare the CFBundleShortVersionString values in the way you'd probably want -- "8.0 (build 6300)" would be compared as higher/newer than "8.0.1 (build 6301)". Munki 7 does compare these as most humans would probably want, but, frankly, that's just a happy accident. It could easily compare another set of non-standard strings in a way you don't want. But there's another value in these installs arrays that will definitely be compared as we'd want: CFBundleVersion. You can tell Munki to use the value of that key for version comparisons by changing:
<key>version_comparison_key</key>
<string>CFBundleShortVersionString</string>to
<key>version_comparison_key</key>
<string>CFBundleVersion</string>Now Munki will be comparing "6301" to "6300" and the 8.0.1 version will be seen as newer/higher.
You might be tempted to "normalize" the values for CFBundleShortVersionString or CFBundleVersion in the installs array item(s), or the value of version in a receipts item. Don't do this. Editing the values in the pkginfo won't change the values that are actually on-disk with the installed software, so your changes will not have the desired effect and could easily result in undesired behavior.
If a pkginfo relies on an installs array for version comparisons and there are no plist keys with usable/useful version information, you could instead create an installs array item pointing to a specific file. An example:
<key>installs</key>
<array>
<dict>
<key>md5checksum</key>
<string>b442add2be680402f3a96b2e7d264448</string>
<key>path</key>
<string>/Applications/Foo.app/Contents/MacOS/Foo</string>
<key>type</key>
<string>file</string>
</dict>
</array>
</dict>In this case, Munki will check for the existence of /Applications/Foo.app/Contents/MacOS/Foo, and if the file has the same MD5 checksum as the one given. If the file does not exist, or has a different checksum, Munki will decide it needs to be installed or updated.
This approach has a significant downside. Munki can't actually tell if what is installed is older, the same, or newer -- it can only tell that what is installed is different than expected. This can lead to Munki downgrading (or at least attempting to downgrade) software installs if what is actually installed is newer/higher than the item in Munki.
An installcheck_script can be used to provide a way for Munki to determine if the item needs to be installed. See here for details.
Munki 7 adds support for a version_script, which is yet another way for Munki to determine if the item needs to be installed or updated, with the advantage that it can be used for managed_updates as well as managed_installs. See here for details.