Skip to content
repository.py 46.7 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 uuid
import string
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

class RepositoryFactory(object):
    '''
    Repository factory
    '''

    def __init__(self):
        self.repo_class = {
            1: Repository_v1,
            2: Repository
        }

    def create(self, config):
        db = None
        if not config.offline:
            try:
                db = Database(config.dbpath)
            except ISWarning as e:
                warn('[%s]: %s' % (config.name, e))
                config.offline = True
            except ISError:
                debug(u"Unable to load database %s" % config.dbpath)
                config.offline = True
        if config.offline:
            debug(u"Repository %s is offline" % config.name)
        if db is None:
            return Repository(config)
        else:
            return self.repo_class[int(db.version)](config, db)

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):
        '''
Aurélien Dunand's avatar
Aurélien Dunand committed
        Return a list of repository from a 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):
        '''
Aurélien Dunand's avatar
Aurélien Dunand committed
        Compute a diff between two repositories
Seblu's avatar
Seblu committed
        '''
        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)

    def __init__(self, config, db=None):
        self.config = config
        self.local = istools.isfile(self.config.path)
        self.db = db
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 and upgrade are always accessible
        if name in ("init", "config", "local", "upgrade"):
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

    @property
    def uuid(self):
        '''
        Return repository UUID
        '''
        return self.db.ask("SELECT uuid from repository").fetchone()[0]

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)
Loading full blame...