diff --git a/bin/isimage b/bin/isimage index be2ceae676c4a4075e1495915de9ac0f18ecd9da..ef829a5df78d8c8ffab2496a319a7ac24703015a 100755 --- a/bin/isimage +++ b/bin/isimage @@ -9,11 +9,10 @@ InstallSystems Image Manipulation Tool import os import time import datetime +import argparse import installsystems from installsystems.printer import * from installsystems.image import SourceImage -# The following import can be removed when min version will be python 2.7 -import installsystems.argparse as argparse class DebugAction(argparse.Action): '''Set installsystems in debug mode. Argparse callback''' diff --git a/bin/isinstall b/bin/isinstall index dde932b98e0e9a4eb8447c15acd5e030c071b541..b7008f35f56585e35d4376280d410a059cbac0bd 100755 --- a/bin/isinstall +++ b/bin/isinstall @@ -9,14 +9,14 @@ InstallSystems Installation Tool import os import time import datetime +import argparse import installsystems import installsystems.tools as istools from installsystems.printer import * -from installsystems.repository import RepositoryCache +from installsystems.repository import RepositoryManager from installsystems.image import PackageImage from installsystems.config import ConfigFile -# The following import can be removed when min version will be python 2.7 -import installsystems.argparse as argparse + class DebugAction(argparse.Action): '''Set installsystems in debug mode. Argparse callback''' @@ -38,7 +38,7 @@ p_main.add_argument("-c", "--config", dest="config", type=str, default=None, help="config file path") p_main.add_argument("-v", "--image-version", dest="image_version", type=int, default=None, help="image version") -p_main.add_argument("-t", "--timeout", dest="timeout", type=int, default=3, +p_main.add_argument("-t", "--timeout", dest="timeout", type=int, default=None, help="download timeout") p_main.add_argument("image_name", type=str, help="image to install (path or name)") @@ -53,13 +53,11 @@ try: pkg = PackageImage(istools.abspath(args.image_name)) elif image_name_type == "name": # init repo cache object - repocache = RepositoryCache(config.cache, timeout=args.timeout, verbose=args.verbose) + repoman = RepositoryManager(timeout=args.timeout, verbose=args.verbose) # register repositories - repocache.register(config.repos) - # update remote info - repocache.update() + repoman.register(config.repos) # get image package - pkg = repocache.get(args.image_name, args.image_version) + pkg = repoman.get(args.image_name, args.image_version) else: p_main.print_usage() exit(1) diff --git a/bin/isrepo b/bin/isrepo index 6abbac5c489689d7b0d2f866af6713dd8dc848aa..a4ef2b9d25b75ed9e1d5b5501db3c5434766c563 100755 --- a/bin/isrepo +++ b/bin/isrepo @@ -7,13 +7,12 @@ InstallSystems Repository Manipulation Tool ''' import os +import argparse import installsystems from installsystems.printer import * -from installsystems.repository import Repository +from installsystems.repository import Repository, RepositoryConfig from installsystems.image import PackageImage from installsystems.config import ConfigFile -# The following import can be removed when min version will be python 2.7 -import installsystems.argparse as argparse class DebugAction(argparse.Action): '''Set installsystems in debug mode. Argparse callback''' @@ -27,24 +26,24 @@ def init(args): '''Create an empty fresh repo tree''' # call init from library try: - Repository.create(args.repo, args.verbose) + Repository.create(args.repository, args.verbose) except Exception as e: raise Exception("init failed: %s" % e) def add(args): '''Add a package to repository''' try: - repo = Repository(args.repo, args.verbose) + repo = Repository(args.repository, args.verbose) pkg = PackageImage(args.path, args.verbose) - pkg.check_md5() repo.add(pkg) except Exception as e: + raise raise Exception("add failed: %s" % e) def delete(args): '''Remove a package from repository''' try: - repo = Repository(args.repo, args.verbose) + repo = Repository(args.repository, args.verbose) repo.delete(args.image_name, args.image_version) except Exception as e: raise Exception("del failed: %s" % e) @@ -60,24 +59,20 @@ p_main.add_argument('-q', "--quiet", action="store_false", dest="verbose", defau p_main.add_argument("-c", "--config", dest="config", type=str, default=None, help="config file path") p_main.add_argument("-r", "--repo-name", dest="repo_name", type=str, default=None, - help="repository name") + help="repository name in config file") subparsers = p_main.add_subparsers() # Init command parser p_init = subparsers.add_parser("init", help=init.__doc__.lower()) -p_init.add_argument("repo_image", nargs="?", default=argparse.SUPPRESS, - help="Path of the new image directory") -p_init.add_argument("repo_data", nargs="?", default=argparse.SUPPRESS, - help="Path of the new data directory") p_init.set_defaults(func=init) # Add command parser p_add = subparsers.add_parser("add", help=add.__doc__.lower()) p_add.add_argument("path", type=str) p_add.set_defaults(func=add) # Del command parser -p_del = subparsers.add_parser("del", help=delete.__doc__.lower()) -p_del.add_argument("image_name", type=str) -p_del.add_argument("image_version", type=str) -p_del.set_defaults(func=delete) +#p_del = subparsers.add_parser("del", help=delete.__doc__.lower()) +#p_del.add_argument("image_name", type=str) +#p_del.add_argument("image_version", type=str) +#p_del.set_defaults(func=delete) try: # Parse and run args = p_main.parse_args() @@ -89,13 +84,12 @@ try: else: repos = config.repos if len(repos) == 1: - args.repo = repos[0] + args.repository = repos[0] elif len(repos) > 1: raise Exception("Please select a repository with -r") - else: + if len(repos) == 0 or not isinstance(args.repository, RepositoryConfig): raise Exception("No image repository found") - debug("Image repo: %s" % args.repo.image) - debug("Data repo: %s" % args.repo.data) + debug("Repository: %s" % args.repository.path) args.func(args) except Exception as e: p_main.print_usage() diff --git a/installsystems/config.py b/installsystems/config.py index 5110f95c2e67d15fd2873696494239de6a6a3735..d32a01bcd922a853aaa64e7cd3e7c11646260d50 100644 --- a/installsystems/config.py +++ b/installsystems/config.py @@ -46,7 +46,7 @@ class ConfigFile(object): # each section is a repository for rep in cp.sections(): # check if its a repo section - if "image" not in cp.options(rep): + if "path" not in cp.options(rep): continue # get all options in repo self._repos.append(RepositoryConfig(rep, **dict(cp.items(rep)))) diff --git a/installsystems/database.py b/installsystems/database.py index b5dd8463d9139105d8f14e4290139d7e49eb8375..ec5c158746c9810e2fcd826b5b052925553a04e4 100644 --- a/installsystems/database.py +++ b/installsystems/database.py @@ -10,6 +10,7 @@ import json import os import shutil import tarfile +import cStringIO import installsystems.tools as istools from installsystems.tarball import Tarball from installsystems.printer import * @@ -22,9 +23,12 @@ class Database(object): @classmethod def create(cls, path, verbose=True): arrow("Creating repository database", 1, verbose) + # check locality + if istools.pathtype(path) != "file": + raise NotImplementedError("Database creation must be local") dbpath = istools.abspath(path) if os.path.exists(dbpath): - raise Exception("db already exists") + raise Exception("Database already exists. Remove it before") try: tarball = Tarball.open(dbpath, mode="w:gz", dereference=True) tarball.add_str("format", Database.db_format, tarfile.REGTYPE, 0444) @@ -36,39 +40,67 @@ class Database(object): def __init__(self, path, verbose=True): self.path = istools.abspath(path) self.verbose = verbose + # load db in memory + self.file = cStringIO.StringIO() + shutil.copyfileobj(istools.uopen(self.path), self.file) + + def get(self, name, version): + '''Return a description dict from a package name''' + # parse tarball + try: + self.file.seek(0) + tarball = Tarball.open(fileobj=self.file, mode="r:gz") + rdata = tarball.get_str("%s-%s" % (name, version)) + tarball.close() + except KeyError: + raise Exception("No package %s version %s in metadata" % (name, version)) + except Exception as e: + raise Exception("Unable to read db %s version %s: e" % (name, version, e)) + # convert loaded data into dict (json parser) + try: + return json.loads(rdata) + except Exception as e: + raise Exception("Invalid metadata in package %s version %s: e" % (name, version, e)) def add(self, package): '''Add a packaged image to a db''' arrow("Adding metadata to db", 1, self.verbose) + # check locality + if istools.pathtype(self.path) != "file": + raise NotImplementedError("Database addition must be local") # naming - name = "%s.json" % package.name newdb_path = "%s.new" % self.path # compute md5 - arrow("Compute MD5 of %s" % os.path.relpath(package.path), 2, self.verbose) - md5 = package.md5 arrow("Formating metadata", 2, self.verbose) desc = package.description - desc["md5"] = md5 + desc["md5"] = package.md5 jdesc = json.dumps(desc) try: - arrow("Adding to tarball", 2, self.verbose) - db = Tarball.open(self.path, mode='r:gz') - newdb = Tarball.open(newdb_path, mode='w:gz') + arrow("Adding metadata", 2, self.verbose) + self.file.seek(0) + newfile = cStringIO.StringIO() + db = Tarball.open(fileobj=self.file, mode='r:gz') + newdb = Tarball.open(fileobj=newfile, mode='w:gz') for ti in db.getmembers(): - if ti.name != name: + if ti.name != package.id: newdb.addfile(ti, db.extractfile(ti)) - newdb.add_str(name, jdesc, tarfile.REGTYPE, 0444) + newdb.add_str(package.id, jdesc, tarfile.REGTYPE, 0644) db.close() newdb.close() - # preserve permission and stats when moving - shutil.copystat(self.path, newdb_path) - os.rename(newdb_path, self.path) + # writing to disk + arrow("Writing to disk", 2, self.verbose) + self.file.close() + self.file = newfile + self.write() except Exception as e: raise Exception("Adding metadata fail: %s" % e) def delete(self, name, version): '''Deltete a packaged image''' arrow("Removing metadata from db", 1, self.verbose) + # check locality + if istools.pathtype(self.path) != "file": + raise NotImplementedError("Database deletion must be local") newdb_path = "%s.new" % self.path fname = "%s-%s.json" % (name, version) try: @@ -88,19 +120,21 @@ class Database(object): def databalls(self, name, version): '''List data tarballs filenames''' try: - db = Tarball.open(self.path, mode='r:gz') + self.file.seek(0) + db = Tarball.open(fileobj=self.file, mode='r:gz') jdesc = json.loads(db.get_str("%s-%s.json" % (name, version))) db.close() return jdesc["data"] except Exception as e: - raise Exception("Listing data tarballs fail: %s" % e) + raise Exception("List data tarballs fail: %s" % e) def find(self, name, version=None): '''Find last version of an image''' try: - tarb = Tarball.open(self.path, mode='r:gz') + self.file.seek(0) + tarb = Tarball.open(fileobj=self.file, mode='r:gz') candidates = [ int((os.path.splitext(tpname)[0]).rsplit("-", 1)[1]) - for tpname in tarb.getnames("%s-\d+.json" % name) ] + for tpname in tarb.getnames("%s-\d+" % name) ] tarb.close() except Exception as e: raise Exception("Find in db %s fail: %s" % (self.path, e)) @@ -113,4 +147,15 @@ class Database(object): # check if version exists if int(version) not in candidates: return None - return (name, version) + return self.get(name, version) + + def write(self): + '''Write current dabatase into its file''' + if istools.pathtype(self.path) != "file": + raise NotImplementedError("Database writing must be local") + try: + dest = open(self.path, "w") + self.file.seek(0) + shutil.copyfileobj(self.file, dest) + except Exception as e: + raise Exception("Unable to write database: %s" % e) diff --git a/installsystems/image.py b/installsystems/image.py index fac0400adb1a4d8ed3a85d65caf1022d7a5fbc4a..23063875f996a06da959e889662771ecca5da26d 100644 --- a/installsystems/image.py +++ b/installsystems/image.py @@ -10,11 +10,12 @@ import os import stat import time import json -import StringIO import ConfigParser import subprocess import tarfile import re +import cStringIO +import shutil import installsystems.template as istemplate import installsystems.tools as istools from installsystems.printer import * @@ -43,6 +44,10 @@ class SourceImage(Image): @classmethod def create(cls, path, verbose=True): '''Create an empty source image''' + # check local repository + if istools.pathtype(path) != "file": + raise NotImplementedError("SourceImage must be local") + # main path parser_path = os.path.join(path, "parser") setup_path = os.path.join(path, "setup") data_path = os.path.join(path, "data") @@ -82,16 +87,19 @@ class SourceImage(Image): return cls(path, verbose) def __init__(self, path, verbose=True): + # check local repository + if istools.pathtype(path) != "file": + raise NotImplementedError("SourceImage must be local") Image.__init__(self) self.base_path = path self.parser_path = os.path.join(path, "parser") self.setup_path = os.path.join(path, "setup") self.data_path = os.path.join(path, "data") self.verbose = verbose - self.valid_source_image() + self.validate_source_image() self.description = self.parse_description() - def valid_source_image(self): + def validate_source_image(self): '''Check if we are a valid SourceImage''' for d in (self.base_path, self.parser_path, self.setup_path, self.data_path): if not os.path.exists(d): @@ -112,7 +120,7 @@ class SourceImage(Image): if os.path.exists(tarpath) and overwrite == False: raise Exception("Tarball already exists. Remove it before") # Create data tarballs - data_d = self.create_data_tarballs() + self.create_data_tarballs() # generate description.json jdesc = self.generate_json_description() # creating scripts tarball @@ -139,6 +147,7 @@ class SourceImage(Image): # closing tarball file tarball.close() + @property def data_tarballs(self): '''List all data tarballs in data directory''' databalls = dict() @@ -147,36 +156,41 @@ class SourceImage(Image): self.description["version"], dname, self.extension_data) - databalls[filename] = os.path.abspath(os.path.join(self.data_path, dname)) + databalls[dname] = filename return databalls def create_data_tarballs(self): - '''Create all data tarballs in data directory''' + ''' + Create all data tarballs in data directory + Doen't compute md5 during creation because tarball can + be created manually + ''' arrow("Creating data tarballs", 1, self.verbose) # build list of data tarball candidate - candidates = self.data_tarballs() + candidates = self.data_tarballs if len(candidates) == 0: arrow("No data tarball", 2, self.verbose) return # create tarballs - for candidate in candidates: - path = os.path.join(self.base_path, candidate) - if os.path.exists(path): - arrow("Tarball %s already exists." % candidate, 2, self.verbose) + for (dn, df) in candidates.items(): + source_path = os.path.join(self.data_path, dn) + dest_path = os.path.join(self.base_path, df) + if os.path.exists(dest_path): + arrow("Tarball %s already exists." % df, 2, self.verbose) else: - arrow("Creating tarball %s" % candidate, 2, self.verbose) - self.create_data_tarball(path, candidates[candidate]) + arrow("Creating tarball %s" % df, 2, self.verbose) + self.create_data_tarball(dest_path, source_path) def create_data_tarball(self, tar_path, data_path): '''Create a data tarball''' + # compute dname to set as a base directory dname = os.path.basename(data_path) # not derefence for directory. Verbatim copy. ddref = False if os.path.isdir(data_path) else True try: - # opening file + # Tarballing tarball = Tarball.open(tar_path, "w:gz", dereference=ddref) - tarball.add(data_path, arcname=dname, recursive=True) - # closing tarball file + tarball.add(data_path, arcname="/", recursive=True) tarball.close() except Exception as e: raise Exception("Unable to create data tarball %s: %s" % (tar_path, e)) @@ -191,7 +205,7 @@ class SourceImage(Image): return tinfo def generate_json_description(self): - '''Generate a json description file''' + '''Generate a JSON description file''' arrow("Generating JSON description", 1, self.verbose) # copy description desc = self.description.copy() @@ -200,13 +214,11 @@ class SourceImage(Image): desc["date"] = int(time.time()) # append data tarballs info desc["data"] = dict() - for dt in self.data_tarballs(): - arrow("Compute MD5 of %s" % dt, 2, self.verbose) - path = os.path.join(self.base_path, dt) - desc["data"][dt] = { "size": os.path.getsize(path), - "md5": istools.md5sum(path) } - # create file - filedesc = StringIO.StringIO() + for (dn, df) in self.data_tarballs.items(): + arrow("Compute MD5 of %s" % df, 2, self.verbose) + tb_path = os.path.join(self.base_path, df) + desc["data"][dn] = { "size": os.path.getsize(tb_path), + "md5": istools.md5sum(tb_path) } # serialize return json.dumps(desc) @@ -221,24 +233,36 @@ class SourceImage(Image): for n in ("name","version", "description", "author"): d[n] = cp.get("image", n) except Exception as e: - raise Exception("description: %s" % e) + raise Exception("Invalid description: %s" % e) return d + class PackageImage(Image): '''Packaged image manipulation class''' def __init__(self, path, verbose=True): Image.__init__(self) - self.path = os.path.abspath(path) + self.path = istools.abspath(path) self.base_path = os.path.dirname(self.path) self.verbose = verbose - self.tarball = Tarball.open(self.path, mode='r:gz') + # load image in memory + arrow("Loading tarball in memory", 1, verbose) + self.file = cStringIO.StringIO() + fo = istools.uopen(self.path) + shutil.copyfileobj(fo, self.file) + fo.close() + # set tarball fo + self.file.seek(0) + self.tarball = Tarball.open(fileobj=self.file, mode='r:gz') self.parse() + self._md5 = None @property def md5(self): '''Return md5sum of the current tarball''' - return istools.md5sum(self.path) + if self._md5 is None: + self._md5 = istools.md5sum(self.path) + return self._md5 @property def id(self): @@ -263,7 +287,12 @@ class PackageImage(Image): @property def datas(self): '''Create a dict of data tarballs''' - return dict(self.description["data"]) + d = dict() + for dt in self.description["data"]: + d[dt] = dict(self.description["data"][dt]) + d[dt]["filename"] = "%s-%s%s" % (self.id, dt, self.extension_data) + d[dt]["path"] = os.path.join(self.base_path, d[dt]["filename"]) + return d def parse(self): '''Parse tarball and extract metadata''' @@ -311,10 +340,56 @@ class PackageImage(Image): # order matter! l_scripts.sort() # run scripts - try: - for n_scripts in l_scripts: - arrow(os.path.basename(n_scripts), 2, self.verbose) + for n_scripts in l_scripts: + arrow(os.path.basename(n_scripts), 2, self.verbose) + try: s_scripts = self.tarball.get_str(n_scripts) + except Exception as e: + raise Exception("Extracting script %s fail: %s" % + (os.path.basename(n_scripts), e)) + try: exec(s_scripts, gl, dict()) + except Exception as e: + raise + raise Exception("Execution script %s fail: %s" % + (os.path.basename(n_scripts), e)) + + def extractdata(self, dataname, target, filelist=None): + '''Extract a data tarball into target''' + # check if dataname exists + if dataname not in self.description["data"].keys(): + raise Exception("No such data") + # tarball info + tinfo = self.description["data"][dataname] + # build data tar paths + paths = [ os.path.join(self.base_path, tinfo["md5"]), + os.path.join(self.base_path, "%s-%s%s" % (self.id, + dataname, + self.extension_data)) ] + # try to open path + fo = None + for path in paths: + try: + fo = istools.uopen(path) + break + except Exception: + pass + # error if no file is openned + if fo is None: + raise Exception("Unable to open data tarball") + try: + # create tar object + t = Tarball.open(fileobj=fo, mode="r|gz") + except Exception as e: + raise Exception("Invalid data tarball: %s" % e) + # filter on file to extact + if filelist is not None: + members = [] + for fi in filelist: + members += t.gettarinfo(name) + else: + members = None + try: + t.extractall(target, members) except Exception as e: - raise Exception("%s fail: %s" % (os.path.basename(n_scripts), e)) + raise Exception("Extracting failed: %s" % e) diff --git a/installsystems/repository.py b/installsystems/repository.py index a1bcf618316625efb302acb9dad22e3d8f8062c3..6113d2b311370380f0a8206b39f37362dbf07369 100644 --- a/installsystems/repository.py +++ b/installsystems/repository.py @@ -25,26 +25,27 @@ class Repository(object): def __init__(self, config, verbose=True): self.verbose = verbose self.config = config - self.db = Database(os.path.join(config.image, config.db), verbose=self.verbose) - self.last_path = os.path.join(config.image, config.last) + self.db = Database(os.path.join(config.path, config.dbname), verbose=self.verbose) @classmethod def create(cls, config, verbose=True): '''Create an empty base repository''' + # check local repository + if istools.pathtype(config.path) != "file": + raise NotImplementedError("Repository creation must be local") # create base directories arrow("Creating base directories", 1, verbose) - for d in (config.image, config.data): - try: - if os.path.exists(d): - arrow("%s already exists" % d, 2, verbose) - else: - istools.mkdir(d, config.chown, config.chgroup, config.dchmod) - arrow("%s directory created" % d, 2, verbose) - except Exception as e: - raise - raise Exception("Unable to create directory %s: %s" % (d, e)) + # creating local directory + try: + if os.path.exists(config.path): + arrow("%s already exists" % config.path, 2, verbose) + else: + istools.mkdir(config.path, config.chown, config.chgroup, config.dchmod) + arrow("%s directory created" % config.path, 2, verbose) + except Exception as e: + raise Exception("Unable to create directory %s: %s" % (config.path, e)) # create database - dbpath = os.path.join(config.image, "db") + dbpath = os.path.join(config.path, config.dbname) d = Database.create(dbpath, verbose=verbose) istools.chrights(dbpath, config.chown, config.chgroup, config.fchmod) # create last file @@ -55,46 +56,79 @@ class Repository(object): def update_last(self): '''Update last file to current time''' + # check local repository + if istools.pathtype(self.config.path) != "file": + raise NotImplementedError("Repository addition must be local") try: - open(self.last_path, "w").write("%s\n" % int(time.time())) - os.chown(self.last_path, self.config.chown, self.config.chgroup) - os.chmod(self.last_path, self.config.fchmod) + arrow("Updating last file", 1, self.verbose) + last_path = os.path.join(self.config.path, self.config.lastname) + open(last_path, "w").write("%s\n" % int(time.time())) + os.chown(last_path, self.config.chown, self.config.chgroup) + os.chmod(last_path, self.config.fchmod) except Exception as e: raise Exception("Update last file failed: %s" % e) def last(self): '''Return the last value''' try: - return int(open(self.last_path, "r").read().rstrip()) + last_path = os.path.join(config.path, config.lastname) + return int(istools.uopen(last_path, "r").read().rstrip()) except Exception as e: raise Exception("Read last file failed: %s" % e) return 0 def add(self, package): '''Add a packaged image to repository''' + # check local repository + if istools.pathtype(self.config.path) != "file": + raise NotImplementedError("Repository addition must be local") # copy file to directory - arrow("Adding file to directories", 1, self.verbose) - arrow("Adding %s" % os.path.relpath(package.path), 2, self.verbose) - istools.copy(package.path, self.config.image, - self.config.chown, self.config.chgroup, self.config.fchmod) - for db in package.databalls: - arrow("Adding %s" % os.path.basename(db), 2, self.verbose) - istools.copy(db, self.config.data, - self.config.chown, self.config.chgroup, self.config.fchmod) - # add file to db + arrow("MD5summing tarballs", 1, self.verbose) + # build dict of file to add + filelist = dict() + # script tarball + arrow(package.filename, 2, self.verbose) + filelist[package.md5] = package.path + # data tarballs + datas = package.datas + for dt in datas: + dt_path = datas[dt]["path"] + old_md5 = datas[dt]["md5"] + arrow(os.path.relpath(dt_path), 2, self.verbose) + md5 = istools.md5sum(dt_path) + if md5 != old_md5: + raise Exception("MD5 mismatch on %s" % dt_path) + filelist[md5] = dt_path + # adding file to repository + arrow("Adding files to directory", 1, self.verbose) + for md5 in filelist: + dest = os.path.join(self.config.path, md5) + source = filelist[md5] + if os.path.exists(dest): + arrow("Skipping %s: already exists" % (os.path.basename(source)), + 2, self.verbose) + else: + arrow("Adding %s (%s)" % (os.path.basename(source), md5), 2, self.verbose) + istools.copy(source, dest, + self.config.chown, self.config.chgroup, self.config.fchmod) + # add description to db self.db.add(package) # update last file - arrow("Updating last file", 1, self.verbose) self.update_last() def delete(self, name, version): '''Delete an image from repository''' - if self.db.find(name, version) is None: + raise NotImplementedError() + # check local repository + if istools.pathtype(self.config.path) != "file": + raise NotImplementedError("Repository deletion must be local") + desc = self.db.find(name, version) + if desc is None: error("Unable to find %s version %s in database" % (name, version)) # removing script tarballs arrow("Removing script tarball", 1, self.verbose) - tpath = os.path.join(self.config.image, - "%s-%s%s" % (name, version, Image.image_extension)) + tpath = os.path.join(self.config.path, + "%s-%s%s" % (name, version, Image.extension)) if os.path.exists(tpath): os.unlink(tpath) arrow("%s removed" % os.path.basename(tpath), 2, self.verbose) @@ -111,6 +145,10 @@ class Repository(object): arrow("Updating last file", 1, self.verbose) self.update_last() + def get(self, name, version): + '''return a package from a name and version of pakage''' + desc = self.db.get(name, version) + return PackageImage(os.path.join(self.config.path, desc["md5"]), verbose=self.verbose) class RepositoryConfig(object): '''Repository configuration container''' @@ -118,10 +156,9 @@ class RepositoryConfig(object): def __init__(self, *args, **kwargs): # set default value for arguments self.name = args[0] - self.db = "db" - self.last = "last" - self.image = "" - self.data = "" + self.dbname = "db" + self.lastname = "last" + self.path = "" self.chown = os.getuid() self.chgroup = os.getgid() umask = os.umask(0) @@ -131,8 +168,7 @@ class RepositoryConfig(object): self.update(**kwargs) def update(self, *args, **kwargs): - ''' - Update attribute with checking value + '''Update attribute with checking value All attribute must already exists ''' # autoset parameter in cmdline @@ -172,6 +208,48 @@ class RepositoryConfig(object): def __contains__(self, key): return key in self.__dict__ +class RepositoryManager(object): + '''Manage multiple repostories''' + + def __init__(self, timeout=None, verbose=True): + self.verbose = verbose + self.timeout = 3 if timeout is None else timeout + self.repos = {} + + def register(self, configs): + '''Register a list of repository from its config''' + for conf in configs: + self.repos[conf.name] = Repository(conf, self.verbose) + + def find_image(self, name, version): + '''Find a repository containing image''' + if version is None: + arrow("Serching last version of %s" % name, 1, self.verbose) + else: + arrow("Serching %s version %s " % (name, version), 1, self.verbose) + img = None + # search in all repositories + desc = None + for repo in self.repos: + desc = self.repos[repo].db.find(name, version) + if desc is not None: + # \o/ + break + if desc is None: + arrow("Not found", 2, self.verbose) + if version is None: + error("Unable to find a version of image %s" % name) + else: + error("Unable to find image %s version %s" % (name, version)) + arrow("Found %s version %s " % (desc["name"], desc["version"]), 2, self.verbose) + return (desc, self.repos[repo]) + + def get(self, name, version=None): + '''Return a package object from local cache''' + # find an image name/version in repository + (desc, repo) = self.find_image(name, version) + # get pkg object + return repo.get(desc["name"], desc["version"]) class RepositoryCache(object): '''Local repository cache class''' diff --git a/installsystems/template.py b/installsystems/template.py index cbcd614761bbba99cd97825d817a15e5fad5b284..61f57aaa3d44c93516404e0ce83cae22fe906215 100644 --- a/installsystems/template.py +++ b/installsystems/template.py @@ -13,6 +13,8 @@ parser = """# -*- python -*- # -*- coding: utf-8 -*- parser.add_argument("-n", "--hostname", dest="hostname", type=str, required=True) +parser.add_argument("target", type=str, + help="target installation directory") # vim:set ts=2 sw=2 noet: """ @@ -22,5 +24,7 @@ setup = """# -*- python -*- print "hostname: %s" % args.hostname +image.extractdata("rootfs", args.target) + # vim:set ts=2 sw=2 noet: """ diff --git a/installsystems/tools.py b/installsystems/tools.py index 56c086757c21751499261f3b7b4ffa82fafaa0e0..504f048a02735bf2213a9c1bba3d079068fd103b 100644 --- a/installsystems/tools.py +++ b/installsystems/tools.py @@ -81,8 +81,10 @@ def abspath(path): else: return None -def ropen(path): - '''Open a file which can be remote''' +def uopen(path): + '''Universal Open + Create a file-like object to a file which can be remote + ''' ftype = pathtype(path) if ftype == "file": return open(path, "r")