[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