Add endpoint to replace specific plugin

This commit is contained in:
Tulir Asokan 2018-11-08 17:25:00 +02:00
parent 2ba1f46149
commit 9a21c6ade8
3 changed files with 100 additions and 21 deletions

View file

@ -69,6 +69,45 @@ async def reload_plugin(request: web.Request) -> web.Response:
return resp.ok return resp.ok
@routes.put("/plugin/{id}")
async def put_plugin(request: web.Request) -> web.Response:
plugin_id = request.match_info.get("id", None)
content = await request.read()
file = BytesIO(content)
try:
pid, version = ZippedPluginLoader.verify_meta(file)
except MaubotZipImportError as e:
return resp.plugin_import_error(str(e), traceback.format_exc())
if pid != plugin_id:
return resp.pid_mismatch
plugin = PluginLoader.id_cache.get(plugin_id, None)
if not plugin:
return await upload_new_plugin(content, pid, version)
elif isinstance(plugin, ZippedPluginLoader):
return await upload_replacement_plugin(plugin, content, version)
else:
return resp.unsupported_plugin_loader
@routes.post("/plugins/upload")
async def upload_plugin(request: web.Request) -> web.Response:
content = await request.read()
file = BytesIO(content)
try:
pid, version = ZippedPluginLoader.verify_meta(file)
except MaubotZipImportError as e:
return resp.plugin_import_error(str(e), traceback.format_exc())
plugin = PluginLoader.id_cache.get(pid, None)
if not plugin:
return await upload_new_plugin(content, pid, version)
elif not request.query.get("allow_override"):
return resp.plugin_exists
elif isinstance(plugin, ZippedPluginLoader):
return await upload_replacement_plugin(plugin, content, version)
else:
return resp.unsupported_plugin_loader
async def upload_new_plugin(content: bytes, pid: str, version: str) -> web.Response: async def upload_new_plugin(content: bytes, pid: str, version: str) -> web.Response:
path = os.path.join(get_config()["plugin_directories.upload"], f"{pid}-v{version}.mbp") path = os.path.join(get_config()["plugin_directories.upload"], f"{pid}-v{version}.mbp")
with open(path, "wb") as p: with open(path, "wb") as p:
@ -86,10 +125,10 @@ async def upload_replacement_plugin(plugin: ZippedPluginLoader, content: bytes,
dirname = os.path.dirname(plugin.path) dirname = os.path.dirname(plugin.path)
old_filename = os.path.basename(plugin.path) old_filename = os.path.basename(plugin.path)
if plugin.version in old_filename: if plugin.version in old_filename:
filename = old_filename.replace(plugin.version, new_version) replacement = (new_version if plugin.version != new_version
if filename == old_filename: else f"{new_version}-ts{int(time())}")
filename = re.sub(f"{re.escape(plugin.version)}(-ts[0-9]+)?", filename = re.sub(f"{re.escape(plugin.version)}(-ts[0-9]+)?",
f"{new_version}-ts{int(time())}", old_filename) replacement, old_filename)
else: else:
filename = old_filename.rstrip(".mbp") filename = old_filename.rstrip(".mbp")
filename = f"{filename}-v{new_version}.mbp" filename = f"{filename}-v{new_version}.mbp"
@ -110,20 +149,3 @@ async def upload_replacement_plugin(plugin: ZippedPluginLoader, content: bytes,
await plugin.start_instances() await plugin.start_instances()
ZippedPluginLoader.trash(old_path, reason="update") ZippedPluginLoader.trash(old_path, reason="update")
return resp.updated(plugin.to_dict()) return resp.updated(plugin.to_dict())
@routes.post("/plugins/upload")
async def upload_plugin(request: web.Request) -> web.Response:
content = await request.read()
file = BytesIO(content)
try:
pid, version = ZippedPluginLoader.verify_meta(file)
except MaubotZipImportError as e:
return resp.plugin_import_error(str(e), traceback.format_exc())
plugin = PluginLoader.id_cache.get(pid, None)
if not plugin:
return await upload_new_plugin(content, pid, version)
elif isinstance(plugin, ZippedPluginLoader):
return await upload_replacement_plugin(plugin, content, version)
else:
return resp.unsupported_plugin_loader

View file

@ -61,6 +61,13 @@ class _Response:
"errcode": "mxid_mismatch", "errcode": "mxid_mismatch",
}, status=HTTPStatus.BAD_REQUEST) }, status=HTTPStatus.BAD_REQUEST)
@property
def pid_mismatch(self) -> web.Response:
return web.json_response({
"error": "The ID in the path does not match the ID of the uploaded plugin",
"errcode": "pid_mismatch",
}, status=HTTPStatus.BAD_REQUEST)
@property @property
def bad_auth(self) -> web.Response: def bad_auth(self) -> web.Response:
return web.json_response({ return web.json_response({
@ -138,6 +145,13 @@ class _Response:
"errcode": "user_exists", "errcode": "user_exists",
}, status=HTTPStatus.CONFLICT) }, status=HTTPStatus.CONFLICT)
@property
def plugin_exists(self) -> web.Response:
return web.json_response({
"error": "A plugin with the same ID as the uploaded plugin already exists",
"errcode": "plugin_exists"
}, status=HTTPStatus.CONFLICT)
@property @property
def plugin_in_use(self) -> web.Response: def plugin_in_use(self) -> web.Response:
return web.json_response({ return web.json_response({

View file

@ -85,6 +85,14 @@ paths:
summary: Upload a new plugin summary: Upload a new plugin
description: Upload a new plugin. If the plugin already exists, enabled instances will be restarted. description: Upload a new plugin. If the plugin already exists, enabled instances will be restarted.
tags: [Plugins] tags: [Plugins]
parameters:
- name: allow_override
in: query
description: Set to allow overriding existing plugins
required: false
schema:
type: boolean
default: false
responses: responses:
200: 200:
description: Plugin uploaded and replaced current version successfully description: Plugin uploaded and replaced current version successfully
@ -102,6 +110,8 @@ paths:
$ref: '#/components/responses/BadRequest' $ref: '#/components/responses/BadRequest'
401: 401:
$ref: '#/components/responses/Unauthorized' $ref: '#/components/responses/Unauthorized'
409:
description: Plugin
requestBody: requestBody:
content: content:
application/zip: application/zip:
@ -150,6 +160,39 @@ paths:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/Error' $ref: '#/components/schemas/Error'
put:
operationId: put_plugin
summary: Upload a new or replacement plugin
description: |
Upload a new or replacement plugin with the specified ID.
A HTTP 400 will be returned if the ID of the uploaded plugin
doesn't match the ID in the path. If the plugin already
exists, enabled instances will be restarted.
tags: [Plugins]
responses:
200:
description: Plugin uploaded and replaced current version successfully
content:
application/json:
schema:
$ref: '#/components/schemas/Plugin'
201:
description: New plugin uploaded successfully
content:
application/json:
schema:
$ref: '#/components/schemas/Plugin'
400:
$ref: '#/components/responses/BadRequest'
401:
$ref: '#/components/responses/Unauthorized'
requestBody:
content:
application/zip:
schema:
type: string
format: binary
example: The plugin maubot archive (.mbp)
/plugin/{id}/reload: /plugin/{id}/reload:
parameters: parameters:
- name: id - name: id