215 lines
6.6 KiB
Python
215 lines
6.6 KiB
Python
"""Swift Object Storage API v1."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from typing import Annotated
|
|
from uuid import uuid4
|
|
|
|
from asyncpg import Connection
|
|
from fastapi import APIRouter, Depends, Request, Response
|
|
|
|
from app.openstack.auth import TokenContext
|
|
from app.openstack.deps import get_conn, require_token
|
|
from app.openstack.errors import OpenStackError
|
|
|
|
router = APIRouter(tags=["Swift"])
|
|
|
|
|
|
def _account(ctx: TokenContext) -> str:
|
|
return f"AUTH_{ctx.project_id or ctx.user_id}"
|
|
|
|
|
|
@router.get("/info")
|
|
async def swift_info(conn: Annotated[Connection, Depends(get_conn)]) -> dict[str, object]:
|
|
from app.openstack.db_docs import require_doc
|
|
|
|
return await require_doc(conn, service="swift", resource_type="info", name="default")
|
|
|
|
|
|
@router.get("/v1/{account}")
|
|
async def list_containers(
|
|
account: str,
|
|
conn: Annotated[Connection, Depends(get_conn)],
|
|
ctx: Annotated[TokenContext, Depends(require_token)],
|
|
) -> list[dict[str, object]]:
|
|
_ = account
|
|
rows = await conn.fetch(
|
|
"SELECT name, meta, created_at FROM os_swift_containers WHERE account=$1 ORDER BY name",
|
|
_account(ctx),
|
|
)
|
|
result = []
|
|
for r in rows:
|
|
count = await conn.fetchval(
|
|
"SELECT count(*) FROM os_swift_objects WHERE account=$1 AND container=$2",
|
|
_account(ctx),
|
|
r["name"],
|
|
)
|
|
bytes_total = await conn.fetchval(
|
|
"SELECT COALESCE(sum(bytes),0) FROM os_swift_objects WHERE account=$1 AND container=$2",
|
|
_account(ctx),
|
|
r["name"],
|
|
)
|
|
result.append({"name": r["name"], "count": int(count or 0), "bytes": int(bytes_total or 0)})
|
|
return result
|
|
|
|
|
|
@router.put("/v1/{account}/{container}", status_code=201)
|
|
async def create_container(
|
|
account: str,
|
|
container: str,
|
|
conn: Annotated[Connection, Depends(get_conn)],
|
|
ctx: Annotated[TokenContext, Depends(require_token)],
|
|
) -> Response:
|
|
_ = account
|
|
await conn.execute(
|
|
"""INSERT INTO os_swift_containers(account, name, meta)
|
|
VALUES($1,$2,'{}'::jsonb) ON CONFLICT DO NOTHING""",
|
|
_account(ctx),
|
|
container,
|
|
)
|
|
return Response(status_code=201)
|
|
|
|
|
|
@router.delete("/v1/{account}/{container}", status_code=204)
|
|
async def delete_container(
|
|
account: str,
|
|
container: str,
|
|
conn: Annotated[Connection, Depends(get_conn)],
|
|
ctx: Annotated[TokenContext, Depends(require_token)],
|
|
) -> Response:
|
|
_ = account
|
|
acct = _account(ctx)
|
|
objects = await conn.fetchval(
|
|
"SELECT count(*) FROM os_swift_objects WHERE account=$1 AND container=$2",
|
|
acct,
|
|
container,
|
|
)
|
|
if int(objects or 0) > 0:
|
|
raise OpenStackError(
|
|
"Conflict",
|
|
"Container is not empty",
|
|
status_code=409,
|
|
)
|
|
result = await conn.execute(
|
|
"DELETE FROM os_swift_containers WHERE account=$1 AND name=$2",
|
|
acct,
|
|
container,
|
|
)
|
|
if result.endswith("0"):
|
|
raise OpenStackError("NotFound", "Container not found", status_code=404)
|
|
return Response(status_code=204)
|
|
|
|
|
|
@router.get("/v1/{account}/{container}")
|
|
async def list_objects(
|
|
account: str,
|
|
container: str,
|
|
conn: Annotated[Connection, Depends(get_conn)],
|
|
ctx: Annotated[TokenContext, Depends(require_token)],
|
|
) -> list[dict[str, object]]:
|
|
_ = account
|
|
rows = await conn.fetch(
|
|
"""SELECT name, bytes, content_type, created_at FROM os_swift_objects
|
|
WHERE account=$1 AND container=$2 ORDER BY name""",
|
|
_account(ctx),
|
|
container,
|
|
)
|
|
return [
|
|
{
|
|
"name": r["name"],
|
|
"bytes": r["bytes"],
|
|
"content_type": r["content_type"],
|
|
"last_modified": r["created_at"].strftime("%Y-%m-%dT%H:%M:%S.%f"),
|
|
"hash": "0",
|
|
}
|
|
for r in rows
|
|
]
|
|
|
|
|
|
@router.put("/v1/{account}/{container}/{object_name:path}", status_code=201)
|
|
async def put_object(
|
|
account: str,
|
|
container: str,
|
|
object_name: str,
|
|
request: Request,
|
|
conn: Annotated[Connection, Depends(get_conn)],
|
|
ctx: Annotated[TokenContext, Depends(require_token)],
|
|
) -> Response:
|
|
_ = account
|
|
body = await request.body()
|
|
await conn.execute(
|
|
"""INSERT INTO os_swift_containers(account, name, meta)
|
|
VALUES($1,$2,'{}'::jsonb) ON CONFLICT DO NOTHING""",
|
|
_account(ctx),
|
|
container,
|
|
)
|
|
await conn.execute(
|
|
"""INSERT INTO os_swift_objects(id, account, container, name, content_type, bytes, body, meta)
|
|
VALUES($1,$2,$3,$4,$5,$6,$7,'{}'::jsonb)
|
|
ON CONFLICT (account, container, name) DO UPDATE
|
|
SET bytes=EXCLUDED.bytes, body=EXCLUDED.body, content_type=EXCLUDED.content_type""",
|
|
uuid4(),
|
|
_account(ctx),
|
|
container,
|
|
object_name,
|
|
request.headers.get("content-type") or "application/octet-stream",
|
|
len(body),
|
|
body,
|
|
)
|
|
return Response(status_code=201, headers={"Etag": "0"})
|
|
|
|
|
|
@router.post("/v1/{account}/{container}/{object_name:path}", status_code=202)
|
|
async def post_object(
|
|
account: str,
|
|
container: str,
|
|
object_name: str,
|
|
request: Request,
|
|
conn: Annotated[Connection, Depends(get_conn)],
|
|
ctx: Annotated[TokenContext, Depends(require_token)],
|
|
) -> Response:
|
|
# Metadata update / create — reuse PUT semantics
|
|
return await put_object(account, container, object_name, request, conn, ctx)
|
|
|
|
|
|
@router.get("/v1/{account}/{container}/{object_name:path}")
|
|
async def get_object(
|
|
account: str,
|
|
container: str,
|
|
object_name: str,
|
|
conn: Annotated[Connection, Depends(get_conn)],
|
|
ctx: Annotated[TokenContext, Depends(require_token)],
|
|
) -> Response:
|
|
_ = account
|
|
row = await conn.fetchrow(
|
|
"""SELECT body, content_type FROM os_swift_objects
|
|
WHERE account=$1 AND container=$2 AND name=$3""",
|
|
_account(ctx),
|
|
container,
|
|
object_name,
|
|
)
|
|
if row is None:
|
|
raise OpenStackError("NotFound", "Object not found", status_code=404)
|
|
return Response(content=bytes(row["body"] or b""), media_type=row["content_type"])
|
|
|
|
|
|
@router.delete("/v1/{account}/{container}/{object_name:path}", status_code=204)
|
|
async def delete_object(
|
|
account: str,
|
|
container: str,
|
|
object_name: str,
|
|
conn: Annotated[Connection, Depends(get_conn)],
|
|
ctx: Annotated[TokenContext, Depends(require_token)],
|
|
) -> Response:
|
|
_ = account
|
|
result = await conn.execute(
|
|
"DELETE FROM os_swift_objects WHERE account=$1 AND container=$2 AND name=$3",
|
|
_account(ctx),
|
|
container,
|
|
object_name,
|
|
)
|
|
if result.endswith("0"):
|
|
raise OpenStackError("NotFound", "Object not found", status_code=404)
|
|
return Response(status_code=204)
|