mirror of
https://github.com/ChristianLight/tutor.git
synced 2024-12-12 06:07:56 +00:00
Move "-r/--root" option to parent command level
This commit is contained in:
parent
e9f200a102
commit
13de3c8adc
@ -4,6 +4,7 @@ Note: Breaking changes between versions are indicated by "💥".
|
||||
|
||||
## Unreleased
|
||||
|
||||
- [Improvement] Move ``-r/--root`` option to parent command level
|
||||
- [Bugfix] Fix course about page visibility
|
||||
- [Improvement] Print gunicorn access logs in the console
|
||||
- 💥[Improvement] Get rid of the `indexcourses` and `portainer` command (#269)
|
||||
|
@ -5,7 +5,12 @@ Local deployment
|
||||
|
||||
This method is for deploying Open edX locally on a single server, where docker images are orchestrated with `docker-compose <https://docs.docker.com/compose/overview/>`_.
|
||||
|
||||
In the following, environment and data files will be generated in a user-specific project folder which will be referred to as the "**project root**". On Linux, the default project root is ``~/.local/share/tutor``. An alternative project root can be defined by passing the ``--root=...`` option to most commands, or define the ``TUTOR_ROOT=...`` environment variable.
|
||||
In the following, environment and data files will be generated in a user-specific project folder which will be referred to as the "**project root**". On Linux, the default project root is ``~/.local/share/tutor``. An alternative project root can be defined by passing the ``--root=...`` option to the ``tutor`` command, or define the ``TUTOR_ROOT=...`` environment variable::
|
||||
|
||||
tutor --root=/path/to/tutorroot run ...
|
||||
# Or equivalently:
|
||||
export TUTOR_ROOT=/path/to/tutorroot
|
||||
tutor run ...
|
||||
|
||||
Main commands
|
||||
-------------
|
||||
|
@ -3,7 +3,6 @@ import click
|
||||
from .. import config as tutor_config
|
||||
from .. import env as tutor_env
|
||||
from .. import fmt
|
||||
from .. import opts
|
||||
from .. import utils
|
||||
|
||||
|
||||
@ -18,31 +17,31 @@ def build():
|
||||
|
||||
|
||||
@click.command(help="Build the application in debug mode")
|
||||
@opts.root
|
||||
def debug(root):
|
||||
docker_run(root)
|
||||
@click.pass_obj
|
||||
def debug(context):
|
||||
docker_run(context.root)
|
||||
fmt.echo_info(
|
||||
"The debuggable APK file is available in {}".format(
|
||||
tutor_env.data_path(root, "android")
|
||||
tutor_env.data_path(context.root, "android")
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@click.command(help="Build the application in release mode")
|
||||
@opts.root
|
||||
def release(root):
|
||||
docker_run(root, "./gradlew", "assembleProdRelease")
|
||||
@click.pass_obj
|
||||
def release(context):
|
||||
docker_run(context.root, "./gradlew", "assembleProdRelease")
|
||||
fmt.echo_info(
|
||||
"The production APK file is available in {}".format(
|
||||
tutor_env.data_path(root, "android")
|
||||
tutor_env.data_path(context.root, "android")
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@click.command(help="Pull the docker image")
|
||||
@opts.root
|
||||
def pullimage(root):
|
||||
config = tutor_config.load(root)
|
||||
@click.pass_obj
|
||||
def pullimage(context):
|
||||
config = tutor_config.load(context.root)
|
||||
utils.execute("docker", "pull", config["DOCKER_IMAGE_ANDROID"])
|
||||
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
#! /usr/bin/env python3
|
||||
import sys
|
||||
|
||||
import appdirs
|
||||
import click
|
||||
import click_repl
|
||||
|
||||
@ -18,9 +19,15 @@ from .. import exceptions
|
||||
from .. import fmt
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class Context:
|
||||
def __init__(self, root):
|
||||
self.root = root
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
cli()
|
||||
cli() # pylint: disable=no-value-for-parameter
|
||||
except exceptions.TutorError as e:
|
||||
fmt.echo_error("Error: {}".format(e.args[0]))
|
||||
sys.exit(1)
|
||||
@ -28,8 +35,18 @@ def main():
|
||||
|
||||
@click.group(context_settings={"help_option_names": ["-h", "--help", "help"]})
|
||||
@click.version_option(version=__version__)
|
||||
def cli():
|
||||
pass
|
||||
@click.option(
|
||||
"-r",
|
||||
"--root",
|
||||
envvar="TUTOR_ROOT",
|
||||
default=appdirs.user_data_dir(appname="tutor"),
|
||||
show_default=True,
|
||||
type=click.Path(resolve_path=True),
|
||||
help="Root project directory (environment variable: TUTOR_ROOT)",
|
||||
)
|
||||
@click.pass_context
|
||||
def cli(context, root):
|
||||
context.obj = Context(root)
|
||||
|
||||
|
||||
@click.command(help="Print this help", name="help")
|
||||
|
@ -5,7 +5,7 @@ from .. import env
|
||||
from .. import exceptions
|
||||
from .. import fmt
|
||||
from .. import interactive as interactive_config
|
||||
from .. import opts
|
||||
from .. import serialize
|
||||
|
||||
|
||||
@click.group(
|
||||
@ -17,14 +17,24 @@ def config_command():
|
||||
pass
|
||||
|
||||
|
||||
class YamlParamType(click.ParamType):
|
||||
name = "yaml"
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
try:
|
||||
k, v = value.split("=")
|
||||
except ValueError:
|
||||
self.fail("'{}' is not of the form 'key=value'.".format(value), param, ctx)
|
||||
return k, serialize.parse(v)
|
||||
|
||||
|
||||
@click.command(help="Create and save configuration interactively")
|
||||
@opts.root
|
||||
@click.option("-i", "--interactive", is_flag=True, help="Run interactively")
|
||||
@click.option(
|
||||
"-s",
|
||||
"--set",
|
||||
"set_",
|
||||
type=opts.YamlParamType(),
|
||||
type=YamlParamType(),
|
||||
multiple=True,
|
||||
metavar="KEY=VAL",
|
||||
help="Set a configuration value (can be used multiple times)",
|
||||
@ -35,28 +45,31 @@ def config_command():
|
||||
multiple=True,
|
||||
help="Remove a configuration value (can be used multiple times)",
|
||||
)
|
||||
def save(root, interactive, set_, unset):
|
||||
config, defaults = interactive_config.load_all(root, interactive=interactive)
|
||||
@click.pass_obj
|
||||
def save(context, interactive, set_, unset):
|
||||
config, defaults = interactive_config.load_all(
|
||||
context.root, interactive=interactive
|
||||
)
|
||||
if set_:
|
||||
tutor_config.merge(config, dict(set_), force=True)
|
||||
for key in unset:
|
||||
config.pop(key, None)
|
||||
tutor_config.save(root, config)
|
||||
tutor_config.save(context.root, config)
|
||||
tutor_config.merge(config, defaults)
|
||||
env.save(root, config)
|
||||
env.save(context.root, config)
|
||||
|
||||
|
||||
@click.command(help="Print the project root")
|
||||
@opts.root
|
||||
def printroot(root):
|
||||
click.echo(root)
|
||||
@click.pass_obj
|
||||
def printroot(context):
|
||||
click.echo(context.root)
|
||||
|
||||
|
||||
@click.command(help="Print a configuration value")
|
||||
@opts.root
|
||||
@click.argument("key")
|
||||
def printvalue(root, key):
|
||||
config = tutor_config.load(root)
|
||||
@click.pass_obj
|
||||
def printvalue(context, key):
|
||||
config = tutor_config.load(context.root)
|
||||
try:
|
||||
fmt.echo(config[key])
|
||||
except KeyError:
|
||||
|
@ -2,7 +2,6 @@ import click
|
||||
|
||||
from .. import env as tutor_env
|
||||
from .. import fmt
|
||||
from .. import opts
|
||||
from .. import utils
|
||||
|
||||
|
||||
@ -11,53 +10,72 @@ def dev():
|
||||
pass
|
||||
|
||||
|
||||
edx_platform_path_option = click.option(
|
||||
"-P",
|
||||
"--edx-platform-path",
|
||||
envvar="TUTOR_EDX_PLATFORM_PATH",
|
||||
type=click.Path(exists=True, dir_okay=True, resolve_path=True),
|
||||
help="Mount a local edx-platform from the host (environment variable: TUTOR_EDX_PLATFORM_PATH)",
|
||||
)
|
||||
|
||||
edx_platform_development_settings_option = click.option(
|
||||
"-S",
|
||||
"--edx-platform-settings",
|
||||
envvar="TUTOR_EDX_PLATFORM_SETTINGS",
|
||||
default="tutor.development",
|
||||
help="Mount a local edx-platform from the host (environment variable: TUTOR_EDX_PLATFORM_PATH)",
|
||||
)
|
||||
|
||||
|
||||
@click.command(
|
||||
help="Run a command in one of the containers",
|
||||
context_settings={"ignore_unknown_options": True},
|
||||
)
|
||||
@opts.root
|
||||
@opts.edx_platform_path
|
||||
@opts.edx_platform_development_settings
|
||||
@edx_platform_path_option
|
||||
@edx_platform_development_settings_option
|
||||
@click.argument("service")
|
||||
@click.argument("command", default=None, required=False)
|
||||
@click.argument("args", nargs=-1)
|
||||
def run(root, edx_platform_path, edx_platform_settings, service, command, args):
|
||||
@click.pass_obj
|
||||
def run(context, edx_platform_path, edx_platform_settings, service, command, args):
|
||||
run_command = [service]
|
||||
if command:
|
||||
run_command.append(command)
|
||||
if args:
|
||||
run_command += args
|
||||
docker_compose_run(root, edx_platform_path, edx_platform_settings, *run_command)
|
||||
docker_compose_run(
|
||||
context.root, edx_platform_path, edx_platform_settings, *run_command
|
||||
)
|
||||
|
||||
|
||||
@click.command(
|
||||
help="Exec a command in a running container",
|
||||
context_settings={"ignore_unknown_options": True},
|
||||
)
|
||||
@opts.root
|
||||
@click.argument("service")
|
||||
@click.argument("command")
|
||||
@click.argument("args", nargs=-1)
|
||||
def execute(root, service, command, args):
|
||||
@click.pass_obj
|
||||
def execute(context, service, command, args):
|
||||
exec_command = ["exec", service, command]
|
||||
if args:
|
||||
exec_command += args
|
||||
docker_compose(root, *exec_command)
|
||||
docker_compose(context.root, *exec_command)
|
||||
|
||||
|
||||
@click.command(help="Run a development server")
|
||||
@opts.root
|
||||
@opts.edx_platform_path
|
||||
@opts.edx_platform_development_settings
|
||||
@edx_platform_path_option
|
||||
@edx_platform_development_settings_option
|
||||
@click.argument("service", type=click.Choice(["lms", "cms"]))
|
||||
def runserver(root, edx_platform_path, edx_platform_settings, service):
|
||||
@click.pass_obj
|
||||
def runserver(context, edx_platform_path, edx_platform_settings, service):
|
||||
port = service_port(service)
|
||||
|
||||
fmt.echo_info(
|
||||
"The {} service will be available at http://localhost:{}".format(service, port)
|
||||
)
|
||||
docker_compose_run(
|
||||
root,
|
||||
context.root,
|
||||
edx_platform_path,
|
||||
edx_platform_settings,
|
||||
"-p",
|
||||
@ -71,9 +89,9 @@ def runserver(root, edx_platform_path, edx_platform_settings, service):
|
||||
|
||||
|
||||
@click.command(help="Stop a running development platform")
|
||||
@opts.root
|
||||
def stop(root):
|
||||
docker_compose(root, "rm", "--stop", "--force")
|
||||
@click.pass_obj
|
||||
def stop(context):
|
||||
docker_compose(context.root, "rm", "--stop", "--force")
|
||||
|
||||
|
||||
def docker_compose_run(root, edx_platform_path, edx_platform_settings, *command):
|
||||
|
@ -5,7 +5,6 @@ import click
|
||||
from .. import config as tutor_config
|
||||
from .. import env as tutor_env
|
||||
from .. import images
|
||||
from .. import opts
|
||||
from .. import plugins
|
||||
|
||||
BASE_IMAGE_NAMES = ["openedx", "forum", "android"]
|
||||
@ -21,7 +20,6 @@ def images_command():
|
||||
short_help="Build docker images",
|
||||
help="Build the docker images necessary for an Open edX platform.",
|
||||
)
|
||||
@opts.root
|
||||
@click.argument("image", nargs=-1)
|
||||
@click.option(
|
||||
"--no-cache", is_flag=True, help="Do not use cache when building the image"
|
||||
@ -32,10 +30,11 @@ def images_command():
|
||||
multiple=True,
|
||||
help="Set build-time docker ARGS in the form 'myarg=value'. This option may be specified multiple times.",
|
||||
)
|
||||
def build(root, image, no_cache, build_arg):
|
||||
config = tutor_config.load(root)
|
||||
@click.pass_obj
|
||||
def build(context, image, no_cache, build_arg):
|
||||
config = tutor_config.load(context.root)
|
||||
for i in image:
|
||||
build_image(root, config, i, no_cache, build_arg)
|
||||
build_image(context.root, config, i, no_cache, build_arg)
|
||||
|
||||
|
||||
def build_image(root, config, image, no_cache, build_arg):
|
||||
@ -77,10 +76,10 @@ def build_image(root, config, image, no_cache, build_arg):
|
||||
|
||||
|
||||
@click.command(short_help="Pull images from the Docker registry")
|
||||
@opts.root
|
||||
@click.argument("image", nargs=-1)
|
||||
def pull(root, image):
|
||||
config = tutor_config.load(root)
|
||||
@click.pass_obj
|
||||
def pull(context, image):
|
||||
config = tutor_config.load(context.root)
|
||||
for i in image:
|
||||
pull_image(config, i)
|
||||
|
||||
@ -101,15 +100,15 @@ def pull_image(config, image):
|
||||
|
||||
|
||||
@click.command(short_help="Push images to the Docker registry")
|
||||
@opts.root
|
||||
@click.argument("image", nargs=-1)
|
||||
def push(root, image):
|
||||
config = tutor_config.load(root)
|
||||
@click.pass_obj
|
||||
def push(context, image):
|
||||
config = tutor_config.load(context.root)
|
||||
for i in image:
|
||||
push_image(root, config, i)
|
||||
push_image(config, i)
|
||||
|
||||
|
||||
def push_image(root, config, image):
|
||||
def push_image(config, image):
|
||||
# Push base images
|
||||
for img in BASE_IMAGE_NAMES:
|
||||
if image in [img, "all"]:
|
||||
|
@ -4,7 +4,6 @@ from .. import config as tutor_config
|
||||
from .. import env as tutor_env
|
||||
from .. import fmt
|
||||
from .. import interactive as interactive_config
|
||||
from .. import opts
|
||||
from .. import scripts
|
||||
from .. import utils
|
||||
|
||||
@ -15,11 +14,11 @@ def k8s():
|
||||
|
||||
|
||||
@click.command(help="Configure and run Open edX from scratch")
|
||||
@opts.root
|
||||
@click.option("-I", "--non-interactive", is_flag=True, help="Run non-interactively")
|
||||
def quickstart(root, non_interactive):
|
||||
@click.pass_obj
|
||||
def quickstart(context, non_interactive):
|
||||
click.echo(fmt.title("Interactive platform configuration"))
|
||||
config = interactive_config.update(root, interactive=(not non_interactive))
|
||||
config = interactive_config.update(context.root, interactive=(not non_interactive))
|
||||
if config["ACTIVATE_HTTPS"] and not config["WEB_PROXY"]:
|
||||
fmt.echo_alert(
|
||||
"Potentially invalid configuration: ACTIVATE_HTTPS=true WEB_PROXY=false\n"
|
||||
@ -28,21 +27,21 @@ def quickstart(root, non_interactive):
|
||||
" more information."
|
||||
)
|
||||
click.echo(fmt.title("Updating the current environment"))
|
||||
tutor_env.save(root, config)
|
||||
tutor_env.save(context, config)
|
||||
click.echo(fmt.title("Starting the platform"))
|
||||
start.callback(root)
|
||||
start.callback()
|
||||
click.echo(fmt.title("Database creation and migrations"))
|
||||
init.callback(root)
|
||||
init.callback()
|
||||
|
||||
|
||||
@click.command(help="Run all configured Open edX services")
|
||||
@opts.root
|
||||
def start(root):
|
||||
@click.pass_obj
|
||||
def start(context):
|
||||
# Create namespace
|
||||
utils.kubectl(
|
||||
"apply",
|
||||
"--kustomize",
|
||||
tutor_env.pathjoin(root),
|
||||
tutor_env.pathjoin(context.root),
|
||||
"--wait",
|
||||
"--selector",
|
||||
"app.kubernetes.io/component=namespace",
|
||||
@ -51,29 +50,29 @@ def start(root):
|
||||
utils.kubectl(
|
||||
"apply",
|
||||
"--kustomize",
|
||||
tutor_env.pathjoin(root),
|
||||
tutor_env.pathjoin(context.root),
|
||||
"--wait",
|
||||
"--selector",
|
||||
"app.kubernetes.io/component=volume",
|
||||
)
|
||||
# Create everything else
|
||||
utils.kubectl("apply", "--kustomize", tutor_env.pathjoin(root))
|
||||
utils.kubectl("apply", "--kustomize", tutor_env.pathjoin(context.root))
|
||||
|
||||
|
||||
@click.command(help="Stop a running platform")
|
||||
@opts.root
|
||||
def stop(root):
|
||||
config = tutor_config.load(root)
|
||||
@click.pass_obj
|
||||
def stop(context):
|
||||
config = tutor_config.load(context.root)
|
||||
utils.kubectl(
|
||||
"delete", *resource_selector(config), "deployments,services,ingress,configmaps"
|
||||
)
|
||||
|
||||
|
||||
@click.command(help="Reboot an existing platform")
|
||||
@opts.root
|
||||
def reboot(root):
|
||||
stop.callback(root)
|
||||
start.callback(root)
|
||||
@click.pass_obj
|
||||
def reboot(context):
|
||||
stop.callback()
|
||||
start.callback()
|
||||
|
||||
|
||||
def resource_selector(config, *selectors):
|
||||
@ -87,24 +86,28 @@ def resource_selector(config, *selectors):
|
||||
|
||||
|
||||
@click.command(help="Completely delete an existing platform")
|
||||
@opts.root
|
||||
@click.option("-y", "--yes", is_flag=True, help="Do not ask for confirmation")
|
||||
def delete(root, yes):
|
||||
@click.pass_obj
|
||||
def delete(context, yes):
|
||||
if not yes:
|
||||
click.confirm(
|
||||
"Are you sure you want to delete the platform? All data will be removed.",
|
||||
abort=True,
|
||||
)
|
||||
utils.kubectl(
|
||||
"delete", "-k", tutor_env.pathjoin(root), "--ignore-not-found=true", "--wait"
|
||||
"delete",
|
||||
"-k",
|
||||
tutor_env.pathjoin(context.root),
|
||||
"--ignore-not-found=true",
|
||||
"--wait",
|
||||
)
|
||||
|
||||
|
||||
@click.command(help="Initialise all applications")
|
||||
@opts.root
|
||||
def init(root):
|
||||
config = tutor_config.load(root)
|
||||
runner = K8sScriptRunner(root, config)
|
||||
@click.pass_obj
|
||||
def init(context):
|
||||
config = tutor_config.load(context.root)
|
||||
runner = K8sScriptRunner(context.root, config)
|
||||
for service in ["mysql", "elasticsearch", "mongodb"]:
|
||||
if runner.is_activated(service):
|
||||
wait_for_pod_ready(config, service)
|
||||
@ -112,7 +115,6 @@ def init(root):
|
||||
|
||||
|
||||
@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.option(
|
||||
@ -122,9 +124,10 @@ def init(root):
|
||||
)
|
||||
@click.argument("name")
|
||||
@click.argument("email")
|
||||
def createuser(root, superuser, staff, password, name, email):
|
||||
config = tutor_config.load(root)
|
||||
runner = K8sScriptRunner(root, config)
|
||||
@click.pass_obj
|
||||
def createuser(context, superuser, staff, password, name, email):
|
||||
config = tutor_config.load(context.root)
|
||||
runner = K8sScriptRunner(context.root, config)
|
||||
runner.check_service_is_activated("lms")
|
||||
command = scripts.create_user_command(
|
||||
superuser, staff, name, email, password=password
|
||||
@ -133,31 +136,31 @@ def createuser(root, superuser, staff, password, name, email):
|
||||
|
||||
|
||||
@click.command(help="Import the demo course")
|
||||
@opts.root
|
||||
def importdemocourse(root):
|
||||
@click.pass_obj
|
||||
def importdemocourse(context):
|
||||
fmt.echo_info("Importing demo course")
|
||||
config = tutor_config.load(root)
|
||||
runner = K8sScriptRunner(root, config)
|
||||
config = tutor_config.load(context.root)
|
||||
runner = K8sScriptRunner(context.root, config)
|
||||
scripts.import_demo_course(runner)
|
||||
|
||||
|
||||
@click.command(name="exec", help="Execute a command in a pod of the given application")
|
||||
@opts.root
|
||||
@click.argument("service")
|
||||
@click.argument("command")
|
||||
def exec_command(root, service, command):
|
||||
config = tutor_config.load(root)
|
||||
@click.pass_obj
|
||||
def exec_command(context, service, command):
|
||||
config = tutor_config.load(context.root)
|
||||
kubectl_exec(config, service, command, attach=True)
|
||||
|
||||
|
||||
@click.command(help="View output from containers")
|
||||
@opts.root
|
||||
@click.option("-c", "--container", help="Print the logs of this specific container")
|
||||
@click.option("-f", "--follow", is_flag=True, help="Follow log output")
|
||||
@click.option("--tail", type=int, help="Number of lines to show from each container")
|
||||
@click.argument("service")
|
||||
def logs(root, container, follow, tail, service):
|
||||
config = tutor_config.load(root)
|
||||
@click.pass_obj
|
||||
def logs(context, container, follow, tail, service):
|
||||
config = tutor_config.load(context.root)
|
||||
|
||||
command = ["logs"]
|
||||
selectors = ["app.kubernetes.io/name=" + service] if service else []
|
||||
|
@ -6,7 +6,6 @@ from .. import config as tutor_config
|
||||
from .. import env as tutor_env
|
||||
from .. import fmt
|
||||
from .. import interactive as interactive_config
|
||||
from .. import opts
|
||||
from .. import scripts
|
||||
from .. import utils
|
||||
|
||||
@ -20,48 +19,48 @@ def local():
|
||||
|
||||
|
||||
@click.command(help="Configure and run Open edX from scratch")
|
||||
@opts.root
|
||||
@click.option("-I", "--non-interactive", is_flag=True, help="Run non-interactively")
|
||||
@click.option(
|
||||
"-p", "--pullimages", "pullimages_", is_flag=True, help="Update docker images"
|
||||
)
|
||||
def quickstart(root, non_interactive, pullimages_):
|
||||
@click.pass_obj
|
||||
def quickstart(context, non_interactive, pullimages_):
|
||||
click.echo(fmt.title("Interactive platform configuration"))
|
||||
config = interactive_config.update(root, interactive=(not non_interactive))
|
||||
config = interactive_config.update(context.root, interactive=(not non_interactive))
|
||||
click.echo(fmt.title("Updating the current environment"))
|
||||
tutor_env.save(root, config)
|
||||
tutor_env.save(context.root, config)
|
||||
click.echo(fmt.title("Stopping any existing platform"))
|
||||
stop.callback(root, [])
|
||||
stop.callback([])
|
||||
click.echo(fmt.title("HTTPS certificates generation"))
|
||||
https_create.callback(root)
|
||||
https_create.callback()
|
||||
if pullimages_:
|
||||
click.echo(fmt.title("Docker image updates"))
|
||||
pullimages.callback(root)
|
||||
pullimages.callback()
|
||||
click.echo(fmt.title("Starting the platform in detached mode"))
|
||||
start.callback(root, True, [])
|
||||
start.callback(True, [])
|
||||
click.echo(fmt.title("Database creation and migrations"))
|
||||
init.callback(root)
|
||||
init.callback()
|
||||
echo_platform_info(config)
|
||||
|
||||
|
||||
@click.command(help="Update docker images")
|
||||
@opts.root
|
||||
def pullimages(root):
|
||||
config = tutor_config.load(root)
|
||||
docker_compose(root, config, "pull")
|
||||
@click.pass_obj
|
||||
def pullimages(context):
|
||||
config = tutor_config.load(context.root)
|
||||
docker_compose(context.root, config, "pull")
|
||||
|
||||
|
||||
@click.command(help="Run all or a selection of configured Open edX services")
|
||||
@opts.root
|
||||
@click.option("-d", "--detach", is_flag=True, help="Start in daemon mode")
|
||||
@click.argument("services", metavar="service", nargs=-1)
|
||||
def start(root, detach, services):
|
||||
@click.pass_obj
|
||||
def start(context, detach, services):
|
||||
command = ["up", "--remove-orphans"]
|
||||
if detach:
|
||||
command.append("-d")
|
||||
|
||||
config = tutor_config.load(root)
|
||||
docker_compose(root, config, *command, *services)
|
||||
config = tutor_config.load(context.root)
|
||||
docker_compose(context.root, config, *command, *services)
|
||||
|
||||
|
||||
def echo_platform_info(config):
|
||||
@ -82,23 +81,22 @@ def echo_platform_info(config):
|
||||
|
||||
|
||||
@click.command(help="Stop a running platform")
|
||||
@opts.root
|
||||
@click.argument("services", metavar="service", nargs=-1)
|
||||
def stop(root, services):
|
||||
config = tutor_config.load(root)
|
||||
docker_compose(root, config, "rm", "--stop", "--force", *services)
|
||||
@click.pass_obj
|
||||
def stop(context, services):
|
||||
config = tutor_config.load(context.root)
|
||||
docker_compose(context.root, config, "rm", "--stop", "--force", *services)
|
||||
|
||||
|
||||
@click.command(
|
||||
short_help="Reboot an existing platform",
|
||||
help="This is more than just a restart: with reboot, the platform is fully stopped before being restarted again",
|
||||
)
|
||||
@opts.root
|
||||
@click.option("-d", "--detach", is_flag=True, help="Start in daemon mode")
|
||||
@click.argument("services", metavar="service", nargs=-1)
|
||||
def reboot(root, detach, services):
|
||||
stop.callback(root, services)
|
||||
start.callback(root, detach, services)
|
||||
def reboot(detach, services):
|
||||
stop.callback(services)
|
||||
start.callback(detach, services)
|
||||
|
||||
|
||||
@click.command(
|
||||
@ -108,10 +106,10 @@ restart all services. Note that this performs a 'docker-compose restart', so new
|
||||
may not be taken into account. It is useful for reloading settings, for instance. To
|
||||
fully stop the platform, use the 'reboot' command.""",
|
||||
)
|
||||
@opts.root
|
||||
@click.argument("service")
|
||||
def restart(root, service):
|
||||
config = tutor_config.load(root)
|
||||
@click.pass_obj
|
||||
def restart(context, service):
|
||||
config = tutor_config.load(context.root)
|
||||
command = ["restart"]
|
||||
if service == "openedx":
|
||||
if config["ACTIVATE_LMS"]:
|
||||
@ -120,19 +118,19 @@ def restart(root, service):
|
||||
command += ["cms", "cms_worker"]
|
||||
elif service != "all":
|
||||
command += [service]
|
||||
docker_compose(root, config, *command)
|
||||
docker_compose(context.root, config, *command)
|
||||
|
||||
|
||||
@click.command(
|
||||
help="Run a command in one of the containers",
|
||||
context_settings={"ignore_unknown_options": True},
|
||||
)
|
||||
@opts.root
|
||||
@click.option("--entrypoint", help="Override the entrypoint of the image")
|
||||
@click.argument("service")
|
||||
@click.argument("command", default=None, required=False)
|
||||
@click.argument("args", nargs=-1)
|
||||
def run(root, entrypoint, service, command, args):
|
||||
@click.pass_obj
|
||||
def run(context, entrypoint, service, command, args):
|
||||
run_command = ["run", "--rm"]
|
||||
if entrypoint:
|
||||
run_command += ["--entrypoint", entrypoint]
|
||||
@ -141,31 +139,31 @@ def run(root, entrypoint, service, command, args):
|
||||
run_command.append(command)
|
||||
if args:
|
||||
run_command += args
|
||||
config = tutor_config.load(root)
|
||||
docker_compose(root, config, *run_command)
|
||||
config = tutor_config.load(context.root)
|
||||
docker_compose(context.root, config, *run_command)
|
||||
|
||||
|
||||
@click.command(
|
||||
help="Exec a command in a running container",
|
||||
context_settings={"ignore_unknown_options": True},
|
||||
)
|
||||
@opts.root
|
||||
@click.argument("service")
|
||||
@click.argument("command")
|
||||
@click.argument("args", nargs=-1)
|
||||
def execute(root, service, command, args):
|
||||
@click.pass_obj
|
||||
def execute(context, service, command, args):
|
||||
exec_command = ["exec", service, command]
|
||||
if args:
|
||||
exec_command += args
|
||||
config = tutor_config.load(root)
|
||||
docker_compose(root, config, *exec_command)
|
||||
config = tutor_config.load(context.root)
|
||||
docker_compose(context.root, config, *exec_command)
|
||||
|
||||
|
||||
@click.command(help="Initialise all applications")
|
||||
@opts.root
|
||||
def init(root):
|
||||
config = tutor_config.load(root)
|
||||
runner = ScriptRunner(root, config)
|
||||
@click.pass_obj
|
||||
def init(context):
|
||||
config = tutor_config.load(context.root)
|
||||
runner = ScriptRunner(context.root, config)
|
||||
scripts.initialise(runner)
|
||||
|
||||
|
||||
@ -179,12 +177,12 @@ that is convenient when developing new plugins. Ex:
|
||||
tutor local hook discovery discovery hooks discovery init""",
|
||||
name="hook",
|
||||
)
|
||||
@opts.root
|
||||
@click.argument("service")
|
||||
@click.argument("path", nargs=-1)
|
||||
def run_hook(root, service, path):
|
||||
config = tutor_config.load(root)
|
||||
runner = ScriptRunner(root, config)
|
||||
@click.pass_obj
|
||||
def run_hook(context, service, path):
|
||||
config = tutor_config.load(context.root)
|
||||
runner = ScriptRunner(context.root, config)
|
||||
fmt.echo_info(
|
||||
"Running '{}' hook in '{}' container...".format(".".join(path), service)
|
||||
)
|
||||
@ -197,8 +195,8 @@ def https():
|
||||
|
||||
|
||||
@click.command(help="Create https certificates", name="create")
|
||||
@opts.root
|
||||
def https_create(root):
|
||||
@click.pass_obj
|
||||
def https_create(context):
|
||||
"""
|
||||
Note: there are a couple issues with https certificate generation.
|
||||
1. Certificates are generated and renewed by using port 80, which is not necessarily open.
|
||||
@ -206,8 +204,8 @@ def https_create(root):
|
||||
b. It may be occupied by an external web server
|
||||
2. On certificate renewal, nginx is not reloaded
|
||||
"""
|
||||
config = tutor_config.load(root)
|
||||
runner = ScriptRunner(root, config)
|
||||
config = tutor_config.load(context.root)
|
||||
runner = ScriptRunner(context.root, config)
|
||||
if not config["ACTIVATE_HTTPS"]:
|
||||
fmt.echo_info("HTTPS is not activated: certificate generation skipped")
|
||||
return
|
||||
@ -230,7 +228,7 @@ See the official certbot documentation for your platform: https://certbot.eff.or
|
||||
|
||||
utils.docker_run(
|
||||
"--volume",
|
||||
"{}:/etc/letsencrypt/".format(tutor_env.data_path(root, "letsencrypt")),
|
||||
"{}:/etc/letsencrypt/".format(tutor_env.data_path(context.root, "letsencrypt")),
|
||||
"-p",
|
||||
"80:80",
|
||||
"--entrypoint=sh",
|
||||
@ -242,9 +240,9 @@ See the official certbot documentation for your platform: https://certbot.eff.or
|
||||
|
||||
|
||||
@click.command(help="Renew https certificates", name="renew")
|
||||
@opts.root
|
||||
def https_renew(root):
|
||||
config = tutor_config.load(root)
|
||||
@click.pass_obj
|
||||
def https_renew(context):
|
||||
config = tutor_config.load(context.root)
|
||||
if not config["ACTIVATE_HTTPS"]:
|
||||
fmt.echo_info("HTTPS is not activated: certificate renewal skipped")
|
||||
return
|
||||
@ -261,7 +259,7 @@ See the official certbot documentation for your platform: https://certbot.eff.or
|
||||
return
|
||||
docker_run = [
|
||||
"--volume",
|
||||
"{}:/etc/letsencrypt/".format(tutor_env.data_path(root, "letsencrypt")),
|
||||
"{}:/etc/letsencrypt/".format(tutor_env.data_path(context.root, "letsencrypt")),
|
||||
"-p",
|
||||
"80:80",
|
||||
"certbot/certbot:latest",
|
||||
@ -271,23 +269,22 @@ See the official certbot documentation for your platform: https://certbot.eff.or
|
||||
|
||||
|
||||
@click.command(help="View output from containers")
|
||||
@opts.root
|
||||
@click.option("-f", "--follow", is_flag=True, help="Follow log output")
|
||||
@click.option("--tail", type=int, help="Number of lines to show from each container")
|
||||
@click.argument("service", nargs=-1)
|
||||
def logs(root, follow, tail, service):
|
||||
@click.pass_obj
|
||||
def logs(context, follow, tail, service):
|
||||
command = ["logs"]
|
||||
if follow:
|
||||
command += ["--follow"]
|
||||
if tail is not None:
|
||||
command += ["--tail", str(tail)]
|
||||
command += service
|
||||
config = tutor_config.load(root)
|
||||
docker_compose(root, config, *command)
|
||||
config = tutor_config.load(context.root)
|
||||
docker_compose(context.root, config, *command)
|
||||
|
||||
|
||||
@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.option(
|
||||
@ -297,9 +294,10 @@ def logs(root, follow, tail, service):
|
||||
)
|
||||
@click.argument("name")
|
||||
@click.argument("email")
|
||||
def createuser(root, superuser, staff, password, name, email):
|
||||
config = tutor_config.load(root)
|
||||
runner = ScriptRunner(root, config)
|
||||
@click.pass_obj
|
||||
def createuser(context, superuser, staff, password, name, email):
|
||||
config = tutor_config.load(context.root)
|
||||
runner = ScriptRunner(context.root, config)
|
||||
runner.check_service_is_activated("lms")
|
||||
command = scripts.create_user_command(
|
||||
superuser, staff, name, email, password=password
|
||||
@ -308,10 +306,10 @@ def createuser(root, superuser, staff, password, name, email):
|
||||
|
||||
|
||||
@click.command(help="Import the demo course")
|
||||
@opts.root
|
||||
def importdemocourse(root):
|
||||
config = tutor_config.load(root)
|
||||
runner = ScriptRunner(root, config)
|
||||
@click.pass_obj
|
||||
def importdemocourse(context):
|
||||
config = tutor_config.load(context.root)
|
||||
runner = ScriptRunner(context.root, config)
|
||||
fmt.echo_info("Importing demo course")
|
||||
scripts.import_demo_course(runner)
|
||||
|
||||
|
@ -5,7 +5,6 @@ import click
|
||||
from .. import config as tutor_config
|
||||
from .. import env as tutor_env
|
||||
from .. import fmt
|
||||
from .. import opts
|
||||
from .. import plugins
|
||||
|
||||
|
||||
@ -22,47 +21,57 @@ def plugins_command():
|
||||
|
||||
|
||||
@click.command(name="list", help="List installed plugins")
|
||||
@opts.root
|
||||
def list_command(root):
|
||||
config = tutor_config.load_user(root)
|
||||
@click.pass_obj
|
||||
def list_command(context):
|
||||
config = tutor_config.load_user(context.root)
|
||||
for name, _ in plugins.iter_installed():
|
||||
status = "" if plugins.is_enabled(config, name) else " (disabled)"
|
||||
print("{plugin}{status}".format(plugin=name, status=status))
|
||||
|
||||
|
||||
@click.command(help="Enable a plugin")
|
||||
@opts.root
|
||||
@click.argument("plugin_names", metavar="plugin", nargs=-1)
|
||||
def enable(root, plugin_names):
|
||||
config = tutor_config.load_user(root)
|
||||
@click.pass_obj
|
||||
def enable(context, plugin_names):
|
||||
config = tutor_config.load_user(context.root)
|
||||
for plugin in plugin_names:
|
||||
plugins.enable(config, plugin)
|
||||
fmt.echo_info("Plugin {} enabled".format(plugin))
|
||||
tutor_config.save(root, config)
|
||||
tutor_config.save(context.root, config)
|
||||
fmt.echo_info(
|
||||
"You should now re-generate your environment with `tutor config save`."
|
||||
)
|
||||
|
||||
|
||||
@click.command(help="Disable a plugin")
|
||||
@opts.root
|
||||
@click.argument("plugin_names", metavar="plugin", nargs=-1)
|
||||
def disable(root, plugin_names):
|
||||
config = tutor_config.load_user(root)
|
||||
@click.pass_obj
|
||||
def disable(context, plugin_names):
|
||||
config = tutor_config.load_user(context.root)
|
||||
for plugin in plugin_names:
|
||||
plugins.disable(config, plugin)
|
||||
|
||||
plugin_dir = tutor_env.pathjoin(root, "plugins", plugin)
|
||||
plugin_dir = tutor_env.pathjoin(context.root, "plugins", plugin)
|
||||
if os.path.exists(plugin_dir):
|
||||
shutil.rmtree(plugin_dir)
|
||||
fmt.echo_info("Plugin {} disabled".format(plugin))
|
||||
|
||||
tutor_config.save(root, config)
|
||||
tutor_config.save(context.root, config)
|
||||
fmt.echo_info(
|
||||
"You should now re-generate your environment with `tutor config save`."
|
||||
)
|
||||
|
||||
|
||||
def iter_extra_plugin_commands():
|
||||
"""
|
||||
TODO document this. Merge with plugins.iter_commands? It's good to keepo
|
||||
click-related stuff outside of the plugins module.
|
||||
"""
|
||||
for plugin_name, command in plugins.iter_commands():
|
||||
command.name = plugin_name
|
||||
yield command
|
||||
|
||||
|
||||
plugins_command.add_command(list_command)
|
||||
plugins_command.add_command(enable)
|
||||
plugins_command.add_command(disable)
|
||||
|
@ -11,7 +11,6 @@ import click
|
||||
# Note: it is important that this module does not depend on config, such that
|
||||
# the web ui can be launched even where there is no configuration.
|
||||
from .. import fmt
|
||||
from .. import opts
|
||||
from .. import env as tutor_env
|
||||
from .. import serialize
|
||||
|
||||
@ -24,7 +23,6 @@ def webui():
|
||||
|
||||
|
||||
@click.command(help="Start the web UI")
|
||||
@opts.root
|
||||
@click.option(
|
||||
"-p",
|
||||
"--port",
|
||||
@ -36,15 +34,16 @@ def webui():
|
||||
@click.option(
|
||||
"-h", "--host", default="0.0.0.0", show_default=True, help="Host address to listen"
|
||||
)
|
||||
def start(root, port, host):
|
||||
check_gotty_binary(root)
|
||||
@click.pass_obj
|
||||
def start(context, port, host):
|
||||
check_gotty_binary(context.root)
|
||||
fmt.echo_info("Access the Tutor web UI at http://{}:{}".format(host, port))
|
||||
while True:
|
||||
config = load_config(root)
|
||||
config = load_config(context.root)
|
||||
user = config["user"]
|
||||
password = config["password"]
|
||||
command = [
|
||||
gotty_path(root),
|
||||
gotty_path(context.root),
|
||||
"--permit-write",
|
||||
"--address",
|
||||
host,
|
||||
@ -66,7 +65,7 @@ def start(root, port, host):
|
||||
try:
|
||||
p.wait(timeout=2)
|
||||
except subprocess.TimeoutExpired:
|
||||
new_config = load_config(root)
|
||||
new_config = load_config(context.root)
|
||||
if new_config != config:
|
||||
click.echo(
|
||||
"WARNING configuration changed. Tutor web UI is now going to restart. Reload this page to continue."
|
||||
@ -77,7 +76,6 @@ def start(root, port, host):
|
||||
|
||||
|
||||
@click.command(help="Configure authentication")
|
||||
@opts.root
|
||||
@click.option("-u", "--user", prompt="User name", help="Authentication user name")
|
||||
@click.option(
|
||||
"-p",
|
||||
@ -87,12 +85,13 @@ def start(root, port, host):
|
||||
confirmation_prompt=True,
|
||||
help="Authentication password",
|
||||
)
|
||||
def configure(root, user, password):
|
||||
save_config(root, {"user": user, "password": password})
|
||||
@click.pass_obj
|
||||
def configure(context, user, password):
|
||||
save_config(context.root, {"user": user, "password": password})
|
||||
fmt.echo_info(
|
||||
"The web UI configuration has been updated. "
|
||||
"If at any point you wish to reset your username and password, "
|
||||
"just delete the following file:\n\n {}".format(config_path(root))
|
||||
"just delete the following file:\n\n {}".format(config_path(context.root))
|
||||
)
|
||||
|
||||
|
||||
|
@ -185,7 +185,9 @@ def check_existing_config(root):
|
||||
"""
|
||||
if not os.path.exists(config_path(root)):
|
||||
raise exceptions.TutorError(
|
||||
"Project root does not exist. Make sure to generate the initial configuration with `tutor config save --interactive` or `tutor config quickstart` prior to running other commands."
|
||||
"Project root does not exist. Make sure to generate the initial "
|
||||
"configuration with `tutor config save --interactive` or `tutor config "
|
||||
"quickstart` prior to running other commands."
|
||||
)
|
||||
env.check_is_up_to_date(root)
|
||||
|
||||
|
@ -1,42 +0,0 @@
|
||||
import appdirs
|
||||
import click
|
||||
|
||||
from . import serialize
|
||||
|
||||
|
||||
root = click.option(
|
||||
"-r",
|
||||
"--root",
|
||||
envvar="TUTOR_ROOT",
|
||||
default=appdirs.user_data_dir(appname="tutor"),
|
||||
show_default=True,
|
||||
type=click.Path(resolve_path=True),
|
||||
help="Root project directory (environment variable: TUTOR_ROOT)",
|
||||
)
|
||||
|
||||
edx_platform_path = click.option(
|
||||
"-P",
|
||||
"--edx-platform-path",
|
||||
envvar="TUTOR_EDX_PLATFORM_PATH",
|
||||
type=click.Path(exists=True, dir_okay=True, resolve_path=True),
|
||||
help="Mount a local edx-platform from the host (environment variable: TUTOR_EDX_PLATFORM_PATH)",
|
||||
)
|
||||
|
||||
edx_platform_development_settings = click.option(
|
||||
"-S",
|
||||
"--edx-platform-settings",
|
||||
envvar="TUTOR_EDX_PLATFORM_SETTINGS",
|
||||
default="tutor.development",
|
||||
help="Mount a local edx-platform from the host (environment variable: TUTOR_EDX_PLATFORM_PATH)",
|
||||
)
|
||||
|
||||
|
||||
class YamlParamType(click.ParamType):
|
||||
name = "yaml"
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
try:
|
||||
k, v = value.split("=")
|
||||
except ValueError:
|
||||
self.fail("'{}' is not of the form 'key=value'.".format(value), param, ctx)
|
||||
return k, serialize.parse(v)
|
@ -156,3 +156,15 @@ def iter_hooks(config, hook_name):
|
||||
|
||||
def iter_templates(config):
|
||||
yield from Plugins.instance(config).iter_templates()
|
||||
|
||||
|
||||
def iter_commands():
|
||||
"""
|
||||
TODO doesn't this slow down the cli? (we need to import all plugins)
|
||||
Also, do we really need the `config` argument? Do we want to make it possible for disabled plugins to be loaded?
|
||||
TODO document this
|
||||
"""
|
||||
for plugin_name, plugin in iter_installed():
|
||||
command = getattr(plugin, "command", None)
|
||||
if command:
|
||||
yield plugin_name, command
|
||||
|
Loading…
Reference in New Issue
Block a user