moat-kv 0.70.24__py3-none-any.whl → 0.71.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- moat/kv/__init__.py +6 -7
- moat/kv/_cfg.yaml +5 -8
- moat/kv/actor/__init__.py +2 -1
- moat/kv/actor/deletor.py +4 -1
- moat/kv/auth/__init__.py +12 -13
- moat/kv/auth/_test.py +4 -1
- moat/kv/auth/password.py +11 -7
- moat/kv/backend/mqtt.py +4 -8
- moat/kv/client.py +20 -39
- moat/kv/code.py +3 -3
- moat/kv/command/data.py +4 -3
- moat/kv/command/dump/__init__.py +29 -29
- moat/kv/command/internal.py +2 -3
- moat/kv/command/job.py +1 -2
- moat/kv/command/type.py +3 -6
- moat/kv/data.py +9 -8
- moat/kv/errors.py +16 -8
- moat/kv/mock/__init__.py +2 -12
- moat/kv/model.py +28 -32
- moat/kv/obj/__init__.py +3 -3
- moat/kv/obj/command.py +3 -3
- moat/kv/runner.py +4 -5
- moat/kv/server.py +106 -126
- moat/kv/types.py +8 -6
- {moat_kv-0.70.24.dist-info → moat_kv-0.71.6.dist-info}/METADATA +7 -6
- moat_kv-0.71.6.dist-info/RECORD +47 -0
- {moat_kv-0.70.24.dist-info → moat_kv-0.71.6.dist-info}/WHEEL +1 -1
- moat_kv-0.71.6.dist-info/licenses/LICENSE +3 -0
- moat_kv-0.71.6.dist-info/licenses/LICENSE.APACHE2 +202 -0
- moat_kv-0.71.6.dist-info/licenses/LICENSE.MIT +20 -0
- moat_kv-0.71.6.dist-info/top_level.txt +1 -0
- build/lib/docs/source/conf.py +0 -201
- build/lib/examples/pathify.py +0 -45
- build/lib/moat/kv/__init__.py +0 -19
- build/lib/moat/kv/_cfg.yaml +0 -97
- build/lib/moat/kv/_main.py +0 -91
- build/lib/moat/kv/actor/__init__.py +0 -98
- build/lib/moat/kv/actor/deletor.py +0 -139
- build/lib/moat/kv/auth/__init__.py +0 -444
- build/lib/moat/kv/auth/_test.py +0 -166
- build/lib/moat/kv/auth/password.py +0 -234
- build/lib/moat/kv/auth/root.py +0 -58
- build/lib/moat/kv/backend/__init__.py +0 -67
- build/lib/moat/kv/backend/mqtt.py +0 -74
- build/lib/moat/kv/backend/serf.py +0 -45
- build/lib/moat/kv/client.py +0 -1025
- build/lib/moat/kv/code.py +0 -236
- build/lib/moat/kv/codec.py +0 -11
- build/lib/moat/kv/command/__init__.py +0 -1
- build/lib/moat/kv/command/acl.py +0 -180
- build/lib/moat/kv/command/auth.py +0 -261
- build/lib/moat/kv/command/code.py +0 -293
- build/lib/moat/kv/command/codec.py +0 -186
- build/lib/moat/kv/command/data.py +0 -265
- build/lib/moat/kv/command/dump/__init__.py +0 -143
- build/lib/moat/kv/command/error.py +0 -149
- build/lib/moat/kv/command/internal.py +0 -248
- build/lib/moat/kv/command/job.py +0 -433
- build/lib/moat/kv/command/log.py +0 -53
- build/lib/moat/kv/command/server.py +0 -114
- build/lib/moat/kv/command/type.py +0 -201
- build/lib/moat/kv/config.py +0 -46
- build/lib/moat/kv/data.py +0 -216
- build/lib/moat/kv/errors.py +0 -561
- build/lib/moat/kv/exceptions.py +0 -126
- build/lib/moat/kv/mock/__init__.py +0 -101
- build/lib/moat/kv/mock/mqtt.py +0 -159
- build/lib/moat/kv/mock/serf.py +0 -250
- build/lib/moat/kv/mock/tracer.py +0 -63
- build/lib/moat/kv/model.py +0 -1069
- build/lib/moat/kv/obj/__init__.py +0 -646
- build/lib/moat/kv/obj/command.py +0 -241
- build/lib/moat/kv/runner.py +0 -1347
- build/lib/moat/kv/server.py +0 -2809
- build/lib/moat/kv/types.py +0 -513
- debian/moat-kv/usr/lib/python3/dist-packages/docs/source/conf.py +0 -201
- debian/moat-kv/usr/lib/python3/dist-packages/examples/pathify.py +0 -45
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/__init__.py +0 -19
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/_cfg.yaml +0 -97
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/_main.py +0 -91
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/actor/__init__.py +0 -98
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/actor/deletor.py +0 -139
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/__init__.py +0 -444
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/_test.py +0 -166
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/password.py +0 -234
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/root.py +0 -58
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/__init__.py +0 -67
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/mqtt.py +0 -74
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/serf.py +0 -45
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/client.py +0 -1025
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/code.py +0 -236
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/codec.py +0 -11
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/__init__.py +0 -1
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/acl.py +0 -180
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/auth.py +0 -261
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/code.py +0 -293
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/codec.py +0 -186
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/data.py +0 -265
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/dump/__init__.py +0 -143
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/error.py +0 -149
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/internal.py +0 -248
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/job.py +0 -433
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/log.py +0 -53
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/server.py +0 -114
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/type.py +0 -201
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/config.py +0 -46
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/data.py +0 -216
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/errors.py +0 -561
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/exceptions.py +0 -126
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/__init__.py +0 -101
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/mqtt.py +0 -159
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/serf.py +0 -250
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/tracer.py +0 -63
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/model.py +0 -1069
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/obj/__init__.py +0 -646
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/obj/command.py +0 -241
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/runner.py +0 -1347
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/server.py +0 -2809
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/types.py +0 -513
- docs/source/conf.py +0 -201
- examples/pathify.py +0 -45
- moat/kv/backend/serf.py +0 -45
- moat/kv/codec.py +0 -11
- moat/kv/mock/serf.py +0 -250
- moat_kv-0.70.24.dist-info/RECORD +0 -137
- moat_kv-0.70.24.dist-info/top_level.txt +0 -9
- {moat_kv-0.70.24.dist-info → moat_kv-0.71.6.dist-info}/licenses/LICENSE.txt +0 -0
build/lib/moat/kv/code.py
DELETED
@@ -1,236 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
This module locally stores code that's been uploaded to MoaT-KV
|
3
|
-
so that it can be called easily.
|
4
|
-
|
5
|
-
"Code" consists of either Python modules or single procedures.
|
6
|
-
|
7
|
-
"""
|
8
|
-
from __future__ import annotations
|
9
|
-
|
10
|
-
import logging
|
11
|
-
import sys
|
12
|
-
from functools import partial
|
13
|
-
|
14
|
-
import anyio
|
15
|
-
from moat.util import NotGiven, P, make_module, make_proc
|
16
|
-
|
17
|
-
from .obj import ClientEntry, ClientRoot
|
18
|
-
|
19
|
-
logger = logging.getLogger(__name__)
|
20
|
-
|
21
|
-
|
22
|
-
class EmptyCode(Exception):
|
23
|
-
"""
|
24
|
-
There is no code here.
|
25
|
-
"""
|
26
|
-
|
27
|
-
pass
|
28
|
-
|
29
|
-
|
30
|
-
class ModuleRoot(ClientRoot):
|
31
|
-
"""
|
32
|
-
Modules are stored at ``(*PREFIX,*name)``.
|
33
|
-
The prefix defaults to ``("code","module")``.
|
34
|
-
|
35
|
-
The module code is stored textually. Content that is not UTF-8 is TODO.
|
36
|
-
"""
|
37
|
-
|
38
|
-
CFG = "modules"
|
39
|
-
|
40
|
-
err: ErrorRoot = None # noqa: F821
|
41
|
-
|
42
|
-
@classmethod
|
43
|
-
def child_type(cls, name):
|
44
|
-
return ModuleEntry
|
45
|
-
|
46
|
-
async def run_starting(self):
|
47
|
-
from .errors import ErrorRoot
|
48
|
-
|
49
|
-
self.err = await ErrorRoot.as_handler(self.client)
|
50
|
-
await super().run_starting()
|
51
|
-
|
52
|
-
async def add(self, path, code=None):
|
53
|
-
"""
|
54
|
-
Add or replace this code at this location.
|
55
|
-
"""
|
56
|
-
# test-compile the code for validity
|
57
|
-
if code is None:
|
58
|
-
return await self.remove(path)
|
59
|
-
|
60
|
-
make_module(code, path)
|
61
|
-
|
62
|
-
r = await self.client.set(self._path + path, value=dict(code=code), nchain=2)
|
63
|
-
await self.wait_chain(r.chain)
|
64
|
-
|
65
|
-
async def remove(self, path):
|
66
|
-
"""
|
67
|
-
Remove code at this location.
|
68
|
-
"""
|
69
|
-
entry = self.follow(self._path + path, create=False)
|
70
|
-
return await entry.delete()
|
71
|
-
|
72
|
-
|
73
|
-
class ModuleEntry(ClientEntry):
|
74
|
-
_module = None
|
75
|
-
|
76
|
-
@property
|
77
|
-
def name(self):
|
78
|
-
return ".".join(self.subpath)
|
79
|
-
|
80
|
-
async def set_value(self, value):
|
81
|
-
await super().set_value(value)
|
82
|
-
if value is NotGiven:
|
83
|
-
self._module = None
|
84
|
-
try:
|
85
|
-
del sys.modules[self.name]
|
86
|
-
except KeyError:
|
87
|
-
pass
|
88
|
-
return
|
89
|
-
|
90
|
-
try:
|
91
|
-
c = self.value.code
|
92
|
-
if not isinstance(c, str):
|
93
|
-
raise RuntimeError("Not a string, cannot compile")
|
94
|
-
m = make_module(c, self.subpath)
|
95
|
-
except Exception as exc:
|
96
|
-
self._module = None
|
97
|
-
logger.warning("Could not compile @%r", self.subpath)
|
98
|
-
await self.root.err.record_error(
|
99
|
-
"compile",
|
100
|
-
self.subpath,
|
101
|
-
exc=exc,
|
102
|
-
message="compiler error",
|
103
|
-
)
|
104
|
-
else:
|
105
|
-
await self.root.err.record_working("compile", self.subpath)
|
106
|
-
self._module = m
|
107
|
-
|
108
|
-
|
109
|
-
class CodeRoot(ClientRoot):
|
110
|
-
"""
|
111
|
-
This class represents the root of a code storage hierarchy. Ideally
|
112
|
-
there should only be one, but you can configure more.
|
113
|
-
|
114
|
-
You typically don't create this class directly; instead, call
|
115
|
-
:meth:`CodeRoot.as_handler`::
|
116
|
-
|
117
|
-
code = await CodeRoot.as_handler(client, your_config.get("code-storage",{})
|
118
|
-
|
119
|
-
The prefix defaults to ``("code","proc")``.
|
120
|
-
|
121
|
-
Configuration:
|
122
|
-
|
123
|
-
Arguments:
|
124
|
-
prefix (list): Where to store the code in MoaT-KV.
|
125
|
-
The default is ``('.moat','kv','code','proc')``.
|
126
|
-
|
127
|
-
The code is stored as a dict.
|
128
|
-
|
129
|
-
Arguments:
|
130
|
-
code: the actual code to run. It is parsed as a function body; be
|
131
|
-
aware that multi-line strings will be indented more than you'd like.
|
132
|
-
async: flag whether the code should run asynchronously.
|
133
|
-
True: yes, None (default): no, False: run in a separate thread.
|
134
|
-
vars: array of names to be used as named arguments.
|
135
|
-
|
136
|
-
All arguments are mandatory and should be named.
|
137
|
-
Extra arguments will be available in the "kw" dict.
|
138
|
-
|
139
|
-
"""
|
140
|
-
|
141
|
-
CFG = "codes"
|
142
|
-
|
143
|
-
err = None
|
144
|
-
|
145
|
-
@classmethod
|
146
|
-
def child_type(cls, name):
|
147
|
-
return CodeEntry
|
148
|
-
|
149
|
-
async def run_starting(self):
|
150
|
-
from .errors import ErrorRoot
|
151
|
-
|
152
|
-
self.err = await ErrorRoot.as_handler(self.client)
|
153
|
-
await super().run_starting()
|
154
|
-
|
155
|
-
async def add(self, path, code=None, *, is_async=None, variables=()):
|
156
|
-
"""
|
157
|
-
Add or replace this code at this location.
|
158
|
-
"""
|
159
|
-
if code is NotGiven:
|
160
|
-
return await self.remove(path)
|
161
|
-
|
162
|
-
# test-compile the code for validity
|
163
|
-
make_proc(code, variables, path, use_async=is_async)
|
164
|
-
|
165
|
-
r = await self.client.set(
|
166
|
-
self._path + path,
|
167
|
-
value=dict(code=code, is_async=is_async, vars=variables),
|
168
|
-
nchain=2,
|
169
|
-
)
|
170
|
-
await self.wait_chain(r.chain)
|
171
|
-
|
172
|
-
async def remove(self, path):
|
173
|
-
"""Drop this code"""
|
174
|
-
r = await self.client.set(*self._path, *path, value=None, nchain=2)
|
175
|
-
await self.wait_chain(r.chain)
|
176
|
-
|
177
|
-
def __call__(self, name, *a, **kw):
|
178
|
-
if isinstance(name, str):
|
179
|
-
name = name.split(".")
|
180
|
-
c = self
|
181
|
-
for k in name:
|
182
|
-
c = c[k]
|
183
|
-
if c is self:
|
184
|
-
raise RuntimeError("Empty code names don't make sense")
|
185
|
-
return c(*a, **kw)
|
186
|
-
|
187
|
-
|
188
|
-
class CodeEntry(ClientEntry):
|
189
|
-
_code = None
|
190
|
-
is_async = None
|
191
|
-
|
192
|
-
def __init__(self, *a, **kv):
|
193
|
-
self.reload_event = anyio.Event()
|
194
|
-
super().__init__(*a, *kv)
|
195
|
-
|
196
|
-
@property
|
197
|
-
def name(self):
|
198
|
-
return P(self.subpath)
|
199
|
-
|
200
|
-
async def set_value(self, value):
|
201
|
-
await super().set_value(value)
|
202
|
-
if value is NotGiven:
|
203
|
-
self._code = None
|
204
|
-
return
|
205
|
-
|
206
|
-
try:
|
207
|
-
v = self.value
|
208
|
-
c = v["code"]
|
209
|
-
a = v.get("is_async", None)
|
210
|
-
p = make_proc(c, v.get("vars", ()), self.subpath, use_async=a)
|
211
|
-
except Exception as exc:
|
212
|
-
logger.warning("Could not compile @%s", self.subpath)
|
213
|
-
await self.root.err.record_error(
|
214
|
-
"compile",
|
215
|
-
self.subpath,
|
216
|
-
exc=exc,
|
217
|
-
message="compiler error",
|
218
|
-
)
|
219
|
-
self._code = None
|
220
|
-
else:
|
221
|
-
await self.root.err.record_working("compile", self.subpath)
|
222
|
-
self._code = p
|
223
|
-
self.is_async = a
|
224
|
-
self.reload_event.set()
|
225
|
-
self.reload_event = anyio.Event()
|
226
|
-
|
227
|
-
def __call__(self, *a, **kw):
|
228
|
-
if self._code is None:
|
229
|
-
raise EmptyCode(self._path)
|
230
|
-
if self.is_async is False:
|
231
|
-
proc = self._code
|
232
|
-
if kw:
|
233
|
-
proc = partial(proc, **kw)
|
234
|
-
return anyio.to_thread.run_sync(proc, *a)
|
235
|
-
|
236
|
-
return self._code(*a, **kw)
|
build/lib/moat/kv/codec.py
DELETED
@@ -1,11 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
This module contains helper functions for packing+unpacking of single messages,
|
3
|
-
plus an unpacker factory for streams.
|
4
|
-
"""
|
5
|
-
|
6
|
-
# compatibility
|
7
|
-
from __future__ import annotations
|
8
|
-
|
9
|
-
from moat.util import * # noqa: F403
|
10
|
-
|
11
|
-
__all__ = ["packer", "unpacker", "stream_unpacker"] # noqa: F405
|
@@ -1 +0,0 @@
|
|
1
|
-
# empty
|
build/lib/moat/kv/command/acl.py
DELETED
@@ -1,180 +0,0 @@
|
|
1
|
-
# command line interface
|
2
|
-
from __future__ import annotations
|
3
|
-
|
4
|
-
import sys
|
5
|
-
|
6
|
-
import asyncclick as click
|
7
|
-
from moat.util import P, Path, yprint
|
8
|
-
|
9
|
-
from moat.kv.data import data_get
|
10
|
-
|
11
|
-
ACL = set("rwdcxena")
|
12
|
-
# read, write, delete, create, access, enumerate
|
13
|
-
|
14
|
-
|
15
|
-
@click.group() # pylint: disable=undefined-variable
|
16
|
-
async def cli():
|
17
|
-
"""Manage ACLs. Usage: … acl …"""
|
18
|
-
pass
|
19
|
-
|
20
|
-
|
21
|
-
@cli.command("list")
|
22
|
-
@click.pass_obj
|
23
|
-
async def list_(obj):
|
24
|
-
"""List ACLs."""
|
25
|
-
res = await obj.client._request(
|
26
|
-
action="enum_internal",
|
27
|
-
path=("acl",),
|
28
|
-
iter=False,
|
29
|
-
nchain=obj.meta,
|
30
|
-
empty=True,
|
31
|
-
)
|
32
|
-
yprint(res if obj.meta else res.result, stream=obj.stdout)
|
33
|
-
|
34
|
-
|
35
|
-
@cli.command()
|
36
|
-
@click.option(
|
37
|
-
"-d",
|
38
|
-
"--as-dict",
|
39
|
-
default=None,
|
40
|
-
help="Structure as dictionary. The argument is the key to use "
|
41
|
-
"for values. Default: return as list",
|
42
|
-
)
|
43
|
-
@click.argument("name", nargs=1)
|
44
|
-
@click.argument("path", nargs=1)
|
45
|
-
@click.pass_obj
|
46
|
-
async def dump(obj, name, path, as_dict):
|
47
|
-
"""Dump a complete (or partial) ACL."""
|
48
|
-
path = P(path)
|
49
|
-
await data_get(obj, Path("acl", name, path), internal=True, as_dict=as_dict)
|
50
|
-
|
51
|
-
|
52
|
-
@cli.command()
|
53
|
-
@click.argument("name", nargs=1)
|
54
|
-
@click.argument("path", nargs=1)
|
55
|
-
@click.pass_obj
|
56
|
-
async def get(obj, name, path):
|
57
|
-
"""Read an ACL.
|
58
|
-
|
59
|
-
This command does not test a path. Use "… acl test …" for that.
|
60
|
-
"""
|
61
|
-
path = P(path)
|
62
|
-
if not len(path):
|
63
|
-
raise click.UsageError("You need a non-empty path.")
|
64
|
-
res = await obj.client._request(
|
65
|
-
action="get_internal",
|
66
|
-
path=("acl", name) + path,
|
67
|
-
iter=False,
|
68
|
-
nchain=obj.meta,
|
69
|
-
)
|
70
|
-
|
71
|
-
if not obj.meta:
|
72
|
-
try:
|
73
|
-
res = res.value
|
74
|
-
except KeyError:
|
75
|
-
if obj.debug:
|
76
|
-
print("No value.", file=sys.stderr)
|
77
|
-
return
|
78
|
-
yprint(res, stream=obj.stdout)
|
79
|
-
|
80
|
-
|
81
|
-
@cli.command(name="set")
|
82
|
-
@click.option(
|
83
|
-
"-a",
|
84
|
-
"--acl",
|
85
|
-
default="+x",
|
86
|
-
help="The value to set. Start with '+' to add, '-' to remove rights.",
|
87
|
-
)
|
88
|
-
@click.argument("name", nargs=1)
|
89
|
-
@click.argument("path", nargs=1)
|
90
|
-
@click.pass_obj
|
91
|
-
async def set_(obj, acl, name, path):
|
92
|
-
"""Set or change an ACL."""
|
93
|
-
|
94
|
-
path = P(path)
|
95
|
-
if not len(path):
|
96
|
-
raise click.UsageError("You need a non-empty path.")
|
97
|
-
if len(acl) > 1 and acl[0] in "+-":
|
98
|
-
mode = acl[0]
|
99
|
-
acl = acl[1:]
|
100
|
-
else:
|
101
|
-
mode = "x"
|
102
|
-
acl = set(acl)
|
103
|
-
|
104
|
-
if acl - ACL:
|
105
|
-
raise click.UsageError(f"You're trying to set an unknown ACL flag: {acl - ACL!r}")
|
106
|
-
|
107
|
-
res = await obj.client._request(
|
108
|
-
action="get_internal",
|
109
|
-
path=("acl", name) + path,
|
110
|
-
iter=False,
|
111
|
-
nchain=3 if obj.meta else 1,
|
112
|
-
)
|
113
|
-
ov = set(res.get("value", ""))
|
114
|
-
if ov - ACL:
|
115
|
-
print(f"Warning: original ACL contains unknown: {ov - acl!r}", file=sys.stderr)
|
116
|
-
|
117
|
-
if mode == "-" and not acl:
|
118
|
-
res = await obj.client._request(
|
119
|
-
action="delete_internal",
|
120
|
-
path=("acl", name) + path,
|
121
|
-
iter=False,
|
122
|
-
chain=res.chain,
|
123
|
-
)
|
124
|
-
v = "-"
|
125
|
-
|
126
|
-
else:
|
127
|
-
if mode == "+":
|
128
|
-
v = ov + acl
|
129
|
-
elif mode == "-":
|
130
|
-
v = ov - acl
|
131
|
-
else:
|
132
|
-
v = acl
|
133
|
-
res = await obj.client._request(
|
134
|
-
action="set_internal",
|
135
|
-
path=("acl", name) + path,
|
136
|
-
value="".join(v),
|
137
|
-
iter=False,
|
138
|
-
chain=res.get("chain", None),
|
139
|
-
)
|
140
|
-
|
141
|
-
if obj.meta:
|
142
|
-
res = {
|
143
|
-
"old": "".join(ov),
|
144
|
-
"new": "".join(v),
|
145
|
-
"chain": res.chain,
|
146
|
-
"tock": res.tock,
|
147
|
-
}
|
148
|
-
yprint(res, stream=obj.stdout)
|
149
|
-
else:
|
150
|
-
res = {"old": "".join(ov), "new": "".join(v)}
|
151
|
-
yprint(res, stream=obj.stdout)
|
152
|
-
|
153
|
-
|
154
|
-
@cli.command()
|
155
|
-
@click.option("-m", "--mode", default=None, help="Mode letter to test.")
|
156
|
-
@click.option("-a", "--acl", default=None, help="ACL to test. Default: current")
|
157
|
-
@click.argument("path", nargs=1)
|
158
|
-
@click.pass_obj
|
159
|
-
async def test(obj, path, acl, mode):
|
160
|
-
"""Test which ACL entry matches a path"""
|
161
|
-
path = P(path)
|
162
|
-
if not len(path):
|
163
|
-
raise click.UsageError("You need a non-empty path.")
|
164
|
-
|
165
|
-
if mode is not None and len(mode) != 1:
|
166
|
-
raise click.UsageError("Mode must be one letter.")
|
167
|
-
res = await obj.client._request(
|
168
|
-
action="test_acl",
|
169
|
-
path=path,
|
170
|
-
iter=False,
|
171
|
-
nchain=obj.meta,
|
172
|
-
**({} if mode is None else {"mode": mode}),
|
173
|
-
**({} if acl is None else {"acl": acl}),
|
174
|
-
)
|
175
|
-
if obj.meta:
|
176
|
-
yprint(res, stream=obj.stdout)
|
177
|
-
elif isinstance(res.access, bool):
|
178
|
-
print("+" if res.access else "-", file=obj.stdout)
|
179
|
-
else:
|
180
|
-
print(res.access, file=obj.stdout)
|
@@ -1,261 +0,0 @@
|
|
1
|
-
# command line interface
|
2
|
-
from __future__ import annotations
|
3
|
-
|
4
|
-
import asyncclick as click
|
5
|
-
from moat.util import NotGiven, Path, split_arg, yprint
|
6
|
-
|
7
|
-
from moat.kv.auth import gen_auth, loader
|
8
|
-
|
9
|
-
|
10
|
-
@click.group(short_help="Manage authorization") # pylint: disable=undefined-variable
|
11
|
-
@click.option("-m", "--method", default=None, help="Affect/use this auth method")
|
12
|
-
@click.pass_obj
|
13
|
-
async def cli(obj, method):
|
14
|
-
"""Manage authorization. Usage: … auth METHOD command…. Use '.' for 'all methods'."""
|
15
|
-
a = await obj.client._request(action="auth_info")
|
16
|
-
a = a.get("value", None)
|
17
|
-
if a is not None:
|
18
|
-
a = a["current"]
|
19
|
-
obj.auth_current = a
|
20
|
-
if method is None:
|
21
|
-
obj.auth = a or (await one_auth(obj))
|
22
|
-
else:
|
23
|
-
if method == "-":
|
24
|
-
method = None
|
25
|
-
obj.auth = method
|
26
|
-
|
27
|
-
|
28
|
-
async def enum_auth(obj):
|
29
|
-
"""List all configured auth types."""
|
30
|
-
if obj.get("auth", None) is not None:
|
31
|
-
yield obj.auth
|
32
|
-
return
|
33
|
-
# TODO create a method for this
|
34
|
-
res = await obj.client._request(
|
35
|
-
action="enum_internal",
|
36
|
-
path=Path("auth"),
|
37
|
-
iter=False,
|
38
|
-
with_data=False,
|
39
|
-
empty=True,
|
40
|
-
nchain=0,
|
41
|
-
)
|
42
|
-
for r in res.result:
|
43
|
-
print(r)
|
44
|
-
yield r
|
45
|
-
|
46
|
-
|
47
|
-
async def one_auth(obj):
|
48
|
-
"""Return the current auth method (from the command line or as used by the server)."""
|
49
|
-
if obj.get("auth", None) is not None:
|
50
|
-
return obj.auth
|
51
|
-
auth = None
|
52
|
-
async for a in enum_auth(obj):
|
53
|
-
if auth is not None:
|
54
|
-
raise click.UsageError("You need to set the auth method")
|
55
|
-
auth = a
|
56
|
-
if auth is None:
|
57
|
-
raise click.UsageError("You need to set the auth method")
|
58
|
-
return auth
|
59
|
-
|
60
|
-
|
61
|
-
async def enum_typ(obj, kind="user", ident=None, nchain=0):
|
62
|
-
"""List all known auth entries of a kind."""
|
63
|
-
async for auth in enum_auth(obj):
|
64
|
-
if ident is not None:
|
65
|
-
res = await obj.client._request(
|
66
|
-
action="auth_list",
|
67
|
-
typ=auth,
|
68
|
-
kind=kind,
|
69
|
-
ident=ident,
|
70
|
-
iter=False,
|
71
|
-
nchain=nchain,
|
72
|
-
)
|
73
|
-
yield res
|
74
|
-
else:
|
75
|
-
async with obj.client._stream(
|
76
|
-
action="auth_list",
|
77
|
-
typ=auth,
|
78
|
-
kind=kind,
|
79
|
-
nchain=nchain,
|
80
|
-
) as res:
|
81
|
-
async for r in res:
|
82
|
-
yield r
|
83
|
-
|
84
|
-
|
85
|
-
@cli.command("list")
|
86
|
-
@click.pass_obj
|
87
|
-
async def list_(obj):
|
88
|
-
"""List known auth methods"""
|
89
|
-
async for auth in enum_auth(obj):
|
90
|
-
print(auth, file=obj.stdout)
|
91
|
-
|
92
|
-
|
93
|
-
@cli.command()
|
94
|
-
@click.option("-s", "--switch", is_flag=True, help="Switch to a different auth method")
|
95
|
-
@click.pass_obj
|
96
|
-
async def init(obj, switch):
|
97
|
-
"""Setup authorization"""
|
98
|
-
if obj.auth_current is not None and not switch:
|
99
|
-
raise click.UsageError("Authentication is already set up")
|
100
|
-
|
101
|
-
await obj.client._request(action="set_auth_typ", typ=obj.auth)
|
102
|
-
if obj.debug >= 0:
|
103
|
-
if obj.auth:
|
104
|
-
print("Authorization switched to", obj.auth, file=obj.stdout)
|
105
|
-
else:
|
106
|
-
print("Authorization turned off.", file=obj.stdout)
|
107
|
-
|
108
|
-
|
109
|
-
@cli.group()
|
110
|
-
async def user():
|
111
|
-
"""Manage users."""
|
112
|
-
pass
|
113
|
-
|
114
|
-
|
115
|
-
@user.command("list")
|
116
|
-
@click.option(
|
117
|
-
"-v",
|
118
|
-
"--verbose",
|
119
|
-
is_flag=True,
|
120
|
-
help="Print complete results. Default: just the names",
|
121
|
-
)
|
122
|
-
@click.pass_obj # pylint: disable=function-redefined
|
123
|
-
async def list_user(obj, verbose):
|
124
|
-
"""List all users (raw data)."""
|
125
|
-
async for r in enum_typ(obj, nchain=obj.meta):
|
126
|
-
if obj.meta or verbose:
|
127
|
-
if obj.debug < 2:
|
128
|
-
del r["seq"]
|
129
|
-
del r["tock"]
|
130
|
-
yprint(r, stream=obj.stdout)
|
131
|
-
else:
|
132
|
-
print(r.ident, file=obj.stdout)
|
133
|
-
|
134
|
-
|
135
|
-
@user.command()
|
136
|
-
@click.argument("ident", nargs=1)
|
137
|
-
@click.pass_obj
|
138
|
-
async def get(obj, ident):
|
139
|
-
"""Retrieve a user (processed)."""
|
140
|
-
lv = loader(await one_auth(obj), "user", make=True, server=False)
|
141
|
-
if obj.DEBUG:
|
142
|
-
lv._length = 16
|
143
|
-
|
144
|
-
u = await lv.recv(obj.client, ident, _initial=False)
|
145
|
-
yprint(u.export(), stream=obj.stdout)
|
146
|
-
|
147
|
-
|
148
|
-
@user.command()
|
149
|
-
@click.argument("args", nargs=-1)
|
150
|
-
@click.pass_obj
|
151
|
-
async def add(obj, args):
|
152
|
-
"""Add a user."""
|
153
|
-
await add_mod_user(obj, args, None)
|
154
|
-
|
155
|
-
|
156
|
-
@user.command()
|
157
|
-
@click.option("-n", "--new", is_flag=True, help="New: ignore previous content")
|
158
|
-
@click.argument("ident", nargs=1)
|
159
|
-
@click.argument("type", nargs=1)
|
160
|
-
@click.argument("key", nargs=1)
|
161
|
-
@click.argument("args", nargs=-1)
|
162
|
-
@click.pass_obj
|
163
|
-
async def param(obj, new, ident, type, key, args): # pylint: disable=redefined-builtin
|
164
|
-
"""Set user parameters for auth, conversion, etc."""
|
165
|
-
auth = await one_auth(obj)
|
166
|
-
u = loader(auth, "user", make=True, server=False)
|
167
|
-
if obj.DEBUG:
|
168
|
-
u._length = 16
|
169
|
-
# ou = await u.recv(obj.client, ident, _initial=False) # unused
|
170
|
-
res = await obj.client._request(
|
171
|
-
action="get_internal",
|
172
|
-
path=("auth", auth, "user", ident, type),
|
173
|
-
iter=False,
|
174
|
-
nchain=3,
|
175
|
-
)
|
176
|
-
|
177
|
-
kw = res.get("value", NotGiven)
|
178
|
-
if new or kw is NotGiven:
|
179
|
-
kw = {}
|
180
|
-
res.chain = None
|
181
|
-
if key == "-":
|
182
|
-
if args:
|
183
|
-
raise click.UsageError("You can't set params when deleting")
|
184
|
-
res = await obj.client._request(
|
185
|
-
action="delete_internal",
|
186
|
-
path=("auth", auth, "user", ident, type),
|
187
|
-
iter=False,
|
188
|
-
chain=res.chain,
|
189
|
-
)
|
190
|
-
|
191
|
-
else:
|
192
|
-
kw["key"] = key
|
193
|
-
|
194
|
-
for a in args:
|
195
|
-
split_arg(a, kw)
|
196
|
-
|
197
|
-
res = await obj.client._request(
|
198
|
-
action="set_internal",
|
199
|
-
path=("auth", auth, "user", ident, type),
|
200
|
-
iter=False,
|
201
|
-
chain=res.chain,
|
202
|
-
value=kw,
|
203
|
-
)
|
204
|
-
if obj.meta:
|
205
|
-
# res.ident = ident
|
206
|
-
# res.type = type
|
207
|
-
yprint(res, stream=obj.stdout)
|
208
|
-
else:
|
209
|
-
print(ident, type, key, file=obj.stdout)
|
210
|
-
|
211
|
-
|
212
|
-
@user.command()
|
213
|
-
@click.argument("ident", nargs=1)
|
214
|
-
@click.argument("args", nargs=-1)
|
215
|
-
@click.pass_obj
|
216
|
-
async def mod(obj, ident, args):
|
217
|
-
"""Change a user."""
|
218
|
-
await add_mod_user(obj, args, ident)
|
219
|
-
|
220
|
-
|
221
|
-
async def add_mod_user(obj, args, modify):
|
222
|
-
auth = await one_auth(obj)
|
223
|
-
u = loader(auth, "user", make=True, server=False)
|
224
|
-
if obj.DEBUG:
|
225
|
-
u._length = 16
|
226
|
-
if modify:
|
227
|
-
ou = await u.recv(obj.client, modify, _initial=False)
|
228
|
-
kw = ou.export()
|
229
|
-
else:
|
230
|
-
kw = {}
|
231
|
-
for a in args:
|
232
|
-
split_arg(a, kw)
|
233
|
-
|
234
|
-
u = u.build(kw, _initial=False)
|
235
|
-
if modify is None or u.ident != modify:
|
236
|
-
u._chain = None # new user
|
237
|
-
else:
|
238
|
-
u._chain = ou._chain
|
239
|
-
res = await u.send(obj.client)
|
240
|
-
if obj.meta:
|
241
|
-
res.ident = u.ident
|
242
|
-
yprint(res, stream=obj.stdout)
|
243
|
-
else:
|
244
|
-
print(u.ident, file=obj.stdout)
|
245
|
-
|
246
|
-
|
247
|
-
@user.command(name="auth")
|
248
|
-
@click.option(
|
249
|
-
"-a",
|
250
|
-
"--auth",
|
251
|
-
type=str,
|
252
|
-
default="root",
|
253
|
-
help="Auth params. =file or 'type param=value…' Default: root",
|
254
|
-
)
|
255
|
-
@click.pass_obj
|
256
|
-
async def auth_(obj, auth):
|
257
|
-
"""Test user authorization."""
|
258
|
-
user = gen_auth(auth) # pylint: disable=redefined-outer-name
|
259
|
-
await user.auth(obj.client)
|
260
|
-
if obj.debug > 0:
|
261
|
-
print("OK.", file=obj.stdout)
|