Source

stacklet / src / stacklet / task / distro / suse.py

Full commit
# Copyright (C) 2009, dfn@stacklet.com
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.

# This program 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 General Public License for more details.

"""Tasks for creating suse images.

Please see docstring of each class for usage.

"""
from stacklet.util.objectify import _XO_
from stacklet.util.objectify import *
from stacklet.task.stacktask import *
from stacklet.task.stackerror import *

class _XO_bootstrap(_XO_, StackTask):
    """Bootstrap a suse image.  Image will be chrootable upon completion
       of this task with network access to a package repository.

      Internally it downloads all packages listed in <buildContext.mountPoint>/bootstrap/corePackages
      from argument url. Install the packages to buildContext.mountPoint.
      Finally, copy the hosts's resolv.conf into the image.

    Task Arguments:
    url -- the location to download packages from.  Ftp server is required if corePackages uses wildcards
    proxy -- (optional) proxyhost[:port]

    """
    _TASK_NAME = 'bootstrap'

    def __init__(self):
        pass

    def process(self, buildContext):
        try:
            try:
                self._buildContext = buildContext
                self.log_info(buildContext, 'entering')
                self.precheck(buildContext)
                env = {'http_proxy': self._proxy}

                repoURL = self._url + self._repository
                cmd = 'wget -4 -c -nv --base=' + repoURL + ' -P ' + buildContext.mountPoint + '/bootstrap -i ' + self._corePackages
                self.exec_command(buildContext, cmd, env=env)
                
                cmd = 'rpm --root ' + buildContext.mountPoint + ' -i ' +  buildContext.mountPoint + '/bootstrap/*.rpm'
                self.exec_command(buildContext, cmd)

                cmd='cp -f /etc/resolv.conf ' + buildContext.mountPoint + '/etc/'
                self.exec_command(buildContext, cmd)

                cmds = list() 
                cmd = 'rm -f /var/lib/rpm/[A-Z]*'
                cmds.append(cmd)
                
                cmd = 'rpm --force -Uhv /bootstrap/*rpm'
                cmds.append(cmd)
                
                cmd = 'zypper --no-gpg-checks --non-interactive ar ' + self._repository1
                cmds.append(cmd)
                
                cmd = 'zypper --no-gpg-checks --non-interactive ar ' + self._repository2
                cmds.append(cmd)

                self.exec_command_chroot(buildContext, cmds, env=os.environ)

            except TaskError, taskErr:
                if taskErr.criticality == TaskError.ABORT_BUILD:
                    raise
                self.log_error(buildContext, taskErr)
        finally:
            self.log_info(buildContext, 'leaving')

    def precheck(self, buildContext):
        self.is_valid_build_context(buildContext)

        self._url = self.get_value('url')
        self._repository = self.get_value('repository')
        self._repository1 = self.get_value('repository1')
        self._repository2 = self.get_value('repository2')
        
        self._corePackages = buildContext.mountPoint + '/bootstrap/corePackages'
        self._proxy = self.get_value('proxy')

        if self.is_empty(self._url):
            raise FieldError(self._TASK_NAME, TaskError.ABORT_TASK, 'url is required')

        if self.is_empty(self._repository):
            raise FieldError(self._TASK_NAME, TaskError.ABORT_TASK, 'repository is required')

        if not os.access(self._corePackages, os.F_OK):
            raise BuildError(self._TASK_NAME, TaskError.ABORT_TASK, 'Could not find ' + self._corePackages)


class _XO_package(_XO_, StackTask):
    """Install and/or remove package(s)

    Task Arguments:
    add -- space-delimited list of packages to install
    remove -- space-delimited list of packages to remove
    update -- space-delimited list of packages to update
    proxy -- (optional) proxyhost[:port]
    """
    _TASK_NAME = 'package'

    def __init__(self):
        pass

    def process(self, buildContext):
        try:
            try:
                self._buildContext = buildContext
                self.log_info(buildContext, 'entering')
                self.precheck(buildContext)
                env = os.environ
                
                cmds = list()

                if not self.is_empty(self._update):
                    self.log_info(buildContext, "update " + self._update)
                    if self._update == 'all':
                        cmd = 'zypper --no-gpg-checks --non-interactive up'
                    else:
                        cmd = 'zypper --no-gpg-checks --non-interactive up ' + self._update
                    cmds.append(cmd)

                if not self.is_empty(self._add):
                    self.log_info(buildContext, "add " + self._add)
                    cmd = 'zypper --no-gpg-checks --non-interactive install ' + self._add
                    cmds.append(cmd)
                self.exec_command_chroot(buildContext, cmds, env=env)
            except TaskError, taskErr:
                if taskErr.criticality == TaskError.ABORT_BUILD:
                    raise
                self.log_error(buildContext, taskErr)
        finally:
            self.log_info(buildContext, 'leaving')

    def precheck(self, buildContext):
        self.is_valid_build_context(buildContext)

        self._add = self.get_value('add')
        self._remove = self.get_value('remove')
        self._update = self.get_value('update')
        self._proxy = self.get_value('proxy')

        if self.is_empty(self._add) and self.is_empty(self._remove) and self.is_empty(self._update):
            raise FieldError(self._TASK_NAME, TaskError.ABORT_TASK, 'add or remove or update is required')

class _XO_service(_XO_, StackTask):
    """Enable and/or disable service(s)

    Task Arguments:
    enable -- space-delimited list of services to enable
    disable -- space-delimited list of services to disable
    """
    _TASK_NAME = 'service'

    def __init__(self):
        pass

    def process(self, buildContext):
        try:
            try:
                self._buildContext = buildContext
                self.log_info(buildContext, 'entering')
                self.precheck(buildContext)
                cmds = list()

                if not self.is_empty(self._enable):
                    self.log_info(buildContext, "enable " + self._enable)
                    for serv in self._enable.split():
                        cmds.append('chkconfig ' + serv + ' on')

                if not self.is_empty(self._disable):
                    self.log_info(buildContext, "disable " + self._disable)
                    for serv in self._disable.split():
                        cmds.append('chkconfig ' + serv + ' off')

                self.exec_command_chroot(buildContext, cmds)
            except TaskError, taskErr:
                if taskErr.criticality == TaskError.ABORT_BUILD:
                    raise
                self.log_error(buildContext, taskErr)
        finally:
            self.log_info(buildContext, 'leaving')


    def precheck(self, buildContext):
        self.is_valid_build_context(buildContext)

        self._enable = self.get_value('enable')
        self._disable = self.get_value('disable')

        if self.is_empty(self._enable) and self.is_empty(self._disable):
            raise FieldError(self._TASK_NAME, TaskError.ABORT_TASK, 'enable or disable is required')

class _XO_cleanup(_XO_, StackTask):
    """Cleanup log files, /tmp, etc...

    Task Arguments:
    zerofs -- if 'true' the empty space in the image will be zero'ed for better compressibility
    """
    _TASK_NAME = 'cleanup'

    def __init__(self):
        pass

    def process(self, buildContext):
        try:
            try:
                self._buildContext = buildContext
                self.log_info(buildContext, 'entering')
                self.precheck(buildContext)

                cmds = list()
                cmds.append('zypper clean --all')
                cmds.append('rm -f /var/lib/zypp/AnonymousUniqueId')
                cmds.append('rm -f /var/adm/backup/sysconfig/*gz')
                cmds.append('rm -f /var/adm/backup/rpmdb/Packages-*.gz')
                cmds.append('rm -f /var/lib/dbus/machine-id')
                cmds.append('rm -f /var/lib/dhcpcd/*')
                cmds.append('rm -f /etc/ssh/*key*')
                cmds.append('rm -f /var/run/*.pid')
                cmds.append('truncate -c /var/run/utmp --size 0')
                cmds.append('rm -rf /var/spool/mail/*')
                cmds.append('rm -rf /var/tmp/*')
                cmds.append('rm -rf /root/.ssh')
                cmds.append('truncate -c /root/.bash_history --size 0')
                cmds.append('truncate -c /var/log/lastlog /var/log/faillog /var/log/zypper.log /var/log/zypp/history --size 0')
                cmds.append('truncate -c /var/log/YaST2/*log --size 0')
                cmds.append('truncate -c /var/log/acpid /var/log/boot.* /var/log/btmp /var/log/wtmp /var/log/localmessages /var/log/messages /var/log/zypper.log --size 0')
                cmds.append('truncate -c /var/log/ConsoleKit/history /var/log/zypp/history --size 0')
                cmds.append('rm -f /root/.nano_history')
                cmds.append('rm -f /etc/resolv.conf')
                cmds.append('rm -rf /bootstrap')
                cmds.append('rm -rf /tmp/*')
                cmds.append("sed -i -e 's/.*download.opensuse.org/ /' /etc/hosts")

                self.exec_command_chroot(buildContext, cmds, checkReturnValue=False)

            except TaskError, taskErr:
                if taskErr.criticality == TaskError.ABORT_BUILD:
                    raise
                self.log_error(buildContext, taskErr)
        finally:
            self.log_info(buildContext, 'leaving')

    def onerr(self, buildContext, cmd, retval):
        pass

    def precheck(self, buildContext):
        self.is_valid_build_context(buildContext)
        self._zerofs = self.get_value('zerofs')

stacklet.util.objectify._XO_bootstrap = _XO_bootstrap
stacklet.util.objectify._XO_stk_bootstrap = _XO_bootstrap
stacklet.util.objectify._XO_cleanup = _XO_cleanup
stacklet.util.objectify._XO_stk_cleanup = _XO_cleanup
stacklet.util.objectify._XO_package = _XO_package
stacklet.util.objectify._XO_stk_package = _XO_package
stacklet.util.objectify._XO_service = _XO_service
stacklet.util.objectify._XO_stk_service = _XO_service