Skip to content
image.py 35.5 KiB
Newer Older
# -*- python -*-
# -*- coding: utf-8 -*-
# Started 10/05/2011 by Seblu <seblu@seblu.net>

'''
Image stuff
'''

import os
import stat
import time
import json
Seblu's avatar
Seblu committed
import difflib
import ConfigParser
import subprocess
import tarfile
import re
import shutil
import gzip
import gzipstream #until python support gzip not seekable
import cStringIO
import installsystems
import installsystems.template as istemplate
import installsystems.tools as istools
from installsystems.printer import *
from installsystems.tools import PipeFile
from installsystems.tarball import Tarball
Seblu's avatar
Seblu committed

class Image(object):
Seblu's avatar
Seblu committed
    '''
    Abstract class of images
    '''
Seblu's avatar
Seblu committed
    extension = ".isimage"
    @staticmethod
    def check_image_name(buf):
Seblu's avatar
Seblu committed
        '''
        Check if @buf is a valid image name
Seblu's avatar
Seblu committed
        '''
        if re.match("^[-_\w]+$", buf) is None:
            raise Exception("Invalid image name %s" % buf)

    @staticmethod
    def check_image_version(buf):
Seblu's avatar
Seblu committed
        '''
        Check if @buf is a valid image version
Seblu's avatar
Seblu committed
        '''
        if re.match("^\d+$", buf) is None:
            raise Exception("Invalid image version %s" % buf)

class SourceImage(Image):
Seblu's avatar
Seblu committed
    '''
    Image source manipulation class
    '''
    @classmethod
Seblu's avatar
Seblu committed
    def create(cls, path):
Seblu's avatar
Seblu committed
        '''
        Create an empty source image
        '''
        # check local repository
        if not istools.isfile(path):
            raise NotImplementedError("SourceImage must be local")
        # main path
        parser_path = os.path.join(path, "parser")
        setup_path = os.path.join(path, "setup")
Seblu's avatar
Seblu committed
        payload_path = os.path.join(path, "payload")
        # create base directories
Seblu's avatar
Seblu committed
        arrow("Creating base directories")
Seblu's avatar
Seblu committed
            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:
            raise Exception("Unable to create directory: %s: %s" % (d, e))
        # create example files
Seblu's avatar
Seblu committed
        arrow("Creating examples")
        arrowlevel(1)
        try:
            # create description example from template
Seblu's avatar
Seblu committed
            arrow("Creating description example")
            open(os.path.join(path, "description"), "w").write(istemplate.description)
            # create changelog example from template
            arrow("Creating description example")
            open(os.path.join(path, "changelog"), "w").write(istemplate.changelog)
            # create parser example from template
Seblu's avatar
Seblu committed
            arrow("Creating parser script example")
            open(os.path.join(parser_path, "01-parser.py"), "w").write(istemplate.parser)
            # create setup example from template
Seblu's avatar
Seblu committed
            arrow("Creating setup script example")
            open(os.path.join(setup_path, "01-setup.py"), "w").write(istemplate.setup)
        except Exception as e:
            raise Exception("Unable to example file: %s" % e)
        try:
            # setting rights on files in setup and parser
Seblu's avatar
Seblu committed
            arrow("Setting executable rights on scripts")
            umask = os.umask(0)
            os.umask(umask)
            for dpath in (parser_path, setup_path):
                for f in os.listdir(dpath):
Seblu's avatar
Seblu committed
                    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))
Seblu's avatar
Seblu committed
        arrowlevel(-1)
        return cls(path)
Seblu's avatar
Seblu committed
    def __init__(self, path):
        # check local repository
        if not istools.isfile(path):
            raise NotImplementedError("SourceImage must be local")
Seblu's avatar
Seblu committed
        Image.__init__(self)
        self.base_path = path
        self.parser_path = os.path.join(path, "parser")
        self.setup_path = os.path.join(path, "setup")
Seblu's avatar
Seblu committed
        self.payload_path = os.path.join(path, "payload")
        self.validate_source_files()
        self.description = self.parse_description()
        self.changelog = self.parse_changelog()
Seblu's avatar
Seblu committed
        # script tarball path
        self.image_name = "%s-%s%s" % (self.description["name"],
                                       self.description["version"],
                                       self.extension)
Seblu's avatar
Seblu committed
    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)
Seblu's avatar
Seblu committed
        if not os.path.exists(os.path.join(self.base_path, "description")):
            raise Exception("No description file")
    def build(self, force=False, check=True):
Seblu's avatar
Seblu committed
        '''
        Create packaged image
        '''
        # check if free to create script tarball
        if os.path.exists(self.image_name) and force == False:
            raise Exception("Tarball already exists. Remove it before")
        # Check python file
        if check:
Seblu's avatar
Seblu committed
            self.check_scripts(self.parser_path)
            self.check_scripts(self.setup_path)
        # Create payload files
Seblu's avatar
Seblu committed
        payloads = self.create_payloads()
Seblu's avatar
Seblu committed
        # generate a JSON description
        jdesc = self.generate_json_description(payloads)
        # creating scripts tarball
Seblu's avatar
Seblu committed
        self.create_image(jdesc)
Seblu's avatar
Seblu committed

    def create_image(self, jdescription):
Seblu's avatar
Seblu committed
        '''
        Create a script tarball in current directory
        '''
        # create tarball
Seblu's avatar
Seblu committed
        arrow("Creating image tarball")
        arrowlevel(1)
        arrow("Name %s" % self.image_name)
Seblu's avatar
Seblu committed
            tarball = Tarball.open(self.image_name, mode="w:gz", dereference=True)
        except Exception as e:
Seblu's avatar
Seblu committed
            raise Exception("Unable to create tarball %s: %s" % (self.image_name, e))
        # add .description.json
Seblu's avatar
Seblu committed
        arrow("Add description.json")
        tarball.add_str("description.json", jdescription, tarfile.REGTYPE, 0444)
        # add changelog
        if self.changelog is not None:
            arrow("Add changelog")
            tarball.add_str("changelog", self.changelog.verbatim, tarfile.REGTYPE, 0444)
        # add .format
Seblu's avatar
Seblu committed
        arrow("Add format")
        tarball.add_str("format", self.format, tarfile.REGTYPE, 0444)
        # add parser scripts
        self._add_scripts(tarball, self.parser_path)
        # add setup scripts
        self._add_scripts(tarball, self.setup_path)
        # closing tarball file
        tarball.close()
Seblu's avatar
Seblu committed
        arrowlevel(-1)
Seblu's avatar
Seblu committed
    def create_payloads(self):
Seblu's avatar
Seblu committed
        Create all data payloads in current directory
Aurélien Dunand's avatar
Aurélien Dunand committed
        Doesn't compute md5 during creation because tarball can
        be created manually
        '''
Seblu's avatar
Seblu committed
        arrow("Creating payloads")
        arrowlevel(1)
Seblu's avatar
Seblu committed
        # build list of payload files
        candidates = os.listdir(self.payload_path)
        if len(candidates) == 0:
Loading full blame...