#!/usr/bin/env python # encoding: utf-8 # # Copyright 2009-2011 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. """ makecatalogs Created by Greg Neagle on 2009-03-30. Recursively scans a directory, looking for installer item info files. Builds a repo catalog from these files. Assumes a pkgsinfo directory under repopath. User calling this needs to be able to write to repo/catalogs. """ import sys import os import optparse try: from munkilib import FoundationPlist as plistlib except ImportError: try: import FoundationPlist as plistlib except ImportError: # maybe we're not on an OS X machine... print >> sys.stderr, ("WARNING: FoundationPlist is not available, " "using plistlib instead.") import plistlib try: from munkilib.munkicommon import listdir, get_version except ImportError: # munkilib is not available def listdir(path): """OSX HFS+ string encoding safe listdir(). Args: path: path to list contents of Returns: list of contents, items as str or unicode types """ # if os.listdir() is supplied a unicode object for the path, # it will return unicode filenames instead of their raw fs-dependent # version, which is decomposed utf-8 on OSX. # # we use this to our advantage here and have Python do the decoding # work for us, instead of decoding each item in the output list. # # references: # http://docs.python.org/howto/unicode.html#unicode-filenames # http://developer.apple.com/library/mac/#qa/qa2001/qa1235.html # http://lists.zerezo.com/git/msg643117.html # http://unicode.org/reports/tr15/ section 1.2 if type(path) is str: path = unicode(path, 'utf-8') elif type(path) is not unicode: path = unicode(path) return os.listdir(path) def get_version(): '''Placeholder if munkilib is not available''' return 'UNKNOWN' def makecatalogs(repopath): '''Assembles all pkginfo files into catalogs. Assumes a pkgsinfo directory under repopath. User calling this needs to be able to write to repo/catalogs.''' pkgsinfopath = os.path.join(repopath, 'pkgsinfo') if not os.path.exists(pkgsinfopath): print >> sys.stderr, "pkgsinfo path %s doesn't exist!" % pkgsinfopath exit(-1) catalogs = {} catalogs['all'] = [] for dirpath, dirnames, filenames in os.walk(pkgsinfopath): #subdir = dirpath[len(pkgsinfopath):] for name in filenames: if name.startswith("._") or name == ".DS_Store": # don't process these continue filepath = os.path.join(dirpath, name) try: pkginfo = plistlib.readPlist(filepath) except IOError, inst: print >> sys.stderr, ("IO error for %s: %s" % (filepath, inst)) continue except Exception, inst: print >> sys.stderr, ("Unexpected error for %s: %s" % (filepath, inst)) continue #simple sanity checking if not 'installer_item_location' in pkginfo: print >> sys.stderr, \ ("WARNING: file %s is missing installer_item_location" % filepath) continue installeritempath = os.path.join(repopath, "pkgs", pkginfo['installer_item_location']) if not os.path.exists(installeritempath): print >> sys.stderr, ("WARNING: Info file %s refers to " "missing installer item: %s" % (filepath[len(pkgsinfopath)+1:], pkginfo['installer_item_location'])) continue catalogs['all'].append(pkginfo) for catalogname in pkginfo.get("catalogs", []): if not catalogname in catalogs: catalogs[catalogname] = [] catalogs[catalogname].append(pkginfo) print "Adding %s to %s..." % \ (filepath[len(pkgsinfopath)+1:], catalogname) # clear out old catalogs catalogpath = os.path.join(repopath, "catalogs") if not os.path.exists(catalogpath): os.mkdir(catalogpath) else: for item in listdir(catalogpath): itempath = os.path.join(catalogpath, item) if os.path.isfile(itempath): os.remove(itempath) # write the new catalogs for key in catalogs.keys(): catalogpath = os.path.join(repopath, "catalogs", key) plistlib.writePlist(catalogs[key], catalogpath) def main(): '''Main''' usage = "usage: %prog [options] /path/to/repo_root" p = optparse.OptionParser(usage=usage) p.add_option('--version', '-V', action='store_true', help='Print the version of the munki tools and exit.') options, arguments = p.parse_args() if options.version: print get_version() exit(0) if len(arguments) == 0: print >> sys.stderr, "Need to specify a path to the repo root!" exit(-1) repopath = arguments[0].rstrip("/") if not os.path.exists(repopath): print >> sys.stderr, "Repo root path %s doesn't exist!" % repopath exit(-1) makecatalogs(repopath) if __name__ == '__main__': main()