Skip to content

Commit

Permalink
Separate out git application support and restructure code to support it.
Browse files Browse the repository at this point in the history
  • Loading branch information
GrahamDumpleton committed Apr 28, 2022
1 parent f4aa5e3 commit 7783f57
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 112 deletions.
70 changes: 70 additions & 0 deletions session-manager/handlers/application_git.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import random
import string

from .operator_config import OPERATOR_API_GROUP, INGRESS_DOMAIN, INGRESS_PROTOCOL


def git_workshop_spec_patches(application_properties):
characters = string.ascii_letters + string.digits

git_host = f"git-$(session_namespace).{INGRESS_DOMAIN}"
git_username = "$(session_namespace)"
git_password = "".join(random.sample(characters, 32))

return {
"spec": {
"session": {
"ingresses": [
{"name": "git", "port": 10087, "authentication": {"type": "none"}}
],
"variables": [
{
"name": "git_protocol",
"value": INGRESS_PROTOCOL,
},
{
"name": "git_host",
"value": git_host,
},
{
"name": "git_username",
"value": git_username,
},
{
"name": "git_password",
"value": git_password,
},
],
"env": [
{
"name": "GIT_PROTOCOL",
"value": INGRESS_PROTOCOL,
},
{
"name": "GIT_HOST",
"value": git_host,
},
{
"name": "GIT_USERNAME",
"value": git_username,
},
{
"name": "GIT_PASSWORD",
"value": git_password,
},
],
}
}
}


def git_environment_objects_list(application_properties):
return []


def git_session_objects_list(application_properties):
return []


def git_pod_template_spec_patches(application_properties):
return {}
24 changes: 18 additions & 6 deletions session-manager/handlers/application_vcluster.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
from .operator_config import OPERATOR_API_GROUP


def vcluster_workshop_spec_patches(application_properties):
return {
"spec": {
"session": {
"applications": {"console": {"vendor": "octant"}},
"variables": [
{
"name": "vcluster_secret",
"value": "$(session_namespace)-vc-kubeconfig",
},
],
}
}
}


def vcluster_environment_objects_list(application_properties):
return []

Expand Down Expand Up @@ -34,7 +50,7 @@ def vcluster_session_objects_list(application_properties):
"targetNamespaces": {
"nameSelector": {"matchNames": ["$(workshop_namespace)"]}
},
"targetSecret": {"name": "$(session_namespace)-vc-kubeconfig"},
"targetSecret": {"name": "$(vcluster_secret)"},
}
]
},
Expand Down Expand Up @@ -290,11 +306,7 @@ def vcluster_pod_template_spec_patches(application_properties):
"volumes": [
{
"name": "kubeconfig",
"secret": {"secretName": "$(session_namespace)-vc-kubeconfig"},
"secret": {"secretName": "$(vcluster_secret)"},
}
],
}


def vcluster_workshop_config_patches(application_properties):
return {"spec": {"session": {"applications": {"console": {"vendor": "octant"}}}}}
34 changes: 26 additions & 8 deletions session-manager/handlers/applications.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,43 @@
from .application_git import (
git_workshop_spec_patches,
git_environment_objects_list,
git_session_objects_list,
git_pod_template_spec_patches,
)
from .application_vcluster import (
vcluster_workshop_spec_patches,
vcluster_environment_objects_list,
vcluster_session_objects_list,
vcluster_pod_template_spec_patches,
vcluster_workshop_config_patches,
)


registered_applications = {
"git": dict(
workshop_spec_patches=git_workshop_spec_patches,
environment_objects_list=git_environment_objects_list,
session_objects_list=git_session_objects_list,
pod_template_spec_patches=git_pod_template_spec_patches,
),
"vcluster": dict(
workshop_spec_patches=vcluster_workshop_spec_patches,
environment_objects_list=vcluster_environment_objects_list,
session_objects_list=vcluster_session_objects_list,
pod_template_spec_patches=vcluster_pod_template_spec_patches,
workshop_config_patches=vcluster_workshop_config_patches,
)

),
}


def workshop_spec_patches(application, application_properties):
handler = registered_applications.get(application, {}).get(
"workshop_spec_patches"
)
if handler:
return handler(application_properties)
return {}


def environment_objects_list(application, application_properties):
handler = registered_applications.get(application, {}).get(
"environment_objects_list"
Expand All @@ -41,8 +63,4 @@ def pod_template_spec_patches(application, application_properties):
return {}


def workshop_config_patches(application, application_properties):
handler = registered_applications.get(application, {}).get("workshop_config_patches")
if handler:
return handler(application_properties)
return {}

90 changes: 49 additions & 41 deletions session-manager/handlers/workshopenvironment.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from .objects import create_from_dict, Workshop, SecretCopier
from .helpers import substitute_variables, smart_overlay_merge, Applications
from .applications import environment_objects_list, workshop_config_patches
from .applications import environment_objects_list, workshop_spec_patches

from .operator_config import (
OPERATOR_API_GROUP,
Expand Down Expand Up @@ -46,27 +46,26 @@
id=OPERATOR_STATUS_KEY,
)
def workshop_environment_create(name, meta, spec, patch, logger, **_):
# Use the name of the custom resource as the name of the namespace
# under which the workshop environment is created and any workshop
# instances are created.
# Use the name of the custom resource as the name of the namespace under
# which the workshop environment is created and any workshop instances are
# created.

environment_name = name
workshop_namespace = environment_name

# Can optionally be passed name of the training portal via a label
# when the workshop environment is created as a child to a training
# portal.
# Can optionally be passed name of the training portal via a label when the
# workshop environment is created as a child to a training portal.

portal_name = meta.get("labels", {}).get(
f"training.{OPERATOR_API_GROUP}/portal.name", ""
)

# The name of the workshop to be deployed can differ and is taken
# from the specification of the workspace. Lookup the workshop
# resource definition and ensure it exists. Later we will stash a
# copy of this in the status of the custom resource, and we will use
# this copy to avoid being affected by changes in the original after
# the creation of the workshop environment.
# The name of the workshop to be deployed can differ and is taken from the
# specification of the workshop environment. Lookup the workshop resource
# definition and ensure it exists. Later we will stash a copy of this in the
# status of the custom resource, and we will use this copy to avoid being
# affected by changes in the original after the creation of the workshop
# environment.

workshop_name = spec["workshop"]["name"]

Expand All @@ -88,15 +87,25 @@ def workshop_environment_create(name, meta, spec, patch, logger, **_):
workshop_generation = workshop_instance.obj["metadata"]["generation"]
workshop_spec = workshop_instance.obj.get("spec", {})

# Create a wrapper for determining if applications enabled and what
# configuration they provide.
# Create a wrapper for determining what applications are enabled and what
# configuration they provide. This includes allowing applications to patch
# the workshop config. As an application could enable another application
# because it requires it, we calculate the list of applications again after
# patching. It is the modified version of the config which gets saved in
# the status so that it can be used later by the workshop session.

applications = Applications(workshop_spec["session"].get("applications", {}))

# Create the namespace for everything related to this workshop. When
# pod security admission controller is being used, need to set the whole
# namespace as requiring privilged as we need to run docker in docker in
# this namespace.
for application in applications:
if applications.is_enabled(application):
workshop_config_patch = workshop_spec_patches(
application, applications.properties(application)
)
smart_overlay_merge(workshop_spec, workshop_config_patch.get("spec", {}))

applications = Applications(workshop_spec["session"].get("applications", {}))

# Create the namespace for everything related to this workshop.

namespace_body = {
"apiVersion": "v1",
Expand Down Expand Up @@ -131,7 +140,9 @@ def workshop_environment_create(name, meta, spec, patch, logger, **_):
raise kopf.TemporaryError(f"Namespace {workshop_namespace} already exists.")
raise

# Apply pod security policies to whole namespace if enabled.
# When using the pod security admission controller, we need to set the whole
# namespace as requiring privilged as we need to run docker in docker in
# this namespace.

if CLUSTER_SECURITY_POLICY_ENGINE == "pod-security-policies":
psp_role_binding_body = {
Expand Down Expand Up @@ -166,8 +177,8 @@ def workshop_environment_create(name, meta, spec, patch, logger, **_):
# Delete any limit ranges applied to the namespace so they don't cause
# issues with workshop instance deployments or any workshop deployments.
# This can be an issue where namespace/project templates apply them
# automatically to a namespace. The problem is that we may do this query
# too quickly and they may not have been created as yet.
# automatically to a namespace. The problem is that we may do this query too
# quickly and they may not have been created as yet.

for limit_range in pykube.LimitRange.objects(
api, namespace=workshop_namespace
Expand All @@ -178,10 +189,10 @@ def workshop_environment_create(name, meta, spec, patch, logger, **_):
pass

# Delete any resource quotas applied to the namespace so they don't cause
# issues with workshop instance deploymemnts or any workshop resources.
# This can be an issue where namespace/project templates apply them
# automatically to a namespace. The problem is that we may do this query
# too quickly and they may not have been created as yet.
# issues with workshop instance deploymemnts or any workshop resources. This
# can be an issue where namespace/project templates apply them automatically
# to a namespace. The problem is that we may do this query too quickly and
# they may not have been created as yet.

for resource_quota in pykube.ResourceQuota.objects(
api, namespace=workshop_namespace
Expand Down Expand Up @@ -246,9 +257,9 @@ def workshop_environment_create(name, meta, spec, patch, logger, **_):

NetworkPolicy(api, network_policy_body).create()

# Create a config map in the workshop namespace which contains the
# details about the workshop. This will be mounted into workshop
# instances so they can derive information to configure themselves.
# Create a config map in the workshop namespace which contains the details
# about the workshop. This will be mounted into workshop instances so they
# can derive information to configure themselves.

workshop_config = {
"spec": {
Expand All @@ -262,18 +273,6 @@ def workshop_environment_create(name, meta, spec, patch, logger, **_):
}
}

for application in applications:
if applications.is_enabled(application):
workshop_config_patch = workshop_config_patches(
application, applications.properties(application)
)
smart_overlay_merge(workshop_config, workshop_config_patch)

if applications.is_enabled("git"):
workshop_config["spec"]["session"]["ingresses"].append(
{"name": "git", "port": 10087, "authentication": {"type": "none"}}
)

config_map_body = {
"apiVersion": "v1",
"kind": "ConfigMap",
Expand Down Expand Up @@ -523,6 +522,15 @@ def workshop_environment_create(name, meta, spec, patch, logger, **_):
storage_class=CLUSTER_STORAGE_CLASS,
)

application_variables_list = workshop_spec.get("session").get("variables", [])

application_variables_list = substitute_variables(
application_variables_list, environment_variables
)

for variable in application_variables_list:
environment_variables[variable["name"]] = variable["value"]

if workshop_spec.get("environment", {}).get("objects"):
objects = []

Expand Down
Loading

0 comments on commit 7783f57

Please sign in to comment.