<commit_before>"""igcommit - Git routines

Copyright (c) 2016, InnoGames GmbH
"""

from subprocess import check_output, CalledProcessError, Popen, PIPE, STDOUT

from igcommit.utils import get_exe_path

git_exe_path = get_exe_path('git')

class CommitList(list):
    """Routines on a list of sequential commits"""
    ref_path = None

    def __init__(self, other, ref_path=None):
        super(CommitList, self).__init__(other)
        if ref_path:
            self.ref_path = ref_path

    def __str__(self):
        name = '{}..{}'.format(self[0], self[-1])
        if self.ref_path:
            name += ' ({})'.format(self.ref_path)
        return name

class Commit(object):
    """Routines on a single commit"""
    null_commit_id = '0000000000000000000000000000000000000000'

    def __init__(self, commit_id, commit_list=None):
        self.commit_id = commit_id
        self.commit_list = commit_list
        self._message = None
        self.changed_files = None

    def __str__(self):
        return self.commit_id[:8]

    def __bool__(self):
        return self.commit_id != Commit.null_commit_id

    def __nonzero__(self):
        return self.__bool__()

    def __eq__(self, other):
        return isinstance(other, Commit) and self.commit_id == other.commit_id

    def get_new_commit_list(self, ref_path):
        """Get the list of parent new commits in order"""
        output = check_output([
            git_exe_path,
            'rev-list',
            self.commit_id,
            '--not',
            '--all',
            '--reverse',
        ]).decode()
        commit_list = CommitList([], ref_path)
        for commit_id in output.splitlines():
            commit = Commit(commit_id, commit_list)
            commit_list.append(commit)
        return commit_list

    def _fetch_content(self):
        proc = Popen(
            [git_exe_path, 'cat-file', '-p', self.commit_id],
            stdout=PIPE,
        )
        line = None
        while line != b'':
            line = proc.stdout.readline().rstrip()
            if line.startswith(b'author '):
                self._author = Contribution.parse(line[len(b'author '):])
            if line.startswith(b'committer '):
                self._committer = Contribution.parse(line[len(b'committer '):])
        self._message = proc.stdout.read().decode('utf8')
        check_returncode(proc)

    def get_author(self):
        if not self._message:
            self._fetch_content()
        return self._author

    def get_committer(self):
        if not self._message:
            self._fetch_content()
        return self._committer

    def get_message(self):
        if not self._message:
            self._fetch_content()
        return self._message

    def get_summary(self):
        for line in self.get_message().splitlines():
            return line

    def parse_tags(self):
        tags = []
        rest = self.get_summary()
        while rest.startswith('[') and ']' in rest:
            end_index = rest.index(']')
            tags.append(rest[1:end_index])
            rest = rest[end_index + 1:]
        return tags, rest

    def content_can_fail(self):
        return any(
            t in ['HOTFIX', 'MESS', 'WIP'] for t in self.parse_tags()[0]
        )

    def get_changed_files(self):
        """Return the list of added or modified files on a commit"""
        if self.changed_files is None:
            output = check_output([
                git_exe_path,
                'diff-tree',
                '-r',
                '--no-commit-id',
                '--break-rewrites',     # Get rewrites as additions
                '--no-renames',         # Get renames as additions
                '--diff-filter=AM',     # Only additions and modifications
                self.commit_id,
            ]).decode()
            changed_files = []
            for line in output.splitlines():
                line_split = line.split()
                assert len(line_split) == 6
                assert line_split[0].startswith(':')
                file_mode = line_split[1]
                file_path = line_split[5]
                changed_files.append(CommittedFile(file_path, self, file_mode))
            self.changed_files = changed_files
        return self.changed_files

class Contribution(object):
    """Routines on contribution properties of a commit"""

    def __init__(self, name, email, timestamp):
        self.name = name
        self.email = email
        self.timestamp = timestamp

    @classmethod
    def parse(cls, line):
        """Parse the contribution line as bytes"""
        name, line = line.split(b' <', 1)
        email, line = line.split(b'> ', 1)
        timestamp, line = line.split(b' ', 1)
        return cls(name.decode('utf8'), email.decode(), int(timestamp))

class CommittedFile(object):
    """Routines on a single committed file"""

    def __init__(self, path, commit=None, mode=None):
        self.path = path
        self.commit = commit
        assert mode is None or len(mode) == 6
        self.mode = mode
        self._not_consumed_content_proc = None
        self._first_content_line = None

    def __str__(self):
        return '{} at {}'.format(self.path, self.commit)

    def __eq__(self, other):
        return (
            isinstance(other, CommittedFile) and
            self.path == other.path and
            self.commit == other.commit
        )

    def exists(self):
        return bool(check_output([
            git_exe_path,
            'ls-tree',
            '--name-only',
            '-r',
            self.commit.commit_id,
            self.path,
        ]))

    def changed(self):
        return self in self.commit.get_changed_files()

    def owner_can_execute(self):
        owner_bits = int(self.mode[-3])
        return bool(owner_bits & 1)

    def get_extension(self):
        if '.' in self.path:
            return self.path.rsplit('.', 1)[1]

    def _spawn_content_proc(self, stdout=PIPE):
        """Spawn and return git process to get file content"""
        return Popen((
            git_exe_path, 'show', self.commit.commit_id + ':' + self.path
        ), stdout=stdout)

    def _get_first_content_line(self):
        """Save and return the first line of the file content

        We need the first line of the content for the symlinks and the shebang
        of the scripts.  We are saving the first line and the process we have
        used to get the first line to be re-used by the other methods below.
        """
        if self._first_content_line is None:
            assert self._not_consumed_content_proc is None
            proc = self._spawn_content_proc()
            self._first_content_line = proc.stdout.readline()
            check_returncode(proc)
            self._not_consumed_content_proc = proc
        return self._first_content_line

    def get_symlink_target(self):
        """Check if the file is a symlink and return its target"""
        if self.mode[1] == '2':
            return self._get_first_content_line().strip()
        return None

    def get_shebang(self):
        """Get the shebang from the file content"""
        line = self._get_first_content_line()
        if line.startswith(b'#!'):
            return line[len(b'#!'):].decode()
        return None

    def get_exe(self):
        """Get the executable from the shebang"""
        shebang_split = self.get_shebang().split(None, 2)
        if not shebang_split:
            return ''
        if shebang_split[0] == '/usr/bin/env' and len(shebang_split) > 1:
            return shebang_split[1]
        return shebang_split[0].rsplit('/', 1)[-1]

    def get_content(self):
        """Get the file content as binary

        We have two ways of get the content.  If get_shebang() method is
        called before, we must have a file descriptor which has only the first
        line consumed.  We can consume the rest and append to the first line.
        """
        if self._not_consumed_content_proc is not None:
            assert self._first_content_line is not None
            content = self._first_content_line
            proc = self._not_consumed_content_proc
            self._not_consumed_content_proc = None
        else:
            content = b''
            proc = self._spawn_content_proc()

        content += proc.communicate()[0]
        check_returncode(proc)
        return content

    def pass_content(self, proc_args):
        """Pass the file content to another process

        We have two ways of passing the content.  If get_shebang() method is
        called before, we must have a file descriptor which has only the first
        line consumed.  In this case, we need to read and pass the content
        by ourselves.  If we don't yet have a file descriptor, we can take
        the shortcut and just pipe two processes together.

        It is not very nice to have two different ways of doing the same
        thing, though that is the most efficient method.  We would be wasting
        the Git content process by only reading the first line of it to get
        the shebang, if we wouldn't be using it in here.  Likewise things
        would be slower, if we buffer the content of every file in here
        to pass their content to the target process.  The shebang is only
        read for executable files, so most of the files should benefit from
        the optimization.  This is also an important optimization for
        parallelization.  The less we do on the main process, the more we can
        benefit from multiple cores on the system.  Before doing thing this
        way, I had the cute idea to pass the stdout of the content process
        we have used to get the shebang to the target process directly.  It
        was causing the line numbers on the syntax errors to be 1 off.
        """
        if self._not_consumed_content_proc is not None:
            content_proc = self._not_consumed_content_proc
            stdin = PIPE
        else:
            content_proc = self._spawn_content_proc()
            stdin = content_proc.stdout
        target_proc = Popen(proc_args, stdin=stdin, stdout=PIPE, stderr=STDOUT)
        # Someone has to check the output of the content process later.
        target_proc.content_proc = content_proc

        if self._not_consumed_content_proc is not None:
            stdin = target_proc.stdin
            stdin.write(self.get_content())
        stdin.close()   # Allow it to receive a SIGPIPE

        return target_proc

    def write(self):
        """Write the file contents to the location its supposed to be

        The Git pre-receive hooks can work on bare Git repositories.  Those
        repositories has no actual files, only the .git database.  We need
        to materialize the configuration files on their locations for
        the syntax checking tools to find them.  We are doing it efficiently
        by passing the writable file descriptor to the Git content process.
        """
        with open(self.path, 'wb') as fd:
            proc = self._spawn_content_proc(fd)
        proc.wait()
        check_returncode(proc)

def check_returncode(proc):
    if proc.returncode is None:
        proc.poll()
        if proc.returncode is None:
            return

    if proc.returncode != 0:
        raise CalledProcessError(proc.returncode, proc.args)
<commit_msg>Fix crash on executables without shebang again<commit_after>"""igcommit - Git routines

Copyright (c) 2016, InnoGames GmbH
"""

from subprocess import check_output, CalledProcessError, Popen, PIPE, STDOUT

from igcommit.utils import get_exe_path

git_exe_path = get_exe_path('git')

class CommitList(list):
    """Routines on a list of sequential commits"""
    ref_path = None

    def __init__(self, other, ref_path=None):
        super(CommitList, self).__init__(other)
        if ref_path:
            self.ref_path = ref_path

    def __str__(self):
        name = '{}..{}'.format(self[0], self[-1])
        if self.ref_path:
            name += ' ({})'.format(self.ref_path)
        return name

class Commit(object):
    """Routines on a single commit"""
    null_commit_id = '0000000000000000000000000000000000000000'

    def __init__(self, commit_id, commit_list=None):
        self.commit_id = commit_id
        self.commit_list = commit_list
        self._message = None
        self.changed_files = None

    def __str__(self):
        return self.commit_id[:8]

    def __bool__(self):
        return self.commit_id != Commit.null_commit_id

    def __nonzero__(self):
        return self.__bool__()

    def __eq__(self, other):
        return isinstance(other, Commit) and self.commit_id == other.commit_id

    def get_new_commit_list(self, ref_path):
        """Get the list of parent new commits in order"""
        output = check_output([
            git_exe_path,
            'rev-list',
            self.commit_id,
            '--not',
            '--all',
            '--reverse',
        ]).decode()
        commit_list = CommitList([], ref_path)
        for commit_id in output.splitlines():
            commit = Commit(commit_id, commit_list)
            commit_list.append(commit)
        return commit_list

    def _fetch_content(self):
        proc = Popen(
            [git_exe_path, 'cat-file', '-p', self.commit_id],
            stdout=PIPE,
        )
        line = None
        while line != b'':
            line = proc.stdout.readline().rstrip()
            if line.startswith(b'author '):
                self._author = Contribution.parse(line[len(b'author '):])
            if line.startswith(b'committer '):
                self._committer = Contribution.parse(line[len(b'committer '):])
        self._message = proc.stdout.read().decode('utf8')
        check_returncode(proc)

    def get_author(self):
        if not self._message:
            self._fetch_content()
        return self._author

    def get_committer(self):
        if not self._message:
            self._fetch_content()
        return self._committer

    def get_message(self):
        if not self._message:
            self._fetch_content()
        return self._message

    def get_summary(self):
        for line in self.get_message().splitlines():
            return line

    def parse_tags(self):
        tags = []
        rest = self.get_summary()
        while rest.startswith('[') and ']' in rest:
            end_index = rest.index(']')
            tags.append(rest[1:end_index])
            rest = rest[end_index + 1:]
        return tags, rest

    def content_can_fail(self):
        return any(
            t in ['HOTFIX', 'MESS', 'WIP'] for t in self.parse_tags()[0]
        )

    def get_changed_files(self):
        """Return the list of added or modified files on a commit"""
        if self.changed_files is None:
            output = check_output([
                git_exe_path,
                'diff-tree',
                '-r',
                '--no-commit-id',
                '--break-rewrites',     # Get rewrites as additions
                '--no-renames',         # Get renames as additions
                '--diff-filter=AM',     # Only additions and modifications
                self.commit_id,
            ]).decode()
            changed_files = []
            for line in output.splitlines():
                line_split = line.split()
                assert len(line_split) == 6
                assert line_split[0].startswith(':')
                file_mode = line_split[1]
                file_path = line_split[5]
                changed_files.append(CommittedFile(file_path, self, file_mode))
            self.changed_files = changed_files
        return self.changed_files

class Contribution(object):
    """Routines on contribution properties of a commit"""

    def __init__(self, name, email, timestamp):
        self.name = name
        self.email = email
        self.timestamp = timestamp

    @classmethod
    def parse(cls, line):
        """Parse the contribution line as bytes"""
        name, line = line.split(b' <', 1)
        email, line = line.split(b'> ', 1)
        timestamp, line = line.split(b' ', 1)
        return cls(name.decode('utf8'), email.decode(), int(timestamp))

class CommittedFile(object):
    """Routines on a single committed file"""

    def __init__(self, path, commit=None, mode=None):
        self.path = path
        self.commit = commit
        assert mode is None or len(mode) == 6
        self.mode = mode
        self._not_consumed_content_proc = None
        self._first_content_line = None

    def __str__(self):
        return '{} at {}'.format(self.path, self.commit)

    def __eq__(self, other):
        return (
            isinstance(other, CommittedFile) and
            self.path == other.path and
            self.commit == other.commit
        )

    def exists(self):
        return bool(check_output([
            git_exe_path,
            'ls-tree',
            '--name-only',
            '-r',
            self.commit.commit_id,
            self.path,
        ]))

    def changed(self):
        return self in self.commit.get_changed_files()

    def owner_can_execute(self):
        owner_bits = int(self.mode[-3])
        return bool(owner_bits & 1)

    def get_extension(self):
        if '.' in self.path:
            return self.path.rsplit('.', 1)[1]

    def _spawn_content_proc(self, stdout=PIPE):
        """Spawn and return git process to get file content"""
        return Popen((
            git_exe_path, 'show', self.commit.commit_id + ':' + self.path
        ), stdout=stdout)

    def _get_first_content_line(self):
        """Save and return the first line of the file content

        We need the first line of the content for the symlinks and the shebang
        of the scripts.  We are saving the first line and the process we have
        used to get the first line to be re-used by the other methods below.
        """
        if self._first_content_line is None:
            assert self._not_consumed_content_proc is None
            proc = self._spawn_content_proc()
            self._first_content_line = proc.stdout.readline()
            check_returncode(proc)
            self._not_consumed_content_proc = proc
        return self._first_content_line

    def get_symlink_target(self):
        """Check if the file is a symlink and return its target"""
        if self.mode[1] == '2':
            return self._get_first_content_line().strip()
        return None

    def get_shebang(self):
        """Get the shebang from the file content"""
        line = self._get_first_content_line()
        if line.startswith(b'#!'):
            return line[len(b'#!'):].decode()
        return ''

    def get_exe(self):
        """Get the executable from the shebang"""
        shebang_split = self.get_shebang().split(None, 2)
        if not shebang_split:
            return ''
        if shebang_split[0] == '/usr/bin/env' and len(shebang_split) > 1:
            return shebang_split[1]
        return shebang_split[0].rsplit('/', 1)[-1]

    def get_content(self):
        """Get the file content as binary

        We have two ways of get the content.  If get_shebang() method is
        called before, we must have a file descriptor which has only the first
        line consumed.  We can consume the rest and append to the first line.
        """
        if self._not_consumed_content_proc is not None:
            assert self._first_content_line is not None
            content = self._first_content_line
            proc = self._not_consumed_content_proc
            self._not_consumed_content_proc = None
        else:
            content = b''
            proc = self._spawn_content_proc()

        content += proc.communicate()[0]
        check_returncode(proc)
        return content

    def pass_content(self, proc_args):
        """Pass the file content to another process

        We have two ways of passing the content.  If get_shebang() method is
        called before, we must have a file descriptor which has only the first
        line consumed.  In this case, we need to read and pass the content
        by ourselves.  If we don't yet have a file descriptor, we can take
        the shortcut and just pipe two processes together.

        It is not very nice to have two different ways of doing the same
        thing, though that is the most efficient method.  We would be wasting
        the Git content process by only reading the first line of it to get
        the shebang, if we wouldn't be using it in here.  Likewise things
        would be slower, if we buffer the content of every file in here
        to pass their content to the target process.  The shebang is only
        read for executable files, so most of the files should benefit from
        the optimization.  This is also an important optimization for
        parallelization.  The less we do on the main process, the more we can
        benefit from multiple cores on the system.  Before doing thing this
        way, I had the cute idea to pass the stdout of the content process
        we have used to get the shebang to the target process directly.  It
        was causing the line numbers on the syntax errors to be 1 off.
        """
        if self._not_consumed_content_proc is not None:
            content_proc = self._not_consumed_content_proc
            stdin = PIPE
        else:
            content_proc = self._spawn_content_proc()
            stdin = content_proc.stdout
        target_proc = Popen(proc_args, stdin=stdin, stdout=PIPE, stderr=STDOUT)
        # Someone has to check the output of the content process later.
        target_proc.content_proc = content_proc

        if self._not_consumed_content_proc is not None:
            stdin = target_proc.stdin
            stdin.write(self.get_content())
        stdin.close()   # Allow it to receive a SIGPIPE

        return target_proc

    def write(self):
        """Write the file contents to the location its supposed to be

        The Git pre-receive hooks can work on bare Git repositories.  Those
        repositories has no actual files, only the .git database.  We need
        to materialize the configuration files on their locations for
        the syntax checking tools to find them.  We are doing it efficiently
        by passing the writable file descriptor to the Git content process.
        """
        with open(self.path, 'wb') as fd:
            proc = self._spawn_content_proc(fd)
        proc.wait()
        check_returncode(proc)

def check_returncode(proc):
    if proc.returncode is None:
        proc.poll()
        if proc.returncode is None:
            return

    if proc.returncode != 0:
        raise CalledProcessError(proc.returncode, proc.args)
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
#
# Copyright (C) 2012  Red Hat, Inc.
#
# This program 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 2.1 of the License, or
# (at your option) any later version.
#
# 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# Author: Fabian Deutsch <fabiand@fedoraproject.org>
#

import logging
import xmlrpclib
from string import Template

import testing

logger = logging.getLogger(__name__)

class CobblerOrigin(testing.Origin):
    cobbler = None
    kargs = None
    kargs_post = None
    def __init__(self, server_url, user, pw, kargs, kargs_post):
        self.cobbler = Cobbler(server_url, (user, pw))
        self.kargs = kargs
        self.kargs_post = kargs_post

    def name(self):
        return "CobblerOrigin(%s)" % self.cobbler.server_url

    def items(self):
        items = {}
        with self.cobbler.new_session() as cblr_sess:
            for cpname in cblr_sess.profiles():
                iprofile = self._igor_profile_from_cobbler_profile(cpname)
                items[cpname] = iprofile
        logger.debug("Number of profiles: %s" % len(items))
    #    logger.debug("Profiles: %s" % profiles)
        return items

    def _igor_profile_from_cobbler_profile(self, cprofile):
        self.cobbler.new_profile(cprofile, {
            "kernel_options": " ".join([self.kargs, self.kargs_post]),
            "kernel_options_post": self.kargs,
        })

    # def lookup could be done w/ cobbler native functions

    def create_item(self, pname, vmlinuz_file, initrd_file, kargs_file, \
                        kargs_post_file):
        profile = self.cobbler.new_profile(pname)
        profile.populate_with(vmlinuz_file, initrd_file, kargs_file, \
                              kargs_post_file)

#pydoc cobbler.remote
class Cobbler(object):
    """A simple wrapper around Cobbler's XMLRPC API.

    Cobbler also provides it's own python bindings but those are just
    distributed with all the other stuff. This small wrapper can be used
    as long as the bindigs are not split from the rest.
    """
    server_url = None
    server = None
    credentials = None

    def __init__(self, server_url, c=("cobbler", "cobbler")):
#        "http://cobbler-server.example.org/cobbler_api"
        self.credentials = c
        self.server_url = server_url
        self.server = xmlrpclib.Server(server_url)

    def new_session(self):
        return Cobbler.Session(self.server, self.credentials)

    def new_profile(self, profile_name, additional_args=None):
        return Cobbler.Profile(self.new_session, profile_name, \
                               additional_args)

    class Profile(testing.Profile):
        cobbler_session_cb = None
        name = None
        additional_args = None

        def __init__(self, cobbler_session_cb, profile_name, additional_args):
            self.cobbler_session_cb = cobbler_session_cb
            self.name = profile_name
            self.additional_args = additional_args

        def get_name(self):
            return self.name

        def assign_to(self, host):
            with self.cobbler_session_cb() as session:
                if self.name not in session.profiles():
                    logger.info("Available profiles: %s" % session.profiles())
                    raise Exception("Unknown profile '%s'" % (self.name))
                additional_args = {}
                for k, v in self.additional_args.items():
                    additional_args[k] = v.format(
                            igor_cookie=host.session.cookie
                        )
                session.add_system(host.get_name(), host.get_mac_address(), \
                                   self.name, additional_args)
                session.set_netboot_enable(host.get_name(), True)

        def enable_pxe(self, host, enable):
            with self.cobbler_session_cb() as session:
                session.set_netboot_enable(host.get_name(), enable)

        def set_kargs(self, host, kargs):
            raise Exception("Not yet implemented")
            #with self.cobbler_session_cb() as session:
                #session.modify_system(system_id,"kernel_options", v,
                #                      self.token)

        def revoke_from(self, host):
            name = host.get_name()
            logger.debug("Revoking host '%s' from cobbler " % name)
            with self.cobbler_session_cb() as session:
                if name in session.systems():
                    session.remove_system(name)
                else:
                    # Can happen if corresponding distro or profile was deleted
                    logger.info(("Unknown '%s' host when trying to revoke " + \
                                 "igor profile.") % name)

        def populate_with(self, vmlinuz, initrd, kargs, kargs_post=None):
            raise Exception("Not yet implemented")

    class Session:
        """Helper to login and sync
        """
        server = None
        credentials = None
        token = None

        def __init__(self, server, credentials):
            self.server = server
            self.credentials = credentials
            self.login()

        def __enter__(self):
            self.login()
            return self

        def __exit__(self, type, value, traceback):
            pass

        def login(self):
            logger.debug("Logging into cobbler")
            self.token = self.server.login(*(self.credentials))

        def sync(self):
            logger.debug("Syncing")
            self.server.sync(self.token)

        def add_system(self, name, mac, profile, additional_args=None):
            """Add a new system.
            """
            logger.debug("Adding system %s" % name)

            args = {
                "name": name,
                "mac": mac,
                "profile": profile,
                "status": "testing",
                "kernel_options": "",
                "kernel_options_post": "",
                "modify_interface": {
                    "macaddress-eth0": mac
                }
            }

            if additional_args is not None:
                logger.debug("Adding additional args: %s" % additional_args)
                args.update(additional_args)

            system_id = self.server.new_system(self.token)

            for k, v in args.items():
                logger.debug("Modifying system %s: %s=%s" % (name, k, v))
                self.server.modify_system(system_id, k, v, self.token)

            self.server.save_system(system_id, self.token)

        def set_netboot_enable(self, name, pxe):
            """(Un-)Set netboot.
            """
            args = {
                "netboot-enabled": 1 if pxe else 0
            }

            system_handle = self.server.get_system_handle(name, self.token)

            for k, v in args.items():
                logger.debug("Modifying system: %s %s" % (k, v))
                self.server.modify_system(system_handle, k, v, self.token)

            self.server.save_system(system_handle, self.token)

        def remove_system(self, name):
            self.server.remove_system(name, self.token)

        def profiles(self):
            return [e["name"] for e in self.server.get_profiles(self.token, \
                                                        1, 1000)]

        def systems(self):
            return [e["name"] for e in self.server.get_systems(self.token, \
                                                        1, 1000)]

def example():
    c = Cobbler("http://127.0.0.1/cobbler_api")
    s = c.new_session()
    print (s.systems())
    print (s.profiles())

    p = c.new_profile("abc")
<commit_msg>cobbler: Fix profile lookup<commit_after># -*- coding: utf-8 -*-
#
# Copyright (C) 2012  Red Hat, Inc.
#
# This program 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 2.1 of the License, or
# (at your option) any later version.
#
# 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# Author: Fabian Deutsch <fabiand@fedoraproject.org>
#

import logging
import xmlrpclib
from string import Template

import testing

logger = logging.getLogger(__name__)

class CobblerOrigin(testing.Origin):
    cobbler = None
    kargs = None
    kargs_post = None
    def __init__(self, server_url, user, pw, kargs, kargs_post):
        self.cobbler = Cobbler(server_url, (user, pw))
        self.kargs = kargs
        self.kargs_post = kargs_post

    def name(self):
        return "CobblerOrigin(%s)" % self.cobbler.server_url

    def items(self):
        items = {}
        with self.cobbler.new_session() as cblr_sess:
            for cpname in cblr_sess.profiles():
                iprofile = self._igor_profile_from_cobbler_profile(cpname)
                items[cpname] = iprofile
        logger.debug("Number of profiles: %s" % len(items))
#        logger.debug("Profiles: %s" % items)
        return items

    def _igor_profile_from_cobbler_profile(self, cprofile):
        return self.cobbler.new_profile(cprofile, {
            "kernel_options": " ".join([self.kargs, self.kargs_post]),
            "kernel_options_post": self.kargs,
        })

    # def lookup could be done w/ cobbler native functions

    def create_item(self, pname, vmlinuz_file, initrd_file, kargs_file, \
                        kargs_post_file):
        profile = self.cobbler.new_profile(pname)
        profile.populate_with(vmlinuz_file, initrd_file, kargs_file, \
                              kargs_post_file)

#pydoc cobbler.remote
class Cobbler(object):
    """A simple wrapper around Cobbler's XMLRPC API.

    Cobbler also provides it's own python bindings but those are just
    distributed with all the other stuff. This small wrapper can be used
    as long as the bindigs are not split from the rest.
    """
    server_url = None
    server = None
    credentials = None

    def __init__(self, server_url, c=("cobbler", "cobbler")):
#        "http://cobbler-server.example.org/cobbler_api"
        self.credentials = c
        self.server_url = server_url
        self.server = xmlrpclib.Server(server_url)

    def new_session(self):
        return Cobbler.Session(self.server, self.credentials)

    def new_profile(self, profile_name, additional_args=None):
        return Cobbler.Profile(self.new_session, profile_name, \
                               additional_args)

    class Profile(testing.Profile):
        cobbler_session_cb = None
        name = None
        additional_args = None

        def __init__(self, cobbler_session_cb, profile_name, additional_args):
            self.cobbler_session_cb = cobbler_session_cb
            self.name = profile_name
            self.additional_args = additional_args

        def get_name(self):
            return self.name

        def assign_to(self, host):
            with self.cobbler_session_cb() as session:
                if self.name not in session.profiles():
                    logger.info("Available profiles: %s" % session.profiles())
                    raise Exception("Unknown profile '%s'" % (self.name))
                additional_args = {}
                for k, v in self.additional_args.items():
                    additional_args[k] = v.format(
                            igor_cookie=host.session.cookie
                        )
                session.add_system(host.get_name(), host.get_mac_address(), \
                                   self.name, additional_args)
                session.set_netboot_enable(host.get_name(), True)

        def enable_pxe(self, host, enable):
            with self.cobbler_session_cb() as session:
                session.set_netboot_enable(host.get_name(), enable)

        def set_kargs(self, host, kargs):
            raise Exception("Not yet implemented")
            #with self.cobbler_session_cb() as session:
                #session.modify_system(system_id,"kernel_options", v,
                #                      self.token)

        def revoke_from(self, host):
            name = host.get_name()
            logger.debug("Revoking host '%s' from cobbler " % name)
            with self.cobbler_session_cb() as session:
                if name in session.systems():
                    session.remove_system(name)
                else:
                    # Can happen if corresponding distro or profile was deleted
                    logger.info(("Unknown '%s' host when trying to revoke " + \
                                 "igor profile.") % name)

        def populate_with(self, vmlinuz, initrd, kargs, kargs_post=None):
            raise Exception("Not yet implemented")

    class Session:
        """Helper to login and sync
        """
        server = None
        credentials = None
        token = None

        def __init__(self, server, credentials):
            self.server = server
            self.credentials = credentials
            self.login()

        def __enter__(self):
            self.login()
            return self

        def __exit__(self, type, value, traceback):
            pass

        def login(self):
            logger.debug("Logging into cobbler")
            self.token = self.server.login(*(self.credentials))

        def sync(self):
            logger.debug("Syncing")
            self.server.sync(self.token)

        def add_system(self, name, mac, profile, additional_args=None):
            """Add a new system.
            """
            logger.debug("Adding system %s" % name)

            args = {
                "name": name,
                "mac": mac,
                "profile": profile,
                "status": "testing",
                "kernel_options": "",
                "kernel_options_post": "",
                "modify_interface": {
                    "macaddress-eth0": mac
                }
            }

            if additional_args is not None:
                logger.debug("Adding additional args: %s" % additional_args)
                args.update(additional_args)

            system_id = self.server.new_system(self.token)

            for k, v in args.items():
                logger.debug("Modifying system %s: %s=%s" % (name, k, v))
                self.server.modify_system(system_id, k, v, self.token)

            self.server.save_system(system_id, self.token)

        def set_netboot_enable(self, name, pxe):
            """(Un-)Set netboot.
            """
            args = {
                "netboot-enabled": 1 if pxe else 0
            }

            system_handle = self.server.get_system_handle(name, self.token)

            for k, v in args.items():
                logger.debug("Modifying system: %s %s" % (k, v))
                self.server.modify_system(system_handle, k, v, self.token)

            self.server.save_system(system_handle, self.token)

        def remove_system(self, name):
            self.server.remove_system(name, self.token)

        def profiles(self):
            return [e["name"] for e in self.server.get_profiles(self.token, \
                                                        1, 1000)]

        def systems(self):
            return [e["name"] for e in self.server.get_systems(self.token, \
                                                        1, 1000)]

        def system(self, n):
            return self.server.get_system(self.token, n)["name"]

def example():
    c = Cobbler("http://127.0.0.1/cobbler_api")
    s = c.new_session()
    print (s.systems())
    print (s.profiles())

    p = c.new_profile("abc")
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
#
# Copyright (C) 2012  Red Hat, Inc.
#
# This program 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 2.1 of the License, or
# (at your option) any later version.
#
# 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# Author: Fabian Deutsch <fabiand@fedoraproject.org>
#

import logging
import xmlrpclib
import time
import os

import testing
import hosts
import utils

logger = logging.getLogger(__name__)

identification_tag = "managed-by-igor"

class ProfileOrigin(testing.Origin):
    """This is the source where igor retrieves cobbler profiles
    """
    cobbler = None

    def __init__(self, server_url, user, pw, ssh_uri):
        self.cobbler = Cobbler(server_url, (user, pw), ssh_uri)

    def name(self):
        return "CobblerProfilesOrigin(%s)" % self.cobbler.server_url

    def items(self):
        items = {}
        with self.cobbler as remote:
            for c_pname in remote.profiles():
                i_profile = Profile(remote, c_pname)
                i_profile.origin = self
                items[c_pname] = i_profile
        return items

    def create_item(self, pname, kernel_file, initrd_file, kargs_file):
        profile = Profile(self.cobbler, pname)
        profile.populate_with(kernel_file, initrd_file, kargs_file)

class HostsOrigin(testing.Origin):
    """This is the source where igor retrieves cobbler systems as hosts
    """
    cobbler = None
    expression = None
    whitelist = []

    def __init__(self, server_url, user, pw, ssh_uri, expression="igor-", whitelist=[]):
        self.cobbler = Cobbler(server_url, (user, pw), ssh_uri)
        self.expression = expression
        self.whitelist = whitelist

    def name(self):
        return "CobblerHostsOrigin(%s)" % self.cobbler.server_url

    def items(self):
        items = {}
        with self.cobbler as remote:
            for sysname in remote.systems():
                match = False
                if self.expression in sysname:
                    logger.debug("cobbler host '%s' matched expression" % \
                                                                       sysname)
                    match = True
                if sysname in self.__get_whitelist():
                    logger.debug("cobbler host '%s' is in whitelist" % sysname)
                    match = True
                if not match:
                    continue

                host = Host()
                host.remote = remote
                host.name = sysname
                host.origin = self
                try:
                    host.mac = remote.system(sysname)["mac_address_eth0"]
                except:
                    host.mac = ""
                items[sysname] = host
#        logger.debug("Number of cobbler hosts: %s" % len(items))
#        logger.debug("Hosts: %s" % items)
        return items

    def __get_whitelist(self):
        w = self.whitelist
        if type(w) is str:
            # w is actually a filename
            w = self.__read_whitelist(self.whitelist)
        return w

    def __read_whitelist(self, filename):
        whitelist = []
        with open(filename) as f:
             for line in f:
                line = line.strip()
                if line.startswith("#"):
                    # comment
                    pass
                else:
                    whitelist.append(line)
        return whitelist

class Host(hosts.RealHost):
    """Implemets the methods required by testing.Host
    """
    remote = None

    def get_name(self):
        """Just return the host part - if it's an fqdn
        This is done to prevent leaking more informations than necessary.
        but it has to be taken care of using the real name (host.name) when
        communicating with the remote cobbler server.
        """
        return self.name.split(".")[0]

    def start(self):
        logger.debug("Powering on %s" % self.name)
        with self.remote as s:
            s.power_system(self.name, "reboot")

    def purge(self):
        logger.debug("Powering off %s" % self.name)
        with self.remote as s:
            s.power_system(self.name, "off")

class Profile(testing.Profile):
    remote = None
    name = None
    additional_args = None

    system_existed = False
    previous_profile = None

    remote_path = None

    def __init__(self, remote, profile_name):
        self.remote = remote
        self.name = profile_name

    def get_name(self):
        return self.name

    def assign_to(self, host, additional_kargs=""):
        with self.remote as remote:
            if self.name not in remote.profiles():
                logger.info("Available profiles: %s" % remote.profiles())
                raise Exception("Unknown profile '%s'" % (self.name))

            system_handle = self.__get_or_create_system(remote, \
                                                        host.name)

            kargs_txt = (self.kargs() + " " + additional_kargs)
            kargs = {"kernel_options": kargs_txt.format(
                        igor_cookie=host.session.cookie)
                    }

            remote.assign_defaults(system_handle, \
                                    name=host.name, \
                                    mac=host.get_mac_address(), \
                                    profile=self.name, \
                                    additional_args=kargs)

            remote.set_netboot_enable(host.name, True)

    def __get_or_create_system(self, remote, name):
        system_handle = None
        if name in remote.systems():
            logger.info("Reusing existing system %s" % name)
            system_handle = remote.get_system_handle(name)
            logger.debug("System handle: %s" % system_handle)
            system = remote.system(name)
            logger.debug("System: %s" % system)
            if "profile" in system:
                self.previous_profile = system["profile"]
            self.system_existed = True
        else:
            system_handle = remote.new_system()
        return system_handle

    def kargs(self, kargs=None):
        n_kargs = None
        with self.remote as remote:
            if kargs:
                handle = remote.get_profile_handle(self.get_name())
                remote.modify_profile(handle, {
                        "kernel_options": kargs
                    })
            n_kargs = remote.profile(self.get_name())["kernel_options"]
        return n_kargs

    def enable_pxe(self, host, enable):
        with self.remote as remote:
            remote.set_netboot_enable(host.name, enable)

    def revoke_from(self, host):
        name = host.name
        logger.debug("Revoking host '%s' from cobbler " % name)
        with self.remote as remote:
            if name in remote.systems():
                if self.system_existed:
                    logger.info(("Not removing system %s because it " + \
                                 "existed before") % name)
                    system_handle = remote.get_system_handle(name)
                    remote.modify_system(system_handle, {
                        "profile": self.previous_profile
                    })
                else:
                    remote.remove_system(name)
            else:
                # Can happen if corresponding distro or profile was deleted
                logger.info(("Unknown '%s' host when trying to revoke " + \
                             "igor profile.") % name)

    def delete(self):
        self.remote_path = "/tmp/igor-cobbler-%s" % self.get_name()
        self.__ssh_remove_remote_distro_profile_and_files(self.remote_path)

    def populate_with(self, vmlinuz, initrd, kargs):
        self.remote_path = "/tmp/igor-cobbler-%s" % self.get_name()
        self.__scp_files_to_remote(self.remote_path, vmlinuz, initrd, kargs)
        self.__ssh_create_remote_distro_and_profile(self.remote_path, \
                                                    vmlinuz, initrd, kargs)

    def __scp_files_to_remote(self, remote_path, vmlinuz, initrd, kargs):
        cmd = """
            ssh {url} "mkdir -p '{remote_path}'"
            scp "{vmlinuz}" "{initrd}" "{kargs}" "{url}:/{remote_path}/"
        """.format(
                url=self.remote.ssh_uri,
                remote_path=remote_path,
                profilename=self.get_name(),
                vmlinuz=vmlinuz,
                initrd=initrd,
                kargs=kargs
                )
        utils.run(cmd)

    def __ssh_create_remote_distro_and_profile(self, remote_path, vmlinuz, \
                                               initrd, kargs):
        cmd = """
            ssh {remote_url} '
                cobbler distro add \\
                    --name=\"{profilename}-distro\" \\
                    --kernel=\"{vmlinuz}\" \\
                    --initrd=\"{initrd}\" \\
                    --arch=\"{arch}\" \\
                    --breed=\"other\" \\
                    --os-version=\"\" \\
                    --comment=\"{identification_tag}\"

                cobbler profile add \\
                    --name=\"{profilename}\" \\
                    --distro=\"{profilename}-distro\" \\
                    --kopts=\"$(cat {kargs})\" \\
                    --kickstart=\"\" \\
                    --repos=\"\" \\
                    --comment=\"{identification_tag}\"
                '
        """.format(
            remote_url=self.remote.ssh_uri,
            profilename=self.get_name(),
            vmlinuz=os.path.join(remote_path, os.path.basename(vmlinuz)),
            initrd=os.path.join(remote_path, os.path.basename(initrd)),
            kargs=os.path.join(remote_path, os.path.basename(kargs)),
#            kargs=open(kargs).read().strip(),
            arch="x86_64",
            identification_tag=identification_tag
            )
        utils.run(cmd)

    def __ssh_remove_remote_distro_profile_and_files(self, remote_path):
        profile_comment = self.remote.profile(self.get_name())["comment"]
        if identification_tag not in profile_comment:
            raise Exception("Profile '%s' is not managed by igor" % \
                            self.get_name())
        cmd = """
            ssh {remote_url} "
                cobbler distro remove --name=\"{profilename}-distro\"
                cobbler profile remove --name=\"{profilename}\"
                rm -v \"{remote_path}\"/*
                rmdir -v \"{remote_path}\"
                "
        """.format(
            remote_url=self.remote.ssh_uri,
            remote_path=remote_path,
            profilename=self.get_name()
            )
        utils.run(cmd)

#pydoc cobbler.remote
class Cobbler(object):
    """A simple wrapper around Cobbler's XMLRPC API.

    Cobbler also provides it's own python bindings but those are just
    distributed with all the other stuff. This small wrapper can be used
    as long as the bindigs are not split from the rest.
    """
    server_url = None
    server = None
    credentials = None
    ssh_uri = None
    token = None

#        "http://cobbler-server.example.org/cobbler_api"
    def __init__(self, server_url, c, ssh_uri):
        self.credentials = c
        self.server_url = server_url
        self.server = xmlrpclib.Server(server_url)
        self.ssh_uri= ssh_uri

    def __enter__(self):
        self.login()
        return self

    def __exit__(self, type, value, traceback):
        pass

    def login(self):
        self.token = self.server.login(*(self.credentials))
        return self

    def sync(self):
        logger.debug("Syncing")
        self.server.sync(self.token)

    def assign_defaults(self, system_handle, name, mac, profile, \
                        additional_args):
        args = {
            "name": name,
            "mac": mac,
            "profile": profile,
            "comment": identification_tag,
            "status": "testing",
            "kernel_options": "",
            "kernel_options_post": "",
            "modify_interface": {
                "macaddress-eth0": mac
            }
        }

        if additional_args is not None:
            logger.debug("Adding additional args: %s" % additional_args)
            args.update(additional_args)

        self.modify_system(system_handle, args)

    def new_system(self):
        """Add a new system.
        """
        logger.debug("Adding a new system")
        return self.server.new_system(self.token)

    def get_system_handle(self, name):
        return self.server.get_system_handle(name, self.token)

    def modify_system(self, system_handle, args):
        for k, v in args.items():
            logger.debug("Modifying system: %s=%s" % (k, v))
            self.server.modify_system(system_handle, k, v, self.token)
        self.server.save_system(system_handle, self.token)

    def get_profile_handle(self, name):
        return self.server.get_profile_handle(name, self.token)

    def modify_profile(self, profile_handle, args):
        for k, v in args.items():
            logger.debug("Modifying profile: %s=%s" % (k, v))
            self.server.modify_profile(profile_handle, k, v, self.token)
        self.server.save_profile(profile_handle, self.token)

    def set_netboot_enable(self, name, pxe):
        """(Un-)Set netboot.
        """
        args = {
            "netboot-enabled": 1 if pxe else 0
        }

        system_handle = self.get_system_handle(name)
        self.modify_system(system_handle, args)

    def remove_system(self, name):
        try:
            self.server.remove_system(name, self.token)
        except Exception as e:
            logger.warning("Exception while removing host: %s" % e.message)
            logger.warning("name: %s, token: %s" % (name, self.token))

    def profiles(self):
        return [e["name"] for e in self.server.get_profiles(self.token, \
                                                    1, 1000)]

    def profile(self, name):
        return self.server.get_blended_data(name, "")

    def systems(self):
        return [e["name"] for e in self.server.get_systems(self.token, \
                                                    1, 1000)]

    def system(self, name):
        return self.server.get_system(name, True)
        # Both have problems with None profiles:
#        return self.server.get_system_as_rendered(name)
#        return self.server.get_blended_data("", name)

    def power_system(self, name, power):
        assert power in ["on", "off", "reboot"]
        logger.debug("Setting power '%s' on '%s'" % (power, name))
        return self.server.background_power_system({
            "power": power,
            "systems": name
            }, self.token)

def example():
    c = Cobbler("http://127.0.0.1/cobbler_api")
    c.login()
    print (s.systems())
    print (s.profiles())

    p = c.new_profile("abc")
<commit_msg>cobbler: Debug for system add<commit_after># -*- coding: utf-8 -*-
#
# Copyright (C) 2012  Red Hat, Inc.
#
# This program 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 2.1 of the License, or
# (at your option) any later version.
#
# 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# Author: Fabian Deutsch <fabiand@fedoraproject.org>
#

import logging
import xmlrpclib
import time
import os

import testing
import hosts
import utils

logger = logging.getLogger(__name__)

identification_tag = "managed-by-igor"

class ProfileOrigin(testing.Origin):
    """This is the source where igor retrieves cobbler profiles
    """
    cobbler = None

    def __init__(self, server_url, user, pw, ssh_uri):
        self.cobbler = Cobbler(server_url, (user, pw), ssh_uri)

    def name(self):
        return "CobblerProfilesOrigin(%s)" % self.cobbler.server_url

    def items(self):
        items = {}
        with self.cobbler as remote:
            for c_pname in remote.profiles():
                i_profile = Profile(remote, c_pname)
                i_profile.origin = self
                items[c_pname] = i_profile
        return items

    def create_item(self, pname, kernel_file, initrd_file, kargs_file):
        profile = Profile(self.cobbler, pname)
        profile.populate_with(kernel_file, initrd_file, kargs_file)

class HostsOrigin(testing.Origin):
    """This is the source where igor retrieves cobbler systems as hosts
    """
    cobbler = None
    expression = None
    whitelist = []

    def __init__(self, server_url, user, pw, ssh_uri, expression="igor-", whitelist=[]):
        self.cobbler = Cobbler(server_url, (user, pw), ssh_uri)
        self.expression = expression
        self.whitelist = whitelist

    def name(self):
        return "CobblerHostsOrigin(%s)" % self.cobbler.server_url

    def items(self):
        items = {}
        with self.cobbler as remote:
            for sysname in remote.systems():
                match = False
                if self.expression in sysname:
                    logger.debug("cobbler host '%s' matched expression" % \
                                                                       sysname)
                    match = True
                if sysname in self.__get_whitelist():
                    logger.debug("cobbler host '%s' is in whitelist" % sysname)
                    match = True
                if not match:
                    continue

                host = Host()
                host.remote = remote
                host.name = sysname
                host.origin = self
                try:
                    host.mac = remote.system(sysname)["mac_address_eth0"]
                except:
                    host.mac = ""
                items[sysname] = host
#        logger.debug("Number of cobbler hosts: %s" % len(items))
#        logger.debug("Hosts: %s" % items)
        return items

    def __get_whitelist(self):
        w = self.whitelist
        if type(w) is str:
            # w is actually a filename
            w = self.__read_whitelist(self.whitelist)
        return w

    def __read_whitelist(self, filename):
        whitelist = []
        with open(filename) as f:
             for line in f:
                line = line.strip()
                if line.startswith("#"):
                    # comment
                    pass
                else:
                    whitelist.append(line)
        return whitelist

class Host(hosts.RealHost):
    """Implemets the methods required by testing.Host
    """
    remote = None

    def get_name(self):
        """Just return the host part - if it's an fqdn
        This is done to prevent leaking more informations than necessary.
        but it has to be taken care of using the real name (host.name) when
        communicating with the remote cobbler server.
        """
        return self.name.split(".")[0]

    def start(self):
        logger.debug("Powering on %s" % self.name)
        with self.remote as s:
            s.power_system(self.name, "reboot")

    def purge(self):
        logger.debug("Powering off %s" % self.name)
        with self.remote as s:
            s.power_system(self.name, "off")

class Profile(testing.Profile):
    remote = None
    name = None
    additional_args = None

    system_existed = False
    previous_profile = None

    remote_path = None

    def __init__(self, remote, profile_name):
        self.remote = remote
        self.name = profile_name

    def get_name(self):
        return self.name

    def assign_to(self, host, additional_kargs=""):
        with self.remote as remote:
            if self.name not in remote.profiles():
                logger.info("Available profiles: %s" % remote.profiles())
                raise Exception("Unknown profile '%s'" % (self.name))

            system_handle = self.__get_or_create_system(remote, \
                                                        host.name)

            kargs_txt = (self.kargs() + " " + additional_kargs)
            kargs = {"kernel_options": kargs_txt.format(
                        igor_cookie=host.session.cookie)
                    }

            remote.assign_defaults(system_handle, \
                                    name=host.name, \
                                    mac=host.get_mac_address(), \
                                    profile=self.name, \
                                    additional_args=kargs)

            remote.set_netboot_enable(host.name, True)

    def __get_or_create_system(self, remote, name):
        system_handle = None
        if name in remote.systems():
            logger.info("Reusing existing system %s" % name)
            system_handle = remote.get_system_handle(name)
            logger.debug("System handle: %s" % system_handle)
            system = remote.system(name)
            logger.debug("System: %s" % system)
            if "profile" in system:
                self.previous_profile = system["profile"]
            self.system_existed = True
        else:
            logger.info("Creating new system %s" % name)
            system_handle = remote.new_system()
            remote.modify_profile(system_handle, {
                    "name": name
                })
        return system_handle

    def kargs(self, kargs=None):
        n_kargs = None
        with self.remote as remote:
            if kargs:
                handle = remote.get_profile_handle(self.get_name())
                remote.modify_profile(handle, {
                        "kernel_options": kargs
                    })
            n_kargs = remote.profile(self.get_name())["kernel_options"]
        return n_kargs

    def enable_pxe(self, host, enable):
        with self.remote as remote:
            remote.set_netboot_enable(host.name, enable)

    def revoke_from(self, host):
        name = host.name
        logger.debug("Revoking host '%s' from cobbler " % name)
        with self.remote as remote:
            if name in remote.systems():
                if self.system_existed:
                    logger.info(("Not removing system %s because it " + \
                                 "existed before") % name)
                    system_handle = remote.get_system_handle(name)
                    remote.modify_system(system_handle, {
                        "profile": self.previous_profile
                    })
                else:
                    remote.remove_system(name)
            else:
                # Can happen if corresponding distro or profile was deleted
                logger.info(("Unknown '%s' host when trying to revoke " + \
                             "igor profile.") % name)

    def delete(self):
        self.remote_path = "/tmp/igor-cobbler-%s" % self.get_name()
        self.__ssh_remove_remote_distro_profile_and_files(self.remote_path)

    def populate_with(self, vmlinuz, initrd, kargs):
        self.remote_path = "/tmp/igor-cobbler-%s" % self.get_name()
        self.__scp_files_to_remote(self.remote_path, vmlinuz, initrd, kargs)
        self.__ssh_create_remote_distro_and_profile(self.remote_path, \
                                                    vmlinuz, initrd, kargs)

    def __scp_files_to_remote(self, remote_path, vmlinuz, initrd, kargs):
        cmd = """
            ssh {url} "mkdir -p '{remote_path}'"
            scp "{vmlinuz}" "{initrd}" "{kargs}" "{url}:/{remote_path}/"
        """.format(
                url=self.remote.ssh_uri,
                remote_path=remote_path,
                profilename=self.get_name(),
                vmlinuz=vmlinuz,
                initrd=initrd,
                kargs=kargs
                )
        utils.run(cmd)

    def __ssh_create_remote_distro_and_profile(self, remote_path, vmlinuz, \
                                               initrd, kargs):
        cmd = """
            ssh {remote_url} '
                cobbler distro add \\
                    --name=\"{profilename}-distro\" \\
                    --kernel=\"{vmlinuz}\" \\
                    --initrd=\"{initrd}\" \\
                    --arch=\"{arch}\" \\
                    --breed=\"other\" \\
                    --os-version=\"\" \\
                    --comment=\"{identification_tag}\"

                cobbler profile add \\
                    --name=\"{profilename}\" \\
                    --distro=\"{profilename}-distro\" \\
                    --kopts=\"$(cat {kargs})\" \\
                    --kickstart=\"\" \\
                    --repos=\"\" \\
                    --comment=\"{identification_tag}\"
                '
        """.format(
            remote_url=self.remote.ssh_uri,
            profilename=self.get_name(),
            vmlinuz=os.path.join(remote_path, os.path.basename(vmlinuz)),
            initrd=os.path.join(remote_path, os.path.basename(initrd)),
            kargs=os.path.join(remote_path, os.path.basename(kargs)),
#            kargs=open(kargs).read().strip(),
            arch="x86_64",
            identification_tag=identification_tag
            )
        utils.run(cmd)

    def __ssh_remove_remote_distro_profile_and_files(self, remote_path):
        profile_comment = self.remote.profile(self.get_name())["comment"]
        if identification_tag not in profile_comment:
            raise Exception("Profile '%s' is not managed by igor" % \
                            self.get_name())
        cmd = """
            ssh {remote_url} "
                cobbler distro remove --name=\"{profilename}-distro\"
                cobbler profile remove --name=\"{profilename}\"
                rm -v \"{remote_path}\"/*
                rmdir -v \"{remote_path}\"
                "
        """.format(
            remote_url=self.remote.ssh_uri,
            remote_path=remote_path,
            profilename=self.get_name()
            )
        utils.run(cmd)

#pydoc cobbler.remote
class Cobbler(object):
    """A simple wrapper around Cobbler's XMLRPC API.

    Cobbler also provides it's own python bindings but those are just
    distributed with all the other stuff. This small wrapper can be used
    as long as the bindigs are not split from the rest.
    """
    server_url = None
    server = None
    credentials = None
    ssh_uri = None
    token = None

#        "http://cobbler-server.example.org/cobbler_api"
    def __init__(self, server_url, c, ssh_uri):
        self.credentials = c
        self.server_url = server_url
        self.server = xmlrpclib.Server(server_url)
        self.ssh_uri= ssh_uri

    def __enter__(self):
        self.login()
        return self

    def __exit__(self, type, value, traceback):
        pass

    def login(self):
        self.token = self.server.login(*(self.credentials))
        return self

    def sync(self):
        logger.debug("Syncing")
        self.server.sync(self.token)

    def assign_defaults(self, system_handle, name, mac, profile, \
                        additional_args):
        args = {
            "name": name,
            "mac": mac,
            "profile": profile,
            "comment": identification_tag,
            "status": "testing",
            "kernel_options": "",
            "kernel_options_post": "",
            "modify_interface": {
                "macaddress-eth0": mac
            }
        }

        if additional_args is not None:
            logger.debug("Adding additional args: %s" % additional_args)
            args.update(additional_args)

        self.modify_system(system_handle, args)

    def new_system(self):
        """Add a new system.
        """
        logger.debug("Adding a new system")
        return self.server.new_system(self.token)

    def get_system_handle(self, name):
        return self.server.get_system_handle(name, self.token)

    def modify_system(self, system_handle, args):
        for k, v in args.items():
            logger.debug("Modifying system: %s=%s" % (k, v))
            self.server.modify_system(system_handle, k, v, self.token)
        self.server.save_system(system_handle, self.token)

    def get_profile_handle(self, name):
        return self.server.get_profile_handle(name, self.token)

    def modify_profile(self, profile_handle, args):
        for k, v in args.items():
            logger.debug("Modifying profile: %s=%s" % (k, v))
            self.server.modify_profile(profile_handle, k, v, self.token)
        self.server.save_profile(profile_handle, self.token)

    def set_netboot_enable(self, name, pxe):
        """(Un-)Set netboot.
        """
        args = {
            "netboot-enabled": 1 if pxe else 0
        }

        system_handle = self.get_system_handle(name)
        self.modify_system(system_handle, args)

    def remove_system(self, name):
        try:
            self.server.remove_system(name, self.token)
        except Exception as e:
            logger.warning("Exception while removing host: %s" % e.message)
            logger.warning("name: %s, token: %s" % (name, self.token))

    def profiles(self):
        return [e["name"] for e in self.server.get_profiles(self.token, \
                                                    1, 1000)]

    def profile(self, name):
        return self.server.get_blended_data(name, "")

    def systems(self):
        return [e["name"] for e in self.server.get_systems(self.token, \
                                                    1, 1000)]

    def system(self, name):
        return self.server.get_system(name, True)
        # Both have problems with None profiles:
#        return self.server.get_system_as_rendered(name)
#        return self.server.get_blended_data("", name)

    def power_system(self, name, power):
        assert power in ["on", "off", "reboot"]
        logger.debug("Setting power '%s' on '%s'" % (power, name))
        return self.server.background_power_system({
            "power": power,
            "systems": name
            }, self.token)

def example():
    c = Cobbler("http://127.0.0.1/cobbler_api")
    c.login()
    print (s.systems())
    print (s.profiles())

    p = c.new_profile("abc")
<|endoftext|>


<commit_before>"""
LocalQServer
"""

import time
import threading
from localq.status import Status
import networkx as nx
import os
from localq.job_prioritizer import JobPrioritizer as Prioritizer
from localq.job import Job

class LocalQServer():
    def __init__(self, num_cores_available, interval, priority_method="fifo", use_shell=False):
        self.num_cores_available = int(num_cores_available)
        self.interval = float(interval)
        self.priority_method = priority_method
        self._last_jobid = 0
        self.use_shell = use_shell
        self.graph = nx.MultiDiGraph()
        self.pid = os.getpid()

    def get_pid(self):
        return self.pid

    def add(self, cmd, num_cores, rundir=".", stdout=None, stderr=None, name=None, dependencies=None):
        """
        Add a job to the queue. Dependencies need to be empty or all have status COMPLETED for
        a job to run.
        :param cmd: Command line string or list of command parts
        :param num_cores: Number of cores needed for this job
        :param rundir: Directory where to run the job
        :param stdout: Filename to write stdout
        :param stderr: Filename to write stderr
        :param name: Job name
        :param dependencies: List of job ids of dependencies.
        :return: job ID if successfully submitted. None if number of requested cores
        is bigger the server's total core count.
        """

        job_id = self._get_new_jobid()
        job = Job(job_id, cmd, num_cores, stdout=stdout, stderr=stderr,
                  rundir=rundir, name=name, use_shell=self.use_shell,
                  dependencies=dependencies)

        # if number of requested cores is bigger then the number of cores available to the system, fail submission.
        if job.num_cores > self.num_cores_available:
            return None
        else:
            self.graph.add_node(job)
            # If we have no dependencies get an empty list.
            for dependency_job_id in dependencies or []:
                dependency_job = self.get_job_with_id(dependency_job_id)
                self.graph.add_edge(dependency_job, job)
            return job.jobid

    def add_script(self, script, num_cores, rundir=".", stdout=None, stderr=None, name=None, dependencies=None):
        cmd = ["sh", script]
        return self.add(cmd, num_cores, stdout=stdout, stderr=stderr, rundir=rundir, name=name,
                        dependencies=dependencies)

    def _get_new_jobid(self):
        """
        Method to get a new jobid to use for a job. Increments internal jobid tracker variable each time it's called.
        :return: a job id (int)
        """
        self._last_jobid += 1
        return self._last_jobid

    def get_status_all(self):
        """
        Get the status of all know jobs.
        :return: a dict with job-ids as keys, and their status as values.
        """
        jobs_and_status = map(lambda job: (job.jobid, job.status()), self.graph.nodes())
        return dict(jobs_and_status)

    def list_queue(self):
        retstr = "\t".join(["JOBID", "PRIO", "STATUS", "NUM_CORES", "START_TIME", "END_TIME", "NAME", "CMD", "\n"])
        for j in self.jobs():
            retstr += j.info() + "\n"
        return retstr.strip("\n")

    def jobs(self):
        """
        get all jobs
        :return: a list of all jobs added to this server
        """
        return self.graph.nodes()

    def get_status(self, jobid):
        """
        Check in which state (list) a job is.
        :param jobid: a job id to search for
        :return: Any of Status.PENDING, RUNNING, COMPLETED or FAILED.
        If no job with the given ID is found, Status.NOT_FOUND is returned.
        """
        job = self.get_job_with_id(int(jobid))
        if job:
            return job.status()
        else:
            return Status.NOT_FOUND

    def stop_job_with_id(self, jobid):
        """
        stop a job with a give ID
        :param jobid: Job ID of job to stop
        :return: the jobs new ID after it was tried to be stopped (None if it can not find the job)
        """
        job = self.get_job_with_id(int(jobid))
        if job:
            job.kill()
            return jobid
        else:
            return None

    def stop_all_jobs(self):
        for job in self.jobs():
            print "Sending SIGTERM to " + str(job.jobid)
            job.kill()

    def _prioritize(self, pending_jobs):
        use_this_prioritzer = getattr(Prioritizer, self.priority_method)
        return use_this_prioritzer(
            pending_jobs,
            cores_available=self.num_cores_available,
            cores_idle=self.cores_idle())

    def cores_idle(self):
        n_cores_busy = self.get_n_cores_booked()
        return self.num_cores_available - n_cores_busy

    def get_n_cores_booked(self):
        n_cores_booked = 0
        running_jobs = [j for j in self.jobs() if j.status() == Status.RUNNING]
        for job in running_jobs:
            n_cores_booked += job.num_cores
        return n_cores_booked

    def run(self):
        print "Starting localqd with {0} available cores".format(self.num_cores_available)
        print "Checking queue every {0} seconds".format(self.interval)

        def check_queue():
            while True:
                pending_jobs = self.get_runnable_jobs()
                prioritized_jobs = self._prioritize(pending_jobs)

                # check if new jobs can be started
                for job in prioritized_jobs:
                    if self.cores_idle() >= job.num_cores:
                        job.run()
                    else:  # If highest-priority job can't start, don't try to start next job until next cycle
                        continue

                time.sleep(self.interval)

        thread = threading.Thread(target=check_queue)
        thread.setDaemon(True)
        thread.start()

    def wait(self):
        while True:
            time.sleep(self.interval)
            keep_alive = False
            s = self.get_status_all()
            for jobid in s:
                # if any job has status running or pending,
                # the pipeline needs to run
                if s[jobid] in [Status.RUNNING, Status.PENDING]:
                    keep_alive = True

            if not keep_alive:
                break

    def get_server_cores(self):
        """
        :return: Number of cores available to the server
        """
        return self.num_cores_available

    def write_dot(self, f):
        """ Write a dot file with the pipeline graph
        :param f: file to write
        :return: None
        """
        nx.write_dot(self.graph, f)

    def get_ordered_jobs(self):
        """ Method to order the tasks in the pipeline
        :return: An array of paths for the runner to run
        """
        if not nx.is_directed_acyclic_graph(self.graph):
            raise Exception("The submitted pipeline is not a DAG. Check the pipeline for loops.")

        ordered_jobs = nx.topological_sort(self.graph)
        return ordered_jobs

    def get_runnable_jobs(self):
        """
        Get a list of pending jobs that are ready to be run based on dependencies
        :return: List of Jobs
        """
        pending_jobs = [j for j in self.get_ordered_jobs() if j.status() == Status.PENDING]
        ready_jobs = []
        for job in pending_jobs:
            # if there are no dependencies, the job is always ready
            if not job.dependencies:
                ready_jobs.append(job)
            else:
                # get a list containing the status of all dependencies
                dependency_status = [self.get_status(depid) for depid in job.dependencies]
                # if a uniqiefied list contains a single element, and that element is "COMPLETED"
                # then the job is ready
                if len(set(dependency_status)) == 1 and dependency_status[0] == Status.COMPLETED:
                    ready_jobs.append(job)
        return ready_jobs

    def get_node_with_name(self, name_to_find):
        """
        Function to find a node in a graph by it's taskname
        :param name_to_find: name of task to get
        :return: the Job if it's found, None otherwise
        """
        for job in self.jobs():
            if job.name == name_to_find:
                return job
        return None

    def get_job_with_id(self, jobid_to_find):
        """
        Function to find a node in a graph by it's job id
        :param jobid_to_find: name of task to get
        :return: the Job if it's  found, None otherwise
        """
        for job in self.jobs():
            if job.jobid == jobid_to_find:
                return job
        return None
<commit_msg>write log messaged to stderr when starting localq<commit_after>"""
LocalQServer
"""

import time
import threading

import sys

from localq.status import Status
import networkx as nx
import os
from localq.job_prioritizer import JobPrioritizer as Prioritizer
from localq.job import Job

class LocalQServer():
    def __init__(self, num_cores_available, interval, priority_method="fifo", use_shell=False):
        self.num_cores_available = int(num_cores_available)
        self.interval = float(interval)
        self.priority_method = priority_method
        self._last_jobid = 0
        self.use_shell = use_shell
        self.graph = nx.MultiDiGraph()
        self.pid = os.getpid()

    def get_pid(self):
        return self.pid

    def add(self, cmd, num_cores, rundir=".", stdout=None, stderr=None, name=None, dependencies=None):
        """
        Add a job to the queue. Dependencies need to be empty or all have status COMPLETED for
        a job to run.
        :param cmd: Command line string or list of command parts
        :param num_cores: Number of cores needed for this job
        :param rundir: Directory where to run the job
        :param stdout: Filename to write stdout
        :param stderr: Filename to write stderr
        :param name: Job name
        :param dependencies: List of job ids of dependencies.
        :return: job ID if successfully submitted. None if number of requested cores
        is bigger the server's total core count.
        """

        job_id = self._get_new_jobid()
        job = Job(job_id, cmd, num_cores, stdout=stdout, stderr=stderr,
                  rundir=rundir, name=name, use_shell=self.use_shell,
                  dependencies=dependencies)

        # if number of requested cores is bigger then the number of cores available to the system, fail submission.
        if job.num_cores > self.num_cores_available:
            return None
        else:
            self.graph.add_node(job)
            # If we have no dependencies get an empty list.
            for dependency_job_id in dependencies or []:
                dependency_job = self.get_job_with_id(dependency_job_id)
                self.graph.add_edge(dependency_job, job)
            return job.jobid

    def add_script(self, script, num_cores, rundir=".", stdout=None, stderr=None, name=None, dependencies=None):
        cmd = ["sh", script]
        return self.add(cmd, num_cores, stdout=stdout, stderr=stderr, rundir=rundir, name=name,
                        dependencies=dependencies)

    def _get_new_jobid(self):
        """
        Method to get a new jobid to use for a job. Increments internal jobid tracker variable each time it's called.
        :return: a job id (int)
        """
        self._last_jobid += 1
        return self._last_jobid

    def get_status_all(self):
        """
        Get the status of all know jobs.
        :return: a dict with job-ids as keys, and their status as values.
        """
        jobs_and_status = map(lambda job: (job.jobid, job.status()), self.graph.nodes())
        return dict(jobs_and_status)

    def list_queue(self):
        retstr = "\t".join(["JOBID", "PRIO", "STATUS", "NUM_CORES", "START_TIME", "END_TIME", "NAME", "CMD", "\n"])
        for j in self.jobs():
            retstr += j.info() + "\n"
        return retstr.strip("\n")

    def jobs(self):
        """
        get all jobs
        :return: a list of all jobs added to this server
        """
        return self.graph.nodes()

    def get_status(self, jobid):
        """
        Check in which state (list) a job is.
        :param jobid: a job id to search for
        :return: Any of Status.PENDING, RUNNING, COMPLETED or FAILED.
        If no job with the given ID is found, Status.NOT_FOUND is returned.
        """
        job = self.get_job_with_id(int(jobid))
        if job:
            return job.status()
        else:
            return Status.NOT_FOUND

    def stop_job_with_id(self, jobid):
        """
        stop a job with a give ID
        :param jobid: Job ID of job to stop
        :return: the jobs new ID after it was tried to be stopped (None if it can not find the job)
        """
        job = self.get_job_with_id(int(jobid))
        if job:
            job.kill()
            return jobid
        else:
            return None

    def stop_all_jobs(self):
        for job in self.jobs():
            print "Sending SIGTERM to " + str(job.jobid)
            job.kill()

    def _prioritize(self, pending_jobs):
        use_this_prioritzer = getattr(Prioritizer, self.priority_method)
        return use_this_prioritzer(
            pending_jobs,
            cores_available=self.num_cores_available,
            cores_idle=self.cores_idle())

    def cores_idle(self):
        n_cores_busy = self.get_n_cores_booked()
        return self.num_cores_available - n_cores_busy

    def get_n_cores_booked(self):
        n_cores_booked = 0
        running_jobs = [j for j in self.jobs() if j.status() == Status.RUNNING]
        for job in running_jobs:
            n_cores_booked += job.num_cores
        return n_cores_booked

    def run(self):
        sys.stderr.write("Starting localqd with {0} available cores.\n".format(self.num_cores_available))
        sys.stderr.write("Checking queue every {0} seconds\n\n".format(self.interval))

        def check_queue():
            while True:
                pending_jobs = self.get_runnable_jobs()
                prioritized_jobs = self._prioritize(pending_jobs)

                # check if new jobs can be started
                for job in prioritized_jobs:
                    if self.cores_idle() >= job.num_cores:
                        job.run()
                    else:  # If highest-priority job can't start, don't try to start next job until next cycle
                        continue

                time.sleep(self.interval)

        thread = threading.Thread(target=check_queue)
        thread.setDaemon(True)
        thread.start()

    def wait(self):
        while True:
            time.sleep(self.interval)
            keep_alive = False
            s = self.get_status_all()
            for jobid in s:
                # if any job has status running or pending,
                # the pipeline needs to run
                if s[jobid] in [Status.RUNNING, Status.PENDING]:
                    keep_alive = True

            if not keep_alive:
                break

    def get_server_cores(self):
        """
        :return: Number of cores available to the server
        """
        return self.num_cores_available

    def write_dot(self, f):
        """ Write a dot file with the pipeline graph
        :param f: file to write
        :return: None
        """
        nx.write_dot(self.graph, f)

    def get_ordered_jobs(self):
        """ Method to order the tasks in the pipeline
        :return: An array of paths for the runner to run
        """
        if not nx.is_directed_acyclic_graph(self.graph):
            raise Exception("The submitted pipeline is not a DAG. Check the pipeline for loops.")

        ordered_jobs = nx.topological_sort(self.graph)
        return ordered_jobs

    def get_runnable_jobs(self):
        """
        Get a list of pending jobs that are ready to be run based on dependencies
        :return: List of Jobs
        """
        pending_jobs = [j for j in self.get_ordered_jobs() if j.status() == Status.PENDING]
        ready_jobs = []
        for job in pending_jobs:
            # if there are no dependencies, the job is always ready
            if not job.dependencies:
                ready_jobs.append(job)
            else:
                # get a list containing the status of all dependencies
                dependency_status = [self.get_status(depid) for depid in job.dependencies]
                # if a uniqiefied list contains a single element, and that element is "COMPLETED"
                # then the job is ready
                if len(set(dependency_status)) == 1 and dependency_status[0] == Status.COMPLETED:
                    ready_jobs.append(job)
        return ready_jobs

    def get_node_with_name(self, name_to_find):
        """
        Function to find a node in a graph by it's taskname
        :param name_to_find: name of task to get
        :return: the Job if it's found, None otherwise
        """
        for job in self.jobs():
            if job.name == name_to_find:
                return job
        return None

    def get_job_with_id(self, jobid_to_find):
        """
        Function to find a node in a graph by it's job id
        :param jobid_to_find: name of task to get
        :return: the Job if it's  found, None otherwise
        """
        for job in self.jobs():
            if job.jobid == jobid_to_find:
                return job
        return None
<|endoftext|>


<commit_before>from __future__ import unicode_literals

from django.contrib import admin
from django.contrib.admin.models import LogEntry, ADDITION, CHANGE, DELETION
from django.contrib.contenttypes.models import ContentType
from django.core.urlresolvers import reverse, NoReverseMatch
from django.utils.encoding import force_text
from django.utils.html import escape
from django.utils.translation import ugettext_lazy as _

try:
    from django.contrib.auth import get_user_model
    User = get_user_model()
except ImportError:
    from django.contrib.auth.models import User  # noqa

action_names = {
    ADDITION: _('Addition'),
    DELETION: _('Deletion'),
    CHANGE: _('Change'),
}

class ActionListFilter(admin.SimpleListFilter):
    title = _('Action')
    parameter_name = 'action_flag'

    def lookups(self, request, model_admin):
        return action_names.items()

    def queryset(self, request, queryset):
        if self.value():
            queryset = queryset.filter(action_flag=self.value())
        return queryset

class UserListFilter(admin.SimpleListFilter):
    title = _('staff user')
    parameter_name = 'user'

    def lookups(self, request, model_admin):
        staff = User.objects.filter(is_staff=True)
        return (
            (s.id, force_text(s))
            for s in staff
        )

    def queryset(self, request, queryset):
        if self.value():
            queryset = queryset.filter(user_id=self.value(), user__is_staff=True)
        return queryset

class LogEntryAdmin(admin.ModelAdmin):
    date_hierarchy = 'action_time'

    readonly_fields = ([f.name for f in LogEntry._meta.fields] +
                       ['object_link', 'action_description', 'user_link'])

    fieldsets = (
        (_('Metadata'), {
            'fields': (
                'action_time',
                'user_link',
                'action_description',
                'object_link',
            )
        }),
        (_('Detail'), {
            'fields': (
                'change_message',
                'content_type',
                'object_id',
                'object_repr',
            )
        }),
    )

    list_filter = [
        UserListFilter,
        'content_type',
        ActionListFilter
    ]

    search_fields = [
        'object_repr',
        'change_message'
    ]

    list_display_links = [
        'action_time',
        'change_message',
    ]
    list_display = [
        'action_time',
        'user_link',
        'content_type',
        'object_link',
        'action_description',
        'change_message',
    ]

    def has_add_permission(self, request):
        return False

    def has_change_permission(self, request, obj=None):
        return request.user.is_superuser and request.method != 'POST'

    def has_delete_permission(self, request, obj=None):
        return False

    def object_link(self, obj):
        if obj.action_flag == DELETION:
            link = escape(obj.object_repr)
        else:
            ct = obj.content_type
            try:
                link = '%s' % (
                    reverse(
                        'admin:%s_%s_change' % (ct.app_label, ct.model),
                        args=[obj.object_id]
                    ),
                    escape(obj.object_repr),
                )
            except NoReverseMatch:
                link = escape(obj.object_repr)
        return link
    object_link.allow_tags = True
    object_link.admin_order_field = 'object_repr'
    object_link.short_description = 'object'

    def user_link(self, obj):
        try:
            ct = ContentType.objects.get_for_model(type(obj.user))
            link = '%s' % (
                reverse(
                    'admin:%s_%s_change' % (ct.app_label, ct.model),
                    args=[obj.user.pk]
                ),
                escape(force_text(obj.user)),
            )
        except NoReverseMatch:
            link = escape(force_text(obj.user))
        return link
    user_link.allow_tags = True
    user_link.admin_order_field = 'user'
    user_link.short_description = 'user'

    def get_queryset(self, request):
        queryset = super(LogEntryAdmin, self).get_queryset(request)
        return queryset.prefetch_related('content_type')

    def get_actions(self, request):
        actions = super(LogEntryAdmin, self).get_actions(request)
        if 'delete_selected' in actions:
            del actions['delete_selected']
        return actions

    def action_description(self, obj):
        return action_names[obj.action_flag]
    action_description.short_description = 'Action'

admin.site.register(LogEntry, LogEntryAdmin)
<commit_msg>Remove fallback for get_user_model (unsupported Django versions)<commit_after>from __future__ import unicode_literals

from django.contrib import admin
from django.contrib.admin.models import LogEntry, ADDITION, CHANGE, DELETION
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.core.urlresolvers import reverse, NoReverseMatch
from django.utils.encoding import force_text
from django.utils.html import escape
from django.utils.translation import ugettext_lazy as _

action_names = {
    ADDITION: _('Addition'),
    DELETION: _('Deletion'),
    CHANGE: _('Change'),
}

class ActionListFilter(admin.SimpleListFilter):
    title = _('Action')
    parameter_name = 'action_flag'

    def lookups(self, request, model_admin):
        return action_names.items()

    def queryset(self, request, queryset):
        if self.value():
            queryset = queryset.filter(action_flag=self.value())
        return queryset

class UserListFilter(admin.SimpleListFilter):
    title = _('staff user')
    parameter_name = 'user'

    def lookups(self, request, model_admin):
        staff = get_user_model().objects.filter(is_staff=True)
        return (
            (s.id, force_text(s))
            for s in staff
        )

    def queryset(self, request, queryset):
        if self.value():
            queryset = queryset.filter(user_id=self.value(), user__is_staff=True)
        return queryset

class LogEntryAdmin(admin.ModelAdmin):
    date_hierarchy = 'action_time'

    readonly_fields = ([f.name for f in LogEntry._meta.fields] +
                       ['object_link', 'action_description', 'user_link'])

    fieldsets = (
        (_('Metadata'), {
            'fields': (
                'action_time',
                'user_link',
                'action_description',
                'object_link',
            )
        }),
        (_('Detail'), {
            'fields': (
                'change_message',
                'content_type',
                'object_id',
                'object_repr',
            )
        }),
    )

    list_filter = [
        UserListFilter,
        'content_type',
        ActionListFilter
    ]

    search_fields = [
        'object_repr',
        'change_message'
    ]

    list_display_links = [
        'action_time',
        'change_message',
    ]
    list_display = [
        'action_time',
        'user_link',
        'content_type',
        'object_link',
        'action_description',
        'change_message',
    ]

    def has_add_permission(self, request):
        return False

    def has_change_permission(self, request, obj=None):
        return request.user.is_superuser and request.method != 'POST'

    def has_delete_permission(self, request, obj=None):
        return False

    def object_link(self, obj):
        if obj.action_flag == DELETION:
            link = escape(obj.object_repr)
        else:
            ct = obj.content_type
            try:
                link = '%s' % (
                    reverse(
                        'admin:%s_%s_change' % (ct.app_label, ct.model),
                        args=[obj.object_id]
                    ),
                    escape(obj.object_repr),
                )
            except NoReverseMatch:
                link = escape(obj.object_repr)
        return link
    object_link.allow_tags = True
    object_link.admin_order_field = 'object_repr'
    object_link.short_description = 'object'

    def user_link(self, obj):
        try:
            ct = ContentType.objects.get_for_model(type(obj.user))
            link = '%s' % (
                reverse(
                    'admin:%s_%s_change' % (ct.app_label, ct.model),
                    args=[obj.user.pk]
                ),
                escape(force_text(obj.user)),
            )
        except NoReverseMatch:
            link = escape(force_text(obj.user))
        return link
    user_link.allow_tags = True
    user_link.admin_order_field = 'user'
    user_link.short_description = 'user'

    def get_queryset(self, request):
        queryset = super(LogEntryAdmin, self).get_queryset(request)
        return queryset.prefetch_related('content_type')

    def get_actions(self, request):
        actions = super(LogEntryAdmin, self).get_actions(request)
        if 'delete_selected' in actions:
            del actions['delete_selected']
        return actions

    def action_description(self, obj):
        return action_names[obj.action_flag]
    action_description.short_description = 'Action'

admin.site.register(LogEntry, LogEntryAdmin)
<|endoftext|>