mirror of
https://github.com/YunoHost/moulinette.git
synced 2024-09-03 20:06:31 +02:00
Merge branch 'dev', remote-tracking branch 'origin/dev' into dev
* origin/dev: REST code and header fixes Add txrestapi REST API for moulinette :d Init backup functions sudo_ldap_scheme.yml * dev:
This commit is contained in:
commit
89f8cb2a58
17 changed files with 851 additions and 158 deletions
|
@ -51,6 +51,7 @@ user:
|
|||
### user_list()
|
||||
list:
|
||||
action_help: List users
|
||||
api: GET /user/list
|
||||
arguments:
|
||||
--fields:
|
||||
help: fields to fetch
|
||||
|
@ -68,6 +69,7 @@ user:
|
|||
### user_create()
|
||||
create:
|
||||
action_help: Create user
|
||||
api: POST /user
|
||||
arguments:
|
||||
-u:
|
||||
full: --username
|
||||
|
@ -93,6 +95,7 @@ user:
|
|||
### user_delete()
|
||||
delete:
|
||||
action_help: Delete user
|
||||
api: DELETE /user
|
||||
arguments:
|
||||
-u:
|
||||
full: --users
|
||||
|
@ -106,6 +109,7 @@ user:
|
|||
### user_update()
|
||||
update:
|
||||
action_help: Update user informations
|
||||
api: PUT /user
|
||||
arguments:
|
||||
username:
|
||||
help: Username of user to update
|
||||
|
@ -139,6 +143,7 @@ user:
|
|||
### user_info()
|
||||
info:
|
||||
action_help: Get user informations
|
||||
api: GET /user
|
||||
arguments:
|
||||
user-or-mail:
|
||||
help: Username or mail to get informations
|
||||
|
@ -154,6 +159,7 @@ domain:
|
|||
### domain_list()
|
||||
list:
|
||||
action_help: List domains
|
||||
api: GET /domain/list
|
||||
arguments:
|
||||
-f:
|
||||
full: --filter
|
||||
|
@ -168,6 +174,7 @@ domain:
|
|||
### domain_add()
|
||||
add:
|
||||
action_help: Create a custom domain
|
||||
api: POST /domain
|
||||
arguments:
|
||||
domains:
|
||||
help: Domain name to add
|
||||
|
@ -181,6 +188,7 @@ domain:
|
|||
### domain_remove()
|
||||
remove:
|
||||
action_help: Delete domains
|
||||
api: DELETE /domain
|
||||
arguments:
|
||||
domains:
|
||||
help: Domain(s) to delete
|
||||
|
@ -190,18 +198,12 @@ domain:
|
|||
### domain_info()
|
||||
info:
|
||||
action_help: Get domain informations
|
||||
api: GET /domain
|
||||
arguments:
|
||||
domain:
|
||||
help: ""
|
||||
pattern: '^([a-zA-Z0-9]{1}([a-zA-Z0-9\-]*[a-zA-Z0-9])*)(\.[a-zA-Z0-9]{1}([a-zA-Z0-9\-]*[a-zA-Z0-9])*)*(\.[a-zA-Z]{1}([a-zA-Z0-9\-]*[a-zA-Z0-9])*)$'
|
||||
|
||||
### domain_renewcert()
|
||||
renewcert:
|
||||
action_help: Renew domain certificate
|
||||
arguments:
|
||||
domain:
|
||||
help: ""
|
||||
pattern: '^([a-zA-Z0-9]{1}([a-zA-Z0-9\-]*[a-zA-Z0-9])*)(\.[a-zA-Z0-9]{1}([a-zA-Z0-9\-]*[a-zA-Z0-9])*)*(\.[a-zA-Z]{1}([a-zA-Z0-9\-]*[a-zA-Z0-9])*)$'
|
||||
|
||||
#############################
|
||||
# App #
|
||||
|
@ -213,6 +215,7 @@ app:
|
|||
### app_fetchlist()
|
||||
fetchlist:
|
||||
action_help: Fetch application list from app server
|
||||
api: PUT /app/lists
|
||||
arguments:
|
||||
-u:
|
||||
full: --url
|
||||
|
@ -224,10 +227,12 @@ app:
|
|||
### app_listlists()
|
||||
listlists:
|
||||
action_help: List fetched lists
|
||||
api: GET /app/lists
|
||||
|
||||
### app_removelist()
|
||||
removelist:
|
||||
action_help: Remove list from the repositories
|
||||
api: DELETE /app/lists
|
||||
arguments:
|
||||
-n:
|
||||
full: --name
|
||||
|
@ -238,6 +243,7 @@ app:
|
|||
### app_list()
|
||||
list:
|
||||
action_help: List apps
|
||||
api: GET /app/list
|
||||
arguments:
|
||||
-l:
|
||||
full: --limit
|
||||
|
@ -256,6 +262,7 @@ app:
|
|||
### app_map()
|
||||
map:
|
||||
action_help: List apps by domain
|
||||
api: GET /app/map
|
||||
arguments:
|
||||
-a:
|
||||
full: --app
|
||||
|
@ -268,6 +275,7 @@ app:
|
|||
### app_install() TODO: Write help
|
||||
install:
|
||||
action_help: Install apps
|
||||
api: POST /app
|
||||
arguments:
|
||||
app:
|
||||
help: App to install
|
||||
|
@ -289,6 +297,7 @@ app:
|
|||
### app_remove() TODO: Write help
|
||||
remove:
|
||||
action_help: Remove app
|
||||
api: DELETE /app
|
||||
arguments:
|
||||
app:
|
||||
help: App(s) to delete
|
||||
|
@ -300,6 +309,7 @@ app:
|
|||
### app_upgrade()
|
||||
upgrade:
|
||||
action_help: Upgrade app
|
||||
api: PUT /app
|
||||
arguments:
|
||||
app:
|
||||
help: App(s) to upgrade (default all)
|
||||
|
@ -318,6 +328,7 @@ app:
|
|||
### app_info() TODO: Write help
|
||||
info:
|
||||
action_help: Get app informations
|
||||
api: GET /app
|
||||
arguments:
|
||||
app:
|
||||
help: App ID
|
||||
|
@ -333,6 +344,7 @@ app:
|
|||
### app_addaccess() TODO: Write help
|
||||
addaccess:
|
||||
action_help: Grant access right to users (everyone by default)
|
||||
api: PUT /app/access
|
||||
arguments:
|
||||
apps:
|
||||
nargs: "+"
|
||||
|
@ -343,6 +355,7 @@ app:
|
|||
### app_removeaccess() TODO: Write help
|
||||
removeaccess:
|
||||
action_help: Revoke access right to users (everyone by default)
|
||||
api: DELETE /app/access
|
||||
arguments:
|
||||
apps:
|
||||
nargs: "+"
|
||||
|
@ -352,46 +365,20 @@ app:
|
|||
|
||||
|
||||
#############################
|
||||
# Repository #
|
||||
# Backup #
|
||||
#############################
|
||||
repo:
|
||||
category_help: Manage app repositories
|
||||
backup:
|
||||
category_help: Manage backups
|
||||
actions:
|
||||
|
||||
### repo_list()
|
||||
list:
|
||||
action_help: List repositories
|
||||
### backup_init()
|
||||
init:
|
||||
action_help: Init Tahoe-LAFS configuration
|
||||
api: POST /backup/init
|
||||
arguments:
|
||||
-f:
|
||||
full: --filter
|
||||
help: LDAP filter used to search
|
||||
-l:
|
||||
full: --limit
|
||||
help: Maximum number of repository fetched
|
||||
-o:
|
||||
full: --offset
|
||||
help: Starting number for repository fetching
|
||||
|
||||
### repo_add()
|
||||
add:
|
||||
action_help: Add app repository
|
||||
arguments:
|
||||
url:
|
||||
help: URL of the repository
|
||||
-n:
|
||||
full: --name
|
||||
help: Unique name of the repository
|
||||
|
||||
### repo_remove()
|
||||
remove:
|
||||
action_help: Remove repository
|
||||
arguments:
|
||||
repo:
|
||||
help: Name or URL of the repository
|
||||
|
||||
### repo_update()
|
||||
update:
|
||||
action_help: Update app list from the repositories
|
||||
--helper:
|
||||
help: Init as a helper node rather than a "helped" one
|
||||
action: store_true
|
||||
|
||||
|
||||
#############################
|
||||
|
@ -466,10 +453,12 @@ firewall:
|
|||
### firewall_list()
|
||||
list:
|
||||
action_help: List all firewall rules
|
||||
api: GET /firewall/list
|
||||
|
||||
### firewall_reload()
|
||||
reload:
|
||||
action_help: Reload all firewall rules
|
||||
action_help: Reload all firewall rules
|
||||
api: PUT /firewall/list
|
||||
arguments:
|
||||
-u:
|
||||
full: --upnp
|
||||
|
@ -478,6 +467,7 @@ firewall:
|
|||
### firewall_allow()
|
||||
allow:
|
||||
action_help: Allow connection port/protocol
|
||||
api: POST /firewall/port
|
||||
arguments:
|
||||
port:
|
||||
help: Port to open
|
||||
|
@ -500,6 +490,7 @@ firewall:
|
|||
### firewall_disallow()
|
||||
disallow:
|
||||
action_help: Disallow connection
|
||||
api: DELETE /firewall/port
|
||||
arguments:
|
||||
port:
|
||||
help: Port to open
|
||||
|
@ -522,21 +513,25 @@ firewall:
|
|||
### firewall_installupnp()
|
||||
installupnp:
|
||||
action_help: Add upnp cron
|
||||
api: POST /firewall/upnp
|
||||
|
||||
|
||||
### firewall_removeupnp()
|
||||
removeupnp:
|
||||
action_help: Remove upnp cron
|
||||
api: DELETE /firewall/upnp
|
||||
|
||||
|
||||
### firewall_stop()
|
||||
stop:
|
||||
action_help: Stop iptables and ip6tables
|
||||
api: DELETE /firewall
|
||||
|
||||
|
||||
### firewall_checkupnp()
|
||||
checkupnp:
|
||||
action_help: check if UPNP is install or not (0 yes 1 no)
|
||||
api: GET /firewall/upnp
|
||||
|
||||
|
||||
#############################
|
||||
|
@ -549,6 +544,7 @@ dyndns:
|
|||
### dyndns_subscribe()
|
||||
subscribe:
|
||||
action_help: Subscribe to a DynDNS service
|
||||
api: POST /dyndns
|
||||
arguments:
|
||||
--subscribe-host:
|
||||
help: Dynette HTTP API to subscribe to
|
||||
|
@ -563,6 +559,7 @@ dyndns:
|
|||
### dyndns_update()
|
||||
update:
|
||||
action_help: Update IP on DynDNS platform
|
||||
api: PUT /dyndns
|
||||
arguments:
|
||||
--dyn-host:
|
||||
help: Dynette DNS server to inform
|
||||
|
@ -580,10 +577,12 @@ dyndns:
|
|||
### dyndns_installcron()
|
||||
installcron:
|
||||
action_help: Install IP update cron
|
||||
api: POST /dyndns/cron
|
||||
|
||||
### dyndns_removecron()
|
||||
removecron:
|
||||
action_help: Remove IP update cron
|
||||
api: DELETE /dyndns/cron
|
||||
|
||||
|
||||
#############################
|
||||
|
@ -596,10 +595,12 @@ tools:
|
|||
### tools_ldapinit()
|
||||
ldapinit:
|
||||
action_help: YunoHost LDAP initialization
|
||||
api: POST /ldap
|
||||
|
||||
### tools_adminpw()
|
||||
adminpw:
|
||||
action_help: Change admin password
|
||||
api: PUT /adminpw
|
||||
arguments:
|
||||
-o:
|
||||
full: --old-password
|
||||
|
@ -613,6 +614,7 @@ tools:
|
|||
### tools_maindomain()
|
||||
maindomain:
|
||||
action_help: Main domain change tool
|
||||
api: PUT /domain/main
|
||||
arguments:
|
||||
-o:
|
||||
full: --old-domain
|
||||
|
@ -625,6 +627,7 @@ tools:
|
|||
### tools_postinstall()
|
||||
postinstall:
|
||||
action_help: YunoHost post-install
|
||||
api: POST /postinstall
|
||||
arguments:
|
||||
-d:
|
||||
full: --domain
|
||||
|
|
|
@ -23,7 +23,7 @@ parents:
|
|||
- organizationalUnit
|
||||
- top
|
||||
|
||||
childs:
|
||||
children:
|
||||
cn=admins,ou=groups:
|
||||
cn: admins
|
||||
gidNumber: "4001"
|
||||
|
|
26
sudo_ldap_scheme.yml
Normal file
26
sudo_ldap_scheme.yml
Normal file
|
@ -0,0 +1,26 @@
|
|||
parents:
|
||||
ou=sudo:
|
||||
ou: sudo
|
||||
objectClass:
|
||||
- organizationalUnit
|
||||
- top
|
||||
children:
|
||||
cn=admin,ou=sudo:
|
||||
cn: admin
|
||||
sudoUser: admin
|
||||
sudoHost: ALL
|
||||
sudoCommand: ALL
|
||||
sudoOption: "!authenticate"
|
||||
objectClass:
|
||||
- sudoRole
|
||||
- top
|
||||
|
||||
cn=yunohost-admin,ou=sudo:
|
||||
cn: yunohost-admin
|
||||
sudoUser: yunohost-admin
|
||||
sudoHost: ALL
|
||||
sudoCommand: /usr/bin/yunohost
|
||||
sudoOption: "!authenticate"
|
||||
objectClass:
|
||||
- sudoRole
|
||||
- top
|
3
txrestapi/.gitignore
vendored
Normal file
3
txrestapi/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
_trial_temp
|
||||
txrestapi.egg-info
|
||||
txrestapi/_trial_temp
|
146
txrestapi/README.rst
Normal file
146
txrestapi/README.rst
Normal file
|
@ -0,0 +1,146 @@
|
|||
============
|
||||
Introduction
|
||||
============
|
||||
|
||||
``txrestapi`` makes it easier to create Twisted REST API services. Normally, one
|
||||
would create ``Resource`` subclasses defining each segment of a path; this is
|
||||
cubersome to implement and results in output that isn't very readable.
|
||||
``txrestapi`` provides an ``APIResource`` class allowing complex mapping of path to
|
||||
callback (a la Django) with a readable decorator.
|
||||
|
||||
===============================
|
||||
Basic URL callback registration
|
||||
===============================
|
||||
|
||||
First, let's create a bare API service::
|
||||
|
||||
>>> from txrestapi.resource import APIResource
|
||||
>>> api = APIResource()
|
||||
|
||||
and a web server to serve it::
|
||||
|
||||
>>> from twisted.web.server import Site
|
||||
>>> from twisted.internet import reactor
|
||||
>>> site = Site(api, timeout=None)
|
||||
|
||||
and a function to make it easy for us to make requests (only for doctest
|
||||
purposes; normally you would of course use ``reactor.listenTCP(8080, site)``)::
|
||||
|
||||
>>> from twisted.web.server import Request
|
||||
>>> class FakeChannel(object):
|
||||
... transport = None
|
||||
>>> def makeRequest(method, path):
|
||||
... req = Request(FakeChannel(), None)
|
||||
... req.prepath = req.postpath = None
|
||||
... req.method = method; req.path = path
|
||||
... resource = site.getChildWithDefault(path, req)
|
||||
... return resource.render(req)
|
||||
|
||||
We can now register callbacks for paths we care about. We can provide different
|
||||
callbacks for different methods; they must accept ``request`` as the first
|
||||
argument::
|
||||
|
||||
>>> def get_callback(request): return 'GET callback'
|
||||
>>> api.register('GET', '^/path/to/method', get_callback)
|
||||
>>> def post_callback(request): return 'POST callback'
|
||||
>>> api.register('POST', '^/path/to/method', post_callback)
|
||||
|
||||
Then, when we make a call, the request is routed to the proper callback::
|
||||
|
||||
>>> print makeRequest('GET', '/path/to/method')
|
||||
GET callback
|
||||
>>> print makeRequest('POST', '/path/to/method')
|
||||
POST callback
|
||||
|
||||
We can register multiple callbacks for different requests; the first one that
|
||||
matches wins::
|
||||
|
||||
>>> def default_callback(request):
|
||||
... return 'Default callback'
|
||||
>>> api.register('GET', '^/.*$', default_callback) # Matches everything
|
||||
>>> print makeRequest('GET', '/path/to/method')
|
||||
GET callback
|
||||
>>> print makeRequest('GET', '/path/to/different/method')
|
||||
Default callback
|
||||
|
||||
Our default callback, however, will only match GET requests. For a true default
|
||||
callback, we can either register callbacks for each method individually, or we
|
||||
can use ALL::
|
||||
|
||||
>>> api.register('ALL', '^/.*$', default_callback)
|
||||
>>> print makeRequest('PUT', '/path/to/method')
|
||||
Default callback
|
||||
>>> print makeRequest('DELETE', '/path/to/method')
|
||||
Default callback
|
||||
>>> print makeRequest('GET', '/path/to/method')
|
||||
GET callback
|
||||
|
||||
Let's unregister all references to the default callback so it doesn't interfere
|
||||
with later tests (default callbacks should, of course, always be registered
|
||||
last, so they don't get called before other callbacks)::
|
||||
|
||||
>>> api.unregister(callback=default_callback)
|
||||
|
||||
=============
|
||||
URL Arguments
|
||||
=============
|
||||
|
||||
Since callbacks accept ``request``, they have access to POST data or query
|
||||
arguments, but we can also pull arguments out of the URL by using named groups
|
||||
in the regular expression (similar to Django). These will be passed into the
|
||||
callback as keyword arguments::
|
||||
|
||||
>>> def get_info(request, id):
|
||||
... return 'Information for id %s' % id
|
||||
>>> api.register('GET', '/(?P<id>[^/]+)/info$', get_info)
|
||||
>>> print makeRequest('GET', '/someid/info')
|
||||
Information for id someid
|
||||
|
||||
Bear in mind all arguments will come in as strings, so code should be
|
||||
accordingly defensive.
|
||||
|
||||
================
|
||||
Decorator syntax
|
||||
================
|
||||
|
||||
Registration via the ``register()`` method is somewhat awkward, so decorators
|
||||
are provided making it much more straightforward. ::
|
||||
|
||||
>>> from txrestapi.methods import GET, POST, PUT, ALL
|
||||
>>> class MyResource(APIResource):
|
||||
...
|
||||
... @GET('^/(?P<id>[^/]+)/info')
|
||||
... def get_info(self, request, id):
|
||||
... return 'Info for id %s' % id
|
||||
...
|
||||
... @PUT('^/(?P<id>[^/]+)/update')
|
||||
... @POST('^/(?P<id>[^/]+)/update')
|
||||
... def set_info(self, request, id):
|
||||
... return "Setting info for id %s" % id
|
||||
...
|
||||
... @ALL('^/')
|
||||
... def default_view(self, request):
|
||||
... return "I match any URL"
|
||||
|
||||
Again, registrations occur top to bottom, so methods should be written from
|
||||
most specific to least. Also notice that one can use the decorator syntax as
|
||||
one would expect to register a method as the target for two URLs ::
|
||||
|
||||
>>> site = Site(MyResource(), timeout=None)
|
||||
>>> print makeRequest('GET', '/anid/info')
|
||||
Info for id anid
|
||||
>>> print makeRequest('PUT', '/anid/update')
|
||||
Setting info for id anid
|
||||
>>> print makeRequest('POST', '/anid/update')
|
||||
Setting info for id anid
|
||||
>>> print makeRequest('DELETE', '/anid/delete')
|
||||
I match any URL
|
||||
|
||||
======================
|
||||
Callback return values
|
||||
======================
|
||||
|
||||
You can return Resource objects from a callback if you wish, allowing you to
|
||||
have APIs that send you to other kinds of resources, or even other APIs.
|
||||
Normally, however, you'll most likely want to return strings, which will be
|
||||
wrapped in a Resource object for convenience.
|
1
txrestapi/setup.cfg
Normal file
1
txrestapi/setup.cfg
Normal file
|
@ -0,0 +1 @@
|
|||
[egg_info]
|
26
txrestapi/setup.py
Normal file
26
txrestapi/setup.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
from setuptools import setup, find_packages
|
||||
import sys, os
|
||||
|
||||
version = '0.1'
|
||||
|
||||
setup(name='txrestapi',
|
||||
version=version,
|
||||
description="Easing the creation of REST API services in Python",
|
||||
long_description="""\
|
||||
""",
|
||||
classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
|
||||
keywords='',
|
||||
author='Ian McCracken',
|
||||
author_email='ian.mccracken@gmail.com',
|
||||
url='http://github.com/iancmcc/txrestapi',
|
||||
license='MIT',
|
||||
packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
install_requires=[
|
||||
# -*- Extra requirements: -*-
|
||||
],
|
||||
entry_points="""
|
||||
# -*- Entry points: -*-
|
||||
""",
|
||||
)
|
1
txrestapi/txrestapi/__init__.py
Normal file
1
txrestapi/txrestapi/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
#
|
29
txrestapi/txrestapi/methods.py
Normal file
29
txrestapi/txrestapi/methods.py
Normal file
|
@ -0,0 +1,29 @@
|
|||
from zope.interface.advice import addClassAdvisor
|
||||
|
||||
def method_factory_factory(method):
|
||||
def factory(regex):
|
||||
_f = {}
|
||||
def decorator(f):
|
||||
_f[f.__name__] = f
|
||||
return f
|
||||
def advisor(cls):
|
||||
def wrapped(f):
|
||||
def __init__(self, *args, **kwargs):
|
||||
f(self, *args, **kwargs)
|
||||
for func_name in _f:
|
||||
orig = _f[func_name]
|
||||
func = getattr(self, func_name)
|
||||
if func.im_func==orig:
|
||||
self.register(method, regex, func)
|
||||
return __init__
|
||||
cls.__init__ = wrapped(cls.__init__)
|
||||
return cls
|
||||
addClassAdvisor(advisor)
|
||||
return decorator
|
||||
return factory
|
||||
|
||||
ALL = method_factory_factory('ALL')
|
||||
GET = method_factory_factory('GET')
|
||||
POST = method_factory_factory('POST')
|
||||
PUT = method_factory_factory('PUT')
|
||||
DELETE = method_factory_factory('DELETE')
|
65
txrestapi/txrestapi/resource.py
Normal file
65
txrestapi/txrestapi/resource.py
Normal file
|
@ -0,0 +1,65 @@
|
|||
import re
|
||||
from itertools import ifilter
|
||||
from functools import wraps
|
||||
from twisted.web.resource import Resource, NoResource
|
||||
|
||||
class _FakeResource(Resource):
|
||||
_result = ''
|
||||
isLeaf = True
|
||||
def __init__(self, result):
|
||||
Resource.__init__(self)
|
||||
self._result = result
|
||||
def render(self, request):
|
||||
return self._result
|
||||
|
||||
|
||||
def maybeResource(f):
|
||||
@wraps(f)
|
||||
def inner(*args, **kwargs):
|
||||
result = f(*args, **kwargs)
|
||||
if not isinstance(result, Resource):
|
||||
result = _FakeResource(result)
|
||||
return result
|
||||
return inner
|
||||
|
||||
|
||||
class APIResource(Resource):
|
||||
|
||||
_registry = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
Resource.__init__(self, *args, **kwargs)
|
||||
self._registry = []
|
||||
|
||||
def _get_callback(self, request):
|
||||
filterf = lambda t:t[0] in (request.method, 'ALL')
|
||||
path_to_check = getattr(request, '_remaining_path', request.path)
|
||||
for m, r, cb in ifilter(filterf, self._registry):
|
||||
result = r.search(path_to_check)
|
||||
if result:
|
||||
request._remaining_path = path_to_check[result.span()[1]:]
|
||||
return cb, result.groupdict()
|
||||
return None, None
|
||||
|
||||
def register(self, method, regex, callback):
|
||||
self._registry.append((method, re.compile(regex), callback))
|
||||
|
||||
def unregister(self, method=None, regex=None, callback=None):
|
||||
if regex is not None: regex = re.compile(regex)
|
||||
for m, r, cb in self._registry[:]:
|
||||
if not method or (method and m==method):
|
||||
if not regex or (regex and r==regex):
|
||||
if not callback or (callback and cb==callback):
|
||||
self._registry.remove((m, r, cb))
|
||||
|
||||
def getChild(self, name, request):
|
||||
r = self.children.get(name, None)
|
||||
if r is None:
|
||||
# Go into the thing
|
||||
callback, args = self._get_callback(request)
|
||||
if callback is None:
|
||||
return NoResource()
|
||||
else:
|
||||
return maybeResource(callback)(request, **args)
|
||||
else:
|
||||
return r
|
7
txrestapi/txrestapi/service.py
Normal file
7
txrestapi/txrestapi/service.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
from twisted.web.server import Site
|
||||
from .resource import APIResource
|
||||
|
||||
|
||||
class RESTfulService(Site):
|
||||
def __init__(self, port=8080):
|
||||
self.root = APIResource()
|
194
txrestapi/txrestapi/tests.py
Normal file
194
txrestapi/txrestapi/tests.py
Normal file
|
@ -0,0 +1,194 @@
|
|||
import txrestapi
|
||||
__package__="txrestapi"
|
||||
import re
|
||||
import os.path
|
||||
from twisted.internet import reactor
|
||||
from twisted.internet.defer import inlineCallbacks
|
||||
from twisted.web.resource import Resource, NoResource
|
||||
from twisted.web.server import Request, Site
|
||||
from twisted.web.client import getPage
|
||||
from twisted.trial import unittest
|
||||
from .resource import APIResource
|
||||
from .methods import GET, PUT
|
||||
|
||||
class FakeChannel(object):
|
||||
transport = None
|
||||
|
||||
def getRequest(method, url):
|
||||
req = Request(FakeChannel(), None)
|
||||
req.method = method
|
||||
req.path = url
|
||||
return req
|
||||
|
||||
class APIResourceTest(unittest.TestCase):
|
||||
|
||||
def test_returns_normal_resources(self):
|
||||
r = APIResource()
|
||||
a = Resource()
|
||||
r.putChild('a', a)
|
||||
req = Request(FakeChannel(), None)
|
||||
a_ = r.getChild('a', req)
|
||||
self.assertEqual(a, a_)
|
||||
|
||||
def test_registry(self):
|
||||
compiled = re.compile('regex')
|
||||
r = APIResource()
|
||||
r.register('GET', 'regex', None)
|
||||
self.assertEqual([x[0] for x in r._registry], ['GET'])
|
||||
self.assertEqual(r._registry[0], ('GET', compiled, None))
|
||||
|
||||
def test_method_matching(self):
|
||||
r = APIResource()
|
||||
r.register('GET', 'regex', 1)
|
||||
r.register('PUT', 'regex', 2)
|
||||
r.register('GET', 'another', 3)
|
||||
|
||||
req = getRequest('GET', 'regex')
|
||||
result = r._get_callback(req)
|
||||
self.assert_(result)
|
||||
self.assertEqual(result[0], 1)
|
||||
|
||||
req = getRequest('PUT', 'regex')
|
||||
result = r._get_callback(req)
|
||||
self.assert_(result)
|
||||
self.assertEqual(result[0], 2)
|
||||
|
||||
req = getRequest('GET', 'another')
|
||||
result = r._get_callback(req)
|
||||
self.assert_(result)
|
||||
self.assertEqual(result[0], 3)
|
||||
|
||||
req = getRequest('PUT', 'another')
|
||||
result = r._get_callback(req)
|
||||
self.assertEqual(result, (None, None))
|
||||
|
||||
def test_callback(self):
|
||||
marker = object()
|
||||
def cb(request):
|
||||
return marker
|
||||
r = APIResource()
|
||||
r.register('GET', 'regex', cb)
|
||||
req = getRequest('GET', 'regex')
|
||||
result = r.getChild('regex', req)
|
||||
self.assertEqual(result.render(req), marker)
|
||||
|
||||
def test_longerpath(self):
|
||||
marker = object()
|
||||
r = APIResource()
|
||||
def cb(request):
|
||||
return marker
|
||||
r.register('GET', '/regex/a/b/c', cb)
|
||||
req = getRequest('GET', '/regex/a/b/c')
|
||||
result = r.getChild('regex', req)
|
||||
self.assertEqual(result.render(req), marker)
|
||||
|
||||
def test_args(self):
|
||||
r = APIResource()
|
||||
def cb(request, **kwargs):
|
||||
return kwargs
|
||||
r.register('GET', '/(?P<a>[^/]*)/a/(?P<b>[^/]*)/c', cb)
|
||||
req = getRequest('GET', '/regex/a/b/c')
|
||||
result = r.getChild('regex', req)
|
||||
self.assertEqual(sorted(result.render(req).keys()), ['a', 'b'])
|
||||
|
||||
def test_order(self):
|
||||
r = APIResource()
|
||||
def cb1(request, **kwargs):
|
||||
kwargs.update({'cb1':True})
|
||||
return kwargs
|
||||
def cb(request, **kwargs):
|
||||
return kwargs
|
||||
# Register two regexes that will match
|
||||
r.register('GET', '/(?P<a>[^/]*)/a/(?P<b>[^/]*)/c', cb1)
|
||||
r.register('GET', '/(?P<a>[^/]*)/a/(?P<b>[^/]*)', cb)
|
||||
req = getRequest('GET', '/regex/a/b/c')
|
||||
result = r.getChild('regex', req)
|
||||
# Make sure the first one got it
|
||||
self.assert_('cb1' in result.render(req))
|
||||
|
||||
def test_no_resource(self):
|
||||
r = APIResource()
|
||||
r.register('GET', '^/(?P<a>[^/]*)/a/(?P<b>[^/]*)$', None)
|
||||
req = getRequest('GET', '/definitely/not/a/match')
|
||||
result = r.getChild('regex', req)
|
||||
self.assert_(isinstance(result, NoResource))
|
||||
|
||||
def test_all(self):
|
||||
r = APIResource()
|
||||
def get_cb(r): return 'GET'
|
||||
def put_cb(r): return 'PUT'
|
||||
def all_cb(r): return 'ALL'
|
||||
r.register('GET', '^path', get_cb)
|
||||
r.register('ALL', '^path', all_cb)
|
||||
r.register('PUT', '^path', put_cb)
|
||||
# Test that the ALL registration picks it up before the PUT one
|
||||
for method in ('GET', 'PUT', 'ALL'):
|
||||
req = getRequest(method, 'path')
|
||||
result = r.getChild('path', req)
|
||||
self.assertEqual(result.render(req), 'ALL' if method=='PUT' else method)
|
||||
|
||||
|
||||
class TestResource(Resource):
|
||||
isLeaf = True
|
||||
def render(self, request):
|
||||
return 'aresource'
|
||||
|
||||
|
||||
class TestAPI(APIResource):
|
||||
|
||||
@GET('^/(?P<a>test[^/]*)/?')
|
||||
def _on_test_get(self, request, a):
|
||||
return 'GET %s' % a
|
||||
|
||||
@PUT('^/(?P<a>test[^/]*)/?')
|
||||
def _on_test_put(self, request, a):
|
||||
return 'PUT %s' % a
|
||||
|
||||
@GET('^/gettest')
|
||||
def _on_gettest(self, request):
|
||||
return TestResource()
|
||||
|
||||
|
||||
class DecoratorsTest(unittest.TestCase):
|
||||
def _listen(self, site):
|
||||
return reactor.listenTCP(0, site, interface="127.0.0.1")
|
||||
|
||||
def setUp(self):
|
||||
r = TestAPI()
|
||||
site = Site(r, timeout=None)
|
||||
self.port = self._listen(site)
|
||||
self.portno = self.port.getHost().port
|
||||
|
||||
def tearDown(self):
|
||||
return self.port.stopListening()
|
||||
|
||||
def getURL(self, path):
|
||||
return "http://127.0.0.1:%d/%s" % (self.portno, path)
|
||||
|
||||
@inlineCallbacks
|
||||
def test_get(self):
|
||||
url = self.getURL('test_thing/')
|
||||
result = yield getPage(url, method='GET')
|
||||
self.assertEqual(result, 'GET test_thing')
|
||||
|
||||
@inlineCallbacks
|
||||
def test_put(self):
|
||||
url = self.getURL('test_thing/')
|
||||
result = yield getPage(url, method='PUT')
|
||||
self.assertEqual(result, 'PUT test_thing')
|
||||
|
||||
@inlineCallbacks
|
||||
def test_resource_wrapper(self):
|
||||
url = self.getURL('gettest')
|
||||
result = yield getPage(url, method='GET')
|
||||
self.assertEqual(result, 'aresource')
|
||||
|
||||
|
||||
def test_suite():
|
||||
import unittest as ut
|
||||
suite = unittest.TestSuite()
|
||||
suite.addTest(ut.makeSuite(DecoratorsTest))
|
||||
suite.addTest(ut.makeSuite(APIResourceTest))
|
||||
suite.addTest(unittest.doctest.DocFileSuite(os.path.join('..', 'README.rst')))
|
||||
return suite
|
||||
|
111
yunohost
111
yunohost
|
@ -38,121 +38,12 @@ if not __debug__:
|
|||
gettext.install('YunoHost')
|
||||
|
||||
try:
|
||||
from yunohost import YunoHostError, YunoHostLDAP, str_to_func, colorize, pretty_print_dict, display_error, validate, win
|
||||
from yunohost import YunoHostError, YunoHostLDAP, str_to_func, colorize, pretty_print_dict, display_error, validate, win, parse_dict
|
||||
except ImportError:
|
||||
sys.stderr.write('Error: Yunohost CLI Require YunoHost lib\n')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def parse_dict(action_map):
|
||||
"""
|
||||
Turn action dictionnary to parser, subparsers and arguments
|
||||
|
||||
Keyword arguments:
|
||||
action_map -- Multi-level dictionnary of categories/actions/arguments list
|
||||
|
||||
Returns:
|
||||
Namespace of args
|
||||
|
||||
"""
|
||||
# Intialize parsers
|
||||
parsers = subparsers_category = subparsers_action = {}
|
||||
parsers['general'] = argparse.ArgumentParser()
|
||||
subparsers = parsers['general'].add_subparsers()
|
||||
new_args = []
|
||||
patterns = {}
|
||||
|
||||
# Add general arguments
|
||||
for arg_name, arg_params in action_map['general_arguments'].items():
|
||||
if 'full' in arg_params:
|
||||
arg_names = [arg_name, arg_params['full']]
|
||||
arg_fullname = arg_params['full']
|
||||
del arg_params['full']
|
||||
else: arg_names = [arg_name]
|
||||
parsers['general'].add_argument(*arg_names, **arg_params)
|
||||
|
||||
del action_map['general_arguments']
|
||||
|
||||
# Split categories into subparsers
|
||||
for category, category_params in action_map.items():
|
||||
if 'category_help' not in category_params: category_params['category_help'] = ''
|
||||
subparsers_category[category] = subparsers.add_parser(category, help=category_params['category_help'])
|
||||
subparsers_action[category] = subparsers_category[category].add_subparsers()
|
||||
# Split actions
|
||||
if 'actions' in category_params:
|
||||
for action, action_params in category_params['actions'].items():
|
||||
if 'action_help' not in action_params: action_params['action_help'] = ''
|
||||
parsers[category + '_' + action] = subparsers_action[category].add_parser(action, help=action_params['action_help'])
|
||||
# Set the action s related function
|
||||
parsers[category + '_' + action].set_defaults(
|
||||
func=str_to_func('yunohost_' + category + '.'
|
||||
+ category + '_' + action))
|
||||
# Add arguments
|
||||
if 'arguments' in action_params:
|
||||
for arg_name, arg_params in action_params['arguments'].items():
|
||||
arg_fullname = False
|
||||
|
||||
if 'password' in arg_params:
|
||||
if arg_params['password']: is_password = True
|
||||
del arg_params['password']
|
||||
else: is_password = False
|
||||
|
||||
if 'full' in arg_params:
|
||||
arg_names = [arg_name, arg_params['full']]
|
||||
arg_fullname = arg_params['full']
|
||||
del arg_params['full']
|
||||
else: arg_names = [arg_name]
|
||||
|
||||
if 'ask' in arg_params:
|
||||
require_input = True
|
||||
if '-h' in sys.argv or '--help' in sys.argv:
|
||||
require_input = False
|
||||
if (category != sys.argv[1]) or (action != sys.argv[2]):
|
||||
require_input = False
|
||||
for name in arg_names:
|
||||
if name in sys.argv[2:]: require_input = False
|
||||
|
||||
if require_input:
|
||||
if is_password:
|
||||
if os.isatty(1):
|
||||
pwd1 = getpass.getpass(colorize(arg_params['ask'] + ': ', 'cyan'))
|
||||
pwd2 = getpass.getpass(colorize('Retype ' + arg_params['ask'][0].lower() + arg_params['ask'][1:] + ': ', 'cyan'))
|
||||
if pwd1 != pwd2:
|
||||
raise YunoHostError(22, _("Passwords don't match"))
|
||||
sys.exit(1)
|
||||
else:
|
||||
raise YunoHostError(22, _("Missing arguments") + ': ' + arg_name)
|
||||
if arg_name[0] == '-': arg_extend = [arg_name, pwd1]
|
||||
else: arg_extend = [pwd1]
|
||||
else:
|
||||
if os.isatty(1):
|
||||
arg_value = raw_input(colorize(arg_params['ask'] + ': ', 'cyan'))
|
||||
else:
|
||||
raise YunoHostError(22, _("Missing arguments") + ': ' + arg_name)
|
||||
if arg_name[0] == '-': arg_extend = [arg_name, arg_value]
|
||||
else: arg_extend = [arg_value]
|
||||
new_args.extend(arg_extend)
|
||||
del arg_params['ask']
|
||||
|
||||
if 'pattern' in arg_params:
|
||||
if (category == sys.argv[1]) and (action == sys.argv[2]):
|
||||
if 'dest' in arg_params: name = arg_params['dest']
|
||||
elif arg_fullname: name = arg_fullname[2:]
|
||||
else: name = arg_name
|
||||
name = name.replace('-', '_')
|
||||
patterns[name] = arg_params['pattern']
|
||||
del arg_params['pattern']
|
||||
|
||||
parsers[category + '_' + action].add_argument(*arg_names, **arg_params)
|
||||
|
||||
args = parsers['general'].parse_args(sys.argv.extend(new_args))
|
||||
args_dict = vars(args)
|
||||
for key, value in patterns.items():
|
||||
validate(value, args_dict[key])
|
||||
|
||||
return args
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main instructions
|
||||
|
|
117
yunohost.py
117
yunohost.py
|
@ -9,11 +9,15 @@ except ImportError:
|
|||
sys.stderr.write('apt-get install python-ldap\n')
|
||||
sys.exit(1)
|
||||
import ldap.modlist as modlist
|
||||
import yaml
|
||||
import json
|
||||
import re
|
||||
import getpass
|
||||
import random
|
||||
import string
|
||||
import argparse
|
||||
import gettext
|
||||
import getpass
|
||||
if not __debug__:
|
||||
import traceback
|
||||
|
||||
|
@ -93,8 +97,8 @@ def win_msg(astr):
|
|||
global win
|
||||
if os.isatty(1):
|
||||
print('\n' + colorize(_("Success: "), 'green') + astr + '\n')
|
||||
else:
|
||||
win.append(astr)
|
||||
|
||||
win.append(astr)
|
||||
|
||||
|
||||
|
||||
|
@ -444,3 +448,112 @@ class YunoHostLDAP(Singleton):
|
|||
else:
|
||||
raise YunoHostError(17, _('Attribute already exists') + ' "' + attr + '=' + value + '"')
|
||||
return True
|
||||
|
||||
|
||||
def parse_dict(action_map):
|
||||
"""
|
||||
Turn action dictionnary to parser, subparsers and arguments
|
||||
|
||||
Keyword arguments:
|
||||
action_map -- Multi-level dictionnary of categories/actions/arguments list
|
||||
|
||||
Returns:
|
||||
Namespace of args
|
||||
|
||||
"""
|
||||
# Intialize parsers
|
||||
parsers = subparsers_category = subparsers_action = {}
|
||||
parsers['general'] = argparse.ArgumentParser()
|
||||
subparsers = parsers['general'].add_subparsers()
|
||||
new_args = []
|
||||
patterns = {}
|
||||
|
||||
# Add general arguments
|
||||
for arg_name, arg_params in action_map['general_arguments'].items():
|
||||
if 'full' in arg_params:
|
||||
arg_names = [arg_name, arg_params['full']]
|
||||
arg_fullname = arg_params['full']
|
||||
del arg_params['full']
|
||||
else: arg_names = [arg_name]
|
||||
parsers['general'].add_argument(*arg_names, **arg_params)
|
||||
|
||||
del action_map['general_arguments']
|
||||
|
||||
# Split categories into subparsers
|
||||
for category, category_params in action_map.items():
|
||||
if 'category_help' not in category_params: category_params['category_help'] = ''
|
||||
subparsers_category[category] = subparsers.add_parser(category, help=category_params['category_help'])
|
||||
subparsers_action[category] = subparsers_category[category].add_subparsers()
|
||||
# Split actions
|
||||
if 'actions' in category_params:
|
||||
for action, action_params in category_params['actions'].items():
|
||||
if 'action_help' not in action_params: action_params['action_help'] = ''
|
||||
parsers[category + '_' + action] = subparsers_action[category].add_parser(action, help=action_params['action_help'])
|
||||
# Set the action s related function
|
||||
parsers[category + '_' + action].set_defaults(
|
||||
func=str_to_func('yunohost_' + category + '.'
|
||||
+ category + '_' + action))
|
||||
# Add arguments
|
||||
if 'arguments' in action_params:
|
||||
for arg_name, arg_params in action_params['arguments'].items():
|
||||
arg_fullname = False
|
||||
|
||||
if 'password' in arg_params:
|
||||
if arg_params['password']: is_password = True
|
||||
del arg_params['password']
|
||||
else: is_password = False
|
||||
|
||||
if 'full' in arg_params:
|
||||
arg_names = [arg_name, arg_params['full']]
|
||||
arg_fullname = arg_params['full']
|
||||
del arg_params['full']
|
||||
else: arg_names = [arg_name]
|
||||
|
||||
if 'ask' in arg_params:
|
||||
require_input = True
|
||||
if '-h' in sys.argv or '--help' in sys.argv:
|
||||
require_input = False
|
||||
if (category != sys.argv[1]) or (action != sys.argv[2]):
|
||||
require_input = False
|
||||
for name in arg_names:
|
||||
if name in sys.argv[2:]: require_input = False
|
||||
|
||||
if require_input:
|
||||
if is_password:
|
||||
if os.isatty(1):
|
||||
pwd1 = getpass.getpass(colorize(arg_params['ask'] + ': ', 'cyan'))
|
||||
pwd2 = getpass.getpass(colorize('Retype ' + arg_params['ask'][0].lower() + arg_params['ask'][1:] + ': ', 'cyan'))
|
||||
if pwd1 != pwd2:
|
||||
raise YunoHostError(22, _("Passwords don't match"))
|
||||
sys.exit(1)
|
||||
else:
|
||||
raise YunoHostError(22, _("Missing arguments") + ': ' + arg_name)
|
||||
if arg_name[0] == '-': arg_extend = [arg_name, pwd1]
|
||||
else: arg_extend = [pwd1]
|
||||
else:
|
||||
if os.isatty(1):
|
||||
arg_value = raw_input(colorize(arg_params['ask'] + ': ', 'cyan'))
|
||||
else:
|
||||
raise YunoHostError(22, _("Missing arguments") + ': ' + arg_name)
|
||||
if arg_name[0] == '-': arg_extend = [arg_name, arg_value]
|
||||
else: arg_extend = [arg_value]
|
||||
new_args.extend(arg_extend)
|
||||
del arg_params['ask']
|
||||
|
||||
if 'pattern' in arg_params:
|
||||
if (category == sys.argv[1]) and (action == sys.argv[2]):
|
||||
if 'dest' in arg_params: name = arg_params['dest']
|
||||
elif arg_fullname: name = arg_fullname[2:]
|
||||
else: name = arg_name
|
||||
name = name.replace('-', '_')
|
||||
patterns[name] = arg_params['pattern']
|
||||
del arg_params['pattern']
|
||||
|
||||
parsers[category + '_' + action].add_argument(*arg_names, **arg_params)
|
||||
|
||||
args = parsers['general'].parse_args(sys.argv.extend(new_args))
|
||||
args_dict = vars(args)
|
||||
for key, value in patterns.items():
|
||||
validate(value, args_dict[key])
|
||||
|
||||
return args
|
||||
|
|
155
yunohost.tac
Executable file
155
yunohost.tac
Executable file
|
@ -0,0 +1,155 @@
|
|||
# -*- mode: python -*-
|
||||
import os
|
||||
import sys
|
||||
import gettext
|
||||
import ldap
|
||||
import yaml
|
||||
import json
|
||||
from twisted.python import log
|
||||
from twisted.web.server import Site
|
||||
from twisted.web.resource import IResource
|
||||
from twisted.web.guard import HTTPAuthSessionWrapper, BasicCredentialFactory
|
||||
from twisted.internet import reactor, defer
|
||||
from twisted.cred.portal import IRealm, Portal
|
||||
from twisted.cred.checkers import ICredentialsChecker
|
||||
from twisted.cred.credentials import IUsernamePassword
|
||||
from twisted.cred.error import UnauthorizedLogin
|
||||
from zope.interface import implements
|
||||
from txrestapi.resource import APIResource
|
||||
from yunohost import YunoHostError, YunoHostLDAP, str_to_func, colorize, pretty_print_dict, display_error, validate, win, parse_dict
|
||||
|
||||
if not __debug__:
|
||||
import traceback
|
||||
|
||||
gettext.install('YunoHost')
|
||||
|
||||
class LDAPHTTPAuth():
|
||||
implements (ICredentialsChecker)
|
||||
|
||||
credentialInterfaces = IUsernamePassword,
|
||||
|
||||
def requestAvatarId(self, credentials):
|
||||
try:
|
||||
if credentials.username != "admin":
|
||||
raise YunoHostError(22, _("Invalid username") + ': ' + credentials.username)
|
||||
YunoHostLDAP(password=credentials.password)
|
||||
return credentials.username
|
||||
|
||||
except Exception as e:
|
||||
return defer.fail(UnauthorizedLogin())
|
||||
|
||||
|
||||
class SimpleRealm(object):
|
||||
implements(IRealm)
|
||||
|
||||
_api = None
|
||||
|
||||
def __init__(self, api):
|
||||
self._api = api
|
||||
|
||||
def requestAvatar(self, avatarId, mind, *interfaces):
|
||||
if IResource in interfaces:
|
||||
return IResource, self._api, lambda: None
|
||||
raise NotImplementedError()
|
||||
|
||||
action_dict = {}
|
||||
|
||||
def http_exec(request):
|
||||
global win
|
||||
dict = action_dict[request.method+' '+request.path]
|
||||
if 'arguments' in dict: args = dict['arguments']
|
||||
else: args = {}
|
||||
for arg, params in args.items():
|
||||
sanitized_key = arg.replace('-', '_')
|
||||
if sanitized_key is not arg:
|
||||
args[sanitized_key] = args[arg]
|
||||
del args[arg]
|
||||
arg = sanitized_key
|
||||
if arg[0] == '_':
|
||||
if 'nargs' not in params:
|
||||
args[arg]['nargs'] = '*'
|
||||
if 'full' in params:
|
||||
new_key = params['full'][2:]
|
||||
else:
|
||||
new_key = arg[2:]
|
||||
args[new_key] = args[arg]
|
||||
del args[arg]
|
||||
|
||||
try:
|
||||
validated_args = {}
|
||||
for key, value in request.args.items():
|
||||
if key in args:
|
||||
# Validate args
|
||||
if 'pattern' in args[key]: validate(args[key]['pattern'], value)
|
||||
if 'nargs' not in args[key] or ('nargs' != '*' and 'nargs' != '+'): value = value[0]
|
||||
if 'action' in args[key] and args[key]['action'] == 'store_true':
|
||||
yes = ['true', 'True', 'yes', 'Yes']
|
||||
value = value in yes
|
||||
validated_args[key] = value
|
||||
|
||||
func = str_to_func(dict['function'])
|
||||
with YunoHostLDAP(password=request.getPassword()):
|
||||
result = func(**validated_args)
|
||||
if result is None:
|
||||
result = {}
|
||||
if win:
|
||||
result['win'] = win
|
||||
win = []
|
||||
if request.method == 'POST':
|
||||
request.setResponseCode(201, 'Created')
|
||||
elif request.method == 'DELETE':
|
||||
request.setResponseCode(204, 'No Content')
|
||||
else:
|
||||
request.setResponseCode(200, 'OK')
|
||||
|
||||
except YunoHostError, error:
|
||||
server_errors = [1, 111, 169]
|
||||
client_errors = [13, 17, 22, 87, 122, 125, 167, 168]
|
||||
if error.code in client_errors:
|
||||
request.setResponseCode(400, 'Bad Request')
|
||||
else:
|
||||
request.setResponseCode(500, 'Internal Server Error')
|
||||
result = { 'error' : error.message }
|
||||
|
||||
request.setHeader('Content-Type', 'application/json')
|
||||
return json.dumps(result)
|
||||
|
||||
|
||||
def main():
|
||||
global action_dict
|
||||
log.startLogging(sys.stdout)
|
||||
api = APIResource()
|
||||
|
||||
with open('action_map.yml') as f:
|
||||
action_map = yaml.load(f)
|
||||
|
||||
del action_map['general_arguments']
|
||||
for category, category_params in action_map.items():
|
||||
for action, action_params in category_params['actions'].items():
|
||||
if 'help' not in action_params:
|
||||
action_params['help'] = ''
|
||||
if 'api' not in action_params:
|
||||
action_params['api'] = 'GET /'+ category +'/'+ action
|
||||
method, path = action_params['api'].split(' ')
|
||||
api.register(method, path, http_exec)
|
||||
action_dict[action_params['api']] = {
|
||||
'function': 'yunohost_'+ category +'.'+ category +'_'+ action,
|
||||
'help' : action_params['help']
|
||||
}
|
||||
if 'arguments' in action_params:
|
||||
action_dict[action_params['api']]['arguments'] = action_params['arguments']
|
||||
|
||||
ldap_auth = LDAPHTTPAuth()
|
||||
credentialFactory = BasicCredentialFactory("Restricted Area")
|
||||
resource = HTTPAuthSessionWrapper(Portal(SimpleRealm(api), [ldap_auth]), [credentialFactory])
|
||||
try:
|
||||
with open('/etc/yunohost/installed') as f: pass
|
||||
except IOError:
|
||||
resource = APIResource()
|
||||
resource.register('POST', '/postinstall', http_exec)
|
||||
reactor.listenTCP(6767, Site(resource, timeout=None))
|
||||
reactor.run()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
21
yunohost_backup.py
Normal file
21
yunohost_backup.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import yaml
|
||||
import glob
|
||||
from yunohost import YunoHostError, YunoHostLDAP, validate, colorize, win_msg
|
||||
|
||||
def backup_init(helper=False):
|
||||
"""
|
||||
Init Tahoe-LAFS configuration
|
||||
|
||||
Keyword arguments:
|
||||
helper -- Create a helper node rather than a "helped" one
|
||||
|
||||
Returns:
|
||||
Win | Fail
|
||||
|
||||
"""
|
||||
pass
|
|
@ -25,9 +25,21 @@ def tools_ldapinit():
|
|||
for rdn, attr_dict in ldap_map['parents'].items():
|
||||
yldap.add(rdn, attr_dict)
|
||||
|
||||
for rdn, attr_dict in ldap_map['childs'].items():
|
||||
for rdn, attr_dict in ldap_map['children'].items():
|
||||
yldap.add(rdn, attr_dict)
|
||||
|
||||
try:
|
||||
with open('/etc/yunohost/from_script') as f: pass
|
||||
except IOError:
|
||||
with open('sudo_ldap_scheme.yml') as f:
|
||||
ldap_map = yaml.load(f)
|
||||
|
||||
for rdn, attr_dict in ldap_map['parents'].items():
|
||||
yldap.add(rdn, attr_dict)
|
||||
|
||||
for rdn, attr_dict in ldap_map['children'].items():
|
||||
yldap.add(rdn, attr_dict)
|
||||
|
||||
|
||||
admin_dict = {
|
||||
'cn': 'admin',
|
||||
|
|
Loading…
Reference in a new issue