outposts: implement docker controller

This commit is contained in:
Jens Langhammer 2020-10-04 00:36:12 +02:00
parent d3a96ac7aa
commit d506e8f1a3
7 changed files with 135 additions and 47 deletions

View File

@ -1,36 +0,0 @@
"""Docker Compose controller"""
from yaml import safe_dump
from passbook import __version__
from passbook.outposts.controllers.base import BaseController
class DockerComposeController(BaseController):
"""Docker Compose controller"""
image_base = "beryju/passbook"
def run(self):
self.logger.warning("DockerComposeController does not implement run")
raise NotImplementedError
def get_static_deployment(self) -> str:
"""Generate docker-compose yaml for proxy, version 3.5"""
ports = [f"{x}:{x}" for _, x in self.deployment_ports.items()]
compose = {
"version": "3.5",
"services": {
f"passbook_{self.outpost.type}": {
"image": f"{self.image_base}-{self.outpost.type}:{__version__}",
"ports": ports,
"environment": {
"PASSBOOK_HOST": self.outpost.config.passbook_host,
"PASSBOOK_INSECURE": str(
self.outpost.config.passbook_host_insecure
),
"PASSBOOK_TOKEN": self.outpost.token.token_uuid.hex,
},
}
},
}
return safe_dump(compose, default_flow_style=False)

View File

@ -0,0 +1,76 @@
"""Docker controller"""
from docker import DockerClient, from_env
from docker.errors import NotFound
from docker.models.containers import Container
from yaml import safe_dump
from passbook import __version__
from passbook.outposts.controllers.base import BaseController
class DockerController(BaseController):
"""Docker controller"""
client: DockerClient
container: Container
image_base = "beryju/passbook"
def __init__(self, outpost_pk: str) -> None:
super().__init__(outpost_pk)
self.client = from_env()
def _get_container(self) -> Container:
container_name = f"passbook-proxy-{self.outpost.uuid.hex}"
try:
return self.client.containers.get(container_name)
except NotFound:
return self.client.containers.create(
image=f"{self.image_base}-{self.outpost.type}:{__version__}",
name=f"passbook-proxy-{self.outpost.uuid.hex}",
detach=True,
ports={x: x for _, x in self.deployment_ports.items()},
environment={
"PASSBOOK_HOST": self.outpost.config.passbook_host,
"PASSBOOK_INSECURE": str(
self.outpost.config.passbook_host_insecure
),
"PASSBOOK_TOKEN": self.outpost.token.token_uuid.hex,
},
)
def run(self):
container = self._get_container()
# Check if the container is out of date, delete it and retry
if len(container.image.tags) > 0:
tag: str = container.iamge.tags[0]
_, _, version = tag.partition(":")
if version != __version__:
container.kill()
container.remove(force=True)
return self.run()
if container.status != "running":
container.start()
return None
def get_static_deployment(self) -> str:
"""Generate docker-compose yaml for proxy, version 3.5"""
ports = [f"{x}:{x}" for _, x in self.deployment_ports.items()]
compose = {
"version": "3.5",
"services": {
f"passbook_{self.outpost.type}": {
"image": f"{self.image_base}-{self.outpost.type}:{__version__}",
"ports": ports,
"environment": {
"PASSBOOK_HOST": self.outpost.config.passbook_host,
"PASSBOOK_INSECURE": str(
self.outpost.config.passbook_host_insecure
),
"PASSBOOK_TOKEN": self.outpost.token.token_uuid.hex,
},
}
},
}
return safe_dump(compose, default_flow_style=False)

View File

@ -0,0 +1,26 @@
# Generated by Django 3.1.2 on 2020-10-03 22:39
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("passbook_outposts", "0005_auto_20200909_1733"),
]
operations = [
migrations.AlterField(
model_name="outpost",
name="deployment_type",
field=models.TextField(
choices=[
("kubernetes", "Kubernetes"),
("docker", "Docker"),
("custom", "Custom"),
],
default="custom",
help_text="Select between passbook-managed deployment types or a custom deployment.",
),
),
]

View File

@ -59,7 +59,8 @@ class OutpostType(models.TextChoices):
class OutpostDeploymentType(models.TextChoices): class OutpostDeploymentType(models.TextChoices):
"""Deployment types that are managed through passbook""" """Deployment types that are managed through passbook"""
# KUBERNETES = "kubernetes" KUBERNETES = "kubernetes"
DOCKER = "docker"
CUSTOM = "custom" CUSTOM = "custom"

View File

@ -12,6 +12,7 @@ from passbook.outposts.models import (
OutpostModel, OutpostModel,
OutpostType, OutpostType,
) )
from passbook.providers.proxy.controllers.docker import ProxyDockerController
from passbook.providers.proxy.controllers.kubernetes import ProxyKubernetesController from passbook.providers.proxy.controllers.kubernetes import ProxyKubernetesController
from passbook.root.celery import CELERY_APP from passbook.root.celery import CELERY_APP
@ -20,20 +21,27 @@ LOGGER = get_logger()
@CELERY_APP.task(bind=True) @CELERY_APP.task(bind=True)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def outpost_k8s_controller(self): def outpost_controller(self):
"""Launch Kubernetes Controller for all Outposts which are deployed in kubernetes""" """Launch Controller for all Outposts which support it"""
for outpost in Outpost.objects.filter( for outpost in Outpost.objects.exclude(
deployment_type=OutpostDeploymentType.KUBERNETES deployment_type=OutpostDeploymentType.CUSTOM
): ):
outpost_k8s_controller_single.delay(outpost.pk.hex, outpost.type) outpost_controller_single.delay(
outpost.pk.hex, outpost.deployment_type, outpost.type
)
@CELERY_APP.task(bind=True) @CELERY_APP.task(bind=True)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def outpost_k8s_controller_single(self, outpost: str, outpost_type: str): def outpost_controller_single(
"""Launch Kubernetes manager and reconcile deployment/service/etc""" self, outpost: str, deployment_type: str, outpost_type: str
):
"""Launch controller and reconcile deployment/service/etc"""
if outpost_type == OutpostType.PROXY: if outpost_type == OutpostType.PROXY:
ProxyKubernetesController(outpost).run() if deployment_type == OutpostDeploymentType.KUBERNETES:
ProxyKubernetesController(outpost).run()
if deployment_type == OutpostDeploymentType.DOCKER:
ProxyDockerController(outpost).run()
@CELERY_APP.task() @CELERY_APP.task()

View File

@ -11,7 +11,7 @@ from guardian.shortcuts import get_objects_for_user
from structlog import get_logger from structlog import get_logger
from passbook.core.models import User from passbook.core.models import User
from passbook.outposts.controllers.compose import DockerComposeController from passbook.outposts.controllers.docker import DockerController
from passbook.outposts.models import Outpost, OutpostType from passbook.outposts.models import Outpost, OutpostType
from passbook.providers.proxy.controllers.kubernetes import ProxyKubernetesController from passbook.providers.proxy.controllers.kubernetes import ProxyKubernetesController
@ -35,7 +35,7 @@ class DockerComposeView(LoginRequiredMixin, View):
) )
manifest = "" manifest = ""
if outpost.type == OutpostType.PROXY: if outpost.type == OutpostType.PROXY:
controller = DockerComposeController(outpost_pk) controller = DockerController(outpost_pk)
manifest = controller.get_static_deployment() manifest = controller.get_static_deployment()
return HttpResponse(manifest, content_type="text/vnd.yaml") return HttpResponse(manifest, content_type="text/vnd.yaml")

View File

@ -0,0 +1,13 @@
"""Proxy Provider Docker Contoller"""
from passbook.outposts.controllers.docker import DockerController
class ProxyDockerController(DockerController):
"""Proxy Provider Docker Contoller"""
def __init__(self, outpost_pk: str):
super().__init__(outpost_pk)
self.deployment_ports = {
"http": 4180,
"https": 4443,
}