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
69
.drone.yml
69
.drone.yml
|
@ -26,7 +26,7 @@ steps:
|
|||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: build-deploy
|
||||
name: build-pypi
|
||||
depends_on:
|
||||
- lint
|
||||
- test
|
||||
|
@ -38,6 +38,8 @@ steps:
|
|||
- python -m build --wheel
|
||||
- name: publish
|
||||
image: plugins/pypi
|
||||
depends_on:
|
||||
- build
|
||||
when:
|
||||
branch:
|
||||
- main
|
||||
|
@ -46,4 +48,67 @@ steps:
|
|||
password:
|
||||
from_secret: password
|
||||
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"
|
||||
requires-python = ">=3.9"
|
||||
license = {text = "MIT License"}
|
||||
dependencies = []
|
||||
dynamic = ["version"]
|
||||
dependencies = [
|
||||
"typer[all]>=0.6.1",
|
||||
"Flask==2.2.2",
|
||||
"waitress>=2.1.2",
|
||||
"docker==6.0.0"
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
lint = [
|
||||
|
@ -19,3 +24,6 @@ lint = [
|
|||
test = [
|
||||
"pytest>=7.1.2"
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
containerspawner = "containerspawner.__main__:main"
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
[metadata]
|
||||
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