mirror of
https://github.com/munki/munki.git
synced 2026-01-07 23:20:00 -06:00
git-svn-id: http://munki.googlecode.com/svn/trunk@146 a4e17f2e-e282-11dd-95e1-755cbddbdd66
260 lines
9.2 KiB
Python
Executable File
260 lines
9.2 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# encoding: utf-8
|
|
#
|
|
# Copyright 2009 Greg Neagle.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
"""
|
|
makepkginfo
|
|
|
|
Created by Greg Neagle on 2008-11-25.
|
|
Creates a managed install pkg info plist given an Installer item:
|
|
a .pkg, a .mpkg, or a .dmg containing a .pkg or .mpkg
|
|
at the root of the mounted disk image.
|
|
|
|
You may also pass additional items that are installed by the package. These
|
|
are added to the 'installs' key of the catalog item plist and are used when
|
|
processing the catalog to check if the package needs to be installed or
|
|
reinstalled.
|
|
|
|
The generated plist is printed to STDOUT.
|
|
|
|
Usage: makepkginfo /path/to/package_or_dmg [-f /path/to/item/it/installs ...]
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
import re
|
|
import optparse
|
|
from distutils import version
|
|
import plistlib
|
|
import subprocess
|
|
import hashlib
|
|
|
|
import munkilib.munkicommon
|
|
|
|
|
|
def getCatalogInfoFromDmg(dmgpath):
|
|
"""
|
|
* Mounts a disk image
|
|
* Gets catalog info for the first installer item found at the root level.
|
|
* Unmounts the disk image
|
|
|
|
To-do: handle multiple installer items on a disk image
|
|
"""
|
|
cataloginfo = None
|
|
mountpoints = munkilib.munkicommon.mountdmg(dmgpath)
|
|
for mountpoint in mountpoints:
|
|
for fsitem in os.listdir(mountpoint):
|
|
itempath = os.path.join(mountpoint, fsitem)
|
|
if itempath.endswith('.pkg') or itempath.endswith('.mpkg'):
|
|
cataloginfo = munkilib.munkicommon.getPackageMetaData(itempath)
|
|
# get out of fsitem loop
|
|
break
|
|
if cataloginfo:
|
|
# get out of mountpoint loop
|
|
break
|
|
|
|
#unmount all the mountpoints from the dmg
|
|
for mountpoint in mountpoints:
|
|
munkilib.munkicommon.unmountdmg(mountpoint)
|
|
return cataloginfo
|
|
|
|
|
|
def getBundleInfo(path):
|
|
"""
|
|
Returns Info.plist data if available
|
|
for bundle at path
|
|
"""
|
|
infopath = os.path.join(path, "Contents", "Info.plist")
|
|
if not os.path.exists(infopath):
|
|
infopath = os.path.join(path, "Resources", "Info.plist")
|
|
|
|
if os.path.exists(infopath):
|
|
try:
|
|
pl = plistlib.readPlist(infopath)
|
|
return pl
|
|
except:
|
|
pass
|
|
|
|
return None
|
|
|
|
|
|
def getmd5hash(filename):
|
|
"""
|
|
Returns hex of MD5 checksum of a file
|
|
"""
|
|
if not os.path.isfile(filename):
|
|
return "NOT A FILE"
|
|
|
|
f = open(filename, 'rb')
|
|
m = hashlib.md5()
|
|
while 1:
|
|
chunk = f.read(2**16)
|
|
if not chunk:
|
|
break
|
|
m.update(chunk)
|
|
f.close()
|
|
return m.hexdigest()
|
|
|
|
|
|
def getiteminfo(itempath):
|
|
"""
|
|
Gets info for filesystem items passed to makecatalog item, to be used for
|
|
the "installs" key.
|
|
Determines if the item is an application, bundle, Info.plist, or a file or directory
|
|
and gets additional metadata for later comparison.
|
|
"""
|
|
infodict = {}
|
|
if itempath.endswith('.app'):
|
|
infodict['type'] = 'application'
|
|
infodict['path'] = itempath
|
|
pl = getBundleInfo(itempath)
|
|
if 'CFBundleName' in pl:
|
|
infodict['CFBundleName'] = pl['CFBundleName']
|
|
if 'CFBundleIdentifier' in pl:
|
|
infodict['CFBundleIdentifier'] = pl['CFBundleIdentifier']
|
|
if 'CFBundleShortVersionString' in pl:
|
|
infodict['CFBundleShortVersionString'] = pl['CFBundleShortVersionString']
|
|
if 'LSMinimumSystemVersion' in pl:
|
|
infodict['minosversion'] = pl['LSMinimumSystemVersion']
|
|
elif 'SystemVersionCheck:MinimumSystemVersion' in pl:
|
|
infodict['minosversion'] = pl['SystemVersionCheck:MinimumSystemVersion']
|
|
|
|
elif os.path.exists(os.path.join(itempath,'Contents','Info.plist')) or os.path.exists(os.path.join(itempath,'Resources','Info.plist')):
|
|
infodict['type'] = 'bundle'
|
|
infodict['path'] = itempath
|
|
pl = getBundleInfo(itempath)
|
|
if 'CFBundleShortVersionString' in pl:
|
|
infodict['CFBundleShortVersionString'] = pl['CFBundleShortVersionString']
|
|
|
|
elif itempath.endswith("Info.plist") or itempath.endswith("version.plist"):
|
|
infodict['type'] = 'plist'
|
|
infodict['path'] = itempath
|
|
try:
|
|
pl = plistlib.readPlist(itempath)
|
|
if 'CFBundleShortVersionString' in pl:
|
|
infodict['CFBundleShortVersionString'] = pl['CFBundleShortVersionString']
|
|
except:
|
|
pass
|
|
|
|
if not 'CFBundleShortVersionString' in infodict:
|
|
infodict['type'] = 'file'
|
|
infodict['path'] = itempath
|
|
if os.path.isfile(itempath):
|
|
infodict['md5checksum'] = getmd5hash(itempath)
|
|
return infodict
|
|
|
|
|
|
|
|
def main():
|
|
usage = "usage: %prog [options] /path/to/installeritem"
|
|
p = optparse.OptionParser(usage=usage)
|
|
p.add_option('--file', '-f', action="append",
|
|
help='Path to a filesystem item installed by this package. Can be specified multiple times.')
|
|
options, arguments = p.parse_args()
|
|
if len(arguments) == 0:
|
|
print >>sys.stderr, "Need to specify an installer item (.pkg, .mpkg, .dmg)!"
|
|
exit(-1)
|
|
|
|
if len(arguments) > 1:
|
|
print >>sys.stderr, "Can process only one installer item at a time. Ignoring additional installer items."
|
|
|
|
item = arguments[0].rstrip("/")
|
|
if os.path.exists(item):
|
|
# get size of installer item
|
|
itemsize = 0
|
|
if os.path.isfile(item):
|
|
itemsize = int(os.path.getsize(item))
|
|
if os.path.isdir(item):
|
|
# need to walk the dir and add it all up
|
|
for (path, dirs, files) in os.walk(item):
|
|
for f in files:
|
|
filename = os.path.join(path, f)
|
|
# use os.lstat so we don't follow symlinks
|
|
itemsize += int(os.lstat(filename).st_size)
|
|
|
|
if item.endswith('.dmg'):
|
|
catinfo = getCatalogInfoFromDmg(item)
|
|
if not catinfo:
|
|
print >>sys.stderr, "Could not find a supported installer item in %s!" % item
|
|
exit(-1)
|
|
|
|
elif item.endswith('.pkg') or item.endswith('.mpkg'):
|
|
catinfo = munkilib.munkicommon.getPackageMetaData(item)
|
|
if not catinfo:
|
|
print >>sys.stderr, "%s doesn't appear to be a valid installer item!" % item
|
|
exit(-1)
|
|
|
|
else:
|
|
print >>sys.stderr, "%s is not an installer package!" % item
|
|
exit(-1)
|
|
|
|
if catinfo:
|
|
catinfo['installer_item_size'] = int(itemsize/1024)
|
|
minosversion = ""
|
|
if options.file:
|
|
installs = []
|
|
for fitem in options.file:
|
|
# no trailing slashes, please.
|
|
fitem = fitem.rstrip('/')
|
|
if fitem.startswith('/Library/Receipts'):
|
|
# no receipts, please!
|
|
print >>sys.stderr, "Item %s appears to be a receipt. Skipping." % fitem
|
|
continue
|
|
if os.path.exists(fitem):
|
|
iteminfodict = getiteminfo(fitem)
|
|
if 'minosversion' in iteminfodict:
|
|
thisminosversion = iteminfodict.pop('minosversion')
|
|
if not minosversion:
|
|
minosversion = thisminosversion
|
|
elif version.LooseVersion(thisminosversion) < version.LooseVersion(minosversion):
|
|
minosversion = thisminosversion
|
|
installs.append(iteminfodict)
|
|
else:
|
|
print >>sys.stderr, "Item %s doesn't exist. Skipping." % fitem
|
|
catinfo['installs'] = installs
|
|
|
|
# try to generate the correct item location
|
|
temppath = item
|
|
location = ""
|
|
while len(temppath) > 4:
|
|
if temppath.endswith('/pkgs'):
|
|
location = item[len(temppath)+1:]
|
|
break
|
|
else:
|
|
temppath = os.path.dirname(temppath)
|
|
|
|
if not location:
|
|
#just the filename
|
|
location = os.path.split(item)[1]
|
|
catinfo['installer_item_location'] = location
|
|
|
|
if minosversion:
|
|
catinfo['minimum_os_version'] = minosversion
|
|
else:
|
|
catinfo['minimum_os_version'] = "10.4.0"
|
|
|
|
# some metainfo
|
|
catinfo['catalogs'] = ['testing']
|
|
catinfo['uninstallable'] = True
|
|
catinfo['uninstall_method'] = "removepackages"
|
|
|
|
# and now, what we've all been waiting for...
|
|
print plistlib.writePlistToString(catinfo)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|
|
|