diff --git a/installsystems/config.py b/installsystems/config.py index 9b75f8fc2c806cded976d20adf2449942ba900ef..b9799112eaa1124de0aa94e79fc5a2ab0707824c 100644 --- a/installsystems/config.py +++ b/installsystems/config.py @@ -51,7 +51,6 @@ class ConfigFile(object): # get all options in repo self._repos.append(RepositoryConfig(rep, **dict(cp.items(rep)))) except Exception as e: - raise raise Exception("Unable load file %s: %s" % (self.path, e)) else: debug("No config file found") diff --git a/installsystems/database.py b/installsystems/database.py index 874a27a46367108775a472a89d00ccfa9406d324..a774b144217ae491fc13aa426770cb43d7c0cb6a 100644 --- a/installsystems/database.py +++ b/installsystems/database.py @@ -52,7 +52,7 @@ class Database(object): self.conn.execute("PRAGMA foreign_keys = ON") def get(self, name, version): - '''Return a description dict from a package name''' + '''Return a description dict from a image name''' # parse tarball try: self.file.seek(0) @@ -60,20 +60,20 @@ class Database(object): rdata = tarball.get_str("%s-%s" % (name, version)) tarball.close() except KeyError: - raise Exception("No package %s version %s in metadata" % (name, version)) + raise Exception("No image %s version %s in metadata" % (name, version)) except Exception as e: raise Exception("Unable to read db %s version %s: %s" % (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)) + raise Exception("Invalid metadata in image %s version %s: e" % (name, version, e)) def ask(self, sql, args=()): '''Ask question to db''' return self.conn.execute(sql, args) - def add(self, package): + def add(self, image): '''Add a packaged image to a db''' try: # let's go @@ -82,28 +82,28 @@ class Database(object): # insert image information arrow("Add image metadata", 2, self.verbose) self.conn.execute("INSERT OR REPLACE INTO image values (?,?,?,?,?,?,?)", - (package.md5, - package.name, - package.version, - package.date, - package.author, - package.description, - package.size, + (image.md5, + image.name, + image.version, + image.date, + image.author, + image.description, + image.size, )) # insert data informations - arrow("Add data metadata", 2, self.verbose) - for key,value in package.data.items(): - self.conn.execute("INSERT OR REPLACE INTO data values (?,?,?,?)", - (value["md5"], - package.md5, - key, - value["size"] + arrow("Add payload metadata", 2, self.verbose) + for name, obj in image.payload.items(): + self.conn.execute("INSERT OR REPLACE INTO payload values (?,?,?,?,?)", + (obj.md5, + image.md5, + name, + obj.isdir, + obj.size, )) # on commit arrow("Commit transaction to db", 1, self.verbose) self.conn.execute("COMMIT TRANSACTION") except Exception as e: - raise raise Exception("Adding metadata fail: %s" % e) def delete(self, name, version): diff --git a/installsystems/image.py b/installsystems/image.py index 48c24998c7b665d7c0a027a5627a23f24e4f7433..f4900bfa58e185c9f6a17502a1afc3332e45cd95 100644 --- a/installsystems/image.py +++ b/installsystems/image.py @@ -16,45 +16,57 @@ import tarfile import re import cStringIO import shutil +import gzip import installsystems.template as istemplate import installsystems.tools as istools from installsystems.printer import * from installsystems.tarball import Tarball + class Image(object): - '''Abstract class of images''' + ''' + Abstract class of images + ''' - extension = ".isimage" - extension_data = ".isdata" format = "1" + extension = ".isimage" @staticmethod def check_image_name(buf): - '''Check if @name is a valid image name''' + ''' + Check if @name is a valid image name + ''' return re.match("\w+", buf) is not None @staticmethod def check_image_version(buf): - '''Check if @name is a valid image version''' + ''' + Check if @name is a valid image version + ''' return re.match("\d+", buf) is not None + class SourceImage(Image): - '''Image source manipulation class''' + ''' + Image source manipulation class + ''' @classmethod def create(cls, path, verbose=True): - '''Create an empty source image''' + ''' + 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") + payload_path = os.path.join(path, "payload") # create base directories arrow("Creating base directories", 1, verbose) try: - for d in (path, parser_path, setup_path, data_path): + for d in (path, parser_path, setup_path, payload_path): if not os.path.exists(d) or not os.path.isdir(d): os.mkdir(d) except Exception as e: @@ -80,8 +92,7 @@ class SourceImage(Image): os.umask(umask) for dpath in (parser_path, setup_path): for f in os.listdir(dpath): - pf = os.path.join(dpath, f) - os.chmod(pf, 0777 & ~umask) + istools.chrights(os.path.join(dpath, f), mode=0777 & ~umask) except Exception as e: raise Exception("Unable to set rights on %s: %s" % (pf, e)) return cls(path, verbose) @@ -94,109 +105,145 @@ class SourceImage(Image): 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.payload_path = os.path.join(path, "payload") self.verbose = verbose - self.validate_source_image() + self.validate_source_files() self.description = self.parse_description() + # script tarball path + self.image_name = "%s-%s%s" % (self.description["name"], + self.description["version"], + self.extension) - 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): + def validate_source_files(self): + ''' + Check if we are a valid SourceImage directories + ''' + for d in (self.base_path, self.parser_path, self.setup_path, self.payload_path): if not os.path.exists(d): raise Exception("Missing directory: %s" % d) if not os.path.isdir(d): raise Exception("Not a directory: %s" % d) if not os.access(d, os.R_OK|os.X_OK): raise Exception("Unable to access to %s" % d) + if not os.path.exists(os.path.join(self.base_path, "description")): + raise Exception("No description file") def build(self, overwrite=False): - '''Create packaged image''' - # compute script tarball paths - tarpath = os.path.join(self.base_path, - "%s-%s%s" % (self.description["name"], - self.description["version"], - self.extension)) + ''' + Create packaged image + ''' # check if free to create script tarball - if os.path.exists(tarpath) and overwrite == False: + if os.path.exists(self.image_name) and overwrite == False: raise Exception("Tarball already exists. Remove it before") - # Create data tarballs - self.create_data_tarballs() - # generate description.json - jdesc = self.generate_json_description() + # Create payload files + payloads = self._create_payloads() + # generate a JSON description + jdesc = self.generate_json_description(payloads) # creating scripts tarball - arrow("Creating scripts tarball", 1, self.verbose) - arrow("Name %s" % os.path.relpath(tarpath), 2, self.verbose) + self._create_image(jdesc) + + def _create_image(self, description): + ''' + Create a script tarball in current directory + ''' + # create tarball + arrow("Creating image tarball", 1, self.verbose) + arrow("Name %s" % self.image_name, 2, self.verbose) try: - tarball = Tarball.open(tarpath, mode="w:gz", dereference=True) + tarball = Tarball.open(self.image_name, mode="w:gz", dereference=True) except Exception as e: - raise Exception("Unable to create tarball %s: %s" % (tarpath, e)) + raise Exception("Unable to create tarball %s: %s" % (self.image_name, e)) # add .description.json - arrow("Add .description.json", 2, self.verbose) - tarball.add_str("description.json", jdesc, tarfile.REGTYPE, 0444) + arrow("Add description.json", 2, self.verbose) + tarball.add_str("description.json", description, tarfile.REGTYPE, 0444) # add .format - arrow("Add .format", 2, self.verbose) + arrow("Add format", 2, self.verbose) tarball.add_str("format", self.format, tarfile.REGTYPE, 0444) # add parser scripts arrow("Add parser scripts", 2, self.verbose) tarball.add(self.parser_path, arcname="parser", - recursive=True, filter=self.tar_scripts_filter) + recursive=True, filter=self._tar_scripts_filter) # add setup scripts arrow("Add setup scripts", 2, self.verbose) tarball.add(self.setup_path, arcname="setup", - recursive=True, filter=self.tar_scripts_filter) + recursive=True, filter=self._tar_scripts_filter) # closing tarball file tarball.close() - @property - def data_tarballs(self): - '''List all data tarballs in data directory''' - databalls = dict() - for dname in os.listdir(self.data_path): - filename = "%s-%s-%s%s" % (self.description["name"], - self.description["version"], - dname, - self.extension_data) - databalls[dname] = filename - return databalls - - def create_data_tarballs(self): + def _create_payloads(self): ''' - Create all data tarballs in data directory + Create all data payloads in current directory Doesn'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 + arrow("Creating payloads", 1, self.verbose) + # build list of payload files + candidates = os.listdir(self.payload_path) if len(candidates) == 0: - arrow("No data tarball", 2, self.verbose) - return - # create tarballs - for (dn, df) in candidates.items(): - source_path = os.path.join(self.data_path, dn) - dest_path = os.path.join(self.base_path, df) + arrow("No payload", 2, self.verbose) + return [] + # create payload files + l_l = [] + for pay in candidates: + source_path = os.path.join(self.payload_path, pay) + dest_path = "%s-%s-%s%s" % (self.description["name"], + self.description["version"], + pay, + Payload.extension) + source_stat = os.stat(source_path) + isdir = stat.S_ISDIR(source_stat.st_mode) if os.path.exists(dest_path): - arrow("Tarball %s already exists." % df, 2, self.verbose) + arrow("Payload %s already exists" % dest_path, 2, self.verbose) else: - arrow("Creating tarball %s" % df, 2, self.verbose) - self.create_data_tarball(dest_path, source_path) + arrow("Creating payload %s" % dest_path, 2, self.verbose) + if isdir: + self._create_payload_tarball(dest_path, source_path) + else: + self._create_payload_file(dest_path, source_path) + # create payload object + payobj = Payload(pay, dest_path, isdir=isdir) + payobj.uid = source_stat.st_uid + payobj.gid = source_stat.st_gid + payobj.mode = stat.S_IMODE(source_stat.st_mode) + payobj.mtime = source_stat.st_mtime + l_l.append(payobj) + return l_l - def create_data_tarball(self, tar_path, data_path): - '''Create a data tarball''' + def _create_payload_tarball(self, tar_path, data_path): + ''' + Create a payload tarball + This is needed by payload directory + ''' # 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: # Tarballing - tarball = Tarball.open(tar_path, "w:gz", dereference=ddref) + tarball = Tarball.open(tar_path, "w:gz", dereference=False) 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)) + raise Exception("Unable to create payload tarball %s: %s" % (tar_path, e)) + + def _create_payload_file(self, dest, source): + ''' + Create a payload file + Only gzipping it + ''' + fsource = istools.uopen(source) + # open file not done in GzipFile, to escape writing of filename + # in gzip file. This change md5. + fdest = open(dest, "wb") + fdest = gzip.GzipFile(filename=os.path.basename(source), + fileobj=fdest, + mtime=os.stat(source).st_mtime) + istools.copyfileobj(fsource, fdest) + fsource.close() + fdest.close() - def tar_scripts_filter(self, tinfo): - '''Filter files which can be included in scripts tarball''' + def _tar_scripts_filter(self, tinfo): + ''' + Filter files which can be included in scripts tarball + ''' if not tinfo.name in ("parser", "setup") and os.path.splitext(tinfo.name)[1] != ".py": return None tinfo.mode = 0755 @@ -204,26 +251,28 @@ class SourceImage(Image): tinfo.uname = tinfo.gname = "root" return tinfo - def generate_json_description(self): - '''Generate a JSON description file''' + def generate_json_description(self, payloads): + ''' + Generate a JSON description file + ''' arrow("Generating JSON description", 1, self.verbose) # copy description desc = self.description.copy() # timestamp image arrow("Timestamping", 2, self.verbose) desc["date"] = int(time.time()) - # append data tarballs info - desc["data"] = dict() - 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) } + # append payload infos + arrow("Checksumming", 2, self.verbose) + desc["payload"] = {} + for payload in payloads: + desc["payload"][payload.name] = payload.info # serialize return json.dumps(desc) def parse_description(self): - '''Raise an exception is description file is invalid and return vars to include''' + ''' + Raise an exception is description file is invalid and return vars to include + ''' arrow("Parsing description", 1, self.verbose) d = dict() try: @@ -233,12 +282,14 @@ class SourceImage(Image): for n in ("name","version", "description", "author"): d[n] = cp.get("image", n) except Exception as e: - raise Exception("Invalid description: %s" % e) + raise Exception("Bad description: %s" % e) return d class PackageImage(Image): - '''Packaged image manipulation class''' + ''' + Packaged image manipulation class + ''' def __init__(self, path, md5name=False, verbose=True): Image.__init__(self) @@ -257,25 +308,43 @@ class PackageImage(Image): memfile.seek(0) self._tarball = Tarball.open(fileobj=memfile, mode='r:gz') self._metadata = self.read_metadata() + # build payloads + self.payload = {} + for pname, pval in self._metadata["payload"].items(): + if self.md5name: + ppath = os.path.join(self.base_path, + self._metadata["payload"][pname]["md5"]) + else: + ppath = os.path.join(self.base_path, + "%s-%s%s" % (self.id, pname, Payload.extension)) + self.payload[pname] = Payload(pname, ppath, **pval) def __getattr__(self, name): - """Give direct access to description field""" + ''' + Give direct access to description field + ''' if name in self._metadata: return self._metadata[name] raise AttributeError @property def id(self): - '''Return image versionned name / id''' - return "%s-%s" % (self._metadata["name"], self._metadata["version"]) + ''' + Return image versionned name / id + ''' + return "%s-%s" % (self.name, self.version) @property def filename(self): - '''Return image filename''' + ''' + Return image filename + ''' return "%s%s" % (self.id, self.extension) def read_metadata(self): - '''Parse tarball and return metadata dict''' + ''' + Parse tarball and return metadata dict + ''' # extract metadata arrow("Read tarball metadata", 1, self.verbose) img_format = self._tarball.get_str("format") @@ -285,7 +354,7 @@ class PackageImage(Image): if img_format != self.format: raise Exception("Invalid tarball image format") # check description - arrow("Read description file", 2, self.verbose) + arrow("Read image description", 2, self.verbose) try: desc = json.loads(img_desc) except Exception as e: @@ -293,54 +362,36 @@ class PackageImage(Image): # FIXME: we should check valid information here return desc - def data_path(self, name): - '''Return a data filename from its internal name''' - if self.md5name: - return os.path.join(self.base_path, self._metadata["data"][name]["md5"]) - return os.path.join(self.base_path, "%s-%s%s" % (self.id, name, self.extension_data)) - - @property - def tarballs(self): - '''List path of all related tarballs''' - d_d = {} - name = os.path.join(self.base_path, self.md5) if self.md5name else self.path - d_d[name] = {"md5": self.md5, "size": self.size} - for key, value in self._metadata["data"].items(): - d_d[self.data_path(key)] = {"md5": value["md5"], "size": value["size"]} - return d_d - def check(self, message="Check MD5"): - '''Check md5 and size of tarballs are correct''' + ''' + Check md5 and size of tarballs are correct + ''' arrow(message, 1, self.verbose) - # open /dev/null - dn = open("/dev/null", "w") - for key,value in self.tarballs.items(): - arrow(os.path.basename(key), 2, self.verbose) - # open tarball - tfo = istools.uopen(key) - # compute sum and md5 using copy function - size, md5 = istools.copyfileobj(tfo ,dn) - # close tarball fo - tfo.close() - # check md5 - if md5 != value["md5"]: - raise Exception("Invalid md5: %s" % key) - # check size - if size != value["size"]: - raise Exception("Invalid size: %s" % key) - dn.close() + # check image + if self.md5 != istools.md5sum(self.path): + raise Exception("Invalid MD5 of image %s" % self.name) + # check payloads + for pay_name, pay_obj in self.payload.items(): + arrow(pay_name, 2, self.verbose) + pay_obj.check() def run_parser(self, gl): - '''Run parser scripts''' + ''' + Run parser scripts + ''' self._run_scripts(gl, "parser") def run_setup(self, gl): - '''Run setup scripts''' + ''' + Run setup scripts + ''' gl["image"] = self self._run_scripts(gl, "setup") def _run_scripts(self, gl, directory): - '''Run scripts in a tarball directory''' + ''' + Run scripts in a tarball directory + ''' arrow("Run %s" % directory, 1, self.verbose) # get list of parser scripts l_scripts = self._tarball.getnames("%s/.*\.py" % directory) @@ -357,31 +408,205 @@ class PackageImage(Image): 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._metadata["data"].keys(): - raise Exception("No such data: %s" % dataname) - # tarball info - tinfo = self._metadata["data"][dataname] - # build data tar paths - path = self.data_path(dataname) + +class Payload(object): + ''' + Payload class represents a payload object + ''' + extension = ".isdata" + legit_attr = ('isdir', 'md5', 'size', 'uid', 'gid', 'mode', 'mtime') + + def __init__(self, name, path, **kwargs): + object.__setattr__(self, "name", name) + object.__setattr__(self, "path", path) + # register legit param + for attr in self.legit_attr: + setattr(self, attr, None) + # set all named param + for kwarg in kwargs: + if hasattr(self, kwarg): + setattr(self, kwarg, kwargs[kwarg]) + + def __getattr__(self, name): + # get all value with an understance as if there is no underscore + if hasattr(self, "_%s" % name): + return getattr(self, "_%s" % name) + raise AttributeError + + def __setattr__(self, name, value): + # set all value which exists have no underscore, but undesrcore exists + if name in self.legit_attr: + object.__setattr__(self, "_%s" % name, value) + else: + object.__setattr__(self, name, value) + + def checksummize(self): + ''' + Fill missing md5/size about payload + ''' + fileobj = istools.uopen(self.path) + size, md5 = istools.copyfileobj(fileobj, None) + if self._size is None: + self._size = size + if self._md5 is None: + self._md5 = md5 + + @property + def md5(self): + ''' + Return md5 of payload + ''' + if self._md5 is None: + self.checksummize() + return self._md5 + + @property + def size(self): + ''' + Return size of payload + ''' + if self._size is None: + self.checksummize() + return self._size + + @property + def uid(self): + ''' + Return uid of owner of orginal payload + ''' + return self._uid if self._uid is not None else 0 + + @property + def gid(self): + ''' + Return gid of owner of orginal payload + ''' + return self._gid if self._gid is not None else 0 + + @property + def mode(self): + ''' + Return mode of orginal payload + ''' + if self._mode is not None: + return self._mode + else: + umask = os.umask(0) + os.umask(umask) + return 0666 & ~umask + + @property + def mtime(self): + ''' + Return last modification time of orginal payload + ''' + return self._mtime if self._mtime is not None else time.time() + + @property + def info(self): + ''' + return a dict of info about current payload + ''' + return {"md5": self.md5, + "size": self.size, + "isdir": self.isdir, + "uid": self.uid, + "gid": self.gid, + "mode": self.mode, + "mtime": self.mtime} + + def check(self): + ''' + Check that path correspond to current md5 and size + ''' + if self._size is None or self._md5 is None: + debug("Check is called on payload with nothing to check") + return True + fileobj = istools.uopen(self.path) + size, md5 = istools.copyfileobj(fileobj, None) + if self._size != size: + raise Exception("Invalid size of payload %s" % self.name) + if self._md5 != md5: + raise Exception("Invalid MD5 of payload %s" % self._md5) + + def extract(self, dest, force=False, filelist=None): + ''' + Extract payload into dest + filelist is a filter of file in tarball + force will overwrite existing file if exists + ''' + if self.isdir: + self.extract_tar(dest, force=force, filelist=filelist) + else: + self.extract_file(dest, force=force) + + def extract_tar(self, dest, force=False, filelist=None): + ''' + Extract a payload which is a tarball. + This is used mainly to extract payload from a directory + ''' + # check validity of dest + if os.path.exists(dest): + if not os.path.isdir(dest): + raise Exception("Destination %s is not a directory" % dest) + if not force and len(os.listdir(dest)) > 0: + raise Exception("Directory %s is not empty (need force)" % dest) + else: + os.mkdir(dest) + # try to open payload file try: - fo = istools.uopen(path) + fo = istools.uopen(self.path) except Exception as e: - raise Exception("Unable to open data tarball %s" % path) + raise Exception("Unable to open payload file %s" % self.path) + # try to open tarball on payload try: - # create tar object t = Tarball.open(fileobj=fo, mode="r|gz") except Exception as e: - raise Exception("Invalid data tarball: %s" % e) + raise Exception("Invalid payload tarball: %s" % e) # filter on file to extact - members = None if filelist is None else [ t.gettarinfo(name) for name in filelist ] + members = (None if filelist is None + else [ t.gettarinfo(name) for name in filelist ]) try: - t.extractall(target, members) + t.extractall(dest, members) except Exception as e: raise Exception("Extracting failed: %s" % e) + # closing fo + t.close() + fo.close() + + def extract_file(self, dest, force=False): + ''' + Copy a payload directly to a file + Check md5 on the fly + ''' + # if dest is a directory try to create file inside + if os.path.isdir(dest): + dest = os.path.join(dest, self.name) + # check validity of dest + if os.path.exists(dest): + if not os.path.isfile(dest): + raise Exception("Destination %s is not a file" % dest) + if not force: + raise Exception("File %s already exists" % dest) + # opening destination + try: + f_dst = istools.uopen(dest, "wb") + except Exception as e: + raise Exception("Unable to open destination file %s" % dest) + # try to open payload file + try: + f_gsrc = istools.uopen(self.path) + f_src = gzip.GzipFile(fileobj=f_gsrc) + except Exception as e: + raise Exception("Unable to open payload file %s" % self.path) + # launch copy + size, md5 = istools.copyfileobj(f_src, f_dst) + # closing fo + f_dst.close() + f_gsrc.close() + f_src.close() + # settings file orginal rights + istools.chrights(dest, self.uid, self.gid, self.mode, self.mtime) diff --git a/installsystems/repository.py b/installsystems/repository.py index b22fb419d956cdef3055054742837c3eb6df8d31..1b0557483f0d5f463883d32a06a3a5a65b42ab3c 100644 --- a/installsystems/repository.py +++ b/installsystems/repository.py @@ -77,30 +77,33 @@ class Repository(object): raise Exception("Read last file failed: %s" % e) return 0 - def add(self, package): + def add(self, image): '''Add a packaged image to repository''' # check local repository if istools.pathtype(self.config.path) != "file": raise NotImplementedError("Repository addition must be local") # checking data tarballs md5 before copy - package.check("Check tarballs before copy") + image.check("Check image and payload before copy") # adding file to repository - arrow("Copying files", 1, self.verbose) - for src,value in package.tarballs.items(): - dest = os.path.join(self.config.path, value["md5"]) - basesrc = os.path.basename(src) + arrow("Copying images and payload", 1, self.verbose) + for obj in [ image ] + image.payload.values(): + dest = os.path.join(self.config.path, obj.md5) + basesrc = os.path.basename(obj.path) if os.path.exists(dest): arrow("Skipping %s: already exists" % basesrc, 2, self.verbose) else: - arrow("Adding %s (%s)" % (basesrc, value["md5"]), 2, self.verbose) - istools.copy(src, dest, self.config.uid, self.config.gid, self.config.fmod) - # copy is done. create a package inside repo - r_package = PackageImage(os.path.join(self.config.path, package.md5), + arrow("Adding %s (%s)" % (basesrc, obj.md5), 2, self.verbose) + istools.copy(obj.path, dest, + self.config.uid, self.config.gid, self.config.fmod) + # copy is done. create a image inside repo + r_image = PackageImage(os.path.join(self.config.path, image.md5), md5name=True, verbose=self.verbose) - # checking data tarballs md5 after copy - r_package.check("Check tarballs after copy") + # checking must be done with original md5 + r_image.md5 = image.md5 + # checking image and payload after copy + r_image.check("Check image and payload after copy") # add description to db - self.db.add(r_package) + self.db.add(r_image) # update last file self.update_last() @@ -137,7 +140,7 @@ class Repository(object): return self.db.ask("select name,version from image where name = ? and version = ? limit 1", (name,version)).fetchone() is not None def get(self, name, version): - '''return a package from a name and version of pakage''' + '''return a image from a name and version of pakage''' # get file md5 from db r = self.db.ask("select md5 from image where name = ? and version = ? limit 1", (name,version)).fetchone() diff --git a/installsystems/tarball.py b/installsystems/tarball.py index 1c0fe0ff4dedaf9ec30216000ba42a943420dfb0..cf8d52c01b727272c102f9abcf34ec8340882c3a 100644 --- a/installsystems/tarball.py +++ b/installsystems/tarball.py @@ -36,3 +36,10 @@ class Tarball(tarfile.TarFile): else: return [ tpname for tpname in lorig if re.match(reg_pattern, tpname) ] + + def size(self): + '''Return real (uncompressed) size of the tarball''' + total_sz = 0 + for ti in self.getmembers(): + total_sz += ti.size + return total_sz diff --git a/installsystems/template.py b/installsystems/template.py index 748ffd774e02698d1cb51573654e337b890dece8..af960e5801d24f745e5ca1b8064ce389a91581da 100644 --- a/installsystems/template.py +++ b/installsystems/template.py @@ -24,7 +24,7 @@ setup = """# -*- python -*- print "hostname: %s" % args.hostname -image.extractdata("rootfs", args.target) +image.payload["rootfs"].extract(args.target) # vim:set ts=2 sw=2 noet: """ @@ -39,9 +39,10 @@ CREATE TABLE image (md5 TEXT NOT NULL PRIMARY KEY, size INTEGER NOT NULL, UNIQUE(name, version)); -CREATE TABLE data (md5 TEXT NOT NULL, - image_md5 TEXT NOT NULL REFERENCES image(md5), - name TEXT NOT NULL, - size INTEGER NOT NULL, - PRIMARY KEY(md5, image_md5)); +CREATE TABLE payload (md5 TEXT NOT NULL, + image_md5 TEXT NOT NULL REFERENCES image(md5), + name TEXT NOT NULL, + isdir INTEGER NOT NULL, + size INTEGER NOT NULL, + PRIMARY KEY(md5, image_md5)); """ diff --git a/installsystems/tools.py b/installsystems/tools.py index f42f85c78a1cfe3bc012c25d0788d0ca970b9138..d45d04237c67c14a1625a8954b991bbfd8a7e607 100644 --- a/installsystems/tools.py +++ b/installsystems/tools.py @@ -37,7 +37,8 @@ def copyfileobj(sfile, dfile): break f_len += buf_len f_sum.update(buf) - dfile.write(buf) + if dfile is not None: + dfile.write(buf) return (f_len , f_sum.hexdigest()) def copy(source, destination, uid=None, gid=None, mode=None, timeout=None): @@ -67,7 +68,7 @@ def mkdir(path, uid=None, gid=None, mode=None): os.mkdir(path) chrights(path, uid, gid, mode) -def chrights(path, uid=None, gid=None, mode=None): +def chrights(path, uid=None, gid=None, mode=None, mtime=None): '''Set rights on a file''' if uid is not None: os.chown(path, uid, -1) @@ -75,6 +76,8 @@ def chrights(path, uid=None, gid=None, mode=None): os.chown(path, -1, gid) if mode is not None: os.chmod(path, mode) + if mtime is not None: + os.utime(path, (mtime, mtime)) def pathtype(path): '''Return path type. This is usefull to know what kind of path is given''' @@ -103,14 +106,26 @@ def abspath(path): else: return None -def uopen(path): +def uopen(path, mode="rb"): '''Universal Open Create a file-like object to a file which can be remote ''' ftype = pathtype(path) if ftype == "file": - return open(path, "r") + return open(path, mode) elif ftype == "http" or ftype == "ftp": return urllib2.urlopen(path) else: raise NotImplementedError + +def getsize(path): + '''Get size of a path. Recurse if directory''' + total_sz = os.path.getsize(path) + if os.path.isdir(path): + for root, dirs, files in os.walk(path): + for filename in dirs + files: + filepath = os.path.join(root, filename) + filestat = os.lstat(filepath) + if stat.S_ISDIR(filestat.st_mode) or stat.S_ISREG(filestat.st_mode): + total_sz += filestat.st_size + return total_sz