[Pkg-debile-commits] [debile-master] 01/126: Initial import.

Sylvestre Ledru sylvestre at alioth.debian.org
Mon Aug 19 14:56:04 UTC 2013


This is an automated email from the git hooks/post-receive script.

sylvestre pushed a commit to branch scan-build-html
in repository debile-master.

commit ff6dedefd34220cc9c3375278a98f43cf9944028
Author: Paul Tagliamonte <tag at pault.ag>
Date:   Tue May 21 13:29:16 2013 -0400

    Initial import.
---
 .gitignore                      |    3 +
 eg/config.json                  |   32 ++++
 lucy/__init__.py                |    9 +
 lucy/archive.py                 |   88 ++++++++++
 lucy/changes.py                 |  349 +++++++++++++++++++++++++++++++++++++++
 lucy/cli/incoming.py            |   11 ++
 lucy/cli/init.py                |   43 +++++
 lucy/cli/nuke.py                |   29 ++++
 lucy/core.py                    |    5 +
 lucy/models/__init__.py         |   62 +++++++
 lucy/models/config.py           |   11 ++
 lucy/models/job.py              |   47 ++++++
 lucy/models/machine.py          |   17 ++
 lucy/models/package.py          |   18 ++
 lucy/models/report.py           |   15 ++
 lucy/models/user.py             |   22 +++
 lucy/server.py                  |   70 ++++++++
 lucy/tests/__init__.py          |   25 +++
 lucy/tests/test_envelope.py     |   18 ++
 lucy/tests/test_lucy_base.py    |   41 +++++
 lucy/tests/test_lucy_package.py |   26 +++
 lucy/tests/test_lucy_user.py    |   32 ++++
 lucy/utils.py                   |   37 +++++
 maint/import-key.sh             |    8 +
 requirements-dev.txt            |    1 +
 requirements.txt                |    4 +
 setup.py                        |   29 ++++
 27 files changed, 1052 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..cdc9462
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+*pyc
+*swp
+*lucy*egg*
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e69de29
diff --git a/eg/config.json b/eg/config.json
new file mode 100644
index 0000000..5cb439d
--- /dev/null
+++ b/eg/config.json
@@ -0,0 +1,32 @@
+{
+    "configs": [
+        {
+            "_id": "default",
+            "incoming": "/srv/lucy.pault.ag/incoming",
+            "job_classes": [
+                "lintian"
+            ],
+            "pool": "/srv/lucy.pault.ag/pool"
+        }
+    ],
+    "machines": [
+        {
+            "_id": "leliel",
+            "auth": "password",
+            "owner": "paultag"
+        },
+        {
+            "_id": "loki",
+            "auth": "password",
+            "owner": "paultag"
+        }
+    ],
+    "users": [
+        {
+            "_id": "paultag",
+            "email": "tag at pault.ag",
+            "gpg": "57DC4BD33F73E0CDBA98D22AF7EBEE8EB7982329",
+            "name": "Paul R. Tagliamonte"
+        }
+    ]
+}
diff --git a/lucy/__init__.py b/lucy/__init__.py
new file mode 100644
index 0000000..e00d649
--- /dev/null
+++ b/lucy/__init__.py
@@ -0,0 +1,9 @@
+__appname__ = "lucy"
+__version__ = "0.0.1"
+
+
+from lucy.models.machine import Machine  # NOQA
+from lucy.models.package import Package  # NOQA
+from lucy.models.config import Config  # NOQA
+from lucy.models.user import User  # NOQA
+from lucy.models.job import Job  # NOQA
diff --git a/lucy/archive.py b/lucy/archive.py
new file mode 100644
index 0000000..a7bddad
--- /dev/null
+++ b/lucy/archive.py
@@ -0,0 +1,88 @@
+from lucy.models.package import Package
+from lucy.models.user import User
+from lucy.models.job import Job
+
+from lucy.changes import parse_changes_file, ChangesFileException
+from lucy.utils import cd, fglob
+
+import os
+
+
+def reject(changes, config):
+    print("Rejecting: {source}/{version}".format(
+        source=changes['source'],
+        version=changes['version']))
+
+    for f in changes.get_files() + [changes.get_filename()]:
+        print("   Removing: %s" % (f))
+        os.unlink(f)
+
+
+def accept(changes, config, pool):
+    print("Accepting: {source}/{version}".format(
+        source=changes['source'],
+        version=changes['version']))
+
+    name = changes.get_package_name()
+    version = changes['version']
+    key = changes.validate_signature()
+
+    try:
+        who = User.get_by_key(key)
+    except KeyError:
+        # no such user
+        return reject(changes, config)
+
+    obj = Package(source=name,
+                  version=version,
+                  owner=who['_id'])
+    obj.save()
+    path = add_to_pool(pool, obj, changes)
+    obj['path'] = path
+    obj.save()
+    os.unlink(changes.get_filename())
+
+    for job in config['job_classes']:
+        print("  -> New job: %s" % (job))
+        Job(type=job, package=obj['_id']).save()
+
+
+def uuid_to_path(uuid, base=None):
+    nodes = uuid.split("-")
+    ids = os.path.join(*nodes)
+    path = os.path.join(*list(nodes[-1])[:4])
+    path = os.path.join(ids, path)
+    return path
+
+
+def add_to_pool(pool, package, changes):
+    uid = package['_id']
+    ret = uuid_to_path(uid, base=pool)
+
+    path = os.path.join(pool, ret)
+
+    os.makedirs(path)
+    for entry in changes.get_files():
+        bn = os.path.basename(entry)
+        dest = os.path.join(path, bn)
+        os.rename(entry, dest)
+
+    return ret
+
+
+def process(config):
+    pool = config['pool']
+    incoming = config['incoming']
+
+    with cd(incoming):
+        def _pc(x):
+            obj = parse_changes_file(x, directory=incoming)
+            try:
+                obj.validate(check_signature=True)
+            except ChangesFileException:
+                reject(obj, config)
+                return None
+            return obj
+
+        for x in filter(lambda x: x is not None, fglob("*changes", _pc)):
+            accept(x, config, pool)
diff --git a/lucy/changes.py b/lucy/changes.py
new file mode 100644
index 0000000..2ed92ed
--- /dev/null
+++ b/lucy/changes.py
@@ -0,0 +1,349 @@
+# -*- coding: utf-8 -*-
+#
+#   changes.py — .changes file handling class
+#
+#   This file was originally part of debexpo
+#    https://alioth.debian.org/projects/debexpo/
+#
+#   Copyright © 2008 Jonny Lamb <jonny at debian.org>
+#   Copyright © 2010 Jan Dittberner <jandd at debian.org>
+#   Copyright © 2012 Arno Töll <arno at debian.org>
+#   Copyright © 2012 Paul Tagliamonte <paultag at debian.org>
+#
+#   Permission is hereby granted, free of charge, to any person
+#   obtaining a copy of this software and associated documentation
+#   files (the "Software"), to deal in the Software without
+#   restriction, including without limitation the rights to use,
+#   copy, modify, merge, publish, distribute, sublicense, and/or sell
+#   copies of the Software, and to permit persons to whom the
+#   Software is furnished to do so, subject to the following
+#   conditions:
+#
+#   The above copyright notice and this permission notice shall be
+#   included in all copies or substantial portions of the Software.
+#
+#   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+#   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+#   OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+#   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+#   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+#   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+#   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+#   OTHER DEALINGS IN THE SOFTWARE.
+"""
+This code deals with the reading and processing of Debian .changes files. This
+code is copyright (c) Jonny Lamb, and is used by dput, rather then created as
+a result of it. Thank you Jonny.
+"""
+
+__author__ = 'Jonny Lamb'
+__copyright__ = 'Copyright © 2008 Jonny Lamb, Copyright © 2010 Jan Dittberner'
+__license__ = 'MIT'
+
+import sys
+import shlex
+import os.path
+import hashlib
+import subprocess
+from debian import deb822
+
+
+def run_command(command):
+    if not isinstance(command, list):
+        command = shlex.split(command)
+    try:
+        pipe = subprocess.Popen(command,
+                                shell=False,
+                                stdout=subprocess.PIPE,
+                                stderr=subprocess.PIPE)
+    except OSError:
+        return (None, None, -1)
+    (output, stderr) = pipe.communicate()
+    return (output, stderr, pipe.returncode)
+
+
+class ChangesFileException(Exception):
+    pass
+
+
+class Changes(object):
+    """
+    Changes object to help process and store information regarding Debian
+    .changes files, used in the upload process.
+    """
+
+    def __init__(self, filename=None, string=None):
+        """
+        Object constructor. The object allows the user to specify **either**:
+
+        #. a path to a *changes* file to parse
+        #. a string with the *changes* file contents.
+
+        ::
+
+        a = Changes(filename='/tmp/packagename_version.changes')
+        b = Changes(string='Source: packagename\\nMaintainer: ...')
+
+        ``filename``
+            Path to *changes* file to parse.
+
+        ``string``
+            *changes* file in a string to parse.
+        """
+        if (filename and string) or (not filename and not string):
+            raise TypeError
+
+        if filename:
+            self._absfile = os.path.abspath(filename)
+            self._data = deb822.Changes(open(filename))
+        else:
+            self._data = deb822.Changes(string)
+
+        if len(self._data) == 0:
+            raise ChangesFileException('Changes file could not be parsed.')
+        if filename:
+            self.basename = os.path.basename(filename)
+        else:
+            self.basename = None
+        self._directory = ""
+
+        self.is_python3 = False
+        if sys.version_info[0] >= 3:
+            self.is_python3 = True
+
+    def get_filename(self):
+        """
+        Returns the filename from which the changes file was generated from.
+        Please do note this is just the basename, not the entire full path, or
+        even a relative path. For the absolute path to the changes file, please
+        see :meth:`get_changes_file`.
+        """
+        return self.basename
+
+    def get_changes_file(self):
+        """
+        Return the full, absolute path to the changes file. For just the
+        filename, please see :meth:`get_filename`.
+        """
+        return os.path.join(self._directory, self.get_filename())
+
+    def get_files(self):
+        """
+        Returns a list of files referenced in the changes file, such as
+        the .dsc, .deb(s), .orig.tar.gz, and .diff.gz or .debian.tar.gz.
+        All strings in the array will be absolute paths to the files.
+        """
+        return [os.path.join(self._directory, z['name'])
+                for z in self._data['Files']]
+
+    def __getitem__(self, key):
+        """
+        Returns the value of the rfc822 key specified.
+
+        ``key``
+            Key of data to request.
+        """
+        return self._data[key]
+
+    def __contains__(self, key):
+        """
+        Returns whether the specified RFC822 key exists.
+
+        ``key``
+            Key of data to check for existence.
+        """
+        return key in self._data
+
+    def get(self, key, default=None):
+        """
+        Returns the value of the rfc822 key specified, but defaults
+        to a specific value if not found in the rfc822 file.
+
+        ``key``
+            Key of data to request.
+
+        ``default``
+            Default return value if ``key`` does not exist.
+        """
+        return self._data.get(key, default)
+
+    def get_component(self):
+        """
+        Returns the component of the package.
+        """
+        return self._parse_section(self._data['Files'][0]['section'])[0]
+
+    def get_priority(self):
+        """
+        Returns the priority of the package.
+        """
+        return self._parse_section(self._data['Files'][0]['priority'])[1]
+
+    def get_dsc(self):
+        """
+        Returns the name of the .dsc file.
+        """
+        for item in self.get_files():
+            if item.endswith('.dsc'):
+                return item
+
+    def get_diff(self):
+        """
+        Returns the name of the .diff.gz file if there is one, otherwise None.
+        """
+        for item in self.get_files():
+            if item.endswith('.diff.gz') or item.endswith('.debian.tar.gz'):
+                return item
+
+        return None
+
+    def get_pool_path(self):
+        """
+        Returns the path the changes file would be
+        """
+        return self._data.get_pool_path()
+
+    def get_package_name(self):
+        """
+        Returns the source package name
+        """
+        return self.get("Source")
+
+    def _parse_section(self, section):
+        """
+        Works out the component and section from the "Section" field.
+        Sections like `python` or `libdevel` are in main.
+        Sections with a prefix, separated with a forward-slash also show the
+        component.
+        It returns a list of strings in the form [component, section].
+
+        For example, `non-free/python` has component `non-free` and section
+        `python`.
+
+        ``section``
+        Section name to parse.
+        """
+        if '/' in section:
+            return section.split('/')
+        else:
+            return ['main', section]
+
+    def set_directory(self, directory):
+        if directory:
+            self._directory = directory
+        else:
+            self._directory = ""
+
+    def validate(self, check_hash="sha1", check_signature=True):
+        """
+        See :meth:`validate_checksums` for ``check_hash``, and
+        :meth:`validate_signature` if ``check_signature`` is True.
+        """
+        self.validate_checksums(check_hash)
+        if check_signature:
+            self.validate_signature(check_signature)
+
+    def validate_signature(self, check_signature=True):
+        """
+        Validate the GPG signature of a .changes file.
+
+        Throws a :class:`dput.exceptions.ChangesFileException` if there's
+        an issue with the GPG signature. Returns the GPG key ID.
+        """
+        gpg_path = "gpg"
+
+        (gpg_output, gpg_output_stderr, exit_status) = run_command([
+            gpg_path, "--status-fd", "1", "--verify",
+            "--batch", self.get_changes_file()
+        ])
+
+        if exit_status == -1:
+            raise ChangesFileException(
+                "Unknown problem while verifying signature")
+
+        # contains verbose human readable GPG information
+        if self.is_python3:
+            gpg_output_stderr = str(gpg_output_stderr, encoding='utf8')
+
+        if self.is_python3:
+            gpg_output = gpg_output.decode(encoding='UTF-8')
+
+        if gpg_output.count('[GNUPG:] GOODSIG'):
+            pass
+        elif gpg_output.count('[GNUPG:] BADSIG'):
+            raise ChangesFileException("Bad signature")
+        elif gpg_output.count('[GNUPG:] ERRSIG'):
+            raise ChangesFileException("Error verifying signature")
+        elif gpg_output.count('[GNUPG:] NODATA'):
+            raise ChangesFileException("No signature on")
+        else:
+            raise ChangesFileException(
+                "Unknown problem while verifying signature"
+            )
+
+        key = None
+        for line in gpg_output.split("\n"):
+            if line.startswith('[GNUPG:] VALIDSIG'):
+                key = line.split()[2]
+        return key
+
+    def validate_checksums(self, check_hash="sha1"):
+        """
+        Validate checksums for a package, using ``check_hack``'s type
+        to validate the package.
+
+        Valid ``check_hash`` types:
+
+            * sha1
+            * sha256
+            * md5
+            * md5sum
+        """
+        for filename in self.get_files():
+            if check_hash == "sha1":
+                hash_type = hashlib.sha1()
+                checksums = self.get("Checksums-Sha1")
+                field_name = "sha1"
+            elif check_hash == "sha256":
+                hash_type = hashlib.sha256()
+                checksums = self.get("Checksums-Sha256")
+                field_name = "sha256"
+            elif check_hash == "md5":
+                hash_type = hashlib.md5()
+                checksums = self.get("Files")
+                field_name = "md5sum"
+
+            for changed_files in checksums:
+                if changed_files['name'] == os.path.basename(filename):
+                    break
+            else:
+                assert(
+                    "get_files() returns different files than Files: knows?!")
+
+            with open(filename, "rb") as fc:
+                while True:
+                    chunk = fc.read(131072)
+                    if not chunk:
+                        break
+                    hash_type.update(chunk)
+            fc.close()
+
+            if not hash_type.hexdigest() == changed_files[field_name]:
+                raise ChangesFileException(
+                    "Checksum mismatch for file %s: %s != %s" % (
+                        filename,
+                        hash_type.hexdigest(),
+                        changed_files[field_name]
+                    ))
+
+
+def parse_changes_file(filename, directory=None):
+    """
+    Parse a .changes file and return a dput.changes.Change instance with
+    parsed changes file data. The optional directory argument refers to the
+    base directory where the referred files from the changes file are expected
+    to be located.
+    """
+    _c = Changes(filename=filename)
+    _c.set_directory(directory)
+    return(_c)
diff --git a/lucy/cli/__init__.py b/lucy/cli/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/lucy/cli/incoming.py b/lucy/cli/incoming.py
new file mode 100644
index 0000000..e82cc52
--- /dev/null
+++ b/lucy/cli/incoming.py
@@ -0,0 +1,11 @@
+from lucy.models.config import Config
+from lucy.archive import process
+
+
+def main():
+    obj = Config.load('default')
+    process(obj)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/lucy/cli/init.py b/lucy/cli/init.py
new file mode 100644
index 0000000..86191de
--- /dev/null
+++ b/lucy/cli/init.py
@@ -0,0 +1,43 @@
+from clint.textui import progress, puts
+from clint import args
+
+from lucy.models.machine import Machine
+from lucy.models.config import Config
+from lucy.models.user import User
+
+import json
+
+
+def main():
+    if not args.files:
+        raise Exception("WTF - need a config")
+
+
+    config = args.files.pop(0)
+    obj = json.load(open(config, 'r'))
+
+    machines = obj['machines']
+    configs = obj['configs']
+    users = obj['users']
+
+
+    puts("Loading users:")
+    for conf in progress.bar(users):
+        u = User(**conf)
+        u.save()
+
+
+    puts("Loading machines:")
+    for conf in progress.bar(machines):
+        m = Machine(**conf)
+        m.save()
+
+
+    puts("Loading configs:")
+    for conf in progress.bar(configs):
+        c = Config(**conf)
+        c.save()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/lucy/cli/nuke.py b/lucy/cli/nuke.py
new file mode 100644
index 0000000..cb2fb1e
--- /dev/null
+++ b/lucy/cli/nuke.py
@@ -0,0 +1,29 @@
+from lucy.models.config import Config
+from lucy.core import db
+
+from clint.textui import puts
+import shutil
+import sys
+import os
+
+
+def main():
+    try:
+        conf = Config.load('default')
+    except KeyError:
+        puts("Error: Need to init the db")
+        sys.exit(1)
+
+    pool = conf['pool']
+
+    shutil.rmtree(pool)
+    os.makedirs(pool)
+
+    for x in db.collection_names():
+        if x.startswith('system'):
+            continue
+        db.drop_collection(x)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/lucy/core.py b/lucy/core.py
new file mode 100644
index 0000000..ed5b7c7
--- /dev/null
+++ b/lucy/core.py
@@ -0,0 +1,5 @@
+from pymongo import Connection
+
+
+connection = Connection('localhost', 27017)
+db = connection.lucy
diff --git a/lucy/models/__init__.py b/lucy/models/__init__.py
new file mode 100644
index 0000000..f9f3dd8
--- /dev/null
+++ b/lucy/models/__init__.py
@@ -0,0 +1,62 @@
+import datetime as dt
+import lucy.core
+import uuid
+
+
+def _get_table(what):
+    return getattr(lucy.core.db, what)
+
+
+class LucyObject(dict):
+    _type = None
+
+    def __init__(self, **kwargs):
+        for k, v in kwargs.items():
+            self[k] = v
+
+    def _gen_uuid(self):
+        return str(uuid.uuid1())
+
+    def save(self):
+        if self._type is None:
+            raise ValueError("You done goofed, sucka")
+
+        uuid = self.get('_id')
+        self['updated_at'] = dt.datetime.utcnow()
+        if uuid is None:
+            uuid = self['_id'] = self._gen_uuid()
+            self['created_at'] = dt.datetime.utcnow()
+        _get_table(self._type).save(self)
+        return uuid
+
+    def delete(self):
+        table = _get_table(self._type)
+        table.remove({"_id": self['_id']})
+        return uuid
+
+    @classmethod
+    def load(cls, what):
+        table = _get_table(cls._type)
+        obj = table.find_one({"_id": what})
+        if obj is None:
+            raise KeyError("No such object: `%s' found." % (what))
+        return cls.from_dict(obj)
+
+    @classmethod
+    def query(cls, what):
+        table = _get_table(cls._type)
+        for x in table.find(what):
+            yield cls(**x)
+
+    @classmethod
+    def from_dict(cls, what):
+        klass = cls(**what)
+        return klass
+
+    @classmethod
+    def single(cls, query):
+        os = cls.query(query)
+        try:
+            return next(os)
+        except StopIteration:
+            raise KeyError("Error. No such thing")
diff --git a/lucy/models/config.py b/lucy/models/config.py
new file mode 100644
index 0000000..6ab08ca
--- /dev/null
+++ b/lucy/models/config.py
@@ -0,0 +1,11 @@
+from lucy.models import LucyObject
+
+
+class Config(LucyObject):
+    _type = 'metadata'
+
+    def __init__(self, _id, incoming, pool, **kwargs):
+        super(Config, self).__init__(_id=_id,
+                                     incoming=incoming,
+                                     pool=pool,
+                                     **kwargs)
diff --git a/lucy/models/job.py b/lucy/models/job.py
new file mode 100644
index 0000000..5d70a59
--- /dev/null
+++ b/lucy/models/job.py
@@ -0,0 +1,47 @@
+from lucy.models.package import Package
+from lucy.models.machine import Machine
+from lucy.models import LucyObject
+
+
+class Job(LucyObject):
+    _type = 'jobs'
+
+    def __init__(self, type, package, builder=None,
+                 finished_at=None, **kwargs):
+
+        package = Package.load(package)['_id']
+
+        if builder:
+            builder = Machine.load(builder)['_id']
+
+        super(Job, self).__init__(
+            type=type,
+            package=package,
+            builder=builder,
+            finished_at=finished_at,
+            **kwargs)
+
+    def get_builder(self):
+        builder = self.get('builder', None)
+        if builder is None:
+            return None
+        return Machine.load(builder)
+
+    def is_finished(self):
+        return self.get('finished_at', None) is None
+
+    @classmethod
+    def unassigned_jobs(cls, **kwargs):
+        k = kwargs.copy()
+        k.update({"builder": None, "finished_at": None})
+
+        for x in cls.query(**k):
+            yield x
+
+    @classmethod
+    def unfinished_jobs(cls, **kwargs):
+        k = kwargs.copy()
+        k.update({"finished_at": None})
+
+        for x in cls.query(**k):
+            yield x
diff --git a/lucy/models/machine.py b/lucy/models/machine.py
new file mode 100644
index 0000000..f07fc7c
--- /dev/null
+++ b/lucy/models/machine.py
@@ -0,0 +1,17 @@
+from lucy.models import LucyObject
+from lucy.models.user import User
+
+
+class Machine(LucyObject):
+    _type = 'machines'
+
+    def __init__(self, _id, owner, auth, **kwargs):
+        owner = User.load(owner)['_id']
+
+        super(Machine, self).__init__(_id=_id,
+                                      owner=owner,
+                                      auth=auth,
+                                      **kwargs)
+
+    def auth(self, auth):
+        return self['auth'] == auth
diff --git a/lucy/models/package.py b/lucy/models/package.py
new file mode 100644
index 0000000..7e65b20
--- /dev/null
+++ b/lucy/models/package.py
@@ -0,0 +1,18 @@
+from lucy.models import LucyObject
+from lucy.models.user import User
+
+
+class Package(LucyObject):
+    _type = 'packages'
+
+    def __init__(self, source, version, owner, **kwargs):
+        owner = User.load(owner)['_id']
+        super(Package, self).__init__(source=source,
+                                      version=version,
+                                      owner=owner,
+                                      **kwargs)
+
+    @classmethod
+    def get_all_versions(cls, source):
+        for x in cls.query({"source": source}):
+            yield cls.from_dict(x)
diff --git a/lucy/models/report.py b/lucy/models/report.py
new file mode 100644
index 0000000..19a8358
--- /dev/null
+++ b/lucy/models/report.py
@@ -0,0 +1,15 @@
+from lucy.models import LucyObject
+from lucy.models.machine import Machine
+from lucy.models.package import Package
+
+
+class Report(LucyObject):
+    _type = 'reports'
+
+    def __init__(self, report, builder, package, **kwargs):
+        builder = Machine.load(builder)['_id']
+        package = Package.load(package)['_id']
+        super(Report, self).__init__(builder=builder,
+                                     package=package,
+                                     report=report,
+                                     **kwargs)
diff --git a/lucy/models/user.py b/lucy/models/user.py
new file mode 100644
index 0000000..2c2f2b8
--- /dev/null
+++ b/lucy/models/user.py
@@ -0,0 +1,22 @@
+from lucy.models import LucyObject
+
+
+class User(LucyObject):
+    _type = 'users'
+
+    def __init__(self, _id, name, email, gpg, **kwargs):
+        super(User, self).__init__(_id=_id,
+                                   gpg=gpg,
+                                   name=name,
+                                   email=email,
+                                   **kwargs)
+
+    @classmethod
+    def get_by_email(cls, email):
+        return cls.single({"email": email})
+
+    @classmethod
+    def get_by_key(cls, key):
+        return cls.single({"gpg": key})
+
+    get_by_uid = LucyObject.load
diff --git a/lucy/server.py b/lucy/server.py
new file mode 100644
index 0000000..3c09b72
--- /dev/null
+++ b/lucy/server.py
@@ -0,0 +1,70 @@
+from lucy.models.machine import Machine
+from lucy.models.report import Report
+
+from xmlrpc.server import SimpleXMLRPCServer
+from xmlrpc.server import SimpleXMLRPCRequestHandler
+
+from base64 import b64decode
+import socketserver
+import threading
+
+NAMESPACE = threading.local()
+
+
+class LucyAuthMixIn(SimpleXMLRPCRequestHandler):
+    def authenticate(self):
+        (basic, _, encoded) = self.headers.get('Authorization').partition(' ')
+        if basic.lower() != 'basic':
+            self.send_error(401, 'Only allowed basic type thing')
+
+        machine, password = b64decode(encoded.encode()).decode().split(":", 1)
+        machine = Machine.load(machine)
+        if machine.auth(password):
+            NAMESPACE.machine = machine
+            return True
+        NAMESPACE.machine = None
+        return False
+
+    def parse_request(self, *args):
+        if super(LucyAuthMixIn, self).parse_request(*args):
+            if self.authenticate():
+                return True
+            else:
+                self.send_error(401, 'Authentication failed')
+        return False
+
+
+class AsyncXMLRPCServer(socketserver.ThreadingMixIn, LucyAuthMixIn):
+    pass
+
+
+class LucyInterface(object):
+    def version(self):
+        return "1.0"
+
+    def identify(self):
+        return NAMESPACE.machine['_id']
+
+    def submit_report(self, package, report):
+        r = Report(builder=NAMESPACE.machine['_id'],
+                   report=report,
+                   package=package)
+        return r.save()
+
+
+def serve(server, port):
+    print("Serving on `{server}' on port `{port}'".format(**locals()))
+    server = SimpleXMLRPCServer((server, port),
+                                requestHandler=AsyncXMLRPCServer,
+                                allow_none=True)
+    server.register_introspection_functions()
+    server.register_instance(LucyInterface())
+    server.serve_forever()
+
+
+if __name__ == "__main__":
+    main()
+
+
+def main():
+    serve("localhost", 20017)
diff --git a/lucy/tests/__init__.py b/lucy/tests/__init__.py
new file mode 100644
index 0000000..44473ae
--- /dev/null
+++ b/lucy/tests/__init__.py
@@ -0,0 +1,25 @@
+from pymongo import Connection
+import os
+
+from lucy.models.config import Config
+import lucy.core
+
+ROOT = "%s/../../resources/" % (os.path.dirname(__file__))
+
+
+def setup():
+    connection = Connection('localhost', 27017)
+    db = connection.lucy_test
+    lucy.core.db = db
+    for x in db.collection_names():
+        if x.startswith('system'):
+            continue
+
+        db.drop_collection(x)
+
+    name = "default"
+    incoming = "%s/incoming/" % (ROOT)
+    pool = "%s/pool/" % (ROOT)
+
+    if name != Config(_id=name, incoming=incoming, pool=pool).save():
+        raise Exception
diff --git a/lucy/tests/test_envelope.py b/lucy/tests/test_envelope.py
new file mode 100644
index 0000000..d665389
--- /dev/null
+++ b/lucy/tests/test_envelope.py
@@ -0,0 +1,18 @@
+from lucy.envelope import Envelope
+
+tfoo = """Hello: world
+Who: me
+Where: kruft
+
+foo
+bar"""
+
+
+
+def test_env_round_trip():
+    env = Envelope.load(tfoo)
+    assert env['hello'] == 'world'
+    assert env['who'] == 'me'
+    assert env['Where'] == 'kruft'
+    assert env.data == """foo
+bar"""
diff --git a/lucy/tests/test_lucy_base.py b/lucy/tests/test_lucy_base.py
new file mode 100644
index 0000000..56862b2
--- /dev/null
+++ b/lucy/tests/test_lucy_base.py
@@ -0,0 +1,41 @@
+from lucy.models import LucyObject
+
+
+class LucyFord(LucyObject):
+    _type = 'test'
+
+
+def test_basic_actions():
+    """ Test that basic save / load works. """
+
+    lo = LucyFord()
+    lo['foo'] = 'bar'
+    uid = lo.save()
+
+    obj = LucyFord.load(uid)
+    obj.pop('updated_at')
+    obj.pop('created_at')
+    lo.pop('updated_at')
+    lo.pop('created_at')
+    assert obj == lo, "Make sure loaded object == generated object"
+    obj['bar'] = 'baz'
+    assert obj != lo, "Make sure loaded object != generated object"
+
+    obj.save()
+    lo = obj
+    obj = LucyFord.load(uid)
+
+    obj.pop('updated_at')
+    lo.pop('updated_at')
+
+    assert obj == lo, "Make sure loaded object == generated object"
+
+    assert len(list(LucyFord.query({}))) == 1, "Count sucks"
+    obj.delete()
+    assert len(list(LucyFord.query({}))) == 0, "Count sucks post remove"
+
+    try:
+        obj = LucyFord.load("INVALID")
+        assert True is False, "Invalid query went through"
+    except KeyError:
+        pass
diff --git a/lucy/tests/test_lucy_package.py b/lucy/tests/test_lucy_package.py
new file mode 100644
index 0000000..77a53e2
--- /dev/null
+++ b/lucy/tests/test_lucy_package.py
@@ -0,0 +1,26 @@
+from lucy.models.package import Package
+
+
+def test_basic_package():
+    """ Test that package routines works """
+
+    p = Package(source='fluxbox',
+                version='1.0',
+                owner=None)
+    p.save()
+
+    p = Package(source='fluxbox',
+                version='2.0',
+                owner=None)
+    p.save()
+
+    p = Package(source='frucksbox',
+                version='2.0',
+                owner=None)
+    x = p.save()
+
+    assert len(list(Package.get_all_versions("fluxbox"))) == 2
+    assert len(list(Package.get_all_versions("frucksbox"))) == 1
+
+    obj = Package.load(x)
+    assert obj['version'] == '2.0'
diff --git a/lucy/tests/test_lucy_user.py b/lucy/tests/test_lucy_user.py
new file mode 100644
index 0000000..9c15161
--- /dev/null
+++ b/lucy/tests/test_lucy_user.py
@@ -0,0 +1,32 @@
+from lucy.models.user import User
+
+
+def test_basic_user_foo():
+    """ Test that user routines works """
+
+    u = User(_id='joe',
+             name='Joe Bar',
+             gpg="8F049AD82C92066C7352D28A7B585B30807C2A87",
+             email="noreply at example.com")
+
+    assert 'joe' == u.save()
+    u.save()
+
+    joe = User.get_by_email('noreply at example.com')
+
+    joe.pop('updated_at')
+    u.pop('updated_at')
+
+    assert joe == u
+
+    joe = User.get_by_key("8F049AD82C92066C7352D28A7B585B30807C2A87")
+
+    joe.pop('updated_at')
+
+    assert joe == u
+
+    try:
+        joe = User.get_by_key("foo")
+        assert True is False, "KeyCheck failed"
+    except KeyError:
+        pass
diff --git a/lucy/utils.py b/lucy/utils.py
new file mode 100644
index 0000000..4b8a37a
--- /dev/null
+++ b/lucy/utils.py
@@ -0,0 +1,37 @@
+import subprocess
+import contextlib
+import glob
+import os
+
+
+ at contextlib.contextmanager
+def cd(where):
+    ncwd = os.getcwd()
+    try:
+        yield os.chdir(where)
+    finally:
+        os.chdir(ncwd)
+
+
+def fglob(glo, globular):
+    for x in glob.glob(glo):
+        yield globular(x)
+
+
+def run_command(command, stdin=None):
+    if not isinstance(command, list):
+        command = shlex.split(command)
+    try:
+        pipe = subprocess.Popen(command, shell=False,
+                                stdin=subprocess.PIPE,
+                                stdout=subprocess.PIPE,
+                                stderr=subprocess.PIPE)
+    except OSError:
+        return (None, None, -1)
+
+    kwargs = {}
+    if stdin:
+        kwargs['input'] = stdin.read()
+
+    (output, stderr) = pipe.communicate(**kwargs)
+    return (output, stderr, pipe.returncode)
diff --git a/maint/import-key.sh b/maint/import-key.sh
new file mode 100755
index 0000000..f0848f4
--- /dev/null
+++ b/maint/import-key.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+
+gpg \
+  --no-default-keyring \
+  --keyring /var/lib/lucy/keyring \
+  --import \
+  -
diff --git a/requirements-dev.txt b/requirements-dev.txt
new file mode 100644
index 0000000..3930480
--- /dev/null
+++ b/requirements-dev.txt
@@ -0,0 +1 @@
+flake8
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..4c6ea59
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,4 @@
+pymongo
+nose
+clint
+-e git://github.com/fantix/gevent.git#egg=gevent
diff --git a/resources/.hold b/resources/.hold
new file mode 100644
index 0000000..e69de29
diff --git a/resources/incoming/.hold b/resources/incoming/.hold
new file mode 100644
index 0000000..e69de29
diff --git a/resources/pool/.hold b/resources/pool/.hold
new file mode 100644
index 0000000..e69de29
diff --git a/setup.py b/setup.py
new file mode 100755
index 0000000..3414440
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,29 @@
+from lucy import __appname__, __version__
+from setuptools import setup
+
+
+long_description = ""
+
+setup(
+    name=__appname__,
+    version=__version__,
+    scripts=[],
+    packages=[
+        'lucy',
+    ],
+    author="Paul Tagliamonte",
+    author_email="tag at pault.ag",
+    long_description=long_description,
+    description='Lucy!',
+    license="Expat",
+    url="http://deb.io/",
+    platforms=['any'],
+    entry_points = {
+        'console_scripts': [
+            'lucy-nuke = lucy.cli.nuke:main',
+            'lucy-process-incoming = lucy.cli.incoming:main',
+            'lucy-init = lucy.cli.init:main',
+            'lucyd = lucy.server:main',
+        ],
+    }
+)

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-debile/debile-master.git



More information about the Pkg-debile-commits mailing list