Compare commits

...

18 Commits

Author SHA1 Message Date
0c2af63888 Implement logs and stats 2022-01-17 20:51:13 +13:00
18ef42023f Change test user 2022-01-17 20:50:32 +13:00
49a49f7ea6 Use server side events 2022-01-17 05:27:26 +00:00
d8b8c0f801 Add cors midleware 2022-01-17 05:26:59 +00:00
9cdc0ad923 Add dependencies 2022-01-17 05:26:27 +00:00
af0954b6ff Add dependencies and use uvicorn 2022-01-17 05:25:02 +00:00
6df31460e8 Add production file 2022-01-17 05:23:16 +00:00
b00e0faedc Remove ports, tty and stdin_open 2022-01-16 07:10:06 +00:00
02097426ba Add user creation script 2022-01-16 07:09:23 +00:00
bd6afffd77 Ignore users.json 2022-01-16 07:07:55 +00:00
f2e4478edc Move and rename 2022-01-16 07:06:45 +00:00
3a5716a2f9 Remove requirements.txt 2022-01-16 07:05:00 +00:00
26490887ee Add users, docker.sock 2022-01-16 07:04:39 +00:00
9a254575f2 Add pip requirements 2022-01-15 07:35:11 +00:00
db218cb8c2 Add server test 2022-01-13 03:13:04 +00:00
fd5fc0a85d Remove items 2022-01-13 03:12:19 +00:00
7307abf7d8 Add enva to disable auth 2022-01-13 03:11:59 +00:00
b6f41f4d0a Rename 2022-01-13 03:11:02 +00:00
14 changed files with 136 additions and 50 deletions

3
.gitignore vendored
View File

@@ -4,7 +4,8 @@ __pycache__/
*.py[cod] *.py[cod]
*$py.class *$py.class
requirements.txt requirements.txt
users.json
app/users.json
# C extensions # C extensions
*.so *.so

View File

@@ -1,6 +1,14 @@
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9-slim FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9-slim
ENV DOCKER=1 ENV DOCKER=1
COPY requirements.txt /app/requirements.txt RUN pip install \
RUN pip install -r /app/requirements.txt python-jose \
passlib \
python-multipart \
docker \
aiodocker \
sse-starlette \
anyio
COPY ./app /app/app COPY ./app /app/app
CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"]

View File

@@ -12,6 +12,7 @@ passlib = "*"
python-multipart = "*" python-multipart = "*"
docker = "*" docker = "*"
aiodocker = "*" aiodocker = "*"
sse-starlette = "*"
[dev-packages] [dev-packages]
pytest = "*" pytest = "*"
@@ -24,5 +25,5 @@ mypy = "*"
python_version = "3.9" python_version = "3.9"
[scripts] [scripts]
test = "pytest app/test/test_main.py -s" test = "pytest app/test/test_auth.py app/test/test_server.py -W ignore::DeprecationWarning -s"
dev = "uvicorn app.main:app --reload" dev = "uvicorn app.main:app --reload"

View File

@@ -1,8 +1,20 @@
from fastapi import FastAPI, Depends from fastapi import FastAPI, Depends
from fastapi.middleware.cors import CORSMiddleware
from app import auth, user, server from app import auth, user, server
from os import getenv
app = FastAPI(docs_url="/")
app = FastAPI() app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
dependencies = list()
if not getenv('DISABLE_AUTH'):
dependencies.append(Depends(auth.authorise))
app.include_router(auth.router) app.include_router(auth.router)
app.include_router(user.router) app.include_router(user.router)
app.include_router(server.router, dependencies=[Depends(auth.authorise)]) app.include_router(server.router, dependencies=dependencies)

View File

@@ -1,7 +1,9 @@
from fastapi import APIRouter, HTTPException, status, WebSocket from fastapi import APIRouter, HTTPException, status, WebSocket, Request
from sse_starlette.sse import EventSourceResponse
import base64 import base64
import docker import docker
import aiodocker import aiodocker
from asyncio import sleep
router = APIRouter() router = APIRouter()
client = docker.from_env() client = docker.from_env()
@@ -42,24 +44,33 @@ async def commnd(server, command):
return f"{server} {base64.urlsafe_b64decode(command).decode('utf_8')}" return f"{server} {base64.urlsafe_b64decode(command).decode('utf_8')}"
@router.websocket("/server/{server}/logs") @router.get("/server/{server}/logs")
async def logs(websocket: WebSocket, server: str): async def logs(server: str, request: Request, follow: bool = True, tail: int = 1000):
await websocket.accept()
container = await getContainer(server) container = await getContainer(server)
async for line in container.log(stdout=True, follow=True, tail=5000): #event_generator = logGenerator(request, server)
#print(line) if follow:
await websocket.send_bytes(line) return EventSourceResponse(logStream(container, tail))
print("Closed") else:
return server return await container.log(stdout=True, follow=False, tail=tail)
async def logStream(container, tail):
async for line in container.log(stdout=True, follow=True, tail=tail):
yield line
yield '\n'
@router.get("/server/{server}/stats") @router.get("/server/{server}/stats")
async def stats(websocket: WebSocket, server: str): async def stats(server: str, request: Request, stream: bool = True, delay: int = 2):
await websocket.accept()
container = await getContainer(server) container = await getContainer(server)
async for line in container.log(): if stream:
await websocket.send_bytes(line) return EventSourceResponse(statStream(request, container, delay))
return server return await container.stats(stream=False)
async def statStream(request, container, delay):
while True:
yield await container.stats(stream=False)
_delay = delay - 1 if delay else 0
print(_delay)
await sleep(_delay)
async def getContainer(server): async def getContainer(server):
return await docker.containers.get(server) return await docker.containers.get(server)

21
app/test/test_server.py Normal file
View File

@@ -0,0 +1,21 @@
#curl -i -X POST http://localhost:8000/token -H "Content-Type: application/x-www-form-urlencoded" -d "username=johndoe&password=secret"
# curl -X 'GET' \
# 'http://localhost:8000/users/me/' \
# -H 'accept: application/json' \
# -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJqb2huZG9lIiwiZXhwIjoxNjMxNDQ4MjQ1fQ.DrM92jgRiry0uXBXn-61rRehATW4zDhHUWoGR6lv6Us'
from fastapi import FastAPI
from fastapi.testclient import TestClient
import docker
from server import *
app = FastAPI()
app.include_router(router)
testclient = TestClient(app)
def test_start():
#response = testclient.post("/server/minecraft/start")
#assert response.status_code == 200
pass

View File

@@ -9,6 +9,3 @@ async def read_users_me(current_user: User = Depends(get_current_active_user)):
return current_user return current_user
@router.get("/users/me/items/")
async def read_own_items(current_user: User = Depends(get_current_active_user)):
return [{"item_id": "Foo", "owner": current_user.username}]

18
app/useradd.py Normal file
View File

@@ -0,0 +1,18 @@
from distutils.fancy_getopt import fancy_getopt
from site import USER_BASE
from passlib.context import CryptContext
from json import load, dump
from sys import argv
with open("app/users.json", 'r+') as f:
fake_users_db = load(f)
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
fake_users_db[argv[1]] = {"username": argv[1], "hashed_password": pwd_context.hash(argv[2]),
"disabled": False, "servers": argv[3:]}
f.seek(0)
dump(fake_users_db, f, indent=2)
print(fake_users_db)

10
app/users.json Normal file → Executable file
View File

@@ -1,10 +0,0 @@
{
"johndoe": {
"username": "johndoe",
"full_name": "John Doe",
"email": "johndoe@example.com",
"hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
"disabled": "False",
"servers": ["a", "b","minecraft"]
}
}

View File

@@ -1,14 +1,14 @@
version: '3.7' version: '3.7'
services: services:
app: api:
build: . build: .
env_file: env_file:
- .env - .env
ports: ports:
- 8000:8000 - 8000:8000
tty: true
stdin_open: true
volumes: volumes:
- ./app:/app/app - ./app:/app/app
- /var/run/docker.sock:/var/run/docker.sock
- ./users.json:/app/app/users.json
command: uvicorn app.main:app --host 0.0.0.0 --reload command: uvicorn app.main:app --host 0.0.0.0 --reload

View File

@@ -5,10 +5,7 @@ services:
build: . build: .
env_file: env_file:
- .env - .env
ports:
- 8000:80
tty: true
stdin_open: true
volumes: volumes:
- ./app:/app/app - ./app:/app/app
- /var/run/docker.sock:/var/run/docker.sock
command: pytest app/test/test_main.py -s command: pytest app/test/test_main.py -s

View File

@@ -1,13 +1,33 @@
version: '3.7' version: '3.7'
services: services:
app: api:
build: . build: ./
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./users.json:/app/app/users.json
restart: unless-stopped
networks:
- caddy
labels:
caddy: console
caddy.reverse_proxy: "{{upstreams 80}}"
#caddy.tls: "admin@localhost"
caddy.tls: "internal"
env_file: env_file:
- .env - .env
caddy:
image: lucaslorentz/caddy-docker-proxy:ci-alpine
ports: ports:
- 8000:80 - 80:80
tty: true - 443:443
stdin_open: true volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- caddy
restart: unless-stopped
networks:
caddy:
name: caddy

10
users.json.sample Normal file
View File

@@ -0,0 +1,10 @@
{
"test": {
"username": "test",
"hashed_password": "$2b$12$VS1k1fdA4x2EeF1a/LMIyex.evEQGF5EsviIcG22S55YO8uUQCT7q",
"disabled": false,
"servers": [
"test"
]
}
}