mirror of
https://github.com/munki/munki.git
synced 2026-01-08 07:29:52 -06:00
1290 lines
45 KiB
Python
Executable File
1290 lines
45 KiB
Python
Executable File
#!/usr/bin/python
|
|
# encoding: utf-8
|
|
#
|
|
# Copyright 2011-2016 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
|
|
#
|
|
# 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.
|
|
"""
|
|
manifestutil
|
|
|
|
Created by Greg Neagle on 2011-03-04.
|
|
"""
|
|
|
|
import ctypes
|
|
import fnmatch
|
|
import optparse
|
|
import os
|
|
import plistlib
|
|
import readline
|
|
import shlex
|
|
import subprocess
|
|
import sys
|
|
import thread
|
|
import time
|
|
|
|
from ctypes.util import find_library
|
|
from xml.parsers.expat import ExpatError
|
|
|
|
try:
|
|
from munkilib.munkicommon import get_version
|
|
except ImportError:
|
|
# munkilib is not available
|
|
def get_version():
|
|
'''Placeholder if munkilib is not available'''
|
|
return 'UNKNOWN'
|
|
|
|
# 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
|
|
import objc
|
|
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 (-6600, errno.EINVAL, errno.ENOTSUP, errno.EAUTH):
|
|
# -6600 is kNetAuthErrorInternal in NetFS.h 10.9+
|
|
# errno.EINVAL is returned if an afp share needs a login in
|
|
# some versions of OS X
|
|
# errno.ENOTSUP is returned if an afp share needs a login in
|
|
# some versions of OS X
|
|
# 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)
|
|
|
|
|
|
try:
|
|
# PyLint cannot properly find names inside Cocoa libraries, so issues bogus
|
|
# No name 'Foo' in module 'Bar' warnings. Disable them.
|
|
# pylint: disable=E0611
|
|
from Foundation import CFPreferencesAppSynchronize
|
|
from Foundation import CFPreferencesCopyAppValue
|
|
from Foundation import CFPreferencesSetAppValue
|
|
# pylint: enable=E0611
|
|
|
|
BUNDLE_ID = 'com.googlecode.munki.munkiimport'
|
|
def pref(prefname):
|
|
"""Return a preference. Since this uses CFPreferencesCopyAppValue,
|
|
Preferences can be defined several places. Precedence is:
|
|
- MCX/Configuration Profile
|
|
- ~/Library/Preferences/ByHost/
|
|
com.googlecode.munki.munkiimport.XX.plist
|
|
- ~/Library/Preferences/com.googlecode.munki.munkiimport.plist
|
|
- /Library/Preferences/com.googlecode.munki.munkiimport.plist
|
|
"""
|
|
return CFPreferencesCopyAppValue(prefname, BUNDLE_ID)
|
|
|
|
|
|
def configure(args):
|
|
"""Configures manifestutil for use"""
|
|
_prefs = {}
|
|
if len(args):
|
|
print >> sys.stderr, 'Usage: configure'
|
|
return 22 # Invalid argument
|
|
for (key, prompt) in [
|
|
('repo_path', 'Path to munki repo (example: /Volumes/repo)'),
|
|
('repo_url',
|
|
'Repo fileshare URL (example: afp://munki.example.com/repo)')]:
|
|
|
|
newvalue = raw_input_with_default('%15s: ' % prompt, pref(key))
|
|
_prefs[key] = newvalue or pref(key) or ''
|
|
|
|
for key, value in _prefs.items():
|
|
try:
|
|
CFPreferencesSetAppValue(key, value, BUNDLE_ID)
|
|
except BaseException:
|
|
print >> sys.stderr, 'Could not save configuration!'
|
|
finally:
|
|
CFPreferencesAppSynchronize(BUNDLE_ID)
|
|
|
|
except ImportError:
|
|
# Foundation isn't available
|
|
PREFSNAME = 'com.googlecode.munki.munkiimport.plist'
|
|
PREFSPATH = os.path.expanduser(
|
|
os.path.join('~/Library/Preferences', PREFSNAME))
|
|
_PREFS = {}
|
|
def pref(prefname):
|
|
"""Returns a preference for prefname"""
|
|
global _PREFS
|
|
if not _PREFS:
|
|
try:
|
|
_PREFS = plistlib.readPlist(PREFSPATH)
|
|
except (IOError, OSError, ExpatError):
|
|
pass
|
|
if prefname in _PREFS:
|
|
return _PREFS[prefname]
|
|
else:
|
|
return None
|
|
|
|
|
|
def configure(args):
|
|
"""Configures manifestutil for use"""
|
|
if len(args):
|
|
print >> sys.stderr, 'Usage: configure'
|
|
return 22 # Invalid argument
|
|
for (key, prompt) in [
|
|
('repo_path', 'Path to munki repo (example: /Volumes/repo)'),
|
|
('repo_url',
|
|
'Repo fileshare URL (example: afp://munki.example.com/repo)')]:
|
|
|
|
newvalue = raw_input_with_default('%15s: ' % prompt, pref(key))
|
|
_PREFS[key] = newvalue or pref(key) or ''
|
|
|
|
try:
|
|
plistlib.writePlist(_PREFS, PREFSPATH)
|
|
return 0
|
|
except (IOError, OSError, ExpatError):
|
|
print >> sys.stderr, (
|
|
'Could not save configuration to %s' % PREFSPATH)
|
|
return 1 # Operation not permitted
|
|
|
|
|
|
if 'libedit' in readline.__doc__:
|
|
# readline module was compiled against libedit
|
|
LIBEDIT = ctypes.cdll.LoadLibrary(find_library('libedit'))
|
|
else:
|
|
LIBEDIT = None
|
|
|
|
|
|
def raw_input_with_default(prompt, default_text):
|
|
'''A nasty, nasty hack to get around Python readline limitations under
|
|
OS X. Gives us editable default text for munkiimport choices'''
|
|
|
|
def insert_default_text(prompt, text):
|
|
'''Helper function'''
|
|
time.sleep(0.01)
|
|
LIBEDIT.rl_set_prompt(prompt)
|
|
readline.insert_text(text)
|
|
LIBEDIT.rl_forced_update_display()
|
|
|
|
readline.clear_history()
|
|
if not default_text:
|
|
return unicode(raw_input(prompt), encoding=sys.stdin.encoding)
|
|
elif LIBEDIT:
|
|
# readline module was compiled against libedit
|
|
thread.start_new_thread(insert_default_text, (prompt, default_text))
|
|
return unicode(raw_input(), encoding=sys.stdin.encoding)
|
|
else:
|
|
readline.set_startup_hook(lambda: readline.insert_text(default_text))
|
|
try:
|
|
return unicode(raw_input(prompt), encoding=sys.stdin.encoding)
|
|
finally:
|
|
readline.set_startup_hook()
|
|
|
|
|
|
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):
|
|
if filename in cataloglist:
|
|
try:
|
|
catalog = plistlib.readPlist(
|
|
os.path.join(catalogs_path, filename))
|
|
except (IOError, OSError, ExpatError):
|
|
# skip items that aren't valid plists
|
|
# or that we can't read
|
|
pass
|
|
else:
|
|
item_list.extend([item['name']
|
|
for item in catalog
|
|
if not item.get('update_for')])
|
|
item_list = list(set(item_list))
|
|
item_list.sort()
|
|
return item_list
|
|
|
|
|
|
def get_manifest_names():
|
|
'''Returns a list of available manifests'''
|
|
manifests_path = os.path.join(pref('repo_path'), 'manifests')
|
|
manifests = []
|
|
for dirpath, dirnames, filenames in os.walk(manifests_path):
|
|
for dirname in dirnames:
|
|
# don't recurse into directories that start
|
|
# with a period.
|
|
if dirname.startswith('.'):
|
|
dirnames.remove(dirname)
|
|
subdir = dirpath[len(manifests_path):]
|
|
for name in filenames:
|
|
if name.startswith("."):
|
|
# don't process these
|
|
continue
|
|
manifests.append(os.path.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 = []
|
|
for name in os.listdir(catalogs_path):
|
|
if name.startswith(".") or name == 'all':
|
|
# don't process these
|
|
continue
|
|
try:
|
|
_ = plistlib.readPlist(os.path.join(catalogs_path, name))
|
|
except (IOError, OSError, ExpatError):
|
|
# skip items that aren't valid plists
|
|
pass
|
|
else:
|
|
catalogs.append(name)
|
|
catalogs.sort()
|
|
return catalogs
|
|
|
|
|
|
def get_manifest_pkg_sections():
|
|
'''Returns a list of manifest sections that can contain pkg names'''
|
|
return ['managed_installs',
|
|
'managed_uninstalls',
|
|
'managed_updates',
|
|
'optional_installs']
|
|
|
|
|
|
def printplistitem(label, value, indent=0):
|
|
"""Prints a plist item in an 'attractive' way"""
|
|
indentspace = ' '
|
|
if value is None:
|
|
print indentspace*indent, '%s: !NONE!' % label
|
|
elif isinstance(value, list) or type(value).__name__ == 'NSCFArray':
|
|
if label:
|
|
print indentspace*indent, '%s:' % label
|
|
for item in value:
|
|
printplistitem('', item, indent+1)
|
|
elif isinstance(value, dict) or type(value).__name__ == 'NSCFDictionary':
|
|
if label:
|
|
print indentspace*indent, '%s:' % label
|
|
keys = list(value.keys())
|
|
keys.sort()
|
|
for subkey in keys:
|
|
printplistitem(subkey, value[subkey], indent+1)
|
|
else:
|
|
if label:
|
|
print indentspace*indent, '%s: %s' % (label, value)
|
|
else:
|
|
print indentspace*indent, '%s' % value
|
|
|
|
|
|
def printplist(plistdict):
|
|
"""Prints plist dictionary in a pretty(?) way"""
|
|
keys = list(plistdict.keys())
|
|
keys.sort()
|
|
for key in keys:
|
|
printplistitem(key, plistdict[key])
|
|
|
|
|
|
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):
|
|
try:
|
|
return plistlib.readPlist(manifest_path)
|
|
except (IOError, OSError, ExpatError):
|
|
print >> sys.stderr, (
|
|
'Could not read manifest %s' % manifest_name)
|
|
return None
|
|
else:
|
|
print >> sys.stderr, 'Manifest %s doesn\'t exist!' % manifest_name
|
|
return None
|
|
|
|
|
|
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)
|
|
if not overwrite_existing:
|
|
if os.path.exists(manifest_path):
|
|
print >> sys.stderr, '%s already exists!' % manifest_name
|
|
return False
|
|
try:
|
|
plistlib.writePlist(manifest_dict, manifest_path)
|
|
return True
|
|
except (IOError, OSError, ExpatError), err:
|
|
print >> sys.stderr, 'Saving %s failed: %s' % (manifest_name, err)
|
|
return False
|
|
|
|
def manifest_rename(source_manifest_name, dest_manifest_name,
|
|
overwrite_existing=False):
|
|
'''Renames an existing manifest'''
|
|
source_manifest_path = os.path.join(
|
|
pref('repo_path'), 'manifests', source_manifest_name)
|
|
dest_manifest_path = os.path.join(
|
|
pref('repo_path'), 'manifests', dest_manifest_name)
|
|
if not overwrite_existing:
|
|
if os.path.exists(dest_manifest_path):
|
|
print >> sys.stderr, '%s already exists!' % dest_manifest_name
|
|
return False
|
|
try:
|
|
os.rename(source_manifest_path, dest_manifest_path)
|
|
return True
|
|
except (IOError, OSError, ExpatError), err:
|
|
print >> sys.stderr, 'Renaming %s to %s failed: %s' % (
|
|
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:
|
|
answer = raw_input('Unmount the repo fileshare? [y/n] ')
|
|
if answer.lower().startswith('y'):
|
|
result = unmount_repo_cli()
|
|
exit(exitcode or result)
|
|
|
|
|
|
def update_cached_manifest_list():
|
|
'''Updates our cached list of available manifests so our completer
|
|
will return all the available manifests.'''
|
|
CMD_ARG_DICT['manifests'] = get_manifest_names()
|
|
|
|
|
|
##### subcommand functions #####
|
|
|
|
class MyOptParseError(Exception):
|
|
'''Exception for our custom option parser'''
|
|
pass
|
|
|
|
|
|
class MyOptParseExit(Exception):
|
|
'''Raised when optparse calls self.exit() so we can handle it instead
|
|
of exiting'''
|
|
pass
|
|
|
|
|
|
class MyOptionParser(optparse.OptionParser):
|
|
'''Custom option parser that overrides the error handler and
|
|
the exit handler so printing help doesn't exit the interactive
|
|
psuedo-shell'''
|
|
def error(self, msg):
|
|
"""error(msg : string)
|
|
|
|
"""
|
|
self.print_usage(sys.stderr)
|
|
raise MyOptParseError('option error: %s' % msg)
|
|
|
|
def exit(self, status=0, msg=None):
|
|
if msg:
|
|
sys.stderr.write(msg)
|
|
raise MyOptParseExit
|
|
|
|
|
|
def version(args):
|
|
'''Prints version number'''
|
|
if len(args) != 0:
|
|
print >> sys.stderr, 'Usage: version'
|
|
return 22 # Invalid argument
|
|
print get_version()
|
|
return 0
|
|
|
|
|
|
def list_catalogs(args):
|
|
'''Prints the names of the available catalogs'''
|
|
parser = MyOptionParser()
|
|
parser.set_usage('''list-catalogs
|
|
Prints the names of the available catalogs''')
|
|
try:
|
|
_, arguments = parser.parse_args(args)
|
|
except MyOptParseError, errmsg:
|
|
print >> sys.stderr, str(errmsg)
|
|
return 22 # Invalid argument
|
|
|
|
if len(arguments) != 0:
|
|
parser.print_usage(sys.stderr)
|
|
return 22 # Invalid argument
|
|
for item in get_catalogs():
|
|
print item
|
|
return 0
|
|
|
|
|
|
def list_catalog_items(args):
|
|
'''Lists items in the given catalogs'''
|
|
parser = MyOptionParser()
|
|
parser.set_usage('''list-catalog-items CATALOG_NAME [CATALOG_NAME ...]
|
|
Lists items in the given catalogs''')
|
|
try:
|
|
_, arguments = parser.parse_args(args)
|
|
except MyOptParseError, errmsg:
|
|
print >> sys.stderr, str(errmsg)
|
|
return 22 # Invalid argument
|
|
|
|
if len(arguments) == 0:
|
|
parser.print_usage(sys.stderr)
|
|
return 1 # Operation not permitted
|
|
available_catalogs = get_catalogs()
|
|
for catalog in arguments:
|
|
if catalog not in available_catalogs:
|
|
print >> sys.stderr, '%s: no such catalog!' % catalog
|
|
return 2 # No such file or directory
|
|
for pkg in get_installer_item_names(arguments):
|
|
print pkg
|
|
return 0
|
|
|
|
|
|
def list_manifests(args):
|
|
'''Prints names of available manifests, filtering on arg[0]
|
|
similar to Unix file globbing'''
|
|
parser = MyOptionParser()
|
|
parser.set_usage('''list-manifests [FILE_NAME_MATCH_STRING]
|
|
Prints names of available manifests.''')
|
|
try:
|
|
_, arguments = parser.parse_args(args)
|
|
except MyOptParseError, errmsg:
|
|
print >> sys.stderr, str(errmsg)
|
|
return 22 # Invalid argument
|
|
|
|
if len(arguments) == 1:
|
|
list_filter = arguments[0]
|
|
elif len(arguments) == 0:
|
|
list_filter = '*'
|
|
else:
|
|
parser.print_usage(sys.stderr)
|
|
return 7 # Argument list too long
|
|
for item in get_manifest_names():
|
|
if fnmatch.fnmatch(item, list_filter):
|
|
print item
|
|
return 0
|
|
|
|
|
|
def find(args):
|
|
'''Find text in manifests, optionally searching just a specific manifest
|
|
section specified by keyname'''
|
|
parser = MyOptionParser()
|
|
parser.set_usage('''find FIND_TEXT [--section SECTION_NAME]
|
|
Find text in manifests, optionally searching a specific manifest
|
|
section''')
|
|
parser.add_option('--section',
|
|
metavar='SECTION_NAME',
|
|
help=('(Optional) Section of the manifest to search for '
|
|
'FIND_TEXT'))
|
|
try:
|
|
options, arguments = parser.parse_args(args)
|
|
except MyOptParseError, errmsg:
|
|
print >> sys.stderr, str(errmsg)
|
|
return 22 # Invalid argument
|
|
if not options.section and len(arguments) == 2:
|
|
options.section = arguments[1]
|
|
del arguments[1]
|
|
if len(arguments) != 1:
|
|
parser.print_usage(sys.stderr)
|
|
return 7 # Argument list too long
|
|
findtext = arguments[0]
|
|
keyname = options.section
|
|
|
|
manifests_path = os.path.join(pref('repo_path'), 'manifests')
|
|
count = 0
|
|
for name in get_manifest_names():
|
|
pathname = os.path.join(manifests_path, name)
|
|
try:
|
|
manifest = plistlib.readPlist(pathname)
|
|
except (IOError, OSError, ExpatError):
|
|
print >> sys.stderr, 'Error reading %s' % pathname
|
|
continue
|
|
if keyname:
|
|
if keyname in manifest:
|
|
value = manifest[keyname]
|
|
if type(value) == list or type(value).__name__ == 'NSCFArray':
|
|
for item in value:
|
|
try:
|
|
if findtext.upper() in item.upper():
|
|
print '%s: %s' % (name, item)
|
|
count += 1
|
|
except AttributeError:
|
|
pass
|
|
elif findtext.upper() in value.upper():
|
|
print '%s: %s' % (name, value)
|
|
count += 1
|
|
else:
|
|
for key in manifest.keys():
|
|
value = manifest[key]
|
|
if type(value) == list or type(value).__name__ == 'NSCFArray':
|
|
for item in value:
|
|
try:
|
|
if findtext.upper() in item.upper():
|
|
print '%s (%s): %s' % (name, key, item)
|
|
count += 1
|
|
except AttributeError:
|
|
pass
|
|
elif findtext.upper() in value.upper():
|
|
print '%s (%s): %s' % (name, key, value)
|
|
count += 1
|
|
|
|
print '%s matches found.' % count
|
|
return 0
|
|
|
|
|
|
def display_manifest(args):
|
|
'''Prints contents of a given manifest'''
|
|
parser = MyOptionParser()
|
|
parser.set_usage('''display-manifest MANIFESTNAME
|
|
Prints the contents of the specified manifest''')
|
|
try:
|
|
_, arguments = parser.parse_args(args)
|
|
except MyOptParseError, errmsg:
|
|
print >> sys.stderr, str(errmsg)
|
|
return 22 # Invalid argument
|
|
if len(arguments) != 1:
|
|
parser.print_usage(sys.stderr)
|
|
return 7 # Argument list too long
|
|
manifestname = arguments[0]
|
|
manifest = get_manifest(manifestname)
|
|
if manifest:
|
|
printplist(manifest)
|
|
return 0
|
|
else:
|
|
return 2 # No such file or directory
|
|
|
|
|
|
def expand_included_manifests(args):
|
|
'''Prints a manifest, expanding any included manifests.'''
|
|
parser = MyOptionParser()
|
|
parser.set_usage('''expand-included-manifest MANIFESTNAME
|
|
Prints included manifests in the specified manifest''')
|
|
try:
|
|
_, arguments = parser.parse_args(args)
|
|
except MyOptParseError, errmsg:
|
|
print >> sys.stderr, str(errmsg)
|
|
return 22 # Invalid argument
|
|
if len(arguments) != 1:
|
|
parser.print_usage(sys.stderr)
|
|
return 7 # Argument list too long
|
|
manifestname = arguments[0]
|
|
manifest = get_manifest(manifestname)
|
|
if manifest:
|
|
printplist(manifest_recurser(manifest))
|
|
else:
|
|
return 2 # No such file or directory
|
|
|
|
|
|
def manifest_recurser(manifest):
|
|
'''Recursive expansion of included manifests'''
|
|
# No infinite loop checking! Be wary!
|
|
if 'included_manifests' in manifest:
|
|
for (index, item) in enumerate(manifest['included_manifests']):
|
|
included_manifest = get_manifest(item)
|
|
if included_manifest:
|
|
included_manifest = manifest_recurser(included_manifest)
|
|
manifest['included_manifests'][index] = {
|
|
item: included_manifest
|
|
}
|
|
return manifest
|
|
|
|
|
|
def new_manifest(args):
|
|
'''Creates a new, empty manifest'''
|
|
parser = MyOptionParser()
|
|
parser.set_usage('''new-manifest MANIFESTNAME
|
|
Creates a new empty manifest''')
|
|
try:
|
|
_, arguments = parser.parse_args(args)
|
|
except MyOptParseError, errmsg:
|
|
print >> sys.stderr, str(errmsg)
|
|
return 22 # Invalid argument
|
|
if len(arguments) != 1:
|
|
parser.print_usage(sys.stderr)
|
|
return 7 # Argument list too long
|
|
manifest_name = arguments[0]
|
|
manifest = {'catalogs': [],
|
|
'included_manifests': [],
|
|
'managed_installs': [],
|
|
'managed_uninstalls': []}
|
|
if save_manifest(manifest, manifest_name):
|
|
update_cached_manifest_list()
|
|
return 0
|
|
else:
|
|
return 1 # Operation not permitted
|
|
|
|
|
|
def copy_manifest(args):
|
|
'''Copies one manifest to another'''
|
|
parser = MyOptionParser()
|
|
parser.set_usage(
|
|
'''copy-manifest SOURCE_MANIFEST DESTINATION_MANIFEST
|
|
Copies the contents of one manifest to another''')
|
|
try:
|
|
_, arguments = parser.parse_args(args)
|
|
except MyOptParseError, errmsg:
|
|
print >> sys.stderr, str(errmsg)
|
|
return 22 # Invalid argument
|
|
if len(arguments) != 2:
|
|
parser.print_usage(sys.stderr)
|
|
return 7 # Argument list too long
|
|
source_manifest = arguments[0]
|
|
dest_manifest = arguments[1]
|
|
manifest = get_manifest(source_manifest)
|
|
if manifest and save_manifest(manifest, dest_manifest):
|
|
update_cached_manifest_list()
|
|
return 0
|
|
else:
|
|
return 1 # Operation not permitted
|
|
|
|
|
|
def rename_manifest(args):
|
|
'''Renames a manifest'''
|
|
parser = MyOptionParser()
|
|
parser.set_usage(
|
|
'''rename-manifest SOURCE_MANIFEST DESTINATION_MANIFEST
|
|
Renames the manifest''')
|
|
try:
|
|
_, arguments = parser.parse_args(args)
|
|
except MyOptParseError, errmsg:
|
|
print >> sys.stderr, str(errmsg)
|
|
return 22 # Invalid argument
|
|
if len(arguments) != 2:
|
|
parser.print_usage(sys.stderr)
|
|
return 7 # Argument list too long
|
|
source_manifest = arguments[0]
|
|
dest_manifest = arguments[1]
|
|
if manifest_rename(source_manifest, dest_manifest):
|
|
print ('Renamed manifest %s to %s.'
|
|
% (source_manifest, dest_manifest))
|
|
update_cached_manifest_list()
|
|
return 0
|
|
else:
|
|
return 1 # Operation not permitted
|
|
|
|
|
|
def add_pkg(args):
|
|
'''Adds a package to a manifest.'''
|
|
parser = MyOptionParser()
|
|
parser.set_usage(
|
|
'''add-pkg PKGNAME --manifest MANIFESTNAME [--section SECTIONNAME]
|
|
Adds a package to a manifest. Package is added to managed_installs
|
|
unless a different manifest section is specified with the
|
|
--section option''')
|
|
parser.add_option('--manifest',
|
|
metavar='MANIFESTNAME',
|
|
help='name of manifest on which to operate')
|
|
parser.add_option('--section', default='managed_installs',
|
|
metavar='SECTIONNAME',
|
|
help=('manifest section to which to add the package. '
|
|
'Defaults to managed_installs.'))
|
|
try:
|
|
options, arguments = parser.parse_args(args)
|
|
except MyOptParseError, errmsg:
|
|
print >> sys.stderr, str(errmsg)
|
|
return 22 # Invalid argument
|
|
if not options.manifest:
|
|
if len(arguments) == 2:
|
|
options.manifest = arguments[1]
|
|
del arguments[1]
|
|
else:
|
|
parser.print_usage(sys.stderr)
|
|
return 7 # Argument list too long
|
|
if len(arguments) != 1:
|
|
parser.print_usage(sys.stderr)
|
|
return 7 # Argument list too long
|
|
pkgname = arguments[0]
|
|
|
|
manifest = get_manifest(options.manifest)
|
|
if not manifest:
|
|
return 2 # No such file or directory
|
|
for section in get_manifest_pkg_sections():
|
|
if pkgname in manifest.get(section, []):
|
|
print >> sys.stderr, (
|
|
'Package %s is already in section %s of manifest %s.'
|
|
% (pkgname, section, options.manifest))
|
|
return 1 # Operation not permitted
|
|
|
|
manifest_catalogs = manifest.get('catalogs', [])
|
|
available_pkgnames = get_installer_item_names(manifest_catalogs)
|
|
if pkgname not in available_pkgnames:
|
|
print >> sys.stderr, (
|
|
'WARNING: Package %s is not available in catalogs %s '
|
|
'of manifest %s.'
|
|
% (pkgname, manifest_catalogs, options.manifest))
|
|
if not options.section in manifest:
|
|
manifest[options.section] = [pkgname]
|
|
else:
|
|
manifest[options.section].append(pkgname)
|
|
if save_manifest(manifest, options.manifest, overwrite_existing=True):
|
|
print ('Added %s to section %s of manifest %s.'
|
|
% (pkgname, options.section, options.manifest))
|
|
return 0
|
|
else:
|
|
return 1 # Operation not permitted
|
|
|
|
|
|
def remove_pkg(args):
|
|
'''Removes a package from a manifest.'''
|
|
parser = MyOptionParser()
|
|
parser.set_usage(
|
|
'''remove-pkg PKGNAME --manifest MANIFESTNAME [--section SECTIONNAME]
|
|
Removes a package from a manifest. Package is removed from
|
|
managed_installs unless a different manifest section is specified with
|
|
the --section option''')
|
|
parser.add_option('--manifest',
|
|
metavar='MANIFESTNAME',
|
|
help='''name of manifest on which to operate''')
|
|
parser.add_option('--section', default='managed_installs',
|
|
metavar='SECTIONNAME',
|
|
help=('manifest section from which to remove the '
|
|
'package. Defaults to managed_installs.'))
|
|
try:
|
|
options, arguments = parser.parse_args(args)
|
|
except MyOptParseError, errmsg:
|
|
print >> sys.stderr, str(errmsg)
|
|
return 22 # Invalid argument
|
|
if not options.manifest:
|
|
if len(arguments) == 2:
|
|
options.manifest = arguments[1]
|
|
del arguments[1]
|
|
else:
|
|
parser.print_usage(sys.stderr)
|
|
return 7 # Argument list too long
|
|
if len(arguments) != 1:
|
|
parser.print_usage(sys.stderr)
|
|
return 7 # Argument list too long
|
|
pkgname = arguments[0]
|
|
|
|
manifest = get_manifest(options.manifest)
|
|
if not manifest:
|
|
return 2 # No such file or directory
|
|
if not options.section in manifest:
|
|
print >> sys.stderr, ('Section %s is not in manifest %s.'
|
|
% (options.section, manifest))
|
|
return 1 # Operation not permitted
|
|
if pkgname not in manifest[options.section]:
|
|
print >> sys.stderr, ('Package %s is not in section %s '
|
|
'of manifest %s.'
|
|
% (pkgname, options.section, options.manifest))
|
|
return 1 # Operation not permitted
|
|
else:
|
|
manifest[options.section].remove(pkgname)
|
|
if save_manifest(manifest, options.manifest, overwrite_existing=True):
|
|
print ('Removed %s from section %s of manifest %s.'
|
|
% (pkgname, options.section, options.manifest))
|
|
return 0
|
|
else:
|
|
return 1 # Operation not permitted
|
|
|
|
|
|
def add_catalog(args):
|
|
'''Adds a catalog to a manifest.'''
|
|
parser = MyOptionParser()
|
|
parser.set_usage('''add-catalog CATALOGNAME --manifest MANIFESTNAME
|
|
Adds a catalog to a manifest''')
|
|
parser.add_option('--manifest',
|
|
metavar='MANIFESTNAME',
|
|
help='name of manifest on which to operate')
|
|
try:
|
|
options, arguments = parser.parse_args(args)
|
|
except MyOptParseError, errmsg:
|
|
print >> sys.stderr, str(errmsg)
|
|
return 22 # Invalid argument
|
|
if not options.manifest:
|
|
if len(arguments) == 2:
|
|
options.manifest = arguments[1]
|
|
del arguments[1]
|
|
else:
|
|
parser.print_usage(sys.stderr)
|
|
return 7 # Argument list too long
|
|
if len(arguments) != 1:
|
|
parser.print_usage(sys.stderr)
|
|
return 7 # Argument list too long
|
|
catalogname = arguments[0]
|
|
|
|
available_catalogs = get_catalogs()
|
|
if catalogname not in available_catalogs:
|
|
print >> sys.stderr, 'Unknown catalog name: %s.' % catalogname
|
|
return 2 # no such file or directory
|
|
|
|
manifest = get_manifest(options.manifest)
|
|
if not manifest:
|
|
return 2 # no such file or directory
|
|
if not 'catalogs' in manifest:
|
|
manifest['catalogs'] = []
|
|
if catalogname in manifest['catalogs']:
|
|
print >> sys.stderr, (
|
|
'Catalog %s is already in manifest %s.'
|
|
% (catalogname, options.manifest))
|
|
return 1 # Operation not permitted
|
|
else:
|
|
# put it at the front of the catalog list as that is usually
|
|
# what is wanted...
|
|
manifest['catalogs'].insert(0, catalogname)
|
|
if save_manifest(manifest, options.manifest, overwrite_existing=True):
|
|
print ('Added %s to catalogs of manifest %s.'
|
|
% (catalogname, options.manifest))
|
|
return 0
|
|
else:
|
|
return 1 # Operation not permitted
|
|
|
|
|
|
def remove_catalog(args):
|
|
'''Removes a catalog from a manifest.'''
|
|
parser = MyOptionParser()
|
|
parser.set_usage('''remove-catalog CATALOGNAME --manifest MANIFESTNAME
|
|
Removes a catalog from a manifest''')
|
|
parser.add_option('--manifest',
|
|
metavar='MANIFESTNAME',
|
|
help='name of manifest on which to operate')
|
|
try:
|
|
options, arguments = parser.parse_args(args)
|
|
except MyOptParseError, errmsg:
|
|
print >> sys.stderr, str(errmsg)
|
|
return 22 # Invalid argument
|
|
if not options.manifest:
|
|
if len(arguments) == 2:
|
|
options.manifest = arguments[1]
|
|
del arguments[1]
|
|
else:
|
|
parser.print_usage(sys.stderr)
|
|
return 7 # Argument list too long
|
|
if len(arguments) != 1:
|
|
parser.print_usage(sys.stderr)
|
|
return 7 # Argument list too long
|
|
catalogname = arguments[0]
|
|
|
|
manifest = get_manifest(options.manifest)
|
|
if not manifest:
|
|
return 2 # no such file or directory
|
|
if catalogname not in manifest.get('catalogs', []):
|
|
print >> sys.stderr, (
|
|
'Catalog %s is not in manifest %s.'
|
|
% (catalogname, options.manifest))
|
|
return 1 # Operation not permitted
|
|
else:
|
|
manifest['catalogs'].remove(catalogname)
|
|
if save_manifest(manifest, options.manifest, overwrite_existing=True):
|
|
print ('Removed %s from catalogs of manifest %s.'
|
|
% (catalogname, options.manifest))
|
|
return 0
|
|
else:
|
|
return 1 # Operation not permitted
|
|
|
|
|
|
def add_included_manifest(args):
|
|
'''Adds an included manifest to a manifest.'''
|
|
parser = MyOptionParser()
|
|
parser.set_usage(
|
|
'''add-included-manifest MANIFEST_TO_INCLUDE --manifest TARGET_MANIFEST
|
|
Adds a manifest to the included_manifests of the TARGET_MANIFEST''')
|
|
parser.add_option('--manifest',
|
|
metavar='TARGET_MANIFEST',
|
|
help='name of manifest on which to operate')
|
|
try:
|
|
options, arguments = parser.parse_args(args)
|
|
except MyOptParseError, errmsg:
|
|
print >> sys.stderr, str(errmsg)
|
|
return 22 # Invalid argument
|
|
if not options.manifest:
|
|
if len(arguments) == 2:
|
|
options.manifest = arguments[1]
|
|
del arguments[1]
|
|
else:
|
|
parser.print_usage(sys.stderr)
|
|
return 7 # Argument list too long
|
|
if len(arguments) != 1:
|
|
parser.print_usage(sys.stderr)
|
|
return 7 # Argument list too long
|
|
manifest_to_include = arguments[0]
|
|
|
|
available_manifests = get_manifest_names()
|
|
if manifest_to_include not in available_manifests:
|
|
print >> sys.stderr, ('Unknown manifest name: %s.'
|
|
% manifest_to_include)
|
|
return 2 # no such file or directory
|
|
if manifest_to_include == options.manifest:
|
|
print >> sys.stderr, ('Can\'t include %s in itself!.'
|
|
% manifest_to_include)
|
|
return 1 # Operation not permitted
|
|
|
|
manifest = get_manifest(options.manifest)
|
|
if not manifest:
|
|
return 2 # no such file or directory
|
|
if not 'included_manifests' in manifest:
|
|
manifest['included_manifests'] = []
|
|
if manifest_to_include in manifest['included_manifests']:
|
|
print >> sys.stderr, (
|
|
'Manifest %s is already included in manifest %s.'
|
|
% (manifest_to_include, options.manifest))
|
|
return 1 # Operation not permitted
|
|
else:
|
|
manifest['included_manifests'].append(manifest_to_include)
|
|
if save_manifest(manifest, options.manifest, overwrite_existing=True):
|
|
print ('Added %s to included_manifests of manifest %s.'
|
|
% (manifest_to_include, options.manifest))
|
|
return 0
|
|
else:
|
|
return 1 # Operation not permitted
|
|
|
|
|
|
def remove_included_manifest(args):
|
|
'''Removes an included manifest from a manifest.'''
|
|
parser = MyOptionParser()
|
|
parser.set_usage(
|
|
'''remove-included_manifest INCLUDED_MANIFEST --manifest TARGET_MANIFEST
|
|
Removes a manifest from the included_manifests of TARGET_MANIFEST''')
|
|
parser.add_option('--manifest',
|
|
metavar='TARGET_MANIFEST',
|
|
help='name of manifest on which to operate')
|
|
try:
|
|
options, arguments = parser.parse_args(args)
|
|
except MyOptParseError, errmsg:
|
|
print >> sys.stderr, str(errmsg)
|
|
return 22 # Invalid argument
|
|
if not options.manifest:
|
|
if len(arguments) == 2:
|
|
options.manifest = arguments[1]
|
|
del arguments[1]
|
|
else:
|
|
parser.print_usage(sys.stderr)
|
|
return 7 # Argument list too long
|
|
if len(arguments) != 1:
|
|
parser.print_usage(sys.stderr)
|
|
return 7 # Argument list too long
|
|
included_manifest = arguments[0]
|
|
|
|
manifest = get_manifest(options.manifest)
|
|
if not manifest:
|
|
return 2 # no such file or directory
|
|
if included_manifest not in manifest.get('included_manifests', []):
|
|
print >> sys.stderr, (
|
|
'Manifest %s is not included in manifest %s.'
|
|
% (included_manifest, options.manifest))
|
|
return 1 # Operation not permitted
|
|
else:
|
|
manifest['included_manifests'].remove(included_manifest)
|
|
if save_manifest(manifest, options.manifest, overwrite_existing=True):
|
|
print ('Removed %s from included_manifests of manifest %s.'
|
|
% (included_manifest, options.manifest))
|
|
return 0
|
|
else:
|
|
return 1 # Operation not permitted
|
|
|
|
|
|
def refresh_cache(args):
|
|
'''Refreshes the repo data if changes were made while manifestutil was
|
|
running. Updates manifests, catalogs, and packages.'''
|
|
parser = MyOptionParser()
|
|
parser.set_usage('''refresh-cache
|
|
Refreshes the repo data''')
|
|
try:
|
|
_, arguments = parser.parse_args(args)
|
|
except MyOptParseError, errmsg:
|
|
print >> sys.stderr, str(errmsg)
|
|
return 22 # Invalid argument
|
|
|
|
if len(arguments) != 0:
|
|
parser.print_usage(sys.stderr)
|
|
return 22 # Invalid argument
|
|
CMD_ARG_DICT['manifests'] = get_manifest_names()
|
|
CMD_ARG_DICT['catalogs'] = get_catalogs()
|
|
CMD_ARG_DICT['pkgs'] = get_installer_item_names(get_catalogs())
|
|
|
|
|
|
def show_help():
|
|
'''Prints available subcommands'''
|
|
print "Available sub-commands:"
|
|
subcommands = CMD_ARG_DICT['cmds'].keys()
|
|
subcommands.sort()
|
|
for item in subcommands:
|
|
print '\t%s' % item
|
|
return 0
|
|
|
|
|
|
def tab_completer(text, state):
|
|
'''Called by the readline lib to calculate possible completions'''
|
|
array_to_match = None
|
|
if readline.get_begidx() == 0:
|
|
# since we are at the start of the line
|
|
# we are matching commands
|
|
array_to_match = 'cmds'
|
|
match_list = CMD_ARG_DICT.get('cmds', {}).keys()
|
|
else:
|
|
# we are matching args
|
|
cmd_line = readline.get_line_buffer()[0:readline.get_begidx()]
|
|
cmd = shlex.split(cmd_line)[-1]
|
|
array_to_match = CMD_ARG_DICT.get('cmds', {}).get(cmd)
|
|
if array_to_match:
|
|
match_list = CMD_ARG_DICT[array_to_match]
|
|
else:
|
|
array_to_match = CMD_ARG_DICT.get('options', {}).get(cmd)
|
|
if array_to_match:
|
|
match_list = CMD_ARG_DICT[array_to_match]
|
|
else:
|
|
array_to_match = 'options'
|
|
match_list = CMD_ARG_DICT.get('options', {}).keys()
|
|
|
|
matches = [item for item in match_list
|
|
if item.upper().startswith(text.upper())]
|
|
try:
|
|
return matches[state]
|
|
except IndexError:
|
|
return None
|
|
|
|
|
|
def set_up_tab_completer():
|
|
'''Starts our tab-completer when running interactively'''
|
|
readline.set_completer(tab_completer)
|
|
if LIBEDIT:
|
|
# readline module was compiled against libedit
|
|
readline.parse_and_bind("bind ^I rl_complete")
|
|
else:
|
|
readline.parse_and_bind("tab: complete")
|
|
|
|
|
|
def handle_subcommand(args):
|
|
'''Does all our subcommands'''
|
|
# check if any arguments are passed.
|
|
# if not, list subcommands.
|
|
if len(args) < 1:
|
|
show_help()
|
|
return 2
|
|
|
|
# strip leading hyphens and
|
|
# replace embedded hyphens with underscores
|
|
# so '--add-pkg' becomes 'add_pkg'
|
|
# and 'new-manifest' becomes 'new_manifest'
|
|
subcommand = args[0].lstrip('-').replace('-', '_')
|
|
|
|
# special case the exit command
|
|
if subcommand == 'exit':
|
|
cleanup_and_exit(0)
|
|
|
|
if (subcommand not in ['version', 'configure', 'help']
|
|
and '-h' not in args and '--help' not in args):
|
|
if not repo_available():
|
|
exit(-1)
|
|
|
|
try:
|
|
# find function to call by looking in the global name table
|
|
# for a function with a name matching the subcommand
|
|
subcommand_function = globals()[subcommand]
|
|
except MyOptParseExit:
|
|
return 0
|
|
except (TypeError, KeyError):
|
|
print >> sys.stderr, 'Unknown subcommand: %s' % args[0]
|
|
show_help()
|
|
return 2
|
|
else:
|
|
return subcommand_function(args[1:])
|
|
|
|
|
|
WE_MOUNTED_THE_REPO = False
|
|
INTERACTIVE_MODE = False
|
|
CMD_ARG_DICT = {}
|
|
|
|
def main():
|
|
'''Our main routine'''
|
|
global INTERACTIVE_MODE
|
|
|
|
cmds = {'add-pkg': 'pkgs',
|
|
'add-catalog': 'catalogs',
|
|
'add-included-manifest': 'manifests',
|
|
'remove-pkg': 'pkgs',
|
|
'remove-catalog': 'catalogs',
|
|
'remove-included-manifest': 'manifests',
|
|
'list-manifests': 'manifests',
|
|
'list-catalogs': 'default',
|
|
'list-catalog-items': 'catalogs',
|
|
'display-manifest': 'manifests',
|
|
'expand-included-manifests': 'manifests',
|
|
'find': 'default',
|
|
'new-manifest': 'default',
|
|
'copy-manifest': 'manifests',
|
|
'rename-manifest': 'manifests',
|
|
'refresh-cache': 'default',
|
|
'exit': 'default',
|
|
'help': 'default',
|
|
'configure': 'default',
|
|
'version': 'default'
|
|
}
|
|
CMD_ARG_DICT['cmds'] = cmds
|
|
|
|
if len(sys.argv) > 1:
|
|
# some commands or options were passed at the command line
|
|
cmd = sys.argv[1].lstrip('-')
|
|
retcode = handle_subcommand(sys.argv[1:])
|
|
cleanup_and_exit(retcode)
|
|
else:
|
|
# if we get here, no options or commands,
|
|
# so let's enter interactive mode
|
|
INTERACTIVE_MODE = True
|
|
# must have an available repo for interfactive mode
|
|
if not repo_available():
|
|
exit(-1)
|
|
# build the rest of our dict to enable tab completion
|
|
CMD_ARG_DICT['options'] = {'--manifest': 'manifests',
|
|
'--section': 'sections'}
|
|
|
|
CMD_ARG_DICT['default'] = []
|
|
CMD_ARG_DICT['sections'] = get_manifest_pkg_sections()
|
|
CMD_ARG_DICT['manifests'] = get_manifest_names()
|
|
CMD_ARG_DICT['catalogs'] = get_catalogs()
|
|
CMD_ARG_DICT['pkgs'] = get_installer_item_names(get_catalogs())
|
|
|
|
set_up_tab_completer()
|
|
print 'Entering interactive mode... (type "help" for commands)'
|
|
while 1:
|
|
try:
|
|
cmd = raw_input('> ')
|
|
except (KeyboardInterrupt, EOFError):
|
|
# React to Control-C and Control-D
|
|
print # so we finish off the raw_input line
|
|
cleanup_and_exit(0)
|
|
args = shlex.split(cmd)
|
|
handle_subcommand(args)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|