outposts: fix defaults and tests for outposts

This commit is contained in:
Jens Langhammer 2020-11-04 10:54:44 +01:00
parent 706448dc14
commit 3b76af4eaa
14 changed files with 136 additions and 61 deletions

View file

@ -16,9 +16,9 @@ from passbook import __version__
from passbook.core.models import Application
from passbook.flows.models import Flow
from passbook.outposts.models import (
DockerServiceConnection,
Outpost,
OutpostConfig,
OutpostDeploymentType,
OutpostType,
)
from passbook.providers.proxy.models import ProxyProvider
@ -76,7 +76,6 @@ class TestProviderProxy(SeleniumTestCase):
outpost: Outpost = Outpost.objects.create(
name="proxy_outpost",
type=OutpostType.PROXY,
deployment_type=OutpostDeploymentType.CUSTOM,
)
outpost.providers.add(proxy)
outpost.save()
@ -128,10 +127,11 @@ class TestProviderProxyConnect(ChannelsLiveServerTestCase):
proxy.save()
# we need to create an application to actually access the proxy
Application.objects.create(name="proxy", slug="proxy", provider=proxy)
service_connection = DockerServiceConnection.objects.get(local=True)
outpost: Outpost = Outpost.objects.create(
name="proxy_outpost",
type=OutpostType.PROXY,
deployment_type=OutpostDeploymentType.DOCKER,
service_connection=service_connection,
_config=asdict(
OutpostConfig(passbook_host=self.live_server_url, log_level="debug")
),

View file

@ -28,11 +28,11 @@ class PassbookOutpostConfig(AppConfig):
import_module("passbook.outposts.signals")
try:
self.init_local_connection()
except (ProgrammingError):
except ProgrammingError:
pass
def init_local_connection(self):
# Check if local kubernetes or docker connections should be created
"""Check if local kubernetes or docker connections should be created"""
from passbook.outposts.models import (
KubernetesServiceConnection,
DockerServiceConnection,

View file

@ -20,8 +20,9 @@ class BaseController:
outpost: Outpost
connection: OutpostServiceConnection
def __init__(self, outpost: Outpost):
def __init__(self, outpost: Outpost, connection: OutpostServiceConnection):
self.outpost = outpost
self.connection = connection
self.logger = get_logger()
self.deployment_ports = {}

View file

@ -23,8 +23,8 @@ class DockerController(BaseController):
image_base = "beryju/passbook"
def __init__(self, outpost: Outpost) -> None:
super().__init__(outpost)
def __init__(self, outpost: Outpost, connection: DockerServiceConnection) -> None:
super().__init__(outpost, connection)
try:
if self.connection.local:
self.client = DockerClient.from_env()

View file

@ -25,8 +25,10 @@ class KubernetesController(BaseController):
connection: KubernetesServiceConnection
def __init__(self, outpost: Outpost) -> None:
super().__init__(outpost)
def __init__(
self, outpost: Outpost, connection: KubernetesServiceConnection
) -> None:
super().__init__(outpost, connection)
try:
if self.connection.local:
load_incluster_config()

View file

@ -10,7 +10,9 @@ def fix_missing_token_identifier(apps: Apps, schema_editor: BaseDatabaseSchemaEd
Token = apps.get_model("passbook_core", "Token")
from passbook.outposts.models import Outpost
for outpost in Outpost.objects.using(schema_editor.connection.alias).all().only('pk'):
for outpost in (
Outpost.objects.using(schema_editor.connection.alias).all().only("pk")
):
user_identifier = outpost.user_identifier
user = User.objects.get(username=user_identifier)
tokens = Token.objects.filter(user=user)

View file

@ -11,16 +11,23 @@ from django.db.backends.base.schema import BaseDatabaseSchemaEditor
def migrate_to_service_connection(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
db_alias = schema_editor.connection.alias
Outpost = apps.get_model("passbook_outposts", "Outpost")
DockerServiceConnection = apps.get_model("passbook_outposts", "DockerServiceConnection")
KubernetesServiceConnection = apps.get_model("passbook_outposts", "KubernetesServiceConnection")
DockerServiceConnection = apps.get_model(
"passbook_outposts", "DockerServiceConnection"
)
KubernetesServiceConnection = apps.get_model(
"passbook_outposts", "KubernetesServiceConnection"
)
from passbook.outposts.apps import PassbookOutpostConfig
# Ensure that local connection have been created
PassbookOutpostConfig.init_local_connection(None)
docker = DockerServiceConnection.objects.filter(local=True)
k8s = KubernetesServiceConnection.objects.filter(local=True)
for outpost in Outpost.objects.using(db_alias).all().exclude(deployment_type="custom"):
for outpost in (
Outpost.objects.using(db_alias).all().exclude(deployment_type="custom")
):
if outpost.deployment_type == "kubernetes":
outpost.service_connection = k8s
elif outpost.deployment_type == "docker":
@ -31,43 +38,85 @@ def migrate_to_service_connection(apps: Apps, schema_editor: BaseDatabaseSchemaE
class Migration(migrations.Migration):
dependencies = [
('passbook_outposts', '0009_fix_missing_token_identifier'),
("passbook_outposts", "0009_fix_missing_token_identifier"),
]
operations = [
migrations.CreateModel(
name='OutpostServiceConnection',
name="OutpostServiceConnection",
fields=[
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('name', models.TextField()),
('local', models.BooleanField(default=False, help_text='If enabled, use the local connection. Required Docker socket/Kubernetes Integration', unique=True)),
(
"uuid",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
("name", models.TextField()),
(
"local",
models.BooleanField(
default=False,
help_text="If enabled, use the local connection. Required Docker socket/Kubernetes Integration",
unique=True,
),
),
],
),
migrations.CreateModel(
name='DockerServiceConnection',
name="DockerServiceConnection",
fields=[
('outpostserviceconnection_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_outposts.outpostserviceconnection')),
('url', models.TextField()),
('tls', models.BooleanField()),
(
"outpostserviceconnection_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="passbook_outposts.outpostserviceconnection",
),
),
("url", models.TextField()),
("tls", models.BooleanField()),
],
bases=('passbook_outposts.outpostserviceconnection',),
bases=("passbook_outposts.outpostserviceconnection",),
),
migrations.CreateModel(
name='KubernetesServiceConnection',
name="KubernetesServiceConnection",
fields=[
('outpostserviceconnection_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_outposts.outpostserviceconnection')),
('config', models.JSONField()),
(
"outpostserviceconnection_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="passbook_outposts.outpostserviceconnection",
),
),
("config", models.JSONField()),
],
bases=('passbook_outposts.outpostserviceconnection',),
bases=("passbook_outposts.outpostserviceconnection",),
),
migrations.AddField(
model_name='outpost',
name='service_connection',
field=models.ForeignKey(blank=True, default=None, help_text='Select Service-Connection passbook should use to manage this outpost. Leave empty if passbook should not handle the deployment.', null=True, on_delete=django.db.models.deletion.SET_DEFAULT, to='passbook_outposts.outpostserviceconnection'),
model_name="outpost",
name="service_connection",
field=models.ForeignKey(
blank=True,
default=None,
help_text="Select Service-Connection passbook should use to manage this outpost. Leave empty if passbook should not handle the deployment.",
null=True,
on_delete=django.db.models.deletion.SET_DEFAULT,
to="passbook_outposts.outpostserviceconnection",
),
),
migrations.RunPython(migrate_to_service_connection),
migrations.RemoveField(
model_name='outpost',
name='deployment_type',
model_name="outpost",
name="deployment_type",
),
]

View file

@ -18,6 +18,7 @@ from packaging.version import LegacyVersion, Version, parse
from passbook import __version__
from passbook.core.models import Provider, Token, TokenIntents, User
from passbook.lib.config import CONFIG
from passbook.lib.models import InheritanceForeignKey
from passbook.lib.utils.template import render_to_string
OUR_VERSION = parse(__version__)
@ -106,7 +107,7 @@ class Outpost(models.Model):
name = models.TextField()
type = models.TextField(choices=OutpostType.choices, default=OutpostType.PROXY)
service_connection = models.ForeignKey(
service_connection = InheritanceForeignKey(
OutpostServiceConnection,
default=None,
null=True,

View file

@ -10,7 +10,14 @@ from structlog import get_logger
from passbook.lib.tasks import MonitoredTask, TaskResult, TaskResultStatus
from passbook.lib.utils.reflection import path_to_class
from passbook.outposts.controllers.base import ControllerException
from passbook.outposts.models import Outpost, OutpostModel, OutpostState, OutpostType
from passbook.outposts.models import (
DockerServiceConnection,
KubernetesServiceConnection,
Outpost,
OutpostModel,
OutpostState,
OutpostType,
)
from passbook.providers.proxy.controllers.docker import ProxyDockerController
from passbook.providers.proxy.controllers.kubernetes import ProxyKubernetesController
from passbook.root.celery import CELERY_APP
@ -33,10 +40,13 @@ def outpost_controller(self: MonitoredTask, outpost_pk: str):
self.set_uid(slugify(outpost.name))
try:
if outpost.type == OutpostType.PROXY:
if outpost.deployment_type == OutpostDeploymentType.KUBERNETES:
logs = ProxyKubernetesController(outpost).up_with_logs()
if outpost.deployment_type == OutpostDeploymentType.DOCKER:
logs = ProxyDockerController(outpost).up_with_logs()
service_connection = outpost.service_connection
if isinstance(service_connection, DockerServiceConnection):
logs = ProxyDockerController(outpost, service_connection).up_with_logs()
if isinstance(service_connection, KubernetesServiceConnection):
logs = ProxyKubernetesController(
outpost, service_connection
).up_with_logs()
except ControllerException as exc:
self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))
else:
@ -48,10 +58,11 @@ def outpost_pre_delete(outpost_pk: str):
"""Delete outpost objects before deleting the DB Object"""
outpost = Outpost.objects.get(pk=outpost_pk)
if outpost.type == OutpostType.PROXY:
if outpost.deployment_type == OutpostDeploymentType.KUBERNETES:
ProxyKubernetesController(outpost).down()
if outpost.deployment_type == OutpostDeploymentType.DOCKER:
ProxyDockerController(outpost).down()
service_connection = outpost.service_connection
if isinstance(service_connection, DockerServiceConnection):
ProxyDockerController(outpost, service_connection).down()
if isinstance(service_connection, KubernetesServiceConnection):
ProxyKubernetesController(outpost, service_connection).down()
@CELERY_APP.task()

View file

@ -11,7 +11,7 @@ from passbook.flows.models import Flow
from passbook.outposts.controllers.k8s.base import NeedsUpdate
from passbook.outposts.controllers.k8s.deployment import DeploymentReconciler
from passbook.outposts.controllers.kubernetes import KubernetesController
from passbook.outposts.models import Outpost, OutpostDeploymentType, OutpostType
from passbook.outposts.models import KubernetesServiceConnection, Outpost, OutpostType
from passbook.providers.proxy.models import ProxyProvider
@ -29,7 +29,6 @@ class OutpostTests(TestCase):
outpost: Outpost = Outpost.objects.create(
name="test",
type=OutpostType.PROXY,
deployment_type=OutpostDeploymentType.CUSTOM,
)
# Before we add a provider, the user should only have access to the outpost
@ -79,17 +78,18 @@ class OutpostKubernetesTests(TestCase):
external_host="http://localhost",
authorization_flow=Flow.objects.first(),
)
self.service_connection = KubernetesServiceConnection.objects.get(local=True)
self.outpost: Outpost = Outpost.objects.create(
name="test",
type=OutpostType.PROXY,
deployment_type=OutpostDeploymentType.KUBERNETES,
service_connection=self.service_connection,
)
self.outpost.providers.add(self.provider)
self.outpost.save()
def test_deployment_reconciler(self):
"""test that deployment requires update"""
controller = KubernetesController(self.outpost)
controller = KubernetesController(self.outpost, self.service_connection)
deployment_reconciler = DeploymentReconciler(controller)
self.assertIsNotNone(deployment_reconciler.retrieve())

View file

@ -12,7 +12,12 @@ from structlog import get_logger
from passbook.core.models import User
from passbook.outposts.controllers.docker import DockerController
from passbook.outposts.models import Outpost, OutpostType
from passbook.outposts.models import (
DockerServiceConnection,
KubernetesServiceConnection,
Outpost,
OutpostType,
)
from passbook.providers.proxy.controllers.kubernetes import ProxyKubernetesController
LOGGER = get_logger()
@ -35,7 +40,7 @@ class DockerComposeView(LoginRequiredMixin, View):
)
manifest = ""
if outpost.type == OutpostType.PROXY:
controller = DockerController(outpost)
controller = DockerController(outpost, DockerServiceConnection())
manifest = controller.get_static_deployment()
return HttpResponse(manifest, content_type="text/vnd.yaml")
@ -53,7 +58,9 @@ class KubernetesManifestView(LoginRequiredMixin, View):
)
manifest = ""
if outpost.type == OutpostType.PROXY:
controller = ProxyKubernetesController(outpost)
controller = ProxyKubernetesController(
outpost, KubernetesServiceConnection()
)
manifest = controller.get_static_deployment()
return HttpResponse(manifest, content_type="text/vnd.yaml")

View file

@ -3,15 +3,15 @@ from typing import Dict
from urllib.parse import urlparse
from passbook.outposts.controllers.docker import DockerController
from passbook.outposts.models import Outpost
from passbook.outposts.models import DockerServiceConnection, Outpost
from passbook.providers.proxy.models import ProxyProvider
class ProxyDockerController(DockerController):
"""Proxy Provider Docker Contoller"""
def __init__(self, outpost: Outpost):
super().__init__(outpost)
def __init__(self, outpost: Outpost, connection: DockerServiceConnection):
super().__init__(outpost, connection)
self.deployment_ports = {
"http": 4180,
"https": 4443,

View file

@ -1,14 +1,14 @@
"""Proxy Provider Kubernetes Contoller"""
from passbook.outposts.controllers.kubernetes import KubernetesController
from passbook.outposts.models import Outpost
from passbook.outposts.models import KubernetesServiceConnection, Outpost
from passbook.providers.proxy.controllers.k8s.ingress import IngressReconciler
class ProxyKubernetesController(KubernetesController):
"""Proxy Provider Kubernetes Contoller"""
def __init__(self, outpost: Outpost):
super().__init__(outpost)
def __init__(self, outpost: Outpost, connection: KubernetesServiceConnection):
super().__init__(outpost, connection)
self.deployment_ports = {
"http": 4180,
"https": 4443,

View file

@ -6,7 +6,7 @@ import yaml
from django.test import TestCase
from passbook.flows.models import Flow
from passbook.outposts.models import Outpost, OutpostDeploymentType, OutpostType
from passbook.outposts.models import KubernetesServiceConnection, Outpost, OutpostType
from passbook.providers.proxy.controllers.kubernetes import ProxyKubernetesController
from passbook.providers.proxy.models import ProxyProvider
@ -23,15 +23,16 @@ class TestControllers(TestCase):
external_host="http://localhost",
authorization_flow=Flow.objects.first(),
)
service_connection = KubernetesServiceConnection.objects.get(local=True)
outpost: Outpost = Outpost.objects.create(
name="test",
type=OutpostType.PROXY,
deployment_type=OutpostDeploymentType.KUBERNETES,
service_connection=service_connection,
)
outpost.providers.add(provider)
outpost.save()
controller = ProxyKubernetesController(outpost)
controller = ProxyKubernetesController(outpost, service_connection)
manifest = controller.get_static_deployment()
self.assertEqual(len(list(yaml.load_all(manifest, Loader=yaml.SafeLoader))), 4)
@ -43,14 +44,15 @@ class TestControllers(TestCase):
external_host="http://localhost",
authorization_flow=Flow.objects.first(),
)
service_connection = KubernetesServiceConnection.objects.get(local=True)
outpost: Outpost = Outpost.objects.create(
name="test",
type=OutpostType.PROXY,
deployment_type=OutpostDeploymentType.KUBERNETES,
service_connection=service_connection,
)
outpost.providers.add(provider)
outpost.save()
controller = ProxyKubernetesController(outpost)
controller = ProxyKubernetesController(outpost, service_connection)
controller.up()
controller.down()