outposts: implement docker controller
This commit is contained in:
parent
d3a96ac7aa
commit
d506e8f1a3
|
@ -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)
|
|
|
@ -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)
|
|
@ -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.",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
if deployment_type == OutpostDeploymentType.KUBERNETES:
|
||||||
ProxyKubernetesController(outpost).run()
|
ProxyKubernetesController(outpost).run()
|
||||||
|
if deployment_type == OutpostDeploymentType.DOCKER:
|
||||||
|
ProxyDockerController(outpost).run()
|
||||||
|
|
||||||
|
|
||||||
@CELERY_APP.task()
|
@CELERY_APP.task()
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
Reference in New Issue