mirror of
https://github.com/ChristianLight/tutor.git
synced 2024-05-28 20:00:49 +00:00
07b3d113d4
Environment is no longer generated separately for each target, but only once the configuration is saved. Note that the environment is automatically updated during re-configuration, based on a "version" file stored in the environment.
179 lines
6.2 KiB
Python
179 lines
6.2 KiB
Python
import click
|
|
import kubernetes
|
|
|
|
from . import config as tutor_config
|
|
from . import env as tutor_env
|
|
from . import exceptions
|
|
from . import fmt
|
|
from . import opts
|
|
from . import ops
|
|
from . import utils
|
|
|
|
|
|
@click.group(help="Run Open edX on Kubernetes [BETA FEATURE]")
|
|
def k8s():
|
|
pass
|
|
|
|
@click.command(
|
|
help="Configure and run Open edX from scratch"
|
|
)
|
|
@opts.root
|
|
def quickstart(root):
|
|
click.echo(fmt.title("Interactive platform configuration"))
|
|
tutor_config.save.callback(root, False, [])
|
|
click.echo(fmt.title("Environment generation"))
|
|
click.echo(fmt.title("Stopping any existing platform"))
|
|
stop.callback()
|
|
click.echo(fmt.title("Starting the platform"))
|
|
start.callback(root)
|
|
click.echo(fmt.title("Running migrations. NOTE: this might fail. If it does, please retry 'tutor k9s databases later'"))
|
|
databases.callback(root)
|
|
|
|
@click.command(help="Run all configured Open edX services")
|
|
@opts.root
|
|
def start(root):
|
|
kubectl_no_fail("create", "-f", tutor_env.pathjoin(root, "k8s", "namespace.yml"))
|
|
|
|
kubectl("create", "configmap", "nginx-config", "--from-file", tutor_env.pathjoin(root, "apps", "nginx"))
|
|
kubectl("create", "configmap", "mysql-config", "--from-env-file", tutor_env.pathjoin(root, "apps", "mysql", "auth.env"))
|
|
kubectl("create", "configmap", "openedx-settings-lms", "--from-file", tutor_env.pathjoin(root, "apps", "openedx", "settings", "lms"))
|
|
kubectl("create", "configmap", "openedx-settings-cms", "--from-file", tutor_env.pathjoin(root, "apps", "openedx", "settings", "cms"))
|
|
kubectl("create", "configmap", "openedx-config", "--from-file", tutor_env.pathjoin(root, "apps", "openedx", "config"))
|
|
|
|
kubectl("create", "-f", tutor_env.pathjoin(root, "k8s", "volumes.yml"))
|
|
kubectl("create", "-f", tutor_env.pathjoin(root, "k8s", "ingress.yml"))
|
|
kubectl("create", "-f", tutor_env.pathjoin(root, "k8s", "services.yml"))
|
|
kubectl("create", "-f", tutor_env.pathjoin(root, "k8s", "deployments.yml"))
|
|
|
|
@click.command(help="Stop a running platform")
|
|
def stop():
|
|
kubectl("delete", "deployments,services,ingress,configmaps", "--all")
|
|
|
|
@click.command(help="Completely delete an existing platform")
|
|
@click.option("-y", "--yes", is_flag=True, help="Do not ask for confirmation")
|
|
def delete(yes):
|
|
if not yes:
|
|
click.confirm('Are you sure you want to delete the platform? All data will be removed.', abort=True)
|
|
kubectl("delete", "namespace", K8s.NAMESPACE)
|
|
|
|
@click.command(
|
|
help="Create databases and run database migrations",
|
|
)
|
|
@opts.root
|
|
def databases(root):
|
|
ops.migrate(root, run_bash)
|
|
|
|
@click.command(help="Create an Open edX user and interactively set their password")
|
|
@opts.root
|
|
@click.option("--superuser", is_flag=True, help="Make superuser")
|
|
@click.option("--staff", is_flag=True, help="Make staff user")
|
|
@click.argument("name")
|
|
@click.argument("email")
|
|
def createuser(root, superuser, staff, name, email):
|
|
ops.create_user(root, run_bash, superuser, staff, name, email)
|
|
|
|
@click.command(help="Import the demo course")
|
|
@opts.root
|
|
def importdemocourse(root):
|
|
click.echo(fmt.info("Importing demo course"))
|
|
ops.import_demo_course(root, run_bash)
|
|
click.echo(fmt.info("Re-indexing courses"))
|
|
indexcourses.callback(root)
|
|
|
|
@click.command(help="Re-index courses for better searching")
|
|
@opts.root
|
|
def indexcourses(root):
|
|
# Note: this is currently broken with "pymongo.errors.ConnectionFailure: [Errno 111] Connection refused"
|
|
# I'm not quite sure the settings are correctly picked up. Which is weird because migrations work very well.
|
|
ops.index_courses(root, run_bash)
|
|
|
|
@click.command(
|
|
help="Launch a shell in LMS or CMS",
|
|
)
|
|
@opts.root
|
|
@click.argument("service", type=click.Choice(["lms", "cms"]))
|
|
def shell(root, service):
|
|
K8s().execute(service, "bash")
|
|
|
|
@click.command(help="Create a Kubernetesadmin user")
|
|
@opts.root
|
|
def adminuser(root):
|
|
utils.kubectl("create", "-f", tutor_env.pathjoin(root, "k8s", "adminuser.yml"))
|
|
|
|
@click.command(help="Print the Kubernetes admin user token")
|
|
@opts.root
|
|
def admintoken(root):
|
|
click.echo(K8s().admin_token())
|
|
|
|
def kubectl(*command):
|
|
"""
|
|
Run kubectl commands in the right namespace. Also, errors are completely
|
|
ignored, to avoid stopping on "AlreadyExists" errors.
|
|
"""
|
|
args = list(command)
|
|
args += [
|
|
"--namespace", K8s.NAMESPACE
|
|
]
|
|
kubectl_no_fail(*args)
|
|
|
|
def kubectl_no_fail(*command):
|
|
"""
|
|
Run kubectl commands and ignore exceptions, to avoid stopping on
|
|
"AlreadyExists" errors.
|
|
"""
|
|
try:
|
|
utils.kubectl(*command)
|
|
except exceptions.TutorError:
|
|
pass
|
|
|
|
|
|
class K8s:
|
|
CLIENT = None
|
|
NAMESPACE = "openedx"
|
|
|
|
def __init__(self):
|
|
pass
|
|
|
|
@property
|
|
def client(self):
|
|
if self.CLIENT is None:
|
|
kubernetes.config.load_kube_config()
|
|
self.CLIENT = kubernetes.client.CoreV1Api()
|
|
return self.CLIENT
|
|
|
|
def pod_name(self, app):
|
|
selector = "app=" + app
|
|
try:
|
|
return self.client.list_namespaced_pod("openedx", label_selector=selector).items[0].metadata.name
|
|
except IndexError:
|
|
raise exceptions.TutorError("Pod with app {} does not exist. Make sure that the pod is running.")
|
|
|
|
def admin_token(self):
|
|
# Note: this is a HORRIBLE way of looking for a secret
|
|
try:
|
|
secret = [
|
|
s for s in self.client.list_namespaced_secret("kube-system").items if s.metadata.name.startswith("admin-user-token")
|
|
][0]
|
|
except IndexError:
|
|
raise exceptions.TutorError("Secret 'admin-user-token'. Make sure that admin user was created.")
|
|
return self.client.read_namespaced_secret(secret.metadata.name, "kube-system").data["token"]
|
|
|
|
def execute(self, app, *command):
|
|
podname = self.pod_name(app)
|
|
kubectl_no_fail("exec", "--namespace", self.NAMESPACE, "-it", podname, "--", *command)
|
|
|
|
def run_bash(root, service, command):
|
|
K8s().execute(service, "bash", "-e", "-c", command)
|
|
|
|
k8s.add_command(quickstart)
|
|
k8s.add_command(start)
|
|
k8s.add_command(stop)
|
|
k8s.add_command(delete)
|
|
k8s.add_command(databases)
|
|
k8s.add_command(createuser)
|
|
k8s.add_command(importdemocourse)
|
|
k8s.add_command(indexcourses)
|
|
k8s.add_command(shell)
|
|
k8s.add_command(adminuser)
|
|
k8s.add_command(admintoken)
|