# -*- python -*- # -*- coding: utf-8 -*- # Started 10/05/2011 by Seblu ''' Repository stuff ''' import os import time import shutil import json import installsystems from installsystems.printer import * from installsystems.tarball import Tarball from installsystems.image import Image, PackageImage from installsystems.database import Database class RepositoryBase(object): '''Base repository class''' def complete_path(self, path): '''Format a path to be complete''' if self.path_type(path) in ('http', 'ssh'): return path else: return os.path.abspath(path) def path_type(self, path): '''Return path type''' if path.startswith("http://") or path.startswith("https://"): return "http" # elif path.startswith("ssh://"): # return "ssh" return "file" def cp(self, source, destination): '''Copy a source to destination. Take care of path type''' stype = self.path_type(source) dtype = self.path_type(destination) if stype == dtype == "file": shutil.copy(source, destination) elif stype == "file" and dtype == "": pass class Repository(RepositoryBase): '''Repository class''' last_name = "last" def __init__(self, image_path, data_path, verbose=True): self.image_path = os.path.abspath(image_path) self.last_path = os.path.join(image_path, self.last_name) self.data_path = os.path.abspath(data_path) self.verbose = verbose self.db = Database(os.path.join(image_path, "db"), verbose=self.verbose) @classmethod def create(cls, image_path, data_path, verbose=True): '''Create an empty base repository''' # create base directories arrow("Creating base directories", 1, verbose) try: for d in (image_path, data_path): if os.path.exists(d): arrow("%s already exists" % os.path.relpath(d), 2, verbose) else: os.mkdir(d) arrow("%s directory created" % os.path.relpath(d), 2, verbose) except Exception as e: raise Exception("Unable to create directory %s: %s" % (d, e)) # create database d = Database.create(os.path.join(image_path, "db"), verbose=verbose) # create last file arrow("Creating last file", 1, verbose) self = cls(image_path, data_path, verbose) self.update_last() def update_last(self): '''Update last file to current time''' try: open(self.last_path, "w").write("%s\n" % int(time.time())) 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()) except Exception as e: raise Exception("Read last file failed: %s" % e) return 0 def add(self, package): '''Add a packaged image to repository''' # copy file to directory arrow("Adding file to directories", 1, self.verbose) arrow("Adding %s" % os.path.basename(package.path), 2, self.verbose) shutil.copy(package.path, self.image_path) for db in package.databalls(): arrow("Adding %s" % os.path.basename(db), 2, self.verbose) shutil.copy(db, self.data_path) # add file 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''' name = "%s-%s" % (name, version) fname = "%s.json" % name # FIXME: check tarball exists before doing this tbs = self.tarballs(name) # removing metadata self.db.delete(name, version) # removing tarballs arrow("Removing tarballs", 1, self.verbose) for tb in tbs: arrow("Removing %s" % os.path.basename(tb), 2, self.verbose) os.unlink(tb) # update last file arrow("Updating last file", 1, self.verbose) self.update_last() def tarballs(self, name): '''List all tarballs (script + data)''' ts = list() # add script tarballs ts.append(os.path.abspath(os.path.join(self.image_path, "%s%s" % (name, Image.image_extension)))) tempdb = Tarball.open(self.db_path, mode='r:bz2') jdesc = json.loads(tempdb.get_str("%s.json" % name)) for dt in jdesc["data"]: ts.append(os.path.abspath(os.path.join(self.data_path, dt))) return ts class RepositoryCache(RepositoryBase): '''Local repository cache class''' def __init__(self, cache_path, verbose=True): self.base_path = os.path.abspath(cache_path) self.image_path = os.path.join(self.base_path, "image") self.last_path = os.path.join(self.base_path, "last") self.db_path = os.path.join(self.base_path, "db") for path in (self.base_path, self.image_path, self.last_path, self.db_path): if not os.path.exists(path): os.mkdir(path) if not os.access(path, os.W_OK | os.X_OK): raise Exception("%s is not writable or executable" % path) self.verbose = verbose self.repos = dict() def register(self, name, image, data): '''Register a repository to track''' self.repos[name] = Repository(self.complete_path(image), self.complete_path(data), verbose=self.verbose) def update(self): '''Update cache info''' arrow("Updating repositories", 1, self.verbose) for r in self.repos: debug("%s: remote_last: %s, local_last:%s" % (r, self.repos[r].last(), self.last(r))) if self.repos[r].last() > self.last(r): # copy last file self.cp(self.repos[r].last_path, os.path.join(self.last_path, r)) # copy db file self.cp(self.repos[r].db.path, os.path.join(self.db_path, r)) arrow("%s updated" % r, 2, self.verbose) def last(self, reponame): '''Return the last timestamp of a repo''' last_path = os.path.join(self.last_path, reponame) if os.path.exists(last_path): return int(open(last_path, "r").read().rstrip()) return 0 def get_image(self, reponame, imagename, imageversion): '''Obtain a local path in cache for a remote image in repo''' arrow("Getting image", 1, self.verbose) filename = "%s-%s%s" % (imagename, imageversion, Image.image_extension) localpath = os.path.join(self.image_path, filename) # return db path if exists if os.path.exists(localpath): arrow("Found in cache", 2, self.verbose) return localpath # get remote image remotepath = os.path.join(self.repos[reponame].image_path, filename) arrow("Copying from repository", 2, self.verbose) self.cp(remotepath, localpath) return localpath def find_image(self, name, version): '''Find an image in repositories''' 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 for repo in self.repos: tempdb = Database(os.path.join(self.db_path, repo), False) img = tempdb.find(name, version) if img is not None: # \o/ break if img 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 " % (img[0], img[1]), 2, self.verbose) return (repo, img[0], img[1]) def get(self, name, version=None): '''Return a package object from local cache''' r, n, v = self.find_image(name, version) # download image if not in cache path = self.get_image(r, n, v) # create an object image return PackageImage(path, self.verbose)