First working version.
continuous-integration/drone/push Build is failing Details

This commit is contained in:
Jack Hadrill 2022-08-27 18:31:48 +00:00
parent 7488200ca3
commit 9031220efa
9 changed files with 201 additions and 4 deletions

View File

@ -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
@ -47,3 +49,66 @@ steps:
from_secret: password
repository: https://git.jacknet.io/api/packages/jackhadrill/pypi
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

11
Dockerfile Normal file
View File

@ -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"]

View File

@ -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"

View File

@ -1,3 +1,3 @@
[metadata]
name = containerspawner
version = 0.0.0
version = 0.1.0

View File

@ -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

View File

@ -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()

View File

@ -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")