diff --git a/src/mio.py b/src/mio.py index 465cf50c465c807fd073780065ed9e103cefdfd6..d284830d23b7346f7a3de2919596c57cd7c37081 100755 --- a/src/mio.py +++ b/src/mio.py @@ -1,12 +1,13 @@ #!/usr/bin/python +import mio.filecache import mio.installer import mio.log import mio.transform import mio.parser import mio.repository import mio.node -import optparse +import argparse import os import os.path import re @@ -19,13 +20,9 @@ if not hasattr(__builtins__, "True"): __builtins__["True"] = 1 __builtins__["False"] = 0 -class VerboseOptionParser(optparse.OptionParser): - def error(self, msg): - self.print_help() - optparse.OptionParser.error(self, msg) - -def parse(url, repository): - tree = mio.parser.parse(url) +def parse(path, filename, repository): + f = mio.filecache.loadfile(path, filename) + tree = mio.parser.parse(f.filename, url=f.key) if tree._tag == "comps": # Transform comps to canonical form @@ -42,21 +39,23 @@ def parse(url, repository): def find_and_parse(target, builder, url=None): path = list(options.path) if url: + print "CHECK THIS", path, target, url path.insert(0, os.path.dirname(url)) - for dir in path: + for p in path: if target.startswith("/"): m = re.match("^/([^/]*)", target) - file = "%s/%s.mio" % (dir, m.group(1)) + filename = "%s.mio" % (m.group(1)) pass elif target.startswith("@"): - file = "%s/base/comps.xml" % dir + filename = "base/comps.xml" try: # Try to locate group file via repomd.xml - tree = mio.parser.parse("%s/repodata/repomd.xml" % dir) + f = mio.filecache.loadfile(p, '/repodata/repomd.xml') + tree = mio.parser.parse(f.filename) for l in tree._data_._location_: if l.type[1] == 'group': - file = "%s/%s" % (dir, l.href[0]) + filename = l.href[0] break pass pass @@ -64,10 +63,10 @@ def find_and_parse(target, builder, url=None): continue pass else: - file = "%s/hostinfo.xml" % dir + filename = "hostinfo.xml" pass try: - parse(file, builder) + parse(p, filename, builder) return except IOError, e: pass @@ -90,67 +89,69 @@ def find_and_parse(target, builder, url=None): print "Failed to locate: %s" % target if __name__ == '__main__': - optParser = VerboseOptionParser(usage="%prog [options] targets*") - optParser.add_option("--backup", - action="store", - metavar="DIR", - help="saved a backup of changed files in DIR") - optParser.add_option("--backup-suffix", - action="store", - metavar="SUFFIX", - help="append SUFFIX to backup files") - optParser.add_option("--dump", - action="store_true", default=False, - help="when listing, dump XML tree") - optParser.add_option("--exclude-rpm", - action="append", default=[], - metavar="RPM", - help="exclude RPM") - optParser.add_option("--install", - action="append", default=[], - metavar="TARGET", - help="install TARGET") - optParser.add_option("-l","--list", - action="append", default=[], - metavar="XML", - help="list available targets in XML") - optParser.add_option("--list-rpms", - action="append", default=[], - metavar="TARGET", - help="list rpms needed for TARGET") - optParser.add_option("--noyum", - action="store_true", - help="don't run yum (rpms will not be installed)") - optParser.add_option("-p", "--path", - action="append", default=[], - metavar="DIR", - help="search path for files") - optParser.add_option("--prefix", - action="store", - metavar="DIR", - help="prefix for installed files") - optParser.add_option("-q","--quiet", - action="count", default=0, - dest="quiet", - help="decrease verbosity") - optParser.add_option("--reachable-files", - action="append", default=[], - metavar="XML", - help="list files reachable from XML") - optParser.add_option("--reachable-rpms", - action="append", default=[], - metavar="XML", - help="list RPMs reachable from XML") - optParser.add_option("--test", - action="append", default=[], - metavar="TARGET", - help="test TARGET") - optParser.add_option("-v","--verbose", - action="count", - dest="verbose", default=0, - help="increase verbosity") + optParser = argparse.ArgumentParser(usage="%(prog)s [options]") + optParser.add_argument("--backup", + action="store", + metavar="DIR", + help="saved a backup of changed files in DIR") + optParser.add_argument("--backup-suffix", + action="store", + metavar="SUFFIX", + help="append SUFFIX to backup files") + optParser.add_argument("--dump", + action="store_true", default=False, + help="when listing, dump XML tree") + optParser.add_argument("--exclude-rpm", + action="append", default=[], + metavar="RPM", + help="exclude RPM") + optParser.add_argument("--install", + action="append", default=[], + metavar="TARGET", + help="install TARGET") + optParser.add_argument("-l","--list", + action="append", default=[], + metavar="XML", + help="list available targets in XML") + optParser.add_argument("--list-rpms", + action="append", default=[], + metavar="TARGET", + help="list rpms needed for TARGET") + optParser.add_argument("--noyum", + action="store_true", + help="don't run yum (rpms will not be installed)") + optParser.add_argument("-p", "--path", + nargs='+', + action="append", default=[], + metavar="DIR", + help="search path for files") + optParser.add_argument("--prefix", + action="store", + metavar="DIR", + help="prefix for installed files") + optParser.add_argument("-q","--quiet", + action="count", default=0, + dest="quiet", + help="decrease verbosity") + optParser.add_argument("--reachable-files", + action="append", default=[], + metavar="XML", + help="list files reachable from XML") + optParser.add_argument("--reachable-rpms", + action="append", default=[], + metavar="XML", + help="list RPMs reachable from XML") + optParser.add_argument("--test", + action="append", default=[], + metavar="TARGET", + help="test TARGET") + optParser.add_argument("-v","--verbose", + action="count", + dest="verbose", default=0, + help="increase verbosity") - (options, args) = optParser.parse_args(sys.argv[1:]) + options = optParser.parse_args(sys.argv[1:]) + options.path = map(tuple, options.path) mio.log.set_verbosity(NORMAL + options.verbose - options.quiet) diff --git a/src/mio/filecache.py b/src/mio/filecache.py index 5fec2ed70eae6a696a6e49bf63b81560eecb05de..923be5a7e2d135c8ed802805ee3079e9900ec22a 100755 --- a/src/mio/filecache.py +++ b/src/mio/filecache.py @@ -6,44 +6,127 @@ import re import sys import tempfile import time -import urllib2 +import urlgrabber +import urlgrabber.mirror import urlparse +import urllib import mio.exception +import socket +import pycurl + +temp = [] +cache = {} +mirror = {} -def join(elements): - """join and normalize elements to a proper (url) path""" - join = '' - for e in elements: - if e != None: - if len(join) > 0: - join += '/' - join += e - (scheme, host, path, param, query, frag) = urlparse.urlparse(join) - if scheme: - path = re.sub('/+', '/', path) - result = urlparse.urlunparse((scheme, host, path, param, query, frag)) - else: - result = re.sub('/+', '/', join) - return result +def subpath(path, *tail): + result = [] + for p in path: + u = urlparse.urlsplit(p) + result.append(urlparse.urlunsplit([ + u.scheme, + u.hostname, + '/'.join([u.path] + list(tail)), + u.query, + u.fragment])) + pass + return tuple(result) + + +def Key(path, name): + class Key(tuple, object): + @property + def path(self): + return self[0] + @property + def name(self): + return self[1] + def __repr__(self): + return 'Key(path=%s, name=%s)' % (self[0], self[1]) + return Key([path, name]) + +class CacheEntry: + + def __init__(self, filename, key): + self.filename = filename + self.key = key + + def __repr__(self): + return 'CacheEntry(filename=%s, key=%s)' % (self.filename, self.key) -def geturl(p): - (scheme, host, path, param, query, frag) = urlparse.urlparse(p) - if not scheme: - scheme = 'file' - host ='' - path = os.path.abspath(p) - param = '' - query = '' - frag = '' - return urlparse.urlunparse((scheme, host, path, param, query, frag)) - -def getfile(p): - (scheme, host, path, param, query, frag) = urlparse.urlparse(geturl(p)) - if scheme == 'file': - return path - else: - return None - +class Unalias: + + def __init__(self): + self.by_name = {} + + def unalias_host(self, host, port): + if not host in self.by_name: + byaddr = set() + for _,_,_,_,sa in socket.getaddrinfo(host, port, + socket.AF_UNSPEC, + socket.SOCK_STREAM): + byaddr.add(sa[0]) + pass + self.by_name[host] = set() + for name,_,_ in map(socket.gethostbyaddr, byaddr): + self.by_name[host].add(str(name)) + pass + pass + return sorted(self.by_name[host]) + + def __call__(self, url): + result = [] + u = urlparse.urlsplit(str(url)) + if u.hostname: + for hostname in self.unalias_host(u.hostname, u.port or 80): + result.append(urlparse.urlunsplit(( + u.scheme, + u.netloc.replace(u.hostname, hostname), + u.path, + u.query, + u.fragment))) + pass + pass + elif u.scheme in [ '', 'file' ]: + result.append(urlparse.urlunsplit(('file', '', u.path, '', ''))) + pass + else: + print u + pass + return result + +unalias = Unalias() + +class Mirror: + + def __init__(self, path): + self.path = path + self.urls = [] + for m in map(unalias, path): + self.urls.extend(m) + pass + grabber = urlgrabber.grabber.URLGrabber() + self.mirror = urlgrabber.mirror.MirrorGroup(grabber, self.urls) + pass + + def urlopen(self, path): + return self.mirror.urlopen(str(path)) + +def expand_mirror(path): + class Info: + def __init__(self, id, path, urls): + self.id = 'repo_%d' % id + self.path = path + self.urls = urls + pass + def __repr__(self): + return 'Repo(id=%s, path=%s, urls=%s)' % (self.id, + self.path, + self.urls) + pass + result = [] + for p,i in zip(path, range(len(path))): + result.append(Info(i, p, mirror[p].urls)) + return result def cleanup(): for f in temp: @@ -65,56 +148,36 @@ def createdir(): return d -def loadfile(path): - name = join(path) - if not cache.has_key(name): - file = getfile(name) - if file: - cache[name] = file +def loadfile(path, *name): + key = Key(path, os.path.normpath('/'.join(name))) + if not key in cache: + if not path in mirror: + mirror[path] = Mirror(key.path) + pass + u = mirror[path].urlopen(key.name) + if u.scheme == 'file': + p = urllib.unquote(urlparse.urlsplit(u.geturl()).path) + cache[key] = CacheEntry(p, key) pass else: - try: - u = urllib2.urlopen(name) - pass - except urllib2.HTTPError, e: - raise mio.exception.HTTPError(name, e) + mtime = u.curl_obj.getinfo(pycurl.INFO_FILETIME) f = createfile() f.write(u.read()) u.close() f.close() - try: - modified = u.info()["Last-Modified"] - mtime = calendar.timegm( - time.strptime(modified, "%a, %d %b %Y %H:%M:%S %Z")) - os.utime(f.name, (mtime, mtime)) - except: - pass - cache[name] = f.name - return cache[name] + os.utime(f.name, (mtime, mtime)) + cache[key] = CacheEntry(f.name, key) + pass + pass + return cache[key] -def loadscript(path): - result = loadfile(path) - if result in temp: - os.chmod(result, 0700) +def loadscript(path, *name): + result = loadfile(path, *name) + if result.filename in temp: + os.chmod(result.filename, 0700) return result -def locatefile(path): - name = join(path) - if not cache.has_key(name): - file = getfile(name) - if file: - cache[name] = file - else: - raise mio.exception.NonLocalPath(join(path)) - return cache[name] - -def locatedir(path): - dir = getfile(join(path)) - if not dir: - raise mio.exception.NonLocalPath(join(path)) - return dir - +def localpath(prefix, *path): + return os.path.normpath('/'.join(filter(None, [prefix] + list(path)))) -temp = [] -cache = {} atexit.register(cleanup) diff --git a/src/mio/installer.py b/src/mio/installer.py index a945b0f01e0141fd7d7d03b2e83662214d4917c7..ea472d4125bb2d1c91d3d05f5aba5261ed6268dc 100755 --- a/src/mio/installer.py +++ b/src/mio/installer.py @@ -131,14 +131,14 @@ class Installer: def add_rpm(self, rpm): - try: - dir = mio.filecache.locatedir([ os.path.dirname(rpm.decl._url), - rpm.decl.rpms[0:] ]) - except mio.exception.NonLocalPath, e: - dir = e.path - - if not dir in self.rpmdir: - self.rpmdir.append(dir) + if rpm.decl.rpms[0:]: + path = mio.filecache.subpath(rpm.decl._url.path, + rpm.decl.rpms[0:]) + if not path in self.rpmdir: + self.rpmdir.append(path) + pass + pass + if rpm in self.rpm: # All rpm's with the same name are considered identical self.rpm[rpm].append(rpm) diff --git a/src/mio/node.py b/src/mio/node.py index b6704b8293c5021cbc896f31fa97e5d7392e122f..56dddd468ea427d37a561148a4ed7f956ad5dd81 100755 --- a/src/mio/node.py +++ b/src/mio/node.py @@ -186,7 +186,7 @@ class dir_node(target_node): def do_trig(self, prefix, backup, do_pre): result = False - self.target = mio.filecache.locatedir([prefix, self.decl.name[0]]) + self.target = mio.filecache.localpath(prefix, self.decl.name[0]) self.uid = mio.util.uid(self.owner) self.gid = mio.util.uid(self.group) @@ -269,8 +269,7 @@ class file_node(target_node): def do_trig(self, prefix, backup, do_pre): result = False - dir = os.path.dirname(self.decl._url) - self.target = mio.filecache.locatefile([prefix, self.decl.name[0]]) + self.target = mio.filecache.localpath(prefix, self.decl.name[0]) self.uid = mio.util.uid(self.owner) self.gid = mio.util.uid(self.group) @@ -285,9 +284,9 @@ class file_node(target_node): else: if self.decl.source[0] != "": file = self.decl.source[0] or self.decl.name[0] - self.source = mio.filecache.loadfile([dir, - self.decl.files[0:], - file]) + self.source = mio.filecache.loadfile(self.decl._url.path, + self.decl.files[0:], + file).filename source_sum = self.digest(self.source) try: target_sum = self.digest(self.target) @@ -421,7 +420,7 @@ class symlink_node(target_node): def do_trig(self, prefix, backup, do_pre): result = False - self.target = mio.filecache.locatedir([prefix, self.decl.name[0]]) + self.target = mio.filecache.localpath(prefix, self.decl.name[0]) self.uid = mio.util.uid(self.owner) self.gid = mio.util.uid(self.group) @@ -511,12 +510,11 @@ class script_node: self.decl = decl def run(self, when): - dir = os.path.dirname(self.decl._url) scripts = self.decl.scripts[0:] name = self.decl.script[0] - script = mio.filecache.loadscript([dir, scripts, name]) + script = mio.filecache.loadscript(self.decl._url.path, scripts, name) log(NORMAL, declaration(self), "running '%s'" % name) - mio.daemon_cage.system("%s %s %s" % (script, name, when)) + mio.daemon_cage.system("%s %s %s" % (script.filename, name, when)) def test(self, when): dir = os.path.dirname(self.decl._url) diff --git a/src/mio/parser.py b/src/mio/parser.py index 2c4e4a760a16e3f5525b2e27965ffe4bfa645946..2fbd4fdd48c68ec21ab63f41e473521310e8a221 100755 --- a/src/mio/parser.py +++ b/src/mio/parser.py @@ -521,7 +521,7 @@ class Parser: parent = self.current[-1] parent._add(Comment(data, self.parser.CurrentLineNumber)) -def parse(source, include_comments=False): +def parse(source, include_comments=False, url=None): """Parse the given 'source' returning an easily traversable tree""" (scheme, host, path, param, query, frag) = urlparse.urlparse(source) if not scheme: @@ -540,7 +540,7 @@ def parse(source, include_comments=False): pass pass pass - return Parser(data, url=source, mtime=mtime, + return Parser(data, url=url or source, mtime=mtime, include_comments=include_comments).tree def parse_string(source, include_comments=False): diff --git a/src/mio/yum.py b/src/mio/yum.py index 8ad2ed76b7384d4f04d46034768cad95c9ada707..6ce341ef048b8171473e8e15fc4aca056b4fafba 100755 --- a/src/mio/yum.py +++ b/src/mio/yum.py @@ -19,31 +19,29 @@ def newest_rpm(name): def yumdir(path): result = [] - for d in path: + for p in path: repo_date = 0 try: - file = mio.filecache.loadfile([ d, 'repodata/repomd.xml' ]) - if os.path.exists(file): - stat = os.stat(file) - repo_date = max(repo_date, stat.st_mtime) - if not d in result: - result.append(d) + f = mio.filecache.loadfile(p, 'repodata/repomd.xml') + stat = os.stat(f.filename) + repo_date = max(repo_date, stat.st_mtime) + if not p in result: + result.append(p) except IOError: pass header_date = 0 try: - file = mio.filecache.loadfile([ d, 'headers/header.info' ]) - if os.path.exists(file): - stat = os.stat(file) - header_date = max(header_date, stat.st_mtime) - if not d in result: - result.append(d) + f = mio.filecache.loadfile(p, 'headers/header.info') + stat = os.stat(f.filename) + header_date = max(header_date, stat.st_mtime) + if not p in result: + result.append(p) except IOError: pass if header_date or repo_date: - rpm_date = newest_rpm([ d ]) + rpm_date = newest_rpm(p) if repo_date and repo_date < rpm_date: raise Exception("repodata/repomd.xml is too old %s" % d) if header_date and header_date < rpm_date: @@ -64,10 +62,10 @@ def conf(path): "obsoletes=1" ] dir = yumdir(path) - for d in dir: - result.append("[%s_]" % d.replace("/", "_")) - result.append("name=RPM's from %s" % d) - result.append("baseurl=%s" % mio.filecache.geturl(d)) + for m in mio.filecache.expand_mirror(dir): + result.append("[%s]" % m.id) + result.append("name=RPM's from %s" % ','.join(m.path)) + result.append("baseurl=%s" % '\n '.join(m.urls)) return "\n".join(result)