First working version.
continuous-integration/drone/push Build is failing
Details
continuous-integration/drone/push Build is failing
Details
This commit is contained in:
parent
7488200ca3
commit
9031220efa
67
.drone.yml
67
.drone.yml
|
@ -26,7 +26,7 @@ steps:
|
||||||
---
|
---
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
type: docker
|
type: docker
|
||||||
name: build-deploy
|
name: build-pypi
|
||||||
depends_on:
|
depends_on:
|
||||||
- lint
|
- lint
|
||||||
- test
|
- test
|
||||||
|
@ -38,6 +38,8 @@ steps:
|
||||||
- python -m build --wheel
|
- python -m build --wheel
|
||||||
- name: publish
|
- name: publish
|
||||||
image: plugins/pypi
|
image: plugins/pypi
|
||||||
|
depends_on:
|
||||||
|
- build
|
||||||
when:
|
when:
|
||||||
branch:
|
branch:
|
||||||
- main
|
- main
|
||||||
|
@ -47,3 +49,66 @@ steps:
|
||||||
from_secret: password
|
from_secret: password
|
||||||
repository: https://git.jacknet.io/api/packages/jackhadrill/pypi
|
repository: https://git.jacknet.io/api/packages/jackhadrill/pypi
|
||||||
skip_build: true
|
skip_build: true
|
||||||
|
---
|
||||||
|
kind: pipeline
|
||||||
|
type: docker
|
||||||
|
name: build-docker-amd64
|
||||||
|
depends_on:
|
||||||
|
- build-pypi
|
||||||
|
platform:
|
||||||
|
arch: amd64
|
||||||
|
steps:
|
||||||
|
- name: build
|
||||||
|
image: plugins/docker
|
||||||
|
when:
|
||||||
|
branch:
|
||||||
|
- main
|
||||||
|
settings:
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
repo: git.jacknet.io/jackhadrill/container-director
|
||||||
|
tags: amd64
|
||||||
|
username: jackhadrill
|
||||||
|
password:
|
||||||
|
from_secret: password
|
||||||
|
registry: git.jacknet.io
|
||||||
|
---
|
||||||
|
kind: pipeline
|
||||||
|
type: docker
|
||||||
|
name: build-docker-arm64
|
||||||
|
depends_on:
|
||||||
|
- build-pypi
|
||||||
|
platform:
|
||||||
|
arch: arm64
|
||||||
|
steps:
|
||||||
|
- name: build
|
||||||
|
image: plugins/docker
|
||||||
|
when:
|
||||||
|
branch:
|
||||||
|
- main
|
||||||
|
settings:
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
repo: git.jacknet.io/jackhadrill/container-spawner
|
||||||
|
tags: arm64
|
||||||
|
username: jackhadrill
|
||||||
|
password:
|
||||||
|
from_secret: password
|
||||||
|
registry: git.jacknet.io
|
||||||
|
---
|
||||||
|
kind: pipeline
|
||||||
|
type: docker
|
||||||
|
name: manifest
|
||||||
|
depends_on:
|
||||||
|
- build-docker-amd64
|
||||||
|
- build-docker-arm64
|
||||||
|
steps:
|
||||||
|
- name: manifest
|
||||||
|
image: plugins/manifest
|
||||||
|
settings:
|
||||||
|
username: JackNet
|
||||||
|
password:
|
||||||
|
from_secret: password
|
||||||
|
target: git.jacknet.io/jackhadrill/container-spawner:latest
|
||||||
|
template: git.jacknet.io/jackhadrill/container-spawner:ARCH
|
||||||
|
platforms:
|
||||||
|
- linux/amd64
|
||||||
|
- linux/arm64
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
FROM python:3-alpine
|
||||||
|
|
||||||
|
ENV CONTAINER_IMAGE="git.jacknet.io/jackhadrill/code-server:latest"
|
||||||
|
ENV CONTAINER_PREFIX="vscode"
|
||||||
|
ENV CONTAINER_NETWORK="vscode_backend"
|
||||||
|
ENV CONTAINER_PERSIST="/home/coder"
|
||||||
|
|
||||||
|
WORKDIR /src
|
||||||
|
RUN pip install --no-cache-dir --extra-index-url https://git.jacknet.io/api/packages/jackhadrill/pypi/simple containerspawner
|
||||||
|
|
||||||
|
CMD ["containerspawner"]
|
|
@ -8,8 +8,13 @@ description = "A tool for spawning containers."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
license = {text = "MIT License"}
|
license = {text = "MIT License"}
|
||||||
dependencies = []
|
|
||||||
dynamic = ["version"]
|
dynamic = ["version"]
|
||||||
|
dependencies = [
|
||||||
|
"typer[all]>=0.6.1",
|
||||||
|
"Flask==2.2.2",
|
||||||
|
"waitress>=2.1.2",
|
||||||
|
"docker==6.0.0"
|
||||||
|
]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
lint = [
|
lint = [
|
||||||
|
@ -19,3 +24,6 @@ lint = [
|
||||||
test = [
|
test = [
|
||||||
"pytest>=7.1.2"
|
"pytest>=7.1.2"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
containerspawner = "containerspawner.__main__:main"
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
[metadata]
|
[metadata]
|
||||||
name = containerspawner
|
name = containerspawner
|
||||||
version = 0.0.0
|
version = 0.1.0
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
"""Catch all web server to spawn Docker containers."""
|
||||||
|
from flask import Flask, make_response, request
|
||||||
|
from containerspawner.state import StateManager
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
state = StateManager()
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/", defaults={"path": ""})
|
||||||
|
@app.route("/<path:path>")
|
||||||
|
def default(path):
|
||||||
|
"""Catch all endpoint to spawn Docker containers."""
|
||||||
|
username = request.headers.get("X-Forwarded-Preferred-User")
|
||||||
|
if not username:
|
||||||
|
return make_response("No username provided by upstream.", 400)
|
||||||
|
|
||||||
|
if not state.is_running(username):
|
||||||
|
state.spawn(username)
|
||||||
|
|
||||||
|
response = make_response(f"Container spawned. Reloading `{path}`...", 201)
|
||||||
|
response.headers["Refresh"] = "5"
|
||||||
|
return response
|
|
@ -0,0 +1,33 @@
|
||||||
|
"""Launch Container Spawner."""
|
||||||
|
import typer
|
||||||
|
from waitress import serve
|
||||||
|
from containerspawner import app
|
||||||
|
|
||||||
|
|
||||||
|
def cli(
|
||||||
|
host: str = typer.Option(
|
||||||
|
"0.0.0.0",
|
||||||
|
envvar=["CONTAINER_SPAWNER_HOST"],
|
||||||
|
help="Host for Container Spawner to listen on."
|
||||||
|
),
|
||||||
|
port: str = typer.Option(
|
||||||
|
"8080",
|
||||||
|
envvar=["CONTAINER_SPAWNER_PORT"],
|
||||||
|
help="Port for Container Spawner to listen on."
|
||||||
|
)
|
||||||
|
):
|
||||||
|
"""Run Container Spawner application using Waitress."""
|
||||||
|
try:
|
||||||
|
serve(app, host=host, port=port)
|
||||||
|
except OSError as exception:
|
||||||
|
print(str(exception))
|
||||||
|
raise typer.Exit(code=1)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
"""Run CLI parser."""
|
||||||
|
typer.run(cli)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
|
@ -0,0 +1,58 @@
|
||||||
|
"""State management for Container Spawner."""
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
from typing import List
|
||||||
|
import docker
|
||||||
|
from docker.models.containers import Container
|
||||||
|
|
||||||
|
CONTAINER_IMAGE = os.environ.get("CONTAINER_IMAGE") or "codercom/code-server:latest"
|
||||||
|
CONTAINER_PREFIX = os.environ.get("CONTAINER_PREFIX") or "vscode"
|
||||||
|
CONTAINER_NETWORK = os.environ.get("CONTAINER_NETWORK") or "vscode_backend"
|
||||||
|
CONTAINER_PERSIST = os.environ.get("CONTAINER_PERSIST") or "/home/coder"
|
||||||
|
|
||||||
|
|
||||||
|
class StateManager:
|
||||||
|
"""Store container states."""
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self._docker: docker.DockerClient = docker.from_env()
|
||||||
|
self._spawned_containers: List[str] = []
|
||||||
|
|
||||||
|
def is_running(self, username: str) -> bool:
|
||||||
|
"""Determines if the user's container is running.
|
||||||
|
|
||||||
|
:param username: The username to check.
|
||||||
|
:returns: True if contaienr exists.
|
||||||
|
"""
|
||||||
|
container_name = CONTAINER_PREFIX + "-" + username
|
||||||
|
return container_name in [
|
||||||
|
container.name for container in self._docker.containers.list(all=True)
|
||||||
|
]
|
||||||
|
|
||||||
|
def spawn(self, username: str) -> None:
|
||||||
|
"""Spawn a new Docker container for the specified user.
|
||||||
|
|
||||||
|
:param username: The username of the user.
|
||||||
|
"""
|
||||||
|
if self.is_running(username):
|
||||||
|
return
|
||||||
|
|
||||||
|
container_name = CONTAINER_PREFIX + "-" + username
|
||||||
|
self._spawned_containers.append(container_name)
|
||||||
|
print(f"Spawning {container_name}!")
|
||||||
|
container: Container = self._docker.containers.run(
|
||||||
|
image=CONTAINER_IMAGE,
|
||||||
|
name=container_name,
|
||||||
|
network=CONTAINER_NETWORK,
|
||||||
|
volumes={container_name: {"bind": CONTAINER_PERSIST}},
|
||||||
|
detach=True,
|
||||||
|
auto_remove=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Wait for container to start.
|
||||||
|
time.sleep(3)
|
||||||
|
|
||||||
|
# Disable auth.
|
||||||
|
container.exec_run("sed -i 's/auth: password/auth: none/' ~/.config/code-server/config.yaml")
|
||||||
|
|
||||||
|
# Install extensions.
|
||||||
|
container.exec_run("code-server --install-extension ms-python.python")
|
Loading…
Reference in New Issue