Created
October 21, 2016 03:45
-
-
Save poundbangbash/30fae3c27f122c936b3d8400ae6455b1 to your computer and use it in GitHub Desktop.
manifestutil with refresh-cache subcommand attempt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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' | |
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 type(value) == type(None): | |
print indentspace*indent, '%s: !NONE!' % label | |
elif type(value) == list or type(value).__name__ == 'NSCFArray': | |
if label: | |
print indentspace*indent, '%s:' % label | |
for item in value: | |
printplistitem('', item, indent+1) | |
elif type(value) == dict or type(value).__name__ == 'NSCFDictionary': | |
if label: | |
print indentspace*indent, '%s:' % label | |
for subkey in value.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): | |
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 | |
os.mkdir(repo_path) | |
print 'Attempting to mount fileshare %s:' % repo_url | |
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 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(): | |
'''Refreshes the repo data if changes were made while manifestutil was running. Updates manifests, sections, catalogs, and packages''' | |
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()) | |
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] | |
return subcommand_function(args[1:]) | |
except MyOptParseExit: | |
return 0 | |
except (TypeError, KeyError): | |
print >> sys.stderr, 'Unknown subcommand: %s' % subcommand | |
show_help() | |
return 2 | |
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', | |
'refresh-cache': 'default', | |
'display-manifest': 'manifests', | |
'find': 'default', | |
'new-manifest': 'default', | |
'copy-manifest': 'manifests', | |
'rename-manifest': 'manifests', | |
'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() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment