mirror of
https://github.com/munki/munki.git
synced 2026-05-05 20:09:19 -05:00
Merge branch 'cloudrepo' into Munki3
This commit is contained in:
+60
-37
@@ -29,6 +29,7 @@ from optparse import OptionParser
|
||||
from munkilib import munkicommon
|
||||
from munkilib import FoundationPlist
|
||||
from munkilib import iconutils
|
||||
from munkilib import Repo
|
||||
|
||||
# PyLint cannot properly find names inside Cocoa libraries, so issues bogus
|
||||
# No name 'Foo' in module 'Bar' warnings. Disable them.
|
||||
@@ -36,10 +37,9 @@ from munkilib import iconutils
|
||||
from Foundation import CFPreferencesCopyAppValue
|
||||
# pylint: enable=E0611
|
||||
|
||||
def generate_png_from_copy_from_dmg_item(install_item, repo_path):
|
||||
def generate_png_from_copy_from_dmg_item(install_item, repo):
|
||||
'''Generate a PNG from a disk image containing an application'''
|
||||
dmgpath = os.path.join(
|
||||
repo_path, 'pkgs', install_item['installer_item_location'])
|
||||
dmgpath = repo.join('pkgs', install_item['installer_item_location'])
|
||||
mountpoints = munkicommon.mountdmg(dmgpath)
|
||||
if mountpoints:
|
||||
mountpoint = mountpoints[0]
|
||||
@@ -49,9 +49,9 @@ def generate_png_from_copy_from_dmg_item(install_item, repo_path):
|
||||
app_path = os.path.join(mountpoint, apps[0]['source_item'])
|
||||
icon_path = iconutils.findIconForApp(app_path)
|
||||
if icon_path:
|
||||
png_path = os.path.join(
|
||||
repo_path, u'icons', install_item['name'] + u'.png')
|
||||
result = iconutils.convertIconToPNG(icon_path, png_path)
|
||||
png_path = repo.join(u'icons', install_item['name'] + u'.png')
|
||||
png_handle = repo.open(png_path, 'w')
|
||||
result = iconutils.convertIconToPNG(icon_path, png_handle.local_path)
|
||||
if result:
|
||||
print_utf8(u'\tWrote: %s' % png_path)
|
||||
else:
|
||||
@@ -63,13 +63,13 @@ def generate_png_from_copy_from_dmg_item(install_item, repo_path):
|
||||
munkicommon.unmountdmg(mountpoint)
|
||||
|
||||
|
||||
def generate_pngs_from_installer_pkg(install_item, repo_path):
|
||||
def generate_pngs_from_installer_pkg(install_item, repo):
|
||||
'''Generate PNGS from applications inside a pkg'''
|
||||
icon_paths = []
|
||||
mountpoint = None
|
||||
pkg_path = None
|
||||
item_path = os.path.join(
|
||||
repo_path, u'pkgs', install_item['installer_item_location'])
|
||||
pkg_repo = None
|
||||
item_path = repo.join( u'pkgs', install_item['installer_item_location'])
|
||||
if munkicommon.hasValidDiskImageExt(item_path):
|
||||
dmg_path = item_path
|
||||
mountpoints = munkicommon.mountdmg(dmg_path)
|
||||
@@ -86,28 +86,33 @@ def generate_pngs_from_installer_pkg(install_item, repo_path):
|
||||
break
|
||||
elif munkicommon.hasValidPackageExt(item_path):
|
||||
pkg_path = item_path
|
||||
pkg_repo = repo
|
||||
if pkg_path:
|
||||
if os.path.isdir(pkg_path):
|
||||
icon_paths = iconutils.extractAppIconsFromBundlePkg(pkg_path)
|
||||
if (pkg_repo and pkg_repo.isdir(pkg_path)) or (not repo and os.path.isdir(pkg_path)):
|
||||
icon_paths = iconutils.extractAppIconsFromBundlePkg(pkg_path, pkg_repo)
|
||||
else:
|
||||
handle = None
|
||||
if pkg_repo:
|
||||
handle = pkg_repo.open(pkg_path, 'r')
|
||||
pkg_path = handle.local_path
|
||||
icon_paths = iconutils.extractAppIconsFromFlatPkg(pkg_path)
|
||||
|
||||
if mountpoint:
|
||||
munkicommon.unmountdmg(mountpoint)
|
||||
|
||||
if len(icon_paths) == 1:
|
||||
png_path = os.path.join(
|
||||
repo_path, u'icons', install_item['name'] + u'.png')
|
||||
result = iconutils.convertIconToPNG(icon_paths[0], png_path)
|
||||
png_path = repo.join(u'icons', install_item['name'] + u'.png')
|
||||
handle = repo.open(png_path, 'w')
|
||||
result = iconutils.convertIconToPNG(icon_paths[0], handle.local_path)
|
||||
if result:
|
||||
print_utf8(u'\tWrote: %s' % png_path)
|
||||
elif len(icon_paths) > 1:
|
||||
index = 1
|
||||
for icon_path in icon_paths:
|
||||
png_path = os.path.join(
|
||||
repo_path, u'icons',
|
||||
install_item['name'] + '_' + str(index) + u'.png')
|
||||
result = iconutils.convertIconToPNG(icon_path, png_path)
|
||||
png_path = repo.join(
|
||||
u'icons', install_item['name'] + '_' + str(index) + u'.png')
|
||||
handle = repo.open(png_path, 'w')
|
||||
result = iconutils.convertIconToPNG(icon_path, handle.local_path)
|
||||
if result:
|
||||
print_utf8(u'\tWrote: %s' % png_path)
|
||||
index += 1
|
||||
@@ -115,12 +120,13 @@ def generate_pngs_from_installer_pkg(install_item, repo_path):
|
||||
print_utf8(u'\tNo application icons found.')
|
||||
|
||||
|
||||
def find_items_to_check(repo_path, itemlist=None):
|
||||
def findItemsToCheck(repo, itemlist=None):
|
||||
'''Builds a list of items to check; only the latest version
|
||||
of an item is retained. If itemlist is given, include items
|
||||
only on that list.'''
|
||||
all_catalog_path = os.path.join(repo_path, 'catalogs/all')
|
||||
catalogitems = FoundationPlist.readPlist(all_catalog_path)
|
||||
all_catalog_path = repo.join('catalogs', 'all')
|
||||
handle = repo.open(all_catalog_path)
|
||||
catalogitems = FoundationPlist.readPlist(handle.local_path)
|
||||
itemdb = {}
|
||||
for catalogitem in catalogitems:
|
||||
if itemlist and catalogitem['name'] not in itemlist:
|
||||
@@ -137,27 +143,26 @@ def find_items_to_check(repo_path, itemlist=None):
|
||||
return pkg_list
|
||||
|
||||
|
||||
def generate_pngs_from_munki_items(repo_path, force=False, itemlist=None):
|
||||
def generate_pngs_from_munki_items(repo, force=False, itemlist=None):
|
||||
'''Generate PNGs from either pkgs or disk images containing applications'''
|
||||
itemlist = find_items_to_check(repo_path, itemlist=itemlist)
|
||||
icons_dir = os.path.join(repo_path, u'icons')
|
||||
if not os.path.exists(icons_dir):
|
||||
os.mkdir(icons_dir)
|
||||
itemlist = findItemsToCheck(repo, itemlist=itemlist)
|
||||
icons_dir = repo.join(u'icons')
|
||||
if not repo.exists(icons_dir):
|
||||
repo.mkdir(icons_dir)
|
||||
for item in itemlist:
|
||||
print_utf8(u'Processing %s...' % item['name'])
|
||||
icon_name = item.get('icon_name') or item['name']
|
||||
if not os.path.splitext(icon_name)[1]:
|
||||
icon_name += u'.png'
|
||||
icon_path = os.path.join(
|
||||
repo_path, u'icons', icon_name)
|
||||
if os.path.exists(icon_path) and not force:
|
||||
icon_path = repo.join(u'icons', icon_name)
|
||||
if repo.exists(icon_path) and not force:
|
||||
print_utf8(u'Found existing icon at %s' % icon_name)
|
||||
continue
|
||||
installer_type = item.get('installer_type')
|
||||
if installer_type == 'copy_from_dmg':
|
||||
generate_png_from_copy_from_dmg_item(item, repo_path)
|
||||
generate_png_from_copy_from_dmg_item(item, repo)
|
||||
elif installer_type in [None, '']:
|
||||
generate_pngs_from_installer_pkg(item, repo_path)
|
||||
generate_pngs_from_installer_pkg(item, repo)
|
||||
else:
|
||||
print_utf8(u'\tCan\'t process installer_type: %s' % installer_type)
|
||||
|
||||
@@ -185,10 +190,9 @@ def pref(prefname):
|
||||
|
||||
def main():
|
||||
'''Main'''
|
||||
usage = "usage: %prog [options] [/path/to/repo_root]"
|
||||
usage = "usage: %prog [options] [/path/to/repo_root] [repo_url] [plugin]"
|
||||
parser = OptionParser(usage=usage)
|
||||
parser.add_option(
|
||||
'--force', '-f', action='store_true', dest='force',
|
||||
parser.add_option('--force', '-f', action='store_true', dest='force',
|
||||
help='Create pngs even if there is an existing icon in the repo.')
|
||||
parser.add_option(
|
||||
'--item', '-i', action='append', type='string', dest='items',
|
||||
@@ -198,6 +202,8 @@ def main():
|
||||
|
||||
# Make sure we have a path to work with
|
||||
repo_path = None
|
||||
repo_url = None
|
||||
plugin = None
|
||||
if len(arguments) == 0:
|
||||
repo_path = pref('repo_path')
|
||||
if not repo_path:
|
||||
@@ -209,13 +215,30 @@ def main():
|
||||
repo_path = arguments[0].rstrip("/")
|
||||
|
||||
# Make sure the repo path exists
|
||||
if not os.path.exists(repo_path):
|
||||
if len(arguments) > 1:
|
||||
repo_url = arguments[1].rstrip("/")
|
||||
else:
|
||||
repo_url = pref('repo_url')
|
||||
if not repo_url:
|
||||
print_err_utf8("Need to specify a URL for the repo!")
|
||||
exit(-1)
|
||||
else:
|
||||
print_utf8("Using repo url: %s" % repo_url)
|
||||
|
||||
if len(arguments) > 2:
|
||||
plugin = arguments[2]
|
||||
else:
|
||||
plugin = pref('plugin')
|
||||
|
||||
# Make sure the repo exists
|
||||
repo = Repo.Open(repo_path, repo_url, plugin)
|
||||
if not repo.available():
|
||||
print_err_utf8("Repo root path %s doesn't exist!" % repo_path)
|
||||
exit(-1)
|
||||
|
||||
# generate icons!
|
||||
generate_pngs_from_munki_items(repo_path, force=options.force,
|
||||
itemlist=options.items)
|
||||
generate_pngs_from_munki_items(repo, force=options.force,
|
||||
itemlist=options.items)
|
||||
|
||||
# clean up
|
||||
munkicommon.cleanUpTmpDir()
|
||||
|
||||
+44
-33
@@ -31,6 +31,7 @@ import sys
|
||||
import os
|
||||
import optparse
|
||||
import hashlib
|
||||
from munkilib import Repo
|
||||
|
||||
try:
|
||||
from munkilib import FoundationPlist as plistlib
|
||||
@@ -90,7 +91,7 @@ def print_err_utf8(text):
|
||||
print >> sys.stderr, text.encode('UTF-8')
|
||||
|
||||
|
||||
def makecatalogs(repopath, options):
|
||||
def makecatalogs(repo, options):
|
||||
'''Assembles all pkginfo files into catalogs.
|
||||
Assumes a pkgsinfo directory under repopath.
|
||||
User calling this needs to be able to write to the repo/catalogs
|
||||
@@ -101,15 +102,15 @@ def makecatalogs(repopath, options):
|
||||
exit_code = 0
|
||||
|
||||
# Make sure the icons directory exists
|
||||
iconspath = os.path.join(repopath, 'icons')
|
||||
# make sure iconspath is Unicode so that os.walk later gives us
|
||||
iconspath = repo.join('icons')
|
||||
# make sure iconspath is Unicode so that repo.walk later gives us
|
||||
# Unicode names back.
|
||||
if type(iconspath) is str:
|
||||
iconspath = unicode(iconspath, 'utf-8')
|
||||
elif type(iconspath) is not unicode:
|
||||
iconspath = unicode(iconspath)
|
||||
|
||||
if not os.path.exists(iconspath):
|
||||
if not repo.exists(iconspath):
|
||||
print_err_utf8("icons path %s doesn't exist, skipping hashing!"
|
||||
% iconspath)
|
||||
iconhashing = False
|
||||
@@ -119,7 +120,7 @@ def makecatalogs(repopath, options):
|
||||
iconhashing = True
|
||||
|
||||
# Walk through the icon files
|
||||
for dirpath, dirnames, filenames in os.walk(iconspath):
|
||||
for dirpath, dirnames, filenames in repo.walk(iconspath):
|
||||
for dirname in dirnames:
|
||||
# don't recurse into directories that start
|
||||
# with a period.
|
||||
@@ -130,7 +131,7 @@ def makecatalogs(repopath, options):
|
||||
# skip files that start with a period as well
|
||||
continue
|
||||
|
||||
filepath = os.path.join(dirpath, filename)
|
||||
filepath = repo.join(dirpath, filename)
|
||||
|
||||
iconpath = filepath.rsplit(iconspath + os.path.sep, 1)[1]
|
||||
|
||||
@@ -138,7 +139,7 @@ def makecatalogs(repopath, options):
|
||||
try:
|
||||
print_utf8("Hashing %s..." % (iconpath))
|
||||
icons[iconpath] = (
|
||||
hashlib.sha256(open(filepath, 'rb').read()).hexdigest())
|
||||
hashlib.sha256(repo.open(filepath, 'rb').read()).hexdigest())
|
||||
except IOError, inst:
|
||||
errors.append("IO error for %s: %s" % (filepath, inst))
|
||||
exit_code = -1
|
||||
@@ -150,15 +151,15 @@ def makecatalogs(repopath, options):
|
||||
continue
|
||||
|
||||
# Make sure the pkgsinfo directory exists
|
||||
pkgsinfopath = os.path.join(repopath, 'pkgsinfo')
|
||||
# make sure pkgsinfopath is Unicode so that os.walk later gives us
|
||||
pkgsinfopath = repo.join('pkgsinfo')
|
||||
# make sure pkgsinfopath is Unicode so that repo.walk later gives us
|
||||
# Unicode names back.
|
||||
if type(pkgsinfopath) is str:
|
||||
pkgsinfopath = unicode(pkgsinfopath, 'utf-8')
|
||||
elif type(pkgsinfopath) is not unicode:
|
||||
pkgsinfopath = unicode(pkgsinfopath)
|
||||
|
||||
if not os.path.exists(pkgsinfopath):
|
||||
if not repo.exists(pkgsinfopath):
|
||||
print_err_utf8("pkgsinfo path %s doesn't exist!" % pkgsinfopath)
|
||||
exit(-1)
|
||||
|
||||
@@ -167,7 +168,7 @@ def makecatalogs(repopath, options):
|
||||
catalogs['all'] = []
|
||||
|
||||
# Walk through the pkginfo files
|
||||
for dirpath, dirnames, filenames in os.walk(pkgsinfopath, followlinks=True):
|
||||
for dirpath, dirnames, filenames in repo.walk(pkgsinfopath, followlinks=True):
|
||||
for dirname in dirnames:
|
||||
# don't recurse into directories that start
|
||||
# with a period.
|
||||
@@ -178,11 +179,13 @@ def makecatalogs(repopath, options):
|
||||
# skip files that start with a period as well
|
||||
continue
|
||||
|
||||
filepath = os.path.join(dirpath, filename)
|
||||
filepath = repo.join(dirpath, filename)
|
||||
|
||||
# Try to read the pkginfo file
|
||||
try:
|
||||
pkginfo = plistlib.readPlist(filepath)
|
||||
handle = repo.open(filepath, 'r')
|
||||
if handle:
|
||||
pkginfo = plistlib.readPlist(handle.local_path)
|
||||
except IOError, inst:
|
||||
errors.append("IO error for %s: %s" % (filepath, inst))
|
||||
exit_code = -1
|
||||
@@ -249,8 +252,8 @@ def makecatalogs(repopath, options):
|
||||
# Try to form a path and fail if the
|
||||
# installer_item_location is not a valid type
|
||||
try:
|
||||
installeritempath = os.path.join(
|
||||
repopath, "pkgs", pkginfo['installer_item_location'])
|
||||
installeritempath = repo.join(
|
||||
"pkgs", pkginfo['installer_item_location'])
|
||||
except TypeError:
|
||||
errors.append("WARNING: invalid installer_item_location "
|
||||
"in info file %s"
|
||||
@@ -259,7 +262,7 @@ def makecatalogs(repopath, options):
|
||||
continue
|
||||
|
||||
# Check if the installer item actually exists
|
||||
if not os.path.exists(installeritempath):
|
||||
if not repo.exists(installeritempath):
|
||||
errors.append("WARNING: Info file %s refers to "
|
||||
"missing installer item: %s" %
|
||||
(filepath[len(pkgsinfopath)+1:],
|
||||
@@ -285,8 +288,8 @@ def makecatalogs(repopath, options):
|
||||
# if an uninstaller_item_location is specified, sanity-check it
|
||||
if 'uninstaller_item_location' in pkginfo:
|
||||
try:
|
||||
uninstalleritempath = os.path.join(
|
||||
repopath, "pkgs", pkginfo['uninstaller_item_location'])
|
||||
uninstalleritempath = repo.join(
|
||||
"pkgs", pkginfo['uninstaller_item_location'])
|
||||
except TypeError:
|
||||
errors.append("WARNING: invalid uninstaller_item_location "
|
||||
"in info file %s"
|
||||
@@ -295,7 +298,7 @@ def makecatalogs(repopath, options):
|
||||
continue
|
||||
|
||||
# Check if the uninstaller item actually exists
|
||||
if not os.path.exists(uninstalleritempath):
|
||||
if not repo.exists(uninstalleritempath):
|
||||
errors.append("WARNING: Info file %s refers to "
|
||||
"missing uninstaller item: %s" %
|
||||
(filepath[len(pkgsinfopath)+1:],
|
||||
@@ -325,20 +328,20 @@ def makecatalogs(repopath, options):
|
||||
print_err_utf8(error)
|
||||
|
||||
# clear out old catalogs
|
||||
catalogpath = os.path.join(repopath, "catalogs")
|
||||
if not os.path.exists(catalogpath):
|
||||
os.mkdir(catalogpath)
|
||||
catalogpath = repo.join("catalogs")
|
||||
if not repo.exists(catalogpath):
|
||||
repo.mkdir(catalogpath)
|
||||
else:
|
||||
for item in listdir(catalogpath):
|
||||
itempath = os.path.join(catalogpath, item)
|
||||
if os.path.isfile(itempath):
|
||||
os.remove(itempath)
|
||||
for item in repo.listdir(catalogpath):
|
||||
itempath = repo.join(catalogpath, item)
|
||||
if repo.isfile(itempath):
|
||||
repo.remove(itempath)
|
||||
|
||||
# write the new catalogs
|
||||
print
|
||||
for key in catalogs.keys():
|
||||
catalogpath = os.path.join(repopath, "catalogs", key)
|
||||
if os.path.exists(catalogpath):
|
||||
catalogpath = repo.join("catalogs", key)
|
||||
if repo.exists(catalogpath):
|
||||
print_err_utf8(
|
||||
"WARNING: catalog %s already exists at %s. "
|
||||
"Perhaps this is a non-case sensitive filesystem and you "
|
||||
@@ -346,8 +349,10 @@ def makecatalogs(repopath, options):
|
||||
% (key, catalogpath))
|
||||
exit_code = -1
|
||||
elif len(catalogs[key]) != 0:
|
||||
plistlib.writePlist(catalogs[key], catalogpath)
|
||||
print "Created catalog %s..." % (catalogpath)
|
||||
handle = repo.open(catalogpath, 'w')
|
||||
if handle:
|
||||
plistlib.writePlist(catalogs[key], handle.local_path)
|
||||
print "Created catalog %s..." % (catalogpath)
|
||||
else:
|
||||
print_err_utf8(
|
||||
"WARNING: Did not create catalog %s "
|
||||
@@ -386,6 +391,12 @@ def main():
|
||||
help='Print the version of the munki tools and exit.')
|
||||
parser.add_option('--force', '-f', action='store_true', dest='force',
|
||||
help='Disable sanity checks.')
|
||||
parser.add_option('--repo_url', '--repo-url', default=pref('repo_url'),
|
||||
help='Optional repo fileshare URL that takes precedence '
|
||||
'over the default repo_url specified via '
|
||||
'--configure.')
|
||||
parser.add_option('--plugin', '--plugin', default=pref('plugin'),
|
||||
help='Specify a custom plugin to run for munkiimport Repo.')
|
||||
parser.set_defaults(force=False)
|
||||
options, arguments = parser.parse_args()
|
||||
|
||||
@@ -406,13 +417,13 @@ def main():
|
||||
repopath = arguments[0].rstrip("/")
|
||||
|
||||
# Make sure the repo path exists
|
||||
if not os.path.exists(repopath):
|
||||
repo = Repo.Open(repopath, options.repo_url, options.plugin)
|
||||
if not repo.exists():
|
||||
print_err_utf8("Repo root path %s doesn't exist!" % repopath)
|
||||
exit(-1)
|
||||
|
||||
# Make the catalogs
|
||||
makecatalogs(repopath, options)
|
||||
makecatalogs(repo, options)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
|
||||
+39
-91
@@ -35,6 +35,9 @@ import time
|
||||
from ctypes.util import find_library
|
||||
from xml.parsers.expat import ExpatError
|
||||
|
||||
from munkilib import munkicommon
|
||||
from munkilib import Repo
|
||||
|
||||
try:
|
||||
from munkilib.munkicommon import get_version
|
||||
except ImportError:
|
||||
@@ -269,12 +272,12 @@ def get_installer_item_names(cataloglist):
|
||||
'''Returns a list of unique installer item (pkg) names
|
||||
from the given list of catalogs'''
|
||||
item_list = []
|
||||
catalogs_path = os.path.join(pref('repo_path'), 'catalogs')
|
||||
for filename in os.listdir(catalogs_path):
|
||||
catalogs_path = repo.join('catalogs')
|
||||
for filename in repo.listdir(catalogs_path):
|
||||
if filename in cataloglist:
|
||||
try:
|
||||
catalog = plistlib.readPlist(
|
||||
os.path.join(catalogs_path, filename))
|
||||
handle = repo.open(repo.join(catalogs_path, filename), 'r')
|
||||
catalog = plistlib.readPlist(handle.local_path)
|
||||
except (IOError, OSError, ExpatError):
|
||||
# skip items that aren't valid plists
|
||||
# or that we can't read
|
||||
@@ -290,9 +293,9 @@ def get_installer_item_names(cataloglist):
|
||||
|
||||
def get_manifest_names():
|
||||
'''Returns a list of available manifests'''
|
||||
manifests_path = os.path.join(pref('repo_path'), 'manifests')
|
||||
manifests_path = repo.join('manifests')
|
||||
manifests = []
|
||||
for dirpath, dirnames, filenames in os.walk(manifests_path):
|
||||
for dirpath, dirnames, filenames in repo.walk(manifests_path):
|
||||
for dirname in dirnames:
|
||||
# don't recurse into directories that start
|
||||
# with a period.
|
||||
@@ -303,21 +306,22 @@ def get_manifest_names():
|
||||
if name.startswith("."):
|
||||
# don't process these
|
||||
continue
|
||||
manifests.append(os.path.join(subdir, name).lstrip('/'))
|
||||
manifests.append(repo.join(subdir, name).lstrip('/'))
|
||||
manifests.sort()
|
||||
return manifests
|
||||
|
||||
|
||||
def get_catalogs():
|
||||
'''Returns a list of available catalogs'''
|
||||
catalogs_path = os.path.join(pref('repo_path'), 'catalogs')
|
||||
catalogs_path = repo.join('catalogs')
|
||||
catalogs = []
|
||||
for name in os.listdir(catalogs_path):
|
||||
for name in repo.listdir(catalogs_path):
|
||||
if name.startswith(".") or name == 'all':
|
||||
# don't process these
|
||||
continue
|
||||
try:
|
||||
_ = plistlib.readPlist(os.path.join(catalogs_path, name))
|
||||
handle = repo.open(repo.join(catalogs_path, name), 'r')
|
||||
_ = plistlib.readPlist(handle.local_path)
|
||||
except (IOError, OSError, ExpatError):
|
||||
# skip items that aren't valid plists
|
||||
pass
|
||||
@@ -369,11 +373,11 @@ def printplist(plistdict):
|
||||
|
||||
def get_manifest(manifest_name):
|
||||
'''Gets the contents of a manifest'''
|
||||
manifest_path = os.path.join(
|
||||
pref('repo_path'), 'manifests', manifest_name)
|
||||
if os.path.exists(manifest_path):
|
||||
manifest_path = repo.join('manifests', manifest_name)
|
||||
if repo.exists(manifest_path):
|
||||
try:
|
||||
return plistlib.readPlist(manifest_path)
|
||||
handle = repo.open(manifest_path, 'r')
|
||||
return plistlib.readPlist(handle.local_path)
|
||||
except (IOError, OSError, ExpatError):
|
||||
print >> sys.stderr, (
|
||||
'Could not read manifest %s' % manifest_name)
|
||||
@@ -385,14 +389,14 @@ def get_manifest(manifest_name):
|
||||
|
||||
def save_manifest(manifest_dict, manifest_name, overwrite_existing=False):
|
||||
'''Saves a manifest to disk'''
|
||||
manifest_path = os.path.join(
|
||||
pref('repo_path'), 'manifests', manifest_name)
|
||||
manifest_path = repo.join('manifests', manifest_name)
|
||||
if not overwrite_existing:
|
||||
if os.path.exists(manifest_path):
|
||||
if repo.exists(manifest_path):
|
||||
print >> sys.stderr, '%s already exists!' % manifest_name
|
||||
return False
|
||||
try:
|
||||
plistlib.writePlist(manifest_dict, manifest_path)
|
||||
handle = repo.open(manifest_path, 'w')
|
||||
plistlib.writePlist(manifest_dict, handle.local_path)
|
||||
return True
|
||||
except (IOError, OSError, ExpatError), err:
|
||||
print >> sys.stderr, 'Saving %s failed: %s' % (manifest_name, err)
|
||||
@@ -417,77 +421,14 @@ def manifest_rename(source_manifest_name, dest_manifest_name,
|
||||
source_manifest_name, dest_manifest_name, err)
|
||||
return False
|
||||
|
||||
def repo_available():
|
||||
"""Checks the repo path for proper directory structure.
|
||||
If the directories look wrong we probably don't have a
|
||||
valid repo path. Returns True if things look OK."""
|
||||
repo_path = pref('repo_path')
|
||||
if not repo_path:
|
||||
print >> sys.stderr, 'No repo path specified.'
|
||||
return False
|
||||
if not os.path.exists(repo_path) and pref('repo_url'):
|
||||
mount_repo_cli()
|
||||
if not os.path.exists(repo_path):
|
||||
return False
|
||||
for subdir in ['catalogs', 'manifests', 'pkgs', 'pkgsinfo']:
|
||||
if not os.path.exists(os.path.join(repo_path, subdir)):
|
||||
print >> sys.stderr, "%s is missing %s" % (repo_path, subdir)
|
||||
return False
|
||||
# if we get this far, the repo path looks OK
|
||||
return True
|
||||
|
||||
|
||||
def mount_repo_cli():
|
||||
"""Attempts to connect to the repo fileshare"""
|
||||
global WE_MOUNTED_THE_REPO
|
||||
repo_path = pref('repo_path')
|
||||
repo_url = pref('repo_url')
|
||||
if os.path.exists(repo_path):
|
||||
return
|
||||
print 'Attempting to mount fileshare %s:' % repo_path
|
||||
if NETFSMOUNTURLSYNC_AVAILABLE:
|
||||
# mount the share using the NetFS API
|
||||
try:
|
||||
mount_share_url(repo_url)
|
||||
except ShareMountException, err:
|
||||
print >> sys.stderr, err
|
||||
else:
|
||||
WE_MOUNTED_THE_REPO = True
|
||||
else:
|
||||
# do it the old way
|
||||
os.mkdir(repo_path)
|
||||
if repo_url.startswith('afp:'):
|
||||
cmd = ['/sbin/mount_afp', '-i', repo_url, repo_path]
|
||||
elif repo_url.startswith('smb:'):
|
||||
cmd = ['/sbin/mount_smbfs', repo_url[4:], repo_path]
|
||||
elif repo_url.startswith('nfs://'):
|
||||
cmd = ['/sbin/mount_nfs', repo_url[6:], repo_path]
|
||||
else:
|
||||
print >> sys.stderr, 'Unsupported filesystem URL!'
|
||||
return
|
||||
retcode = subprocess.call(cmd)
|
||||
if retcode:
|
||||
os.rmdir(repo_path)
|
||||
else:
|
||||
WE_MOUNTED_THE_REPO = True
|
||||
|
||||
|
||||
def unmount_repo_cli():
|
||||
"""Attempts to unmount the repo fileshare"""
|
||||
repo_path = pref('repo_path')
|
||||
if not os.path.exists(repo_path):
|
||||
return
|
||||
cmd = ['/sbin/umount', repo_path]
|
||||
return subprocess.call(cmd)
|
||||
|
||||
|
||||
def cleanup_and_exit(exitcode):
|
||||
"""Give the user the chance to unmount the repo when we exit"""
|
||||
result = 0
|
||||
if WE_MOUNTED_THE_REPO:
|
||||
if repo and repo.mounted and repo.WE_MOUNTED_THE_REPO:
|
||||
answer = raw_input('Unmount the repo fileshare? [y/n] ')
|
||||
if answer.lower().startswith('y'):
|
||||
result = unmount_repo_cli()
|
||||
result = repo.unmount()
|
||||
munkicommon.cleanUpTmpDir()
|
||||
exit(exitcode or result)
|
||||
|
||||
|
||||
@@ -629,12 +570,13 @@ def find(args):
|
||||
findtext = arguments[0]
|
||||
keyname = options.section
|
||||
|
||||
manifests_path = os.path.join(pref('repo_path'), 'manifests')
|
||||
manifests_path = repo.join('manifests')
|
||||
count = 0
|
||||
for name in get_manifest_names():
|
||||
pathname = os.path.join(manifests_path, name)
|
||||
pathname = repo.join(manifests_path, name)
|
||||
try:
|
||||
manifest = plistlib.readPlist(pathname)
|
||||
handle = repo.open(pathname, 'r')
|
||||
manifest = plistlib.readPlist(handle.local_path)
|
||||
except (IOError, OSError, ExpatError):
|
||||
print >> sys.stderr, 'Error reading %s' % pathname
|
||||
continue
|
||||
@@ -1184,6 +1126,8 @@ def set_up_tab_completer():
|
||||
|
||||
|
||||
def handle_subcommand(args):
|
||||
global repo
|
||||
|
||||
'''Does all our subcommands'''
|
||||
# check if any arguments are passed.
|
||||
# if not, list subcommands.
|
||||
@@ -1203,8 +1147,10 @@ def handle_subcommand(args):
|
||||
|
||||
if (subcommand not in ['version', 'configure', 'help']
|
||||
and '-h' not in args and '--help' not in args):
|
||||
if not repo_available():
|
||||
exit(-1)
|
||||
if not repo:
|
||||
repo = Repo.Open(pref('repo_path'), pref('repo_url'), pref('plugin'))
|
||||
if not repo.available():
|
||||
exit(-1)
|
||||
|
||||
try:
|
||||
# find function to call by looking in the global name table
|
||||
@@ -1220,13 +1166,14 @@ def handle_subcommand(args):
|
||||
return subcommand_function(args[1:])
|
||||
|
||||
|
||||
WE_MOUNTED_THE_REPO = False
|
||||
INTERACTIVE_MODE = False
|
||||
CMD_ARG_DICT = {}
|
||||
repo = None
|
||||
|
||||
def main():
|
||||
'''Our main routine'''
|
||||
global INTERACTIVE_MODE
|
||||
global repo
|
||||
|
||||
cmds = {'add-pkg': 'pkgs',
|
||||
'add-catalog': 'catalogs',
|
||||
@@ -1261,7 +1208,8 @@ def main():
|
||||
# so let's enter interactive mode
|
||||
INTERACTIVE_MODE = True
|
||||
# must have an available repo for interfactive mode
|
||||
if not repo_available():
|
||||
repo = Repo.Open(pref('repo_path'), pref('repo_url'), pref('plugin'))
|
||||
if not repo.available():
|
||||
exit(-1)
|
||||
# build the rest of our dict to enable tab completion
|
||||
CMD_ARG_DICT['options'] = {'--manifest': 'manifests',
|
||||
|
||||
+121
-141
@@ -31,6 +31,7 @@ import subprocess
|
||||
import sys
|
||||
import time
|
||||
import thread
|
||||
import re
|
||||
|
||||
from ctypes.util import find_library
|
||||
from optparse import OptionParser, BadOptionError, AmbiguousOptionError
|
||||
@@ -40,6 +41,7 @@ import objc
|
||||
from munkilib import iconutils
|
||||
from munkilib import munkicommon
|
||||
from munkilib import FoundationPlist
|
||||
from munkilib import Repo
|
||||
|
||||
# PyLint cannot properly find names inside Cocoa libraries, so issues bogus
|
||||
# No name 'Foo' in module 'Bar' warnings. Disable them.
|
||||
@@ -144,7 +146,6 @@ def mount_share_url(share_url):
|
||||
password = getpass.getpass()
|
||||
mount_share_with_credentials(share_url, username, password)
|
||||
|
||||
|
||||
if 'libedit' in readline.__doc__:
|
||||
# readline module was compiled against libedit
|
||||
LIBEDIT = ctypes.cdll.LoadLibrary(find_library('libedit'))
|
||||
@@ -193,10 +194,8 @@ def raw_input_with_default(prompt, default_text):
|
||||
class PassThroughOptionParser(OptionParser):
|
||||
"""
|
||||
An unknown option pass-through implementation of OptionParser.
|
||||
|
||||
When unknown arguments are encountered, bundle with largs and try again,
|
||||
until rargs is depleted.
|
||||
|
||||
sys.exit(status) will still be called if a known argument is passed
|
||||
incorrectly (e.g. missing arguments or bad argument types, etc.)
|
||||
"""
|
||||
@@ -216,10 +215,10 @@ def make_dmg(pkgpath):
|
||||
"""Wraps a non-flat package into a disk image.
|
||||
Returns path to newly-created disk image."""
|
||||
|
||||
pkgname = os.path.basename(pkgpath)
|
||||
pkgname = repo.basename(pkgpath)
|
||||
print 'Making disk image containing %s...' % pkgname
|
||||
diskimagename = os.path.splitext(pkgname)[0] + '.dmg'
|
||||
diskimagepath = os.path.join(munkicommon.tmpdir(), diskimagename)
|
||||
diskimagename = repo.splitext(pkgname)[0] + '.dmg'
|
||||
diskimagepath = repo.join(munkicommon.tmpdir(), diskimagename)
|
||||
cmd = ['/usr/bin/hdiutil', 'create', '-srcfolder', pkgpath, diskimagepath]
|
||||
proc = subprocess.Popen(cmd, shell=False, bufsize=-1,
|
||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
|
||||
@@ -239,66 +238,6 @@ def make_dmg(pkgpath):
|
||||
return diskimagepath
|
||||
|
||||
|
||||
def repo_available():
|
||||
"""Checks the repo path for proper directory structure.
|
||||
If the directories look wrong we probably don't have a
|
||||
valid repo path. Returns True if things look OK."""
|
||||
if not REPO_PATH:
|
||||
print >> sys.stderr, 'No repo path specified.'
|
||||
return False
|
||||
if not os.path.exists(REPO_PATH) and REPO_URL:
|
||||
mount_repo_cli()
|
||||
if not os.path.exists(REPO_PATH):
|
||||
return False
|
||||
for subdir in ['catalogs', 'manifests', 'pkgs', 'pkgsinfo']:
|
||||
if not os.path.exists(os.path.join(REPO_PATH, subdir)):
|
||||
print >> sys.stderr, "%s is missing %s" % (REPO_PATH, subdir)
|
||||
return False
|
||||
# if we get this far, the repo path looks OK
|
||||
return True
|
||||
|
||||
|
||||
def mount_repo_cli():
|
||||
"""Attempts to connect to the repo fileshare"""
|
||||
global WE_MOUNTED_THE_REPO
|
||||
if os.path.exists(REPO_PATH):
|
||||
return
|
||||
print 'Attempting to mount fileshare %s:' % REPO_URL
|
||||
if NETFSMOUNTURLSYNC_AVAILABLE:
|
||||
# mount the share using the NetFS API
|
||||
try:
|
||||
mount_share_url(REPO_URL)
|
||||
except ShareMountException, err:
|
||||
print >> sys.stderr, err
|
||||
else:
|
||||
WE_MOUNTED_THE_REPO = True
|
||||
else:
|
||||
# do it the old way
|
||||
os.mkdir(REPO_PATH)
|
||||
if REPO_URL.startswith('afp:'):
|
||||
cmd = ['/sbin/mount_afp', '-i', REPO_URL, REPO_PATH]
|
||||
elif REPO_URL.startswith('smb:'):
|
||||
cmd = ['/sbin/mount_smbfs', REPO_URL[4:], REPO_PATH]
|
||||
elif REPO_URL.startswith('nfs://'):
|
||||
cmd = ['/sbin/mount_nfs', REPO_URL[6:], REPO_PATH]
|
||||
else:
|
||||
print >> sys.stderr, 'Unsupported filesystem URL!'
|
||||
return
|
||||
retcode = subprocess.call(cmd)
|
||||
if retcode:
|
||||
os.rmdir(REPO_PATH)
|
||||
else:
|
||||
WE_MOUNTED_THE_REPO = True
|
||||
|
||||
|
||||
def unmount_repo_cli():
|
||||
"""Attempts to unmount the repo fileshare"""
|
||||
if not os.path.exists(REPO_PATH):
|
||||
return
|
||||
cmd = ['/sbin/umount', REPO_PATH]
|
||||
return subprocess.call(cmd)
|
||||
|
||||
|
||||
class RepoCopyError(Exception):
|
||||
"""Error copying installer item to repo"""
|
||||
pass
|
||||
@@ -313,62 +252,61 @@ def copy_item_to_repo(itempath, vers, subdirectory=''):
|
||||
if not os.path.exists(REPO_PATH):
|
||||
raise RepoCopyError('Could not connect to munki repo.')
|
||||
|
||||
destination_path = os.path.join(REPO_PATH, 'pkgs', subdirectory)
|
||||
if not os.path.exists(destination_path):
|
||||
destination_path = repo.join('pkgs', subdirectory)
|
||||
if not repo.exists(destination_path):
|
||||
try:
|
||||
os.makedirs(destination_path)
|
||||
repo.makedirs(destination_path)
|
||||
except OSError, errmsg:
|
||||
raise RepoCopyError('Could not create %s: %s'
|
||||
% (destination_path, errmsg))
|
||||
|
||||
item_name = os.path.basename(itempath)
|
||||
destination_path_name = os.path.join(destination_path, item_name)
|
||||
item_name = repo.basename(itempath)
|
||||
destination_path_name = repo.join(destination_path, item_name)
|
||||
|
||||
if itempath == destination_path_name:
|
||||
# we've been asked to 'import' a repo item.
|
||||
# just return the relative path
|
||||
return os.path.join(subdirectory, item_name)
|
||||
return repo.join(subdirectory, item_name)
|
||||
|
||||
if vers:
|
||||
name, ext = os.path.splitext(item_name)
|
||||
name, ext = repo.splitext(item_name)
|
||||
if not name.endswith(vers):
|
||||
# add the version number to the end of the filename
|
||||
item_name = '%s-%s%s' % (name, vers, ext)
|
||||
destination_path_name = os.path.join(destination_path, item_name)
|
||||
destination_path_name = repo.join(destination_path, item_name)
|
||||
|
||||
index = 0
|
||||
name, ext = os.path.splitext(item_name)
|
||||
while os.path.exists(destination_path_name):
|
||||
name, ext = repo.splitext(item_name)
|
||||
while repo.exists(destination_path_name):
|
||||
print 'File %s already exists...' % destination_path_name
|
||||
# try appending numbers until we have a unique name
|
||||
index += 1
|
||||
item_name = '%s__%s%s' % (name, index, ext)
|
||||
destination_path_name = os.path.join(destination_path, item_name)
|
||||
destination_path_name = repo.join(destination_path, item_name)
|
||||
|
||||
print 'Copying %s to %s...' % (os.path.basename(itempath),
|
||||
print 'Copying %s to %s...' % (repo.basename(itempath),
|
||||
destination_path_name)
|
||||
|
||||
cmd = ['/bin/cp', itempath, destination_path_name]
|
||||
retcode = subprocess.call(cmd)
|
||||
retcode = repo.put(itempath, destination_path_name)
|
||||
if retcode:
|
||||
raise RepoCopyError('Unable to copy %s to %s'
|
||||
% (itempath, destination_path_name))
|
||||
else:
|
||||
return os.path.join(subdirectory, item_name)
|
||||
return repo.join(subdirectory, item_name)
|
||||
|
||||
|
||||
def get_icon_path(pkginfo):
|
||||
"""Return path for icon"""
|
||||
icon_name = pkginfo.get('icon_name') or pkginfo['name']
|
||||
if not os.path.splitext(icon_name)[1]:
|
||||
if not repo.splitext(icon_name)[1]:
|
||||
icon_name += u'.png'
|
||||
return os.path.join(REPO_PATH, u'icons', icon_name)
|
||||
return repo.join(u'icons', icon_name)
|
||||
|
||||
|
||||
def icon_exists_in_repo(pkginfo):
|
||||
"""Returns True if there is an icon for this item in the repo"""
|
||||
icon_path = get_icon_path(pkginfo)
|
||||
if os.path.exists(icon_path):
|
||||
if repo.exists(icon_path):
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -376,7 +314,7 @@ def icon_exists_in_repo(pkginfo):
|
||||
def add_icon_hash_to_pkginfo(pkginfo):
|
||||
"""Adds the icon hash tp pkginfo if the icon exists in repo"""
|
||||
icon_path = get_icon_path(pkginfo)
|
||||
if os.path.isfile(icon_path):
|
||||
if repo.isfile(icon_path):
|
||||
pkginfo['icon_hash'] = munkicommon.getsha256hash(icon_path)
|
||||
|
||||
|
||||
@@ -443,10 +381,10 @@ def generate_pngs_from_installer_pkg(item_path, pkginfo):
|
||||
|
||||
def convert_and_install_icon(pkginfo, icon_path, index=None):
|
||||
'''Convert icon file to png and save to repo icon path'''
|
||||
destination_path = os.path.join(REPO_PATH, 'icons')
|
||||
if not os.path.exists(destination_path):
|
||||
destination_path = 'icons'
|
||||
if not repo.exists(destination_path):
|
||||
try:
|
||||
os.makedirs(destination_path)
|
||||
repo.makedirs(destination_path)
|
||||
except OSError, errmsg:
|
||||
print >> sys.stderr, ('Could not create %s: %s' %
|
||||
(destination_path, errmsg))
|
||||
@@ -456,37 +394,41 @@ def convert_and_install_icon(pkginfo, icon_path, index=None):
|
||||
else:
|
||||
destination_name = pkginfo['name']
|
||||
|
||||
png_path = os.path.join(
|
||||
destination_path, destination_name + u'.png')
|
||||
result = iconutils.convertIconToPNG(icon_path, png_path)
|
||||
png_name = destination_name + u'.png'
|
||||
png_path = repo.join(destination_path, png_name)
|
||||
png_tmp = repo.join(munkicommon.tmpdir(), png_name)
|
||||
result = iconutils.convertIconToPNG(icon_path, png_tmp)
|
||||
if result:
|
||||
print 'Created icon: %s' % png_path
|
||||
result = repo.put(png_tmp, png_path)
|
||||
if result == 0:
|
||||
print 'Created icon: %s' % png_path
|
||||
else:
|
||||
print >> sys.stderr, u'Error uploading icon to %s.' % png_path
|
||||
else:
|
||||
print >> sys.stderr, u'Error converting %s to png.' % icon_path
|
||||
|
||||
|
||||
def copy_icon_to_repo(iconpath):
|
||||
"""Saves a product icon to the repo"""
|
||||
destination_path = os.path.join(REPO_PATH, 'icons')
|
||||
if not os.path.exists(destination_path):
|
||||
destination_path = 'icons'
|
||||
if not repo.exists(destination_path):
|
||||
try:
|
||||
os.makedirs(destination_path)
|
||||
repo.makedirs(destination_path)
|
||||
except OSError, errmsg:
|
||||
raise RepoCopyError('Could not create %s: %s'
|
||||
% (destination_path, errmsg))
|
||||
icon_name = os.path.basename(iconpath)
|
||||
destination_path_name = os.path.join(destination_path, icon_name)
|
||||
icon_name = repo.basename(iconpath)
|
||||
destination_path_name = repo.join(destination_path, icon_name)
|
||||
|
||||
if os.path.exists(destination_path_name):
|
||||
if repo.exists(destination_path_name):
|
||||
# remove any existing icon in the repo
|
||||
try:
|
||||
os.unlink(destination_path_name)
|
||||
repo.unlink(destination_path_name)
|
||||
except OSError, errmsg:
|
||||
raise RepoCopyError('Could not remove existing %s'
|
||||
% (destination_path_name))
|
||||
print 'Copying %s to %s...' % (icon_name, destination_path_name)
|
||||
cmd = ['/bin/cp', iconpath, destination_path_name]
|
||||
retcode = subprocess.call(cmd)
|
||||
retcode = repo.put(iconpath, destination_path_name)
|
||||
if retcode:
|
||||
raise RepoCopyError('Unable to copy %s to %s'
|
||||
% (iconpath, destination_path_name))
|
||||
@@ -496,10 +438,10 @@ def copy_pkginfo_to_repo(pkginfo, subdirectory=''):
|
||||
"""Saves pkginfo to munki_repo_path/pkgsinfo/subdirectory"""
|
||||
# less error checking because we copy the installer_item
|
||||
# first and bail if it fails...
|
||||
destination_path = os.path.join(REPO_PATH, 'pkgsinfo', subdirectory)
|
||||
if not os.path.exists(destination_path):
|
||||
destination_path = repo.join(repo.path, 'pkgsinfo', subdirectory)
|
||||
if not repo.exists(destination_path):
|
||||
try:
|
||||
os.makedirs(destination_path)
|
||||
repo.makedirs(destination_path)
|
||||
except OSError, errmsg:
|
||||
raise RepoCopyError('Could not create %s: %s'
|
||||
% (destination_path, errmsg))
|
||||
@@ -508,17 +450,18 @@ def copy_pkginfo_to_repo(pkginfo, subdirectory=''):
|
||||
pkginfo_ext = '.' + pkginfo_ext
|
||||
pkginfo_name = '%s-%s%s' % (pkginfo['name'], pkginfo['version'],
|
||||
pkginfo_ext)
|
||||
pkginfo_path = os.path.join(destination_path, pkginfo_name)
|
||||
pkginfo_path = repo.join(destination_path, pkginfo_name)
|
||||
index = 0
|
||||
while os.path.exists(pkginfo_path):
|
||||
while repo.exists(pkginfo_path):
|
||||
index += 1
|
||||
pkginfo_name = '%s-%s__%s%s' % (pkginfo['name'], pkginfo['version'],
|
||||
index, pkginfo_ext)
|
||||
pkginfo_path = os.path.join(destination_path, pkginfo_name)
|
||||
pkginfo_path = repo.join(destination_path, pkginfo_name)
|
||||
|
||||
print 'Saving pkginfo to %s...' % pkginfo_path
|
||||
try:
|
||||
FoundationPlist.writePlist(pkginfo, pkginfo_path)
|
||||
handle = repo.open(pkginfo_path, 'w')
|
||||
FoundationPlist.writePlist(pkginfo, handle.local_path)
|
||||
except FoundationPlist.NSPropertyListWriteException, errmsg:
|
||||
raise RepoCopyError(errmsg)
|
||||
return pkginfo_path
|
||||
@@ -545,13 +488,13 @@ def prompt_for_subdirectory(subdirectory):
|
||||
newdir = raw_input(
|
||||
'Upload item to subdirectory path [%s]: ' % subdirectory)
|
||||
if newdir:
|
||||
if not repo_available():
|
||||
if not repo.available():
|
||||
raise RepoCopyError('Could not connect to munki repo.')
|
||||
if APPLEMETADATA:
|
||||
destination_path = os.path.join(REPO_PATH, 'pkgsinfo', newdir)
|
||||
destination_path = repo.join('pkgsinfo', newdir)
|
||||
else:
|
||||
destination_path = os.path.join(REPO_PATH, 'pkgs', newdir)
|
||||
if not os.path.exists(destination_path):
|
||||
destination_path = repo.join('pkgs', newdir)
|
||||
if not repo.exists(destination_path):
|
||||
answer = raw_input('Path %s doesn\'t exist. Create it? [y/n] '
|
||||
% destination_path)
|
||||
if answer.lower().startswith('y'):
|
||||
@@ -571,11 +514,16 @@ class CatalogDBException(Exception):
|
||||
def make_catalog_db():
|
||||
"""Returns a dict we can use like a database"""
|
||||
|
||||
all_items_path = os.path.join(REPO_PATH, 'catalogs', 'all')
|
||||
if not os.path.exists(all_items_path):
|
||||
raise CatalogDBException
|
||||
all_items_path = repo.join('catalogs', 'all')
|
||||
handle = None
|
||||
|
||||
try:
|
||||
catalogitems = FoundationPlist.readPlist(all_items_path)
|
||||
handle = repo.open(all_items_path, 'r')
|
||||
except IOError:
|
||||
raise CatalogDBException
|
||||
|
||||
try:
|
||||
catalogitems = FoundationPlist.readPlist(handle.local_path)
|
||||
except FoundationPlist.NSPropertyListSerializationException:
|
||||
raise CatalogDBException
|
||||
|
||||
@@ -602,9 +550,9 @@ def make_catalog_db():
|
||||
|
||||
# add to installer item table
|
||||
if 'installer_item_location' in item:
|
||||
installer_item_name = os.path.basename(
|
||||
installer_item_name = repo.basename(
|
||||
item['installer_item_location'])
|
||||
(name, ext) = os.path.splitext(installer_item_name)
|
||||
(name, ext) = repo.splitext(installer_item_name)
|
||||
if '-' in name:
|
||||
(name, vers) = munkicommon.nameAndVersion(name)
|
||||
installer_item_name = name + ext
|
||||
@@ -728,7 +676,7 @@ def find_matching_pkginfo(pkginfo):
|
||||
|
||||
# no matches by receipts or installed applications,
|
||||
# let's try to match based on installer_item_name
|
||||
installer_item_name = os.path.basename(
|
||||
installer_item_name = repo.basename(
|
||||
pkginfo.get('installer_item_location', ''))
|
||||
possiblematches = catdb['installer_items'].get(installer_item_name)
|
||||
if possiblematches:
|
||||
@@ -789,17 +737,21 @@ def make_pkginfo(options=None, test_mode=False):
|
||||
def make_catalogs():
|
||||
"""Calls makecatalogs to rebuild our catalogs"""
|
||||
# first look for a makecatalogs in the same dir as us
|
||||
mydir = os.path.dirname(os.path.abspath(__file__))
|
||||
mydir = repo.dirname(os.path.abspath(__file__))
|
||||
makecatalogs_path = os.path.join(mydir, 'makecatalogs')
|
||||
if not os.path.exists(makecatalogs_path):
|
||||
if not repo.exists(makecatalogs_path):
|
||||
# didn't find it; assume the default install path
|
||||
makecatalogs_path = '/usr/local/munki/makecatalogs'
|
||||
if not repo_available():
|
||||
if not repo.available():
|
||||
raise RepoCopyError('Could not connect to munki repo.')
|
||||
if not VERBOSE:
|
||||
print 'Rebuilding catalogs at %s...' % REPO_PATH
|
||||
proc = subprocess.Popen([makecatalogs_path, REPO_PATH],
|
||||
bufsize=-1, stdout=subprocess.PIPE,
|
||||
cmd = [makecatalogs_path]
|
||||
if REPO_URL:
|
||||
cmd.append('--repo-url')
|
||||
cmd.append(REPO_URL)
|
||||
cmd.append(REPO_PATH)
|
||||
proc = subprocess.Popen(cmd, bufsize=-1, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
while True:
|
||||
output = proc.stdout.readline()
|
||||
@@ -817,13 +769,13 @@ def make_catalogs():
|
||||
def cleanup_and_exit(exitcode):
|
||||
"""Unmounts the repo if we mounted it, then exits"""
|
||||
result = 0
|
||||
if WE_MOUNTED_THE_REPO:
|
||||
if repo and repo.mounted and repo.WE_MOUNTED_THE_REPO:
|
||||
if not NOINTERACTIVE:
|
||||
answer = raw_input('Unmount the repo fileshare? [y/n] ')
|
||||
if answer.lower().startswith('y'):
|
||||
result = unmount_repo_cli()
|
||||
result = repo.unmount()
|
||||
else:
|
||||
result = unmount_repo_cli()
|
||||
result = repo.unmount()
|
||||
# clean up tmpdir
|
||||
munkicommon.cleanUpTmpDir()
|
||||
|
||||
@@ -853,8 +805,27 @@ def configure():
|
||||
('editor',
|
||||
'pkginfo editor (examples: /usr/bin/vi or TextMate.app; '
|
||||
'leave empty to not open an editor after import)'),
|
||||
('default_catalog', 'Default catalog to use (example: testing)')]:
|
||||
|
||||
('default_catalog', 'Default catalog to use (example: testing)'),
|
||||
('plugin', 'Use a plugin to write to a custom munki repository')
|
||||
]:
|
||||
if key == 'plugin':
|
||||
# first look for a plugins folder in the same dir as us
|
||||
plugin_path = os.path.dirname(os.path.abspath(__file__))
|
||||
plugin_path = os.path.join(plugin_path, 'munkilib/plugins')
|
||||
if not os.path.exists(plugin_path):
|
||||
# didn't find it; assume the default install path
|
||||
plugin_path = '/usr/local/munki/munkilib/plugins'
|
||||
hasPlugin = False
|
||||
included_extensions = ['py']
|
||||
plugins = [fn for fn in os.listdir(plugin_path)
|
||||
if any(fn.endswith(ext) for ext in included_extensions)]
|
||||
for plugin in plugins:
|
||||
if 'FileRepo' not in plugin:
|
||||
hasPlugin = True
|
||||
if not os.path.isdir(plugin_path):
|
||||
continue
|
||||
if not hasPlugin:
|
||||
continue
|
||||
_prefs[key] = raw_input_with_default('%15s: ' % prompt, pref(key))
|
||||
|
||||
for key, value in _prefs.items():
|
||||
@@ -871,10 +842,10 @@ PREFSPATH = os.path.expanduser(os.path.join('~/Library/Preferences',
|
||||
PREFSNAME))
|
||||
APPLEMETADATA = False
|
||||
NOINTERACTIVE = False
|
||||
WE_MOUNTED_THE_REPO = False
|
||||
VERBOSE = False
|
||||
REPO_PATH = ""
|
||||
REPO_URL = ""
|
||||
repo = None
|
||||
|
||||
def main():
|
||||
"""Main routine"""
|
||||
@@ -883,12 +854,12 @@ def main():
|
||||
global VERBOSE
|
||||
global REPO_PATH
|
||||
global REPO_URL
|
||||
global repo
|
||||
|
||||
usage = """usage: %prog [options] /path/to/installer_item
|
||||
Imports an installer item into a munki repo.
|
||||
Installer item can be a pkg, mpkg, dmg, mobileconfig, or app.
|
||||
Bundle-style pkgs and apps are wrapped in a dmg file before upload.
|
||||
|
||||
Example:
|
||||
munkiimport --subdirectory apps /path/to/installer_item
|
||||
"""
|
||||
@@ -897,7 +868,6 @@ def main():
|
||||
In addition to the options described above, options used with
|
||||
'makepkginfo' may also be specified to customize the resulting
|
||||
pkginfo file.
|
||||
|
||||
Example:
|
||||
munkiimport --subdirectory apps -c production --minimum_os_vers 10.6.8 /path/to/installer_item\n"""
|
||||
|
||||
@@ -924,6 +894,8 @@ def main():
|
||||
help='Optional repo fileshare URL that takes precedence '
|
||||
'over the default repo_url specified via '
|
||||
'--configure.')
|
||||
parser.add_option('--plugin', '--plugin', default='',
|
||||
help='Optional custom plugin to run for munkiimport Repo.')
|
||||
parser.add_option('--icon_path', '--icon-path', default='', type='string',
|
||||
help='Path to an icon file for the package. '
|
||||
'Will overwrite an existing icon.')
|
||||
@@ -945,8 +917,10 @@ def main():
|
||||
|
||||
NOINTERACTIVE = options.nointeractive
|
||||
VERBOSE = options.verbose
|
||||
#default is what user put in munkiimport --configure
|
||||
REPO_PATH = pref('repo_path')
|
||||
REPO_URL = pref('repo_url')
|
||||
REPO_PLUGIN = pref('plugin')
|
||||
|
||||
if options.repo_path:
|
||||
if not os.path.exists(options.repo_path) and not options.repo_url:
|
||||
@@ -957,10 +931,13 @@ def main():
|
||||
exit(-1)
|
||||
|
||||
REPO_PATH = options.repo_path
|
||||
|
||||
#if specified options, override defaults
|
||||
if options.repo_url:
|
||||
REPO_URL = options.repo_url
|
||||
|
||||
if options.plugin:
|
||||
REPO_PLUGIN = options.plugin
|
||||
|
||||
if options.icon_path and not os.path.isfile(options.icon_path):
|
||||
print >> sys.stderr, ('The specified icon file does not exist.')
|
||||
exit(-1)
|
||||
@@ -1015,13 +992,14 @@ def main():
|
||||
'tool, or provide with --repo-path')
|
||||
exit(-1)
|
||||
|
||||
if not repo_available():
|
||||
repo = Repo.Open(REPO_PATH, REPO_URL, REPO_PLUGIN)
|
||||
if not repo.available():
|
||||
print >> sys.stderr, ('Could not connect to munki repo. Check the '
|
||||
'configuration and try again.')
|
||||
exit(-1)
|
||||
|
||||
if not APPLEMETADATA:
|
||||
if os.path.isdir(installer_item): # Start of indent
|
||||
if repo.isdir(installer_item): # Start of indent
|
||||
if munkicommon.hasValidDiskImageExt(installer_item):
|
||||
# a directory named foo.dmg or foo.iso!
|
||||
print >> sys.stderr, '%s is an unknown type.' % installer_item
|
||||
@@ -1042,7 +1020,7 @@ def main():
|
||||
arguments.append(installer_item) # End of indent
|
||||
|
||||
if uninstaller_item:
|
||||
if os.path.isdir(uninstaller_item):
|
||||
if repo.isdir(uninstaller_item):
|
||||
if munkicommon.hasValidDiskImageExt(uninstaller_item):
|
||||
# a directory named foo.dmg or foo.iso!
|
||||
print >> sys.stderr, (
|
||||
@@ -1142,8 +1120,10 @@ def main():
|
||||
pkginfo[key] = raw_input_with_default(prompt, default)
|
||||
if kind == 'bool':
|
||||
value = pkginfo[key].lower().strip()
|
||||
# set key to True/False
|
||||
pkginfo[key] = value.startswith(('y', 't'))
|
||||
if value.startswith(('y', 't')):
|
||||
pkginfo[key] = True
|
||||
else:
|
||||
pkginfo[key] = False
|
||||
|
||||
# special handling for catalogs array
|
||||
prompt = '%20s: ' % 'Catalogs'
|
||||
@@ -1173,11 +1153,11 @@ def main():
|
||||
cleanup_and_exit(0)
|
||||
|
||||
if options.subdirectory == '':
|
||||
pkgs_path = os.path.join(REPO_PATH, 'pkgs')
|
||||
pkgs_path = repo.join('pkgs')
|
||||
if not APPLEMETADATA and installer_item.startswith(pkgs_path):
|
||||
# the installer item is already in the repo.
|
||||
# use its relative path as the subdirectory
|
||||
installer_item_dirpath = os.path.dirname(installer_item)
|
||||
installer_item_dirpath = repo.dirname(installer_item)
|
||||
options.subdirectory = \
|
||||
installer_item_dirpath[len(pkgs_path)+1:]
|
||||
options.subdirectory = prompt_for_subdirectory(
|
||||
@@ -1230,7 +1210,7 @@ def main():
|
||||
# adjust the uninstaller_item_location to match
|
||||
# the actual location and name; update size and hash
|
||||
pkginfo['uninstaller_item_location'] = uploaded_pkgpath
|
||||
itemsize = int(os.path.getsize(uninstaller_item))
|
||||
itemsize = int(repo.getsize(uninstaller_item))
|
||||
itemhash = munkicommon.getsha256hash(uninstaller_item)
|
||||
pkginfo['uninstaller_item_size'] = int(itemsize/1024)
|
||||
pkginfo['uninstaller_item_hash'] = itemhash
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/python
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Copyright 2016 Centrify Corporation.
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# https://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.
|
||||
|
||||
"""
|
||||
Repo
|
||||
Created by Centrify Corporation 2016-06-02.
|
||||
Interface for accessing a repo.
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
import imp
|
||||
import os
|
||||
|
||||
def Open(path, url, plugin):
|
||||
#looks for installtion path for munki
|
||||
# first look for a plugin in the same dir as us in munkilib/plugins
|
||||
munkilib_path = os.path.dirname(os.path.abspath(__file__))
|
||||
munkilib_path = os.path.join(munkilib_path, 'plugins')
|
||||
if not os.path.exists(munkilib_path):
|
||||
# didn't find it; assume the default install path
|
||||
command = "munkiimport"
|
||||
commandPath = os.popen("/usr/bin/which %s" % command).read().strip()
|
||||
commandPath = os.path.split(commandPath)
|
||||
munkilib_path = commandPath[0]
|
||||
#use default munki location if munki installation path is not found
|
||||
if munkilib_path == None or munkilib_path == "":
|
||||
munkilib_path = '/usr/local/munki/munkilib/plugins'
|
||||
else:
|
||||
munkilib_path = munkilib_path + '/munkilib/plugins'
|
||||
#looks for plugin in /usr/local/munki/munkilib/plugins (installation of munki)
|
||||
if plugin == None or plugin == "":
|
||||
#default is FileRepo
|
||||
plugin = 'FileRepo'
|
||||
module = imp.load_source(plugin, munkilib_path + "/" + plugin + ".py")
|
||||
import_class = getattr(module, plugin)
|
||||
parent = import_class
|
||||
|
||||
class Repo(parent):
|
||||
mounted = False
|
||||
|
||||
def available(self):
|
||||
#if path does not exist, mount to local filesystem
|
||||
if not self.exists():
|
||||
retcode = self.mount()
|
||||
if retcode == 0:
|
||||
self.mounted = True
|
||||
#if path still doesn't exist, then cannot find munki_repo
|
||||
if not self.exists():
|
||||
print >> sys.stderr, "repo is missing"
|
||||
return False
|
||||
#checks if all subdirectories are there
|
||||
for subdir in ['catalogs', 'manifests', 'pkgs', 'pkgsinfo']:
|
||||
if not self.exists(subdir):
|
||||
print >> sys.stderr, "repo is missing %s" % subdir
|
||||
return False
|
||||
# if we get this far, the repo path looks OK
|
||||
return True
|
||||
|
||||
return Repo(path, url)
|
||||
@@ -165,42 +165,58 @@ def extractAppIconsFromFlatPkg(pkg_path):
|
||||
return icon_paths
|
||||
|
||||
|
||||
def findInfoPlistPathsInBundlePkg(pkg_path):
|
||||
def findInfoPlistPathsInBundlePkg(pkg_path, repo=None):
|
||||
'''Returns a dict with pkg paths as keys and filename lists
|
||||
as values'''
|
||||
pkg_dict = {}
|
||||
bomfile = os.path.join(pkg_path, u'Contents/Archive.bom')
|
||||
if repo:
|
||||
repo_bomfile = repo.join(pkg_path, u'Contents/Archive.bom')
|
||||
handle = repo.open(repo_bomfile, 'r')
|
||||
bomfile = handle.local_path
|
||||
else:
|
||||
bomfile = os.path.join(pkg_path, u'Contents/Archive.bom')
|
||||
if os.path.exists(bomfile):
|
||||
info_paths = getAppInfoPathsFromBundleComponentPkg(pkg_path)
|
||||
info_paths = getAppInfoPathsFromBOM(bomfile)
|
||||
if info_paths:
|
||||
pkg_dict[pkg_path] = info_paths
|
||||
else:
|
||||
# mpkg or dist pkg; look for component pkgs within
|
||||
pkg_dict = {}
|
||||
original_dir = os.getcwd()
|
||||
pkg_contents_dir = os.path.join(pkg_path, u'Contents')
|
||||
if os.path.isdir(pkg_contents_dir):
|
||||
os.chdir(pkg_contents_dir)
|
||||
pkgs = (glob.glob('*.pkg') + glob.glob('*/*.pkg')
|
||||
+ glob.glob('*/*/*.pkg') + glob.glob('*.mpkg') +
|
||||
glob.glob('*/*.mpkg') + glob.glob('*/*/*.mpkg'))
|
||||
os.chdir(original_dir)
|
||||
pkgs = []
|
||||
if repo:
|
||||
pkg_contents_dir = repo.join(pkg_path, u'Contents')
|
||||
if repo.isdir(pkg_contents_dir):
|
||||
pkgs = repo.glob(pkg_contents_dir, '*.pkg', '*/*.pkg',
|
||||
'*/*/*.pkg', '*.mpkg', '*/*.mpkg', '*/*/*.mpkg')
|
||||
else:
|
||||
pkgs = []
|
||||
pkg_contents_dir = os.path.join(pkg_path, u'Contents')
|
||||
if os.path.isdir(pkg_contents_dir):
|
||||
original_dir = os.getcwd()
|
||||
os.chdir(pkg_contents_dir)
|
||||
pkgs = (glob.glob('*.pkg') + glob.glob('*/*.pkg')
|
||||
+ glob.glob('*/*/*.pkg') + glob.glob('*.mpkg') +
|
||||
glob.glob('*/*.mpkg') + glob.glob('*/*/*.mpkg'))
|
||||
os.chdir(original_dir)
|
||||
for pkg in pkgs:
|
||||
full_path = os.path.join(pkg_contents_dir, pkg)
|
||||
pkg_dict.update(findInfoPlistPathsInBundlePkg(full_path))
|
||||
return pkg_dict
|
||||
|
||||
|
||||
def extractAppIconsFromBundlePkg(pkg_path):
|
||||
def extractAppIconsFromBundlePkg(pkg_path, repo=None):
|
||||
'''Returns a list of paths for application icons found
|
||||
inside the bundle pkg at pkg_path'''
|
||||
pkg_dict = findInfoPlistPathsInBundlePkg(pkg_path)
|
||||
pkg_dict = findInfoPlistPathsInBundlePkg(pkg_path, repo)
|
||||
icon_paths = []
|
||||
exporttmp = tempfile.mkdtemp(dir='/tmp')
|
||||
for pkg in pkg_dict:
|
||||
archive_path = os.path.join(pkg, u'Contents/Archive.pax.gz')
|
||||
handle = None
|
||||
if repo:
|
||||
repo_archive_path = repo.join(pkg, u'Contents/Archive.pax.gz')
|
||||
handle = repo.open(repo_archive_path, 'r')
|
||||
archive_path = handle.local_path
|
||||
else:
|
||||
archive_path = os.path.join(pkg, u'Contents/Archive.pax.gz')
|
||||
err = extractAppBitsFromPkgArchive(archive_path, exporttmp)
|
||||
if err == 0:
|
||||
for info_path in pkg_dict[pkg]:
|
||||
@@ -212,9 +228,8 @@ def extractAppIconsFromBundlePkg(pkg_path):
|
||||
return icon_paths
|
||||
|
||||
|
||||
def getAppInfoPathsFromBundleComponentPkg(pkg_path):
|
||||
def getAppInfoPathsFromBOM(bomfile):
|
||||
'''Returns a list of paths to application Info.plists'''
|
||||
bomfile = os.path.join(pkg_path, u'Contents/Archive.bom')
|
||||
if os.path.exists(bomfile):
|
||||
cmd = ['/usr/bin/lsbom', '-s', bomfile]
|
||||
proc = subprocess.Popen(cmd, shell=False, bufsize=-1,
|
||||
|
||||
@@ -0,0 +1,280 @@
|
||||
#!/usr/bin/python
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Copyright 2016 Centrify Corporation.
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# https://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.
|
||||
|
||||
"""
|
||||
FileRepo
|
||||
Created by Centrify Corporation 2016-06-02.
|
||||
Implementation for accessing a repo via direct file access, including
|
||||
a remote repo mounted via AFP, SMB, or NFS.
|
||||
"""
|
||||
|
||||
from munkilib.munkicommon import listdir
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import objc
|
||||
import glob
|
||||
|
||||
# NetFS share mounting code borrowed and liberally adapted from Michael Lynn's
|
||||
# work here: https://gist.github.com/pudquick/1362a8908be01e23041d
|
||||
try:
|
||||
import errno
|
||||
import getpass
|
||||
from CoreFoundation import CFURLCreateWithString
|
||||
|
||||
class Attrdict(dict):
|
||||
'''Custom dict class'''
|
||||
__getattr__ = dict.__getitem__
|
||||
__setattr__ = dict.__setitem__
|
||||
|
||||
NetFS = Attrdict()
|
||||
# Can cheat and provide 'None' for the identifier, it'll just use
|
||||
# frameworkPath instead
|
||||
# scan_classes=False means only add the contents of this Framework
|
||||
NetFS_bundle = objc.initFrameworkWrapper(
|
||||
'NetFS', frameworkIdentifier=None,
|
||||
frameworkPath=objc.pathForFramework('NetFS.framework'),
|
||||
globals=NetFS, scan_classes=False)
|
||||
|
||||
# https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/
|
||||
# ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html
|
||||
# Fix NetFSMountURLSync signature
|
||||
del NetFS['NetFSMountURLSync']
|
||||
objc.loadBundleFunctions(
|
||||
NetFS_bundle, NetFS, [('NetFSMountURLSync', 'i@@@@@@o^@')])
|
||||
NETFSMOUNTURLSYNC_AVAILABLE = True
|
||||
except (ImportError, KeyError):
|
||||
NETFSMOUNTURLSYNC_AVAILABLE = False
|
||||
|
||||
if NETFSMOUNTURLSYNC_AVAILABLE:
|
||||
class ShareMountException(Exception):
|
||||
'''An exception raised if share mounting failed'''
|
||||
pass
|
||||
|
||||
|
||||
class ShareAuthenticationNeededException(ShareMountException):
|
||||
'''An exception raised if authentication is needed'''
|
||||
pass
|
||||
|
||||
|
||||
def mount_share(share_url):
|
||||
'''Mounts a share at /Volumes, returns the mount point or raises an
|
||||
error'''
|
||||
sh_url = CFURLCreateWithString(None, share_url, None)
|
||||
# Set UI to reduced interaction
|
||||
open_options = {NetFS.kNAUIOptionKey: NetFS.kNAUIOptionNoUI}
|
||||
# Allow mounting sub-directories of root shares
|
||||
mount_options = {NetFS.kNetFSAllowSubMountsKey: True}
|
||||
# Mount!
|
||||
result, output = NetFS.NetFSMountURLSync(
|
||||
sh_url, None, None, None, open_options, mount_options, None)
|
||||
# Check if it worked
|
||||
if result != 0:
|
||||
if result in (errno.ENOTSUP, errno.EAUTH):
|
||||
# errno.ENOTSUP is returned if an afp share needs a login
|
||||
# errno.EAUTH is returned if authentication fails (SMB for sure)
|
||||
raise ShareAuthenticationNeededException()
|
||||
raise ShareMountException(
|
||||
'Error mounting url "%s": %s, error %s'
|
||||
% (share_url, os.strerror(result), result))
|
||||
# Return the mountpath
|
||||
return str(output[0])
|
||||
|
||||
|
||||
def mount_share_with_credentials(share_url, username, password):
|
||||
'''Mounts a share at /Volumes, returns the mount point or raises an
|
||||
error. Include username and password as parameters, not in the
|
||||
share_path URL'''
|
||||
sh_url = CFURLCreateWithString(None, share_url, None)
|
||||
# Set UI to reduced interaction
|
||||
open_options = {NetFS.kNAUIOptionKey: NetFS.kNAUIOptionNoUI}
|
||||
# Allow mounting sub-directories of root shares
|
||||
mount_options = {NetFS.kNetFSAllowSubMountsKey: True}
|
||||
# Mount!
|
||||
result, output = NetFS.NetFSMountURLSync(
|
||||
sh_url, None, username, password, open_options, mount_options, None)
|
||||
# Check if it worked
|
||||
if result != 0:
|
||||
raise ShareMountException(
|
||||
'Error mounting url "%s": %s, error %s'
|
||||
% (share_url, os.strerror(result), result))
|
||||
# Return the mountpath
|
||||
return str(output[0])
|
||||
|
||||
|
||||
def mount_share_url(share_url):
|
||||
'''Mount a share url under /Volumes, prompting for password if needed
|
||||
Raises ShareMountException if there's an error'''
|
||||
try:
|
||||
mount_share(share_url)
|
||||
except ShareAuthenticationNeededException:
|
||||
username = raw_input('Username: ')
|
||||
password = getpass.getpass()
|
||||
mount_share_with_credentials(share_url, username, password)
|
||||
|
||||
class FileRepo(object):
|
||||
WE_MOUNTED_THE_REPO = False
|
||||
'''Repo implementation that access a local or locally-mounted repo.'''
|
||||
def __init__(self, path, url):
|
||||
self.path = path
|
||||
self.url = url
|
||||
|
||||
def exists(self, subdir = None):
|
||||
'''Returns true if the specified path exists in the repo'''
|
||||
full_path = self.path
|
||||
if subdir:
|
||||
full_path = os.path.join(full_path, subdir)
|
||||
return os.path.exists(full_path)
|
||||
|
||||
def isdir(self, path):
|
||||
'''Returns true if the specified path exists in the repo
|
||||
and is a directory.'''
|
||||
return os.path.isdir(os.path.join(self.path, path))
|
||||
|
||||
def isfile(self, path):
|
||||
'''Returns true if the specified path exists in the repo
|
||||
and is a regular file.'''
|
||||
return os.path.isfile(os.path.join(self.path, path))
|
||||
|
||||
def join(self, *args):
|
||||
'''Combines path elements within the repo.'''
|
||||
return os.path.join(*args)
|
||||
|
||||
def dirname(self, path):
|
||||
'''Returns the directory portion of a path.'''
|
||||
return os.path.dirname(path)
|
||||
|
||||
def basename(self, path):
|
||||
'''Returns the filename portion of a path.'''
|
||||
return os.path.basename(path)
|
||||
|
||||
def splitext(self, path):
|
||||
'''Splits the base and extention parts of a path.'''
|
||||
return os.path.splitext(path)
|
||||
|
||||
def mkdir(self, path, mode=0777):
|
||||
'''Creates a directory within the repo.'''
|
||||
return os.mkdir(os.path.join(self.path, path), mode)
|
||||
|
||||
def makedirs(self, path, mode=0777):
|
||||
'''Creates a directory within the repo, including parent directories.'''
|
||||
return os.makedirs(os.path.join(self.path, path), mode)
|
||||
|
||||
def listdir(self, path):
|
||||
'''Lists the contents of a repo directory.'''
|
||||
return listdir(os.path.join(self.path, path))
|
||||
|
||||
def remove(self, path):
|
||||
'''Removes a file from the repo.'''
|
||||
return os.remove(os.path.join(self.path, path))
|
||||
|
||||
def unlink(self, path):
|
||||
'''Removes a file from the repo.'''
|
||||
return os.unlink(os.path.join(self.path, path))
|
||||
|
||||
def get(self, src, dest):
|
||||
'''Copies a file from the repo to a local file.'''
|
||||
cmd = ['/bin/cp', os.path.join(self.path, src), dest]
|
||||
return subprocess.call(cmd)
|
||||
|
||||
def put(self, src, dest):
|
||||
'''Copies a local file to the repo.'''
|
||||
cmd = ['/bin/cp', src, os.path.join(self.path, dest)]
|
||||
return subprocess.call(cmd)
|
||||
|
||||
#
|
||||
# Some callers open a file, but then use the local_path field
|
||||
# to access it rather than reading or writing through the returned
|
||||
# handle. For local repos those callers could just use the
|
||||
# file name directly rather than opening it through this method,
|
||||
# but for the CommandRepo implementation the local_path field
|
||||
# will be a local temporary file that was copied from the remote
|
||||
# repo and/or will be copied to the remote repo on close.
|
||||
#
|
||||
def open(self, path, mode='r'):
|
||||
'''Opens a file in the repo.'''
|
||||
class RepoFile(object):
|
||||
def __init__(self, repo, repo_path, mode):
|
||||
self.repo = repo
|
||||
self.repo_path = repo_path
|
||||
self.repo_mode = mode
|
||||
self.file = open(self.repo_path, mode)
|
||||
self.local_path = self.repo_path
|
||||
|
||||
def read(self):
|
||||
return self.file.read()
|
||||
|
||||
return RepoFile(self, os.path.join(self.path, path), mode)
|
||||
|
||||
def mount(self):
|
||||
'''Mounts the repo locally.'''
|
||||
if os.path.exists(self.path):
|
||||
return
|
||||
print 'Attempting to mount fileshare %s:' % self.url
|
||||
if NETFSMOUNTURLSYNC_AVAILABLE:
|
||||
try:
|
||||
mount_share_url(self.url)
|
||||
except ShareMountException, err:
|
||||
print sys.stderr, err
|
||||
return
|
||||
else:
|
||||
self.WE_MOUNTED_THE_REPO = True
|
||||
return 0
|
||||
else:
|
||||
os.mkdir(self.path)
|
||||
if self.url.startswith('afp:'):
|
||||
cmd = ['/sbin/mount_afp', '-i', self.url, self.path]
|
||||
elif self.url.startswith('smb:'):
|
||||
cmd = ['/sbin/mount_smbfs', self.url[4:], self.path]
|
||||
elif self.url.startswith('nfs://'):
|
||||
cmd = ['/sbin/mount_nfs', self.url[6:], self.path]
|
||||
else:
|
||||
print >> sys.stderr, 'Unsupported filesystem URL!'
|
||||
return
|
||||
retcode = subprocess.call(cmd)
|
||||
if retcode:
|
||||
os.rmdir(self.path)
|
||||
else:
|
||||
self.WE_MOUNTED_THE_REPO = True
|
||||
return retcode
|
||||
|
||||
def unmount(self):
|
||||
'''Unmounts the repo.'''
|
||||
if not os.path.exists(self.path):
|
||||
return
|
||||
retcode = 0
|
||||
if os.path.exists(self.path):
|
||||
cmd = ['/sbin/umount', self.path]
|
||||
retcode = subprocess.call(cmd)
|
||||
return retcode
|
||||
|
||||
def walk(self, path, **kwargs):
|
||||
'''Walks a path in the repo, returning all files and subdirectories.
|
||||
Only a subset of the features of os.walk() are supported.'''
|
||||
for (dirpath, dirnames, filenames) in os.walk(os.path.join(self.path, path), **kwargs):
|
||||
dirpath = dirpath[len(self.path) + 1:]
|
||||
yield (dirpath, dirnames, filenames)
|
||||
|
||||
def glob(self, path, *args):
|
||||
'''Expands a set of glob patterns within a repo path.'''
|
||||
matches = []
|
||||
original_dir = os.getcwd()
|
||||
os.chdir(path)
|
||||
for arg in args:
|
||||
matches += glob.glob(arg)
|
||||
os.chdir(original_dir)
|
||||
return matches
|
||||
Reference in New Issue
Block a user