Skip to content
repository.py 38.1 KiB
Newer Older
# -*- python -*-
# -*- coding: utf-8 -*-
# This file is part of Installsystems.
# Installsystems is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# Installsystems is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with Installsystems.  If not, see <http://www.gnu.org/licenses/>.

'''
Repository stuff
'''
import os
import time
import shutil
import pwd
import grp
Seblu's avatar
Seblu committed
import tempfile
import fnmatch
import installsystems
Seblu's avatar
Seblu committed
import installsystems.tools as istools
from installsystems.exception import *
from installsystems.printer import *
from installsystems.tarball import Tarball
from installsystems.tools import PipeFile
from installsystems.image import Image, PackageImage
from installsystems.database import Database

Seblu's avatar
Seblu committed
class Repository(object):
Seblu's avatar
Seblu committed
    '''
    Repository class
Seblu's avatar
Seblu committed
    '''
    @staticmethod
    def is_repository_name(name):
        return re.match("^[-_\w]+$", name) is not None

    @staticmethod
    def check_repository_name(name):
        '''
        Raise exception is repository name is invalid
        '''
        if not Repository.is_repository_name(name):
            raise ISError(u"Invalid repository name %s" % name)
    @staticmethod
    def split_image_path(path):
        '''
        Split an image path (repo/image:version)
        in a tuple (repo, image, version)
        '''
        x = re.match(u"^(?:([^/:]+)/)?([^/:]+)?(?::v?([^/:]+)?)?$", path)
            raise ISError(u"invalid image path: %s" % path)
        return x.group(1, 2, 3)

    @staticmethod
    def split_repository_list(repolist, filter=None):
        '''
        Return a list of repository from an comma/spaces separated names of repo
        '''
        if filter is None:
            filter = Repository.is_repository_name
        return [r for r in  re.split("[ ,\n\t\v]+", repolist) if filter(r)]

Seblu's avatar
Seblu committed
    @classmethod
    def diff(cls, repo1, repo2):
        '''
        Comptue a diff between two repositories
        '''
        arrow(u"Diff between repositories #y#%s#R# and #g#%s#R#" % (repo1.config.name,
                                                                    repo2.config.name))
Seblu's avatar
Seblu committed
        # Get info from databases
        i_dict1 = dict((b[0], b[1:]) for b in repo1.db.ask(
                "SELECT md5, name, version FROM image").fetchall())
        i_set1 = set(i_dict1.keys())
        i_dict2 = dict((b[0], b[1:]) for b in repo2.db.ask(
                "SELECT md5, name, version FROM image").fetchall())
        i_set2 = set(i_dict2.keys())
        p_dict1 = dict((b[0], b[1:]) for b in  repo1.db.ask(
                "SELECT md5, name FROM payload").fetchall())
        p_set1 = set(p_dict1.keys())
        p_dict2 = dict((b[0], b[1:]) for b in repo2.db.ask(
                "SELECT md5, name FROM payload").fetchall())
        p_set2 = set(p_dict2.keys())
        # computing diff
        i_only1 = i_set1 - i_set2
        i_only2 = i_set2 - i_set1
        p_only1 = p_set1 - p_set2
        p_only2 = p_set2 - p_set1
        # printing functions
        pimg = lambda r,c,m,d,: out("#%s#Image only in repository %s: %s v%s (%s)#R#" %
                                    (c, r.config.name, d[m][0], d[m][1], m))
        ppay = lambda r,c,m,d,: out("#%s#Payload only in repository %s: %s (%s)#R#" %
                                    (c, r.config.name, d[m][0], m))
        # printing image diff
        for md5 in i_only1: pimg(repo1, "y", md5, i_dict1)
        for md5 in p_only1: ppay(repo1, "y", md5, p_dict1)
        for md5 in i_only2: pimg(repo2, "g", md5, i_dict2)
        for md5 in p_only2: ppay(repo2, "g", md5, p_dict2)

Seblu's avatar
Seblu committed
    def __init__(self, config):
        self.config = config
        self.local = istools.isfile(self.config.path)
        if not self.config.offline:
Seblu's avatar
Seblu committed
            try:
                self.db = Database(config.dbpath)
            except:
                debug(u"Unable to load database %s" % config.dbpath)
Seblu's avatar
Seblu committed
                self.config.offline = True
        if self.config.offline:
            debug(u"Repository %s is offline" % config.name)
Seblu's avatar
Seblu committed

    def __getattribute__(self, name):
        '''
        Raise an error if repository is unavailable
        Unavailable can be caused because db is not accessible or
        because repository is not initialized
        '''
        config = object.__getattribute__(self, "config")
        # config, init, local are always accessible
        if name in ("init", "config", "local"):
Seblu's avatar
Seblu committed
            return object.__getattribute__(self, name)
        # if no db (not init or not accessible) raise error
Seblu's avatar
Seblu committed
        if config.offline:
            raise ISError(u"Repository %s is offline" % config.name)
Seblu's avatar
Seblu committed
        return object.__getattribute__(self, name)
    @property
    def version(self):
        '''
        Return repository version
        '''
        return self.db.version

Seblu's avatar
Seblu committed
    def init(self):
Seblu's avatar
Seblu committed
        '''
Seblu's avatar
Seblu committed
        Initialize an empty base repository
Seblu's avatar
Seblu committed
        '''
Seblu's avatar
Seblu committed
        config = self.config
        # check local repository
        if not self.local:
            raise ISError(u"Repository creation must be local")
        # create base directories
Seblu's avatar
Seblu committed
        arrow("Creating base directories")
        arrowlevel(1)
        # creating local directory
        try:
            if os.path.exists(config.path):
                arrow(u"%s already exists" % config.path)
Seblu's avatar
Seblu committed
                istools.mkdir(config.path, config.uid, config.gid, config.dmod)
                arrow(u"%s directory created" % config.path)
        except Exception as e:
            raise ISError(u"Unable to create directory %s" % config.path, e)
Seblu's avatar
Seblu committed
        arrowlevel(-1)
        # create database
Seblu's avatar
Seblu committed
        d = Database.create(config.dbpath)
        istools.chrights(config.dbpath, uid=config.uid,
                         gid=config.gid, mode=config.fmod)
        # load database
        self.db = Database(config.dbpath)
        # mark repo as not offline
        self.config.offline = False
Seblu's avatar
Seblu committed
        # create/update last file
        self.update_last()

    def update_last(self):
Seblu's avatar
Seblu committed
        '''
        Update last file to current time
        '''
        # check local repository
        if not self.local:
            raise ISError(u"Repository addition must be local")
Seblu's avatar
Seblu committed
            arrow("Updating last file")
            last_path = os.path.join(self.config.path, self.config.lastname)
            open(last_path, "w").write("%s\n" % int(time.time()))
Seblu's avatar
Seblu committed
            istools.chrights(last_path, self.config.uid, self.config.gid, self.config.fmod)
        except Exception as e:
            raise ISError(u"Update last file failed", e)
Seblu's avatar
Seblu committed
        '''
        Return last version of name in repo or -1 if not found
Seblu's avatar
Seblu committed
        '''
Loading full blame...